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!
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.