views:

885

answers:

3

Hi, 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?

+4  A: 

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.

Aaron Digulla
Thank you, I understand your answer.Is it possible to detach from the Entitymanager? If not how can i get the current hibernate session? Thank you!!
Michael Bavin
`(Session)em.getDelegate ()` gets you the current session. The EM has no method to detach objects, you can only remove (= delete) them.
Aaron Digulla
@Aaron: Would it work with other JPA implementations? Just curious, because the method return a generic Object type instance and they said its implementation specific.
Adeel Ansari
I can't find the detach method on a org.hibernate.Session? Or am i blind?https://www.hibernate.org/hib_docs/v3/api/org/hibernate/Session.html
Michael Bavin
Even, Session s = ((Session)em.getDelegate()); throws: ClassCastException: org.hibernate.ejb.EntityManagerImpl cannot be cast to org.hibernate.Session
Michael Bavin
Argh ... yeah, no detach(). You must clone the object and make sure the primary key (the ID) is cleared/nulled.
Aaron Digulla
As for the classcast, you'll need to examine the returned object in a debugger. The delegate depends on your JPA framework and how you setup JPA. So it's a bit different for each of them (JBoss, Hibernate, Spring, ...)
Aaron Digulla
+3  A: 

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?

Pascal Thivent
Thank you for your comment, but it was already very clear what Aaron said:)
Michael Bavin
@Michael Indeed but 1. Aaron didn't mention initially how to get the `Session` 2. while answering, you are not notified by comments to answers so I didn't notice he answered that part 3. I wanted to mention that JPA 2.0 does have the `detach()` method and Aaron didn't mention that. And at the end, it looks like you are going this way so at least one guy found it valuable.
Pascal Thivent
A: 

Ok,

Since I'm using glassfish v3, and JPA2.0 is final, i used the EntityManager.detach() Strangely ejb3-persistence.jar was included in my lib, so i throwed it out and used javax.persistence of the glassfish jar. The detach method is there but my hibernate version has no implementation yet

Michael Bavin
So actually, there was some value in mentioning that this method was there in JPA 2.0.
Pascal Thivent
@Michael: Please don't post comments to your question as answers. Edit the question instead.
Aaron Digulla
@Aaron, ok I thought this could be an answer for other using a implementation of JPA 2.0 :)
Michael Bavin