is there a tutorial for a cost-effective entity design?
If I make a small sample and say I want to store users and groups. These groups have a List of users. If any user wants to join this group I have to check is this group existing and the user isn't part of the group.
My question is not how to do this. The question is for a good entity-design or a good objectify-using.
Here is some shortened sample code how I would do this:
User.java
#Entity
#Cache
#Embed
public class User {
#Id Long id;
#Index String name;
String passwordHash;
}
Group.java
#Entity
#Cache
public class Group {
#Id Long id;
#Index Long groupAdministratorUserId;
#Index String name;
List<User> users = new ArrayList<User>();
#Index Boolean isPublic;
}
using
if (!app.authenticate(getRequest(), getResponse()))
{
// Not authenticated
setStatus(Status.CLIENT_ERROR_UNAUTHORIZED);
}
else
{
Group newGroup = ofy().load().type(Group.class).id(Long.parseLong(id)).now(); // is it correct that the embedded data is already loaded?
// following check and insert is only for illustration!
newGroup.getUsers().contains(connectedUser);
newGroup.getUsers().add(connectedUser);
ofy().save().entity(newGroup).now();
}
My "overhead" (authentication)
public class MyVerifier extends LocalVerifier {
private User fetched;
public User getFetched() {
return fetched;
}
#Override
public char[] getLocalSecret(String identifier) {
// this is behind search... and another list()
// User fetched = ofy().load().type(User.class).filter("name", userName).first().now();
fetched = User.searchByExactName(identifier);
if (fetched != null)
{
return fetched.getPasswordHash().toCharArray();
}
return null;
}
}
P.S. I know the page from google: https://code.google.com/p/objectify-appengine/wiki/BestPractices
But that is not what I'm searching for
I would store the list of groups IDs the User entity. No need to use #Embed. What the best solution is really depends on what the most common operations will be in your application. Based on what you said I would recommend the following:
#Entity
#Cache
public class User {
#Id long id;
String name;
String passwordHash;
List<Long> groups;
// constructor left out for brevity.
}
#Entity
#Cache
public class Group {
#Id long id;
long adminId;
String name;
boolean isPublic;
// constructor left out for brevity.
}
User user1 = new User(userName1, passwordHash1);
User user2 = new User(userName2, passwordHash2);
Key<User> user1Key = ofy().save().entity(user1).now(); // Create two users.
Key<User> user2Key = ofy().save().entity(user2).now(); // The don't have any groups yet.
long adminId = user1Key.getId();
Group group = new Group(name, adminId, isPublic)
Key<Group> groupKey = ofy().save().entity(group).now(); // Create a group
user2.addToGroup(groupKey.getId()); // This adds the group ID to the User.groups list.
ofy().save().entity(user2).now(); // Add user2 to group.
To save costs (specifically on small datastore operations during updates) make sure to create as few indexes as possible. Start with few #Indexes and add them as necessary.
Related
In my application I am using projections to map *Entity objects to simplified or modified versions of the actual record in the database.
However, I have a particular use case where I am required to replace a certain value from one of the nested projections. Since these are interfaces and also get proxied by Spring, I am not sure if what I want is actually possible but to bring it down to one very simpel example:
Assume I have a UserEntity and a User projection. For my User projection I can simply execute:
User user = this.userEntityRepository.findById(userId);
However, if I want to change something, I am not sure if that is possible. Namely, I cannot do something like this:
if (user.getAge() < 18) {
user.setDisplayName(null);
}
Now, I am aware that I could create an anonymous class new User() { .. } and just pass in the values I required but in my case the objects are nested and hence this is not an option.
The question
Is there another way to replace a value, e.g. displayName as above, without using an anonymous class?
Elaborative example
Reading the following is not really necessary but in order to illustrate my issue in more detail I have pseudo-coded an example that shows a bit closer what the problem is in my particular case.
We have a simple UserEntity:
#Entity
#Table(name = "app_user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String firstName;
#Column
private String lastName;
#Column
private Integer age;
// Setter & Getter ..
}
#Entity
#Table(name = "event")
public class EventEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "event")
private List<EventAttendeeEntity> attendees;
// ..
}
We have a table which maps users to events:
#Entity
#Table(name = "attendee")
public class AttendeeEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private EventEntity event;
#ManyToOne
private UserEntity user;
// ..
}
Further, we have projections for these entities which we prepare as views for our clients:
/*
* Projection for User
*/
public interface User {
// All the properties ..
}
/*
* Projection for AttendeeEntity
*/
public interface Attendee {
Long getId();
User getUser();
}
/*
* Projection for EventEntity
*/
public interface Event {
Long getId();
String getName();
List<Attendee> getAttendees();
}
In one of the services we fetch UserEvent. Here, let's say, we want to remove the names of all users below 18 and still return userEvent we just fetched.
public Event getEvent(Long id, Boolean anonymize) {
Event event = this.eventRepository.findById(id);
// The "anonymize" is to highlight that I cannot
// simply solve this in a User-projection
if (!anonymize) {
return event;
}
event
.getAttendees();
.stream()
.peek(attendee -> {
User user = attendee.getUser();
if(user.getAge() < 18) {
// Here we create a new user object without a name
User newUser = new User() {
#Override
String getDisplayName() { return null; }
#Override
Integer getAge() { return user.getAge(); }
}
// !! This is where we hit the problem since we cannot
// !! replace the old user object like this
attendee.setUser(newUser);
}
});
return event;
}
One solution is to use SPEL in you projection selector. Please try
public interface Attendee {
Long getId();
#Value("#{target.user.age >= 18 ? target.user : new your.package.UserEntity()}")
User getUser();
}
Replace you.package with the package of UserEntity. Pay attention to put new UserEntity() and not new User(). This way an empty model will be projected as an empty interface User.
You can't use projections to update your code.
As a final note, it's important to remember that projections and excerpts are meant for the read-only purpose.
API Data Rest Projections Baeldung
im having a problem when adding a new entry in a many-to-many relationship because the list is huge. Ex:
Item item = new Item(1);
Category cat = dao.find(1, Category.class);
List<Category> list = new ArrayList<>();
list.add(cat);
item.setCategoryList(list);
cat.getItemList().add(item);
The problem is that the Category Itens list is huge, with a lot of itens, so performing the cat.getItemList() takes a very long time. Everywhere i look for the correct way to add a many-to-many entry says that a need to do that. Can someone help?
Edit:
A little context: I organize my itens with tags, so 1 item can have multiple tags and 1 tag can have multiple itens, the time has pass and now i have tags with a lot of itens ( > 5.000), and now when i save a new item with one of thoses tags it takes a long time, i have debuged my code and found that most of the delay is in the cat.getItensList() line, with makes sense since it has a extensive list o itens. I have searched a lot for how to do this, and everyone says that the correct way to save a entry in a many-to-many case is to add to the list on both sides of the relationship, but if one side is huge, it will takes a lot of time since calling the getItensList() loads them in the context. Im looking for a way to save my item refering the tag witout loading all of the itens of that tag.
Edit 2:
My classes:
Item:
#Entity
#Table(name = "transacao")
#XmlRootElement
public class Transacao implements Serializable {
#ManyToMany(mappedBy = "transacaoList")
private List<Tagtransacao> tagtransacaoList;
...(other stuff)
}
Tag:
#Entity
#Table(name = "tagtransacao")
#XmlRootElement
public class Tagtransacao implements Serializable {
#JoinTable(name = "transacao_has_tagtransacao", joinColumns = {
#JoinColumn(name = "tagtransacao_idTagTransacao", referencedColumnName = "idTagTransacao")}, inverseJoinColumns = {
#JoinColumn(name = "transacao_idTransacao", referencedColumnName = "idTransacao")})
#ManyToMany
private List<Transacao> transacaoList;
...(other stuff)
}
Edit 3:
WHAT I DID TO SOLVE:
As answered by Ariel Kohan, i tried to do a NativeQuery to insert the relationship:
Query query = queryDAO.criarNativeQuery("INSERT INTO " + config.getNomeBanco() + ".`transacao_has_tagtransacao` "
+ "(`transacao_idTransacao`, `tagtransacao_idTagTransacao`) VALUES (:idTransacao, :idTag);");
query.setParameter("idTransacao", transacao.getIdTransacao());
query.setParameter("idTag", tag.getIdTagTransacao());
I was able to reduce the time of que query from 10s to 300milis what it is impressive. In the end its better for my project that it is already runnig to do that instead of creating a new class that represents the many-to-many reletionship. Thanks to everyone who tried to help \o/
In this case, I would prevent your code from load the item list in memory.
To do that, I can think about two options:
Using a #Modyfing query to insert the items directly in the DB.
[Recommended for cases where you want to avoid changing your model]
You can try to create the query using normal JPQL but, depending on your model, you may need to use a native query. Using native query would be something like this:
#Query(value = "insert into ...", nativeQuery = true)
void addItemToCategory(#Param("param1") Long param1, ...);
After creating this query, you will need to update your code removing the parts where you load the objects in memory and adding the parts to call the insert statements.
[Update]
As you mentioned in a comment, doing this improved your performance from 10s to 300milis.
Modify your Entities in order to replace #ManyToMany with #OneToManys relationship
The idea in this solution is to replace a ManyToMany relationship between entities A and B with an intermediate entity RelationAB. I think you can do this in two ways:
Save only the Ids from A and B in RelationAB as a composite key (of course you can add other fields like a Date or whatever you want).
Add an auto-generated Id to RelationAB and add A and B as other fields in the RelationAB entity.
I did an example using the first option (you will see that the classes are not public, this is just because I decided to do it in a single file for the sake of simplicity. Of course, you can do it in multiple files and with public classes if you want):
Entities A and B:
#Entity
class EntityA {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public EntityA() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
#Entity
class EntityB {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public EntityB() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
RelationABEntity and RelationABId:
#Embeddable
class RelationABId implements Serializable {
private Long entityAId;
private Long entityBId;
public RelationABId() {
}
public RelationABId(Long entityAId, Long entityBId) {
this.entityAId = entityAId;
this.entityBId = entityBId;
}
public Long getEntityAId() {
return entityAId;
}
public void setEntityAId(Long entityAId) {
this.entityAId = entityAId;
}
public Long getEntityBId() {
return entityBId;
}
public void setEntityBId(Long entityBId) {
this.entityBId = entityBId;
}
}
#Entity
class RelationABEntity {
#EmbeddedId
private RelationABId id;
public RelationABEntity() {
}
public RelationABEntity(Long entityAId, Long entityBId) {
this.id = new RelationABId(entityAId, entityBId);
}
public RelationABId getId() {
return id;
}
public void setId(RelationABId id) {
this.id = id;
}
}
My Repositories:
#Repository
interface RelationABEntityRepository extends JpaRepository<RelationABEntity, RelationABId> {
}
#Repository
interface ARepository extends JpaRepository<EntityA, Long> {
}
#Repository
interface BRepository extends JpaRepository<EntityB, Long> {
}
A test:
#RunWith(SpringRunner.class)
#DataJpaTest
public class DemoApplicationTest {
#Autowired RelationABEntityRepository relationABEntityRepository;
#Autowired ARepository aRepository;
#Autowired BRepository bRepository;
#Test
public void test(){
EntityA a = new EntityA();
a = aRepository.save(a);
EntityB b = new EntityB();
b = bRepository.save(b);
//Entities A and B in the DB at this point
RelationABId relationABID = new RelationABId(a.getId(), b.getId());
final boolean relationshipExist = relationABEntityRepository.existsById(relationABID);
assertFalse(relationshipExist);
if(! relationshipExist){
RelationABEntity relation = new RelationABEntity(a.getId(), b.getId());
relationABEntityRepository.save(relation);
}
final boolean relationshipExitNow = relationABEntityRepository.existsById(relationABID);
assertTrue(relationshipExitNow);
/**
* As you can see, modifying your model you can create relationships without loading big list and without complex queries.
*/
}
}
The code above explains another way to handle this kind of things. Of course, you can make modifications according to what you exactly need.
Hope this helps :)
This is basically copied from a similar answer I gave earlier but similar question as well. The code below ran when I first write it but I changed the names to match this question so there might be some typos. The spring-data-jpa is a layer on top of JPA. Each entity has its own repository and you have to deal with that. For dealing with the many-to-many relations specifically in spring-data-jpa you can make a separate repository for the link table if you think it's a good idea.
#Entity
public class Item {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemCategory> categories;
#Entity
public class Category {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ItemCategory> items;
#Entity
public class ItemCategory {
#EmbeddedId
private ItemcategoryId id = new ItemcategoryId();
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("itemId")
private Item Item;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("categoryId")
private Category category;
public ItemCategory() {}
public ItemCategory(Item Item, Category category) {
this.item = item;
this.category = category;
}
#SuppressWarnings("serial")
#Embeddable
public class ItemCategoryId implements Serializable {
private Long itemId;
private Long categoryId;
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
ItemCategoryId that = (ItemCategoryId) o;
return Objects.equals(itemId, that.itemId) && Objects.equals(categoryId, that.categoryId);
}
#Override
public int hashCode() {
return Objects.hash(itemId, categoryId);
}
And to use it. Step 3 shows the way you are currently doing it and creates a read of the existing joins before doing the update. Step 4 just inserts a relation directly in the join table and does not cause a pre-read of the existing joins.
#Transactional
private void update() {
System.out.println("Step 1");
Category category1 = new Category();
Item item1 = new Item();
ItemCategory i1c1 = new ItemCategory(Item1, Category1);
categoryRepo.save(Category1);
ItemRepo.save(Item1);
ItemCategoryRepo.save(p1t1);
System.out.println("Step 2");
Category category2 = new Category();
Item item2 = new Item();
ItemCategory p2t2 = new ItemCategory(item2, category2);
ItemRepo.save(item2);
categoryRepo.save(category2);
ItemCategoryRepo.save(p2t2);
System.out.println("Step 3");
category2 = CategoryRepo.getOneWithitems(2L);
category2.getitems().add(new ItemCategory(item1, category2));
categoryRepo.save(Category2);
System.out.println("Step 4 -- better");
ItemCategory i2c1 = new ItemCategory(item2, category1);
itemCategoryRepo.save(i2c1);
}
I don't explicitly set the ItemCategoryId id's. These are handled by the persistence layer (hibernate in this case).
Note also that you can update ItemCategory entries either explicity with its own repo or by adding and removing them from the list since CascadeType.ALL is set, as shown. The problem with using the CascadeType.ALL for spring-data-jpa is that even though you prefetch the join table entities spring-data-jpa will do it again anyway. Trying to update the relationship through the CascadeType.ALL for new entities is problematic.
Without the CascadeType neither the items or categories lists (which should be Sets) are the owners of the relationship so adding to them wouldn't accomplish anything in terms of persistence and would be for query results only.
When reading the ItemCategory relationships you need to specifically fetch them since you don't have FetchType.EAGER. The problem with FetchType.EAGER is the overhead if you don't want the joins and also if you put it on both Category and Item then you will create a recursive fetch that gets all categories and items for any query.
#Query("select c from Category c left outer join fetch c.items is left outer join fetch is.Item where t.id = :id")
Category getOneWithItems(#Param("id") Long id);
Inside a Spring Boot server backend I need to create a bidirectional association between two entities (User/Match). A user could have many matches and a match always contains two users. The problem is that I do not find the correct cascade annotation for the association properties.
The issue is that there is no update of the user's match list, when I call the likeUser method in the MatchService. I create a new match object and set the back reference from user to match (adding the match to its list of matches) in the user-setter method of match and save the match afterwards. But the manipulation of the users match list wont be saved in the database.
Does anyone know the correct way of solving this problem?
Here is a short extract of my code:
#Entity(name = "User_")
public class User {
#Id
#GeneratedValue
private Long id;
private String username;
#JsonIgnore
private String password;
#OneToMany(targetEntity = Match.class, cascade = CascadeType.ALL)
private List<Match> matches;
public void addMatch(Match match) {
matches.add(match);
}
}
#Entity(name = "Match_")
public class Match {
#Id
#GeneratedValue
private Long id;
/**
* User 1 is always the user who likes the other person first
*/
#ManyToOne(targetEntity = User.class, cascade = CascadeType.REFRESH)
private User user1;
/**
* User 2 is the user who has to confirm the match
*/
#ManyToOne(targetEntity = User.class, cascade = CascadeType.REFRESH)
private User user2;
public void setUser1(User user1) {
this.user1 = user1;
user1.addMatch(this);
}
public void setUser2(User user2) {
this.user2 = user2;
user2.addMatch(this);
}
public void setUsers(User user1, User user2) {
this.setUser1(user1);
this.setUser2(user2);
}
}
#Service
public class MatchService {
public void addMatch(Match match) {
matchRepository.save(match);
}
/**
* Method that is called if one user likes another.
*
* #param likedUser - the user who has been liked by initialUser
* #return - the match object that has either been created or was already existing
*/
public Match likeUser(User likedUser) {
User initialUser = userService.getCurrentUser();
//check if likedUser has already like initialUser
for (Match match : likedUser.getMatches()) {
if (match.getUser2().equals(initialUser)) {
match.confirm();
return match; // its a match
}
}
Match unconfirmedMatch = new Match();
unconfirmedMatch.setUsers(initialUser, likedUser);
addMatch(unconfirmedMatch);
return unconfirmedMatch;
}
}
I have two entities, simplified for the case. The isActive field changes with current time so i did not want to store it in db.
Condition of isactive:
isActive = 1 if (current_timestamp between userstatus.datebegin and userstatus.dateend) else isActive = 0
What I want:
I want to get a set of ALL users, with their isActive values set without hitting to the db for every user and preferably; without userStatus collection carried around. Is there a way to satify this programmatically or jpa way? Or what is the best way to achieve this?
User.java:
#Entity
public class User {
#Id
private long id;
#Transient
private int isActive;
#OneToMany(mappedBy = "user",fetch = FetchType.LAZY)
private Set<UserStatus> userStatus = new HashSet<>();
}
UserStatus.java:
public class UserStatus {
#Id
private long id;
#Column
private Date dateBegin;
#Column
private Date dateEnd;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name= "user_tr_id_no")
private UmutUser user;
}
UserRepository.java:
#Repository
public interface UserRepository extends JpaRepository<User, long> {
Set<User> findAllFetchWithIsActive();
}
Ways I tried:
*Way1: PostLoad
Problem: If I dont fetch userStatus, it hits the db for every user object.
*Way2: JPQL Query:
Problem: Couldnt find a query to set transient value except something suggested in here. The problem is it hits the db for every user object again.
*Way3: Eagerly fetch userStatus, calculate isActive values in service, hide the set of userstatus in a DTO before passing around.
Problem: This is my last resort, I have doubts fetching userStatus set for ALL users is a good approach.
I think the best approach would be something like this:
#Query("SELECT e FROM User u WHERE u.userStatus.dateBegin < CURRENT_DATE AND u.userStatus.dateEnd > CURRENT_DATE")
Iterable<User> findAllActiveUsers();
And in your User class
#Entity
public class User {
#Id
private long id;
#OneToMany(mappedBy = "user",fetch = FetchType.LAZY)
private Set<UserStatus> userStatus = new HashSet<>();
#Transient
public int isActive(){
for(UserStatus status: userStatus) {
//Add your logic to check if the current date is between the range
}
}
}
You may want to use the new operator:
select new UserWithStatus(
u,
u.userStatus.dateBegin < CURRENT_DATE AND u.userStatus.dateEnd > CURRENT_DATE)
from User u
And have your object UserWithStatus:
public class UserWithStatus {
private User user;
private boolean active;
public UserWithStatus(User user, boolean active) {
this.user = user;
this.active = active;
}
}
And have this in your repository:
#Query("...")
Set<UserWithStatus> findAllFetchWithIsActive();
Note: I think this will bring you duplicate (eg: one User with several status). You might need to use a subquery and I don't know if JPQL will allow you this:
select new UserWithStatus(u,
(select count(*)
from u.userStatus w
where w.dateBegin < CURRENT_DATE
and w.dateEnd > CURRENT_DATE)) > 0
from User u
I have 2 entities in my DB with one-to-one one directional mapping:
User and PasswordResetToken. The idea behind this is to create new token each time user requests password reset and store only the latest one.
Below are my entities:
#Entity
#Table(name = "USERS")
#Getter #Setter
public class User implements Serializable {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "usersSeq")
#SequenceGenerator(name = "usersSeq", sequenceName = "SEQ_USERS", allocationSize = 1)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#Column(name = "EMAIL")
private String email;
#Column(name = "ROLE")
private Integer role;
}
///...
#Entity
#Table(name = "PASSWORD_RESET_TOKENS")
#Getter
#Setter
public class PasswordResetToken implements Serializable {
private static final int EXPIRATION = 24;
#Column(name = "TOKEN")
private String token;
#Id
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(nullable = false, name = "user_id")
private User user;
#Column(name = "EXPIRY_DATE")
private Instant expiryDate;
public PasswordResetToken() {
}
public void setExpiryDate(ZonedDateTime expiryDate) {
this.expiryDate = expiryDate.plus(EXPIRATION, ChronoUnit.HOURS).toInstant();
}
}
Also, I have DTOs created for both of them to pass them around my app.
Code snippets:
#Getter #Setter
public class PasswordResetTokenModel {
private String token;
private ZonedDateTime expiryDate;
private UserModel user;
}
UserModel is also used for Spring Security
#Getter
#Setter
public class UserModel extends User {
public UserModel(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
private long id;
private String name;
public String getEmail() {
return this.getUsername();
}
}
For population I've created 2 populators:
#Component
public class UserPopulatorImpl implements UserPopulator {
#Autowired
UserDetailsService userDetailsService;
#Override
public UserModel populateToDTO(User user) {
UserModel userModel = new UserModel(user.getEmail(), user.getPassword(), userDetailsService.getAuthorities(user.getRole()));
userModel.setId(user.getId());
return userModel;
}
#Override
public User populateToDAO(UserModel userModel) {
User user = new User();
user.setEmail(userModel.getEmail());
user.setName(userModel.getName());
user.setPassword(userModel.getPassword());
//TODO: change it!
user.setRole(1);
return user;
}
}
//...
#Component
public class PasswordResetTokenPopulatorImpl implements PasswordResetTokenPopulator {
#Autowired
UserPopulator userPopulator;
#Override
public PasswordResetTokenModel populateToDTO(PasswordResetToken passwordResetToken) {
PasswordResetTokenModel passwordResetTokenModel = new PasswordResetTokenModel();
passwordResetTokenModel.setUser(userPopulator.populateToDTO(passwordResetToken.getUser()));
passwordResetTokenModel.setToken(passwordResetToken.getToken());
passwordResetTokenModel.setExpiryDate(ZonedDateTime.ofInstant(passwordResetToken.getExpiryDate(), ZoneId.systemDefault()));
return passwordResetTokenModel;
}
#Override
public PasswordResetToken populateToDAO(PasswordResetTokenModel passwordResetTokenModel) {
PasswordResetToken passwordResetToken = new PasswordResetToken();
passwordResetToken.setExpiryDate(passwordResetTokenModel.getExpiryDate());
passwordResetToken.setUser(userPopulator.populateToDAO(passwordResetTokenModel.getUser()));
passwordResetToken.setToken(passwordResetTokenModel.getToken());
return passwordResetToken;
}
}
I'm saving object using
sessionFactory.getCurrentSession().saveOrUpdate(token);
When I use this code, I'm getting following exception
object references an unsaved transient instance - save the transient instance before flushing: com.demo.megaevents.entities.User
There are currently 2 issues in this code:
Seems like Cascade.ALL in my OneToOne mapping is not working. If
I create separate primary key in Token class everything works almost
as expected but storing every created token in DB (more like
OneToMany relation), however I want to avoid it as I need to store
only one token per user in my DB
I don't like using new in populators, as it forces hibernate to create new object while flushing session. However, I also don't want to do another select to fetch this data from DB because just before mentioned populator I already do this query to fetch it and I think that it's an overhead.
Also, I really want to have DTOs and I don't want to remove DTO layer.
So, my questions:
What is the correct way to handle population between DTO and entities?
Are there any other improvements (probably architectural) to my solution?
Thanks a lot.
I'm not sure why you would let UserModel extend User, but I guess you did that because you didn't want to have to copy all properties from User into UserModel. Too bad, because that's what is going to be needed to have a clean separation between the entity model and data transfer model.
You get that exception because you try to persist a PasswordResetToken that has a reference to a User object with an id, but the User isn't associated with the current session. You don't have to query the user, but at least association it with the session like this:
PasswordResetToken token = // wherever you get that from
Session s = sessionFactory.getCurrentSession();
token.setUser(s.load(User.class, token.getUser().getId());
s.persist(token);
Cascading would cause the User to be created/inserted or updated via a SQL INSERT or UPDATE statement which is apparently not what you want.
You could do the Session.load() call in you populators if you want, but I'd not do that. Actually I would recommend not having populators at all, but instead create the entity objects in your service instead.
Normally you only have a few(mostly 1) ways of actually creating a new entity object, so the full extent of the transformation from DTO to entity will only be relevant in very few cases.
Most of the time you are going to do an update and for that, you should first select the existing entity and apply the fields that are allowed to be changed from the DTO on the entity object.
For providing the presentation layer with DTOs I would recommend using Blaze-Persistence Entity Views to avoid the manual mapping boilerplate and also improve performance of select queries.