Duplicate a collection of entities and persist in Hibernate/JPA

14,014

Solution 1

Aaron Diguila's answer is the way to go here, i.e. you need to detach your instances, set the business key to null and then persist them.

Sadly, there is no way to disconnect one object from the entity manager with JPA 1.x (JPA 2.0 will have EntityManager.detach(Object) and fix this). So, either wait for JPA 2.x (not an option I guess) or use Hibernate's underlying Session.

To do so, you can cast the delegate of an EntityManager to an Hibernate Session.

Session session = (Session) em.getDelegate();

Of course, this only works if you use Hibernate as a Java Persistence provider, because the delegate is the Session API.

Then, to detach your object:

session.evict(object);

UPDATE: According to Be careful while using EntityManager.getDelegate(), with GlassFish one should actually use (and likely in your case too) :

org.hibernate.Session session = ((org.hibernate.ejb.EntityManagerImpl) em.getDelegate()).getSession();

But this would not work in JBoss that suggest to use the code previously mentioned.

org.hibernate.Session session = (Session) em.getDelegate();

While I understand that using getDelegate() makes JPA code non-portable, I must admit that I was not expecting the result of this method call to be implementation specific.

UPDATE2: To answer the updated part of the question, I'm not sure that you eagerly loaded the categories. This is not the best way to do this but what happens if you call categories.get(0) before eviction? Also, I may be missing that part but, where do you nullify the key of categories?

Solution 2

You need to detach your instances from the session. There are three ways to do this:

  1. Close the session (probably not possible in your case).
  2. Serialize the object and deserialize it again.
  3. Clone the object and clear/null the primary key/id field.

Then you must change the business key (so the new instances will return false when calling equals() with an unmodified instance). This is the important step: Without it, Hibernate will reattach the instances to the existing ones in the DB or you'll get other, strange errors.

After that, you can save the new copies just like any other instance.

Share:
14,014
Michael Bavin
Author by

Michael Bavin

Updated on July 13, 2022

Comments

  • Michael Bavin
    Michael Bavin almost 2 years

    I want to duplicate a collection of entities in my database. I retreive the collection with:

    CategoryHistory chNew = new CategoryHistory();
    CategoryHistory chLast =  (CategoryHistory)em.createQuery("SELECT ch from CategoryHistory ch WHERE ch.date = MAX(date)").getSingleResult;
    List<Category> categories = chLast.getCategories();
    chNew.addCategories(categories)// Should be a copy of the categories: OneToMany
    

    Now i want to duplicate a list of 'categories' and persist it with EntityManager. I'm using JPA/Hibernate. UPDATE

    After knowing how to detach my entities, i need to know what to detach: current code:

        CategoryHistory chLast =  (CategoryHistory)em.createQuery("SELECT ch from CategoryHistory ch WHERE ch.date=(SELECT MAX(date) from CategoryHistory)").getSingleResult();
        Set<Category> categories =chLast.getCategories();
    
        //detach
        org.hibernate.Session session = ((org.hibernate.ejb.EntityManagerImpl) em.getDelegate()).getSession();
        session.evict(chLast);//detaches also its child-entities?       
    
        //set the realations
        chNew.setCategories(categories);
        for (Category category : categories) {
            category.setCategoryHistory(chNew);
        }
        //set now create date
        chNew.setDate(Calendar.getInstance().getTime());
    
        //persist
        em.persist(chNew);
    

    This throws a failed to lazily initialize a collection of role: entities.CategoryHistory.categories, no session or session was closed exception.

    I think he wants to lazy load the categories again, as i have them detached. What should i do now?