简体   繁体   中英

Hibernate merge vs. persist. Were there changes?

I had an app with the following code working just fine until I upgraded hibernate (5.3.2 to 5.4.10 )

    List<UserRole> roles = entity.getRoles();
    for(UserRole r : roles) {
        Em.get().remove(r);
    }
    roles.clear();

    for(RoleEnum r : selectedRoles) {
        UserRole role = new UserRole(entity, r);
        Em.get().persist(role);
    }

    Em.get().merge(entity);
    Em.get().flush();

So, then I started getting an exception

Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : WEBPIECESxPACKAGE.base.libs.UserRole.user -> WEBPIECESxPACKAGE.base.libs.UserDbo

This would happen when I 'add' a new user entity. If I edit an old user(it uses the same exact code), then it would be fine.

I changed to Em.get().persist(entity) instead and that works for adding a new entity to DB and for editing an old one.

BUT the documentation still says what old JPA/hibernate used to do for persist which is

@throws EntityExistsException if the entity already exists.

Is everyone using persist now as the add or edit function? (ie. having one function that saves or edits as I don't really care which is very very nice AND hibernate can tell from the DB id existing or not whether it is an add or an edit so there is no reason to not have a single call for both).

I am NOW using em.persist() which is working for UPDATE or SAVE...weird

It can be seen on line 110 here https://github.com/deanhiller/webpieces/blob/master/webserver/webpiecesServerBuilder/templateProject/WEBPIECESxAPPNAME/src/main/java/webpiecesxxxxxpackage/web/crud/CrudUserController.java

I am using Hibernate 5.4.10

thanks, Dean

Possible Duplicate of Update Vs Merge

Whats happening here is: Edit Mode :

List<UserRole> roles = entity.getRoles(); //Gets Existing Roles from DB
for(UserRole r : roles) {
    Em.get().remove(r); //Removes Roles to existing user
}
roles.clear(); // Clean up local memory

for(RoleEnum r : selectedRoles) { // User Input Roles
    UserRole role = new UserRole(entity, r); // New Entity with existing user
    Em.get().persist(role); // Role Entity Referenced to existing user object, saved
}

Em.get().merge(entity); // ?? No Need in edit unless roles are stored in user table
Em.get().flush();

New User Mode :

List<UserRole> roles = entity.getRoles(); // New Detached User Entity Roles
    for(UserRole r : roles) { // Probably Empty Roles Array
        Em.get().remove(r); // Removed roles
    }
    roles.clear(); // Clean up Memory

    for(RoleEnum r : selectedRoles) { // Copy from App Roles
        UserRole role = new UserRole(entity, r); //Create new role
        Em.get().persist(role); //Save Role to DB
    }

    Em.get().merge(entity); // Trying to merge non existing Entity <-- This is where error appears
    Em.get().flush();

The persist method works because it has decides when to use insert or update command. Since new user entity has no ID set to it, it has no idea what to do with it, while it may have worked in past, actual behavior of mergig is very well explain in this thread merging a detached or new entity with an existing entity in hibernate/jpa best practice question

See for yourself :

If your entity is a detached entity the only thing u really need to do is to invoke entityManager.merge(user). You dont need to exec any finder method. If your entity is not detached but rather new (it does not have id specified) you should find appropriate entity in the database prior performing any modification operations on that entity and merge it afterwards.

Another detailed reference is given here : persist() and merge() in JPA and Hibernate

Here is the reference from docs :

Serializable save(Object object) throws HibernateException

 Persist the given transient instance, first assigning a generated identifier. (Or using the current value of the identifier property if the assigned generator is used.) This operation cascades to associated instances if the association is mapped with cascade="save-update". 
 Parameters:
     object - a transient instance of a persistent class 
 Returns:
     the generated identifier 
 Throws:
     HibernateException

persist

void persist(String entityName, Object object) throws HibernateException

Make a transient instance persistent. This operation cascades to associated instances if the association is mapped with cascade="persist".

The semantics of this method are defined by JSR-220.

Parameters:
    object - a transient instance to be made persistent 
Throws:
    HibernateException

merge

Object merge(String entityName, Object object) throws HibernateException

Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance. The given instance does not become associated with the session. This operation cascades to associated instances if the association is mapped with cascade="merge".

The semantics of this method are defined by JSR-220.

Parameters:
    object - a detached instance with state to be copied 
Returns:
    an updated persistent instance 
Throws:
    HibernateException

save() and persist() result in an SQL INSERT, delete() in an SQL DELETE and update() or merge() in an SQL UPDATE. Changes to persistent instances are detected at flush time and also result in an SQL UPDATE. saveOrUpdate() and replicate() result in either an INSERT or an UPDATE.

Conclusion: Functions are behaving as they are intended.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM