Best way to handle JPA merge?
Asked Answered
B

2

11

I'm new to the whole JPA thing so I have multiple questions about the best way to handle JPA merge and persist.

  1. I have an user object which should be updated (some values like date and name). Do I have to merge the passed object first or is it safer to find the new object?

    Currently my code for updating a user looks like this:

    public void updateUserName(User user, String name) {
         // maybe first merge it?
         user.setName(name);
         user.setChangeDate(new Date());
         em.merge(user);   
     }
    

    How can I be sure that the user has not been manipulated before the update method is called? Is it safer to do something like this:

     public void updateUserName(int userId, String name) {
         User user = em.getReference(User.class, userId);
         user.setName(name);
         user.setChangeDate(new Date());
         em.merge(user);   
     }
    

    Maybe other solutions? I watched multiple videos and saw many examples but they all were different and nobody explained what the best practice is.

  2. What is the best approach to add children to relationships? For example my user object has a connection to multiple groups. Should I call the JPA handler for users and just add the new group to the user group list or should I create the group in a so group handler with persist and add it manually to my user object?

Hope somebody here has a clue ;)

Brumal answered 5/4, 2013 at 11:28 Comment(1)
See #1070492Ingratiating
S
8
  1. It depends on what you want to achieve and how much information you have about the origin of the object you're trying to merge.

    Firstly, if you invoke em.merge(user) in the first line of your method or in the end it doesn't matter. If you use JTA and CMT, your entity will be updated when method invocation finishes. The only difference is that if you invoke em.merge(user) before changing the user you should use the returned instance instead of your parameter, so either it is:

    public void updateUserName(User user, String name) {
       User managedUser = em.merge(user);
       managedUser.setChangeDate(new Date());
       // no need of additional em.merge(-) here.
       // JTA automatically commits the transaction for this business method.
    }
    

    or

    public void updateUserName(User user, String name) {
       user.setChangeDate(new Date());
       em.merge(user);
       // JTA automatically commits the transaction for this business method.
    }
    

    Now about updating entity.
    If you just want to update some well-defined fields in your entity - use the second approach as it's safer. You can't be sure if a client of your method hasn't modified some other fields of your entity. Therefore, em.merge(-) will update them as well which might not be what you wanted to achieve.

    On the other hand - if you want to accept all changes made by user and just override / add some properties like changeDate in your example, the first approach is also fine (merge whole entity passed to the business method.) It really depends on your use-case.

  2. I guess it depends on your cascading settings. If you want to automatically persist / merge all Groups whenever the User entity is changed - it's safe to just add it to the User's collection (something like User#addGroup(Group g) { groups.add(g)}. If you don't want cascading, you can always create your own methods that will propagate to the other side of the relationship. It might be something like: User#addGroup(Group g) that automatically invokes g.addUser(this);.

Stedfast answered 5/4, 2013 at 12:15 Comment(0)
A
4

Question 1

  • The merge method must be called on a detached entity.
  • The merge method will return the merged object attached to the entityManager.

What does it mean ?

An entity is detached as soon as the entityManager you use to fetch it is closed. (i.e. most of the time because you fetch it in a previous transaction).

In your second sample of code: user is attached (because you just fetch it) and so calling merge is useless. (BTW : it is not getReference but find)

In your first sample: we don't know the state of user (detached entity or not ?). If it is detached, it make sense to call merge , but careful that merge don't modify it's object passed as an argument. So here is my version of your first sample:

/**
 * @param user : a detached entity
 * @return : the attached updated entity
**/
public User updateUserName(User user, String name) {
   user.setName(name);
   user.setChangeDate(new Date());
   return em.merge(user);   
}

Question 2

Maybe some code sample to explain what you mean by jpa handler can help us to understand your concern. Anyway, I'll try to help you.

If you have a persistent user and you need to create a new group and associating it with the persistent user:

User user = em.find(User.class, userId);
Group group = new Group();
...
em.persist(group);
user.addToGroups(group);
group.addToUsers(user); //JPA won't update the other side of the relationship
                        //so you have to do it by hand OR being aware of that 

If you have a persistent user and a persistent group and you need to associate them:

User user = em.find(User.class, userId);
Group group = em.find(Group.class, groupId);
...
user.addToGroups(group);
group.addToUsers(user);

General considerations

The best practices regarding all of this really depends on you manage the transactions (and so the lifecycle of the entityManager) vs the lifecycle of your objects.

Most of the time: an entityManager is a really short time living object. On the other hand your business objects may live for longer and so you will have to call merge (and being careful about the fact that merge don't modify the object passed in argument !!!).

You can decide to fetch and modify your business objects in the same transaction (i.e. with the same entityManager): it means a lot more database access and this strategy must generally be combined with a second-level cache for performance reason. But in this case you won't have to call merge.

I hope this help.

Ambrosio answered 5/4, 2013 at 12:15 Comment(1)
Thank you for your reply. Regarding to my question 1, isn't it the easiest way just to set my fields like date und new name in my method which calls the JPA handler and just call a method "updateUser(User user)" to just merge the user object and save all changes instead of making methods for several update use cases?Brumal

© 2022 - 2024 — McMap. All rights reserved.