Search code examples
hibernatejpamergeopenjpaentitymanager

OpenJPA vs Hibernate: significant difference between EntityManager.merge


So, I'm seeing major differences in behavior between OpenJPA (2.1.1) and Hibernate (3.3.1) with EntityManager.merge. Consider the following class:

@Entity(name = "ChoiceHolder")
@Table(name = "CHOICEHOLDER")
@Inheritance(strategy = InheritanceType.JOINED)
public class ChoiceHolder
    implements Equals, HashCode
{

    protected int id;
    protected String choice3;
    protected String choice4;

    @Id
    @Column(name = "ID", scale = 0)
    public int getId() {
        return id;
    }

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

    @Basic
    @Column(name = "CHOICE3", length = 255)
    public String getChoice3() {
        return choice3;
    }

    public void setChoice3(String value) {
        this.choice3 = value;
    }

    @Basic
    @Column(name = "CHOICE4", length = 255)
    public String getChoice4() {
        return choice4;
    }
}

And consider the following client code (this is just an SSCCE - in my actual use case, the two calls to merge with the same ID reflect received data from two separate client operations, where the id is intended to be the same between the two ops):

ChoiceHolder holder1a = new ChoiceHolder();
holder1a.setId(1);
holder1a.setChoice3("foo");
ChoiceHolder holder1b = new ChoiceHolder();
holder1b.setId(1);
holder1b.setChoice4("bar");

EntityManager em1 = factory.createEntityManager();
em1.merge(holder1a);
em1.close();
EntityManager em2 = factory.createEntityManager();
em2.merge(holder1b);
em2.close();
EntityManager em1Find = factory.createEntityManager();
ChoiceHolder holder1result = em1Find.find(ChoiceHolder.class, 1);
em1find.close();

ChoiceHolder holder2a = new ChoiceHolder();
holder2a.setId(2);
holder2a.setChoice3("foo2");
ChoiceHolder holder2b = new ChoiceHolder();
holder2b.setId(2);
holder2b.setChoice4("bar2");

EntityManager em3 = factory.createEntityManager();
em3.merge(holder2a);
em3.merge(holder2b);
em3.close();
EntityManager em2Find = factory.createEntityManager();
ChoiceHolder holder2result = em2Find.find(ChoiceHolder.class, 2);
em2Find.close();

When I run the above code using Hibernate, holder1result has null for choice3, and "bar" for choice4. Likewise, holder2result has null for choice3, and "bar2" for choice4. However, when I run the same code using openJPA, I get something very different: holder1result has both choice3 and choice4 set, and the call to em3.merge(holder2b) blows up with a ReportingSQLException, because the INSERT it tries to do violates the primary key constraint on choiceholder's ID.

If it matters, here are the relevant bits from the properties file I'm using to configure Hibernate:

hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.hbm2ddl.auto=create-drop
hibernate.cache.provider_class=org.hibernate.cache.HashtableCacheProvider
hibernate.jdbc.batch_size=0

And here's the corresponding bit from my persistence.xml that is being used to configure openJPA:

<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>
<property name="openjpa.jdbc.DBDictionary" value="postgres"/>
<property name="openjpa.ConnectionDriverName" value="org.postgresql.Driver"/>
<property name="openjpa.jdbc.MappingDefaults" value="DeferConstraints=true,ForeignKeyDeleteAction=restrict,JoinForeignKeyDeleteAction=restrict"/>

In my particular case, what I want is the behavior I'm seeing with Hibernate; however, I'm required by my environment (inside Karaf) to use OpenJPA instead. Are there flags I can set on OpenJPA to alter its behavior to match what I'm looking for?

Is this a horrific abuse of JPA, and if so, is there some other technique/sequence of calls I should be using?

Thanks in advance!


Solution

  • Well, I came up with some partial answers - but I'm not sure if there are any side effects I need to be concerned about.

    For problem #1, where choice3 was not getting nulled out correctly, I had to set openjpa.DetachState to all in my persistence.xml file.

    For problem #2, adding a call to EntityManager.flush in between the calls to merge fixes the problem. Oddly, adding the call before the first call to merge also fixes the problem, which leads me to suspect that flush is setting some state on the EntityManager, but I'm not sure why that would be.