2

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

Dean Hiller
  • 19,235
  • 25
  • 129
  • 212

1 Answers1

1

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.

Maulik Parmar
  • 617
  • 4
  • 10
  • hmmm, did it award the bounty? (or I may need to recreate to award you?). I just saw this answer as I am getting back to this. Also, I should have been more clear in my post. The 'entity' is either created new UserDBO by framework OR in edit mode, is loaded entityMgr.find(id, UserDBO.class). persist USED to fail!! and now it seems to succeed which was just weird to me. – Dean Hiller Feb 09 '20 at 16:08
  • I generally always want this saveOrUpdate() from old hibernate. All the rest, I really never need and like to keep it KISS anyways. Is persist the closest then to saveOrUpdate() now? (it used to be 10 years ago that merge was the closest and people said that was for saveOrUpdate.....which is just funny how in 10 years it completely changed). In fact persist NEVER worked for saveOrUpdate before and merge did, but I have not done hibernate for a long long time. – Dean Hiller Feb 09 '20 at 16:17
  • Yeah, things change with times, when you look at hibernate changelog, it mentions that there were some side effects on cascading and they addressed it in latest release that might be the root of changing behaviour. Hibernate tries to be consistent with JPA and that is why you are witnessing the changes as community grows and learns from mistakes. Apart from that, I don't see any problem using presist and update separately because coding logic allows it and it should be standard way. P.S. I didn't receive points as it is not marked as answer. Anyways, hope you learned something! – Maulik Parmar Feb 10 '20 at 06:28
  • Parmer revisiting this again, the doc said "@throws EntityExistsException if the entity already exists." so you are saying the doc is wrong? ie. calling persist in my example above(as I say above already) works for BOTH editing and new entities. Should that not happen. Man, I really just want the old mgr.saveOrUpdate hibernate method...so less confusing. JPA screwed too many things up over the original hibernate. – Dean Hiller May 29 '20 at 18:31
  • ok, I edited the post and now link the code where I call em.persist() for UPDATES.....I don't get why you say the code is working as it should since the doc says persist should throw if the entity exists which it does exist in the DB. – Dean Hiller May 29 '20 at 18:42
  • "The persist method works because it has decides when to use insert or update command" to be clear. this statement directly conflicts with javadoc on persist method which says "@throws EntityExistsException if the entity already exists." – Dean Hiller May 29 '20 at 18:50
  • Read the last cite, it explains how persisted instance behaves at flush time. In standard runtimes persist is usually done within transaction boundaries. Persist will keep object in session updated till flush happens. If it has happened you are likely to get EntityExists exception. It does not break docs or method itself. It's behaving as it's supposed to. It simply keeps object updated in application memory till Manager actually flushes the data. Save on other hand instantly fires query to db and guarantees to return object identifier. Persist is simply doing it's persisting job. – Maulik Parmar May 29 '20 at 19:05