One DAO per object - how to handle links?

I am writing an application with typical two objects: User and UserGroup. The latter may contain one or more instances of the former. I have the following (more / less) mapping for this:

User:

public class User { @Id @GeneratedValue private long id; @ManyToOne(cascade = {CascadeType.MERGE}) @JoinColumn(name="GROUP_ID") private UserGroup group; public UserGroup getGroup() { return group; } public void setGroup(UserGroup group) { this.group = group; } } 

User Group:

 public class UserGroup { @Id @GeneratedValue private long id; @OneToMany(mappedBy="group", cascade = {CascadeType.REMOVE}, targetEntity = User.class) private Set<User> users; public void setUsers(Set<User> users) { this.users = users; } } 

Now I have a separate DAO class for each of these objects (UserDao and UserGroupDao). All of my DAOs have EntityManager entered using the @PersistenceContext annotation, for example:

 @Transactional public class SomeDao<T> { private Class<T> persistentClass; @PersistenceContext private EntityManager em; public T findById(long id) { return em.find(persistentClass, id); } public void save(T entity) { em.persist(entity); } } 

In this layout, I want to create a new user and assign it to an existing user group. I do it like this:

 UserGroup ug = userGroupDao.findById(1); User u = new User(); u.setName("john"); u.setGroup(ug); userDao.save(u); 

Unfortunately, I get the following exception:

the object refers to an unsaved transient instance - saves the transient instance before flushing: xyzmodel.User.group โ†’ xyzmodel.UserGroup

I researched it, and I think this happens because each DAO instance has a different entityManager assigned (I checked this - the links in each DAO to the object manager are different), and the UserGroup instance passed for the entityManager user does not control.

I tried to combine the user group assigned to the user into the UserDAO object manager. There are two problems:

  • It still doesn't work - the object manager wants to overwrite the existing UserGroup, and it gets an exception (obviously)
  • even if it works, I would end up writing a merge code for each related object

The described case works when searching and saving are performed using the same entity manager. This indicates a question (s):

  • Is my project broken? I think this is very similar to the recommended one in this answer . Should there be a single EntityManager for all DAOs (web applications otherwise)?
  • Or should the assignment of the group within the DAO be done? in this case, I would finish writing a lot of code in the DAO
  • Should I get rid of DAO? If so, how to handle data access correctly?
  • any other solution?

I use Spring as a container and Hibernate as a JPA implementation.

+4
source share
3 answers

Different instances of EntityManager are normal in Spring. It creates proxies that dynamically use the object manager, which is currently in the transaction, if it exists. Otherwise, a new one will be created.

The problem is that your transactions are too short. Getting your user group is done in a transaction (since the findById method findById implicitly @Transactional ). But then the transaction is completed, and the group is disconnected. When you save a new user, he will create a new transaction that will not be executed because the user is referencing a separate object.

A way to solve this problem (and generally do such things) is to create a method that performs the entire operation in one transaction. Just create this method in the service class (any Spring-driven component will work) and also annotate it using @Transactional .

+2
source

I donโ€™t know Spring, but the JPA problem is that you are saving a User that has a link to a UserGroup , but the JPA thinks that the UserGroup is transient .

transient is one of the life cycle states that a JPA can reside in. This means that it has just been created with a new operator, but has not yet been saved (does not yet have permanent identification).

Since you are getting an instance of UserGroup through the DAO, something seems to be wrong there. Your instance should not be transient , but detached . Can you print the identifier of the UserGroup instance immediately after receiving it from the DAO? And perhaps also show the implementation of findById ?

You do not have a cascade in relation to group , so it should usually work if the object is really detached. Without a new entity, the JPA simply does not have the ability to set FK correctly, as this would require an UserGroup instance identifier, but this (seemingly) does not exist.

A merge should also not overwrite your individual object. What exception do you get here?

I only partially agree with the answers that others give here, that everything should be in one transaction. Yes, it really can be more convenient, since the UserGroup instance will still be โ€œattachedโ€, but it should not be - required. JPA perfectly transfers new objects with references to other new entities or existing (separate) objects that were received in another transaction. See Saving the JPA Cascade and references to individual objects throw a PersistentObjectException. Why?

+2
source

I donโ€™t know how, but I managed to solve it. I tried to assign a user group a field with a version of NULL in the database (a field annotated with @Version). I realized this was a problem when I tested the GWT RequestFactory that used this table. When I set the field to 1, everything starts working (no changes to transaction processing are required).

If the NULL version field really caused the problem, this would be one of the most erroneous exception messages I have ever received.

+1
source

Source: https://habr.com/ru/post/1435991/


All Articles