views:

169

answers:

5

Below is my test code:

package jee.jpa2;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
@Test
public class Tester {
    EntityManager em;
    EntityTransaction tx;
    EntityManagerFactory emf;

    @BeforeClass
    public void setup() {
        emf = Persistence.createEntityManagerFactory("basicPU", System.getProperties());
    }

    @Test
    public void insert() {
        Item item = new Item();
        for (int i = 0; i < 1000; ++i) {
            em = emf.createEntityManager();
            tx = em.getTransaction();
            tx.begin();
            item.setId(null);
            em.persist(item);
            tx.commit();
            em.clear();
            em.close();
            tx=null;
            em=null;
        }
    }

    @Test
    public void read() {
        em = emf.createEntityManager();
        tx = em.getTransaction();
        tx.begin();
        Query findAll = em.createNamedQuery("findAll");
        List<Item> all = findAll.getResultList();
        for (Item item : all) {
            System.out.println(item);
        }
        tx.commit();
    }
}

And here is the entity:

package jee.jpa2;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQuery;

@Entity
@NamedQuery(name="findAll", query="SELECT i FROM Item i")
public class Item {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID", nullable = false, updatable= false)
    protected Long id;
    protected String name;

    public Item() {
        name = "Digambar";
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return String.format("Item [id=%s, name=%s]", id, name);
    }

}

After executing test I get Error:

Item [id=1, name=Digambar]
Item [id=2, name=Digambar]
PASSED: read
FAILED: insert
<openjpa-2.0.0-r422266:935683 nonfatal store error> org.apache.openjpa.persistence.EntityExistsException: Attempt to persist detached object "jee.jpa2.Item-2".  If this is a new instance, make sure any version and/or auto-generated primary key fields are null/default when persisting.
FailedObject: jee.jpa2.Item-2
    at org.apache.openjpa.kernel.BrokerImpl.persist(BrokerImpl.java:2563)
    at org.apache.openjpa.kernel.BrokerImpl.persist(BrokerImpl.java:2423)
    at org.apache.openjpa.kernel.DelegatingBroker.persist(DelegatingBroker.java:1069)
    at org.apache.openjpa.persistence.EntityManagerImpl.persist(EntityManagerImpl.java:705)
    at jee.jpa2.Tester.insert(Tester.java:33)

Please Explain whats happening here?

A: 

Have you had a peek at the source code? It's deep in the bowels of the kernel:

http://svn.apache.org/viewvc/openjpa/trunk/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/BrokerImpl.java?view=markup

Line 2563

Ash Kim
please explain whats that secret?
Digambar Daund
A: 

OpenJPA 2.0 can dynamically install runtime enhancer when running on JDK 1.6 (not JRE), see docs. Technically, they attach a java agent dynamically using Attach API, wihtout need for -javaagent option.

So, the Item class is actually enhanced and entity manager knows that it's persisting a detached instance despite the null id and closing of entity manager.

axtavt
I am retriving new EntityManager from entityManagerFactory, how this new instance know that this entity is already persisted?
Digambar Daund
@Digambar: The entity is enhanced, so it holds the information that it has been persisted itself. If you disable the enhancement, you would be able to persist the same instance multiple times.
axtavt
+1  A: 

To strictly answer the question title, runtime enhancement is done using a javaagent that can be dynamically loaded when using JDK 1.6 (this is documented in Entity Enhancement).

Regarding the problem you're facing, I suspect it to be an OpenJPA bug (I've seen several similar issues like OPENJPA-755), the error message is incoherent with your code since you're setting the primary key field to null and don't have any version field. You should report it.

That being said, an easy way to "workaround" the issue would be to create a new Item instance inside the loop. Something like this:

public void insert() {
    for (int i = 0; i < 1000; ++i) {
        Item item = new Item();
        em = emf.createEntityManager();
        tx = em.getTransaction();
        tx.begin();
        em.persist(item);
        tx.commit();
        em.clear();
        em.close();
        tx=null;
        em=null;
    }
}

Additional notes:

Why are you passing System.getProperties() when creating the EntityManagerFactory?

You should close the EntityManagerFactory at the end of your test:

@AfterClass
public static void closeEntityManagerFactory() {
    emf.close();
}

Update: To answer a comment. From the JPA specification:

3.2 Entity Instance’s Life Cycle

This section describes the EntityManager operations for managing an entity instance’s lifecycle. An entity instance may be characterized as being new, managed, detached, or removed.

  • A new entity instance has no persistent identity, and is not yet associated with a persistence context.
  • A managed entity instance is an instance with a persistent identity that is currently associated with a persistence context.
  • A detached entity instance is an instance with a persistent identity that is not (or no longer) associated with a persistence context.
  • A removed entity instance is an instance with a persistent identity, associated with a persistence context, that is scheduled for removal from the database.

So if you have a detached entity (that is thus not associated with a persistent context) and if you set the Id annotated fields to null (it has thus no persistent identity), then you have exactly what the specification defines as a NEW entity, regardless of OpenJPA behavior. That's why I consider this to be a bug in OpenJPA (and the error message is incoherent anyway).

Pascal Thivent
+1 on your first answer, -1 on your second. This isn't a bug ... I think it is a lack of understanding in how OpenJPA works.
Rick
+1 Two out of three popular JPA providers agree with Pascal.
Justin
I don't believe that the spec says that setting an ID field to null transitions a detached Entity to a new instance anywhere. That is an interpretation of the spec.
Rick
@Rick Yes, this is an interpretation and I don't recommend to rely on this interpretation (I indeed believe that a transition from detached to new is not right). The problem is that the spec doesn't close this door (and there should be no room for interpretation in a spec). But anyway, the error message could be improved.
Pascal Thivent
A: 

The error message tells you what the problem is.

Attempt to persist detached object "jee.jpa2.Item-2".

After you persist your Entity on the first loop, all subsequent calls to em.persist(..) you are actually passing in a detached Entity (Not a new Entity). Setting the id field to NULL does not mean that the Entity is now new.

Rick
but I am passing same entity to new entityManager which does not know was this entity already persisted or not?
Digambar Daund
When OpenJPA enhances an Entity it adds a field under the covers which tracks state (Which is supposed to allow for more efficient EntityManager.merge(...) operations). When you pass an instance which was persisted by a different EntityManager to a new one, the new EM is able to detect that this instance is detached and not new.
Rick
Look at your Entity in the debugger and you'll see pcDetachedState and pcStateManager which the OpenJPA enhancer added to track state.
Rick
A: 

Point to note that is "Entity Manager keeps track of entities through Java Object References."

I can only persist same java object/reference again and again in for loop if and only if I move emf = Persistence.createEntityManagerFactory("basicPU") statement in for loop. like this:

Listing 1:

Item item = new Item(); 
    for (int i = 0; i < 1000; ++i) { 
        emf = Persistence.createEntityManagerFactory("basicPU");            
        em = emf.createEntityManager();         
        tx = em.getTransaction();
        tx.begin(); 
        item.setId(null); 
        em.persist(item); 
        tx.commit();
        em.clear();
        em.close(); 
    } 

But if the for loop is like :

Listing 2:

Item item = new Item(); 
    emf = Persistence.createEntityManagerFactory("basicPU");     
    for (int i = 0; i < 1000; ++i) { 
        em = emf.createEntityManager();         
        tx = em.getTransaction();
        tx.begin(); 
        item.setId(null); 
        em.persist(item); 
        tx.commit();
        em.clear();
        em.close(); 
    } 

I get the EntityExistException.

Does it mean that emf.createEntityManager() method returns the cached copy of EntityManager who is keeping track of item object persisted in previous iteration of for loop.? a BIG NO. because when I tried to execute below lines..

emf = Persistence.createEntityManagerFactory("basicPU");
        for (int i = 0 ; i<10; i++){
                System.out.println(emf.createEntityManager());
        }

It printed..

org.apache.openjpa.persistence.EntityManagerImpl@18105e8
org.apache.openjpa.persistence.EntityManagerImpl@9bad5a
org.apache.openjpa.persistence.EntityManagerImpl@91f005
org.apache.openjpa.persistence.EntityManagerImpl@1250ff2
org.apache.openjpa.persistence.EntityManagerImpl@3a0ab1
org.apache.openjpa.persistence.EntityManagerImpl@940f82
org.apache.openjpa.persistence.EntityManagerImpl@864e43
org.apache.openjpa.persistence.EntityManagerImpl@17c2891
org.apache.openjpa.persistence.EntityManagerImpl@4b82d2
org.apache.openjpa.persistence.EntityManagerImpl@179d854

Which means emf.createEntityManager() always returns new instance of entityManager.

So in Listing 1 and Listing 2, I always gets new EntityManager instance and still with Listing 1 I can persist same object in database but in Listing 2 I get EntityExistException. Why So?

I think If I have new EntityManager in both Listings(1 and 2) I should be able to persist same object in database because EntityManager keeps track of entities through java object references and In each iteration I have new EntityManager, which dont know or should not know the object references saved/persisted in database in previous iteration by some other EntityManager object instance.

Digambar Daund
I'm running on trunk and your Listing 1 isn't the behavior that I'm observing *unless* you have the following property set System.setProperty("openjpa.DetachState", "loaded(LiteAutoDetach=true)");. If that's the case, this changes things a little bit.
Rick