Hello,
I am using the Toplink JPA implementation as the JPA provider for IBM Rational Application Developer, and I am having trouble with a multithreading issue. I am using application managed entity managers (my situation necessitates this). Under singlethreaded conditions, the application performs as expected. Unfortunately, when using multiple threads to concurrently create transactions and merge entities, some of the entities simply aren’t persisted.
The problem arises in the following situation (code will follow):
I have an entity “Fact” with an embedded id “FactPK”. The FactPK embeddable itself defines a @ManyToOne relation to another entity “Dimension”--sharing a primary key with it. Each thread uses a thread-local EntityManager created on demand (as I understand that EntityManagers are not thread safe) to merge Fact entities. Each thread is dedicated a “unit of work”, which encompasses processing XML and then, based on the processing of that XML, creating and merging entities with its thread-local entity manager. The thread-local entity manager is obtained from a static method of a class named “PersistCxt”.
Under singlethreaded conditions (where all “units of work” are under a single thread—and thus a single EntityManager), all of the entities are persisted as expected across each “unit of work.” On the other hand, when each “unit of work” is dedicated its own thread (and thus each uses a separate thread local entity manager), the persistence of the “Fact” entities is unpredictable—sometimes they are persisted, sometimes they are not (out of 10 Fact entities to be persisted, usually only 3 or so are actually persisted). It seems there is a race condition somewhere, but I do not believe it is in my code (still to follow)--I have things locked down pretty conservatively.
No exceptions are thrown; the corresponding records simply do not show up in the database.
It is also worth noting that I have yet to observe a situation where a “Dimension” entity is not persisted in the multithreaded scenario. While I understand that the nature of race conditions are such that this may just be a fluke of chance, it seems to suggest that the problem is isolated to the persistence of the “Fact” entity only.
I am not really sure if this is a problem with my own setup or Toplink itself, but that is part of what I’m here to find out. Here is the code:
persistence.xml (I’m not sure if I need to perform any additional configuration under a multithreaded scenario):
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="AnalyzerJPA">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>jdbc/Analyzer</jta-data-source>
<class>entities.Fact</class>
<class>entities.FactPK</class>
<class>entities.Dimension</class>
<!-- toplink properties -->
<properties>
<property name="toplink.target-database" value="DB2"/>
<property name="toplink.logging.level" value="INFO"/>
</properties>
</persistence>
PersistCxt:
public class PersistCxt
{
private static final String PERSISTENCE_UNIT = "AnalyzerJPA";
private static EntityManagerFactory fctEntityManager = null;
private static ThreadLocal<EntityManager> thdLclEntityManager = new ThreadLocal<EntityManager>();
private static Object lock = new Object();
private static EntityManagerFactory getFct()
{
synchronized( lock )
{
if( fctEntityManager == null )
{
fctEntityManager = Persistence.createEntityManagerFactory( PERSISTENCE_UNIT );
}
return fctEntityManager;
}
}
/**
* get the entity manager for this thread
*/
public static EntityManager getEntityManager()
{
synchronized( lock )
{
EntityManager em = null;
// ThreadLocal<>::get(), if not initialized, first calls initialValue().
// initialValue() will return null unless you extend ThreadLocal and override initialValue()
if( (em = thdLclEntityManager.get()) == null )
{
em = getFct().createEntityManager();
thdLclEntityManager.set( em );
}
return em;
}
}
public static void closeEntityManagerFactory()
{
synchronized( lock )
{
if( getFct().isOpen() )
{
getFct().close();
}
}
}
}
Entities:
@Entity
@Table( name="AUTOFACT")
public class Fact implements Serializable
{
@EmbeddedId
private FactPK pk;
// non-key members
public void setPk(FactPK pk) {
this.pk = pk;
}
public FactPK getPk() {
return pk;
}
// non-key member accessors/mutators
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((pk == null) ? 0 : pk.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Fact other = (Fact) obj;
if (pk == null) {
if (other.pk != null)
return false;
} else if (!pk.equals(other.pk))
return false;
return true;
}
}
@Embeddable
public class FactPK implements Serializable
{
private static final long serialVersionUID = 1L;
@JoinColumn(name="autopolicyid",referencedColumnName="")
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.REFRESH})
private Dimension dimension;
// non-key members
public Dimension getDimension() {
return dimension;
}
public void setDimension(Dimension dimension) {
this.dimension = dimension;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((dimension == null) ? 0 : dimension.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
FactPK other = (FactPK) obj;
if (dimension == null) {
if (other.dimension != null)
return false;
} else if (!dimension.equals(other.dimension))
return false;
return true;
}
// non-key member accessors/mutators
}
@Entity
@Table(name="AUTODMT")
public class Dimension implements Serializable
{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private BigDecimal autopolicyid;
// non-key members
public BigDecimal getAutopolicyid() {
return autopolicyid;
}
public void setAutopolicyid(BigDecimal autopolicyid) {
this.autopolicyid = autopolicyid;
}
// non-key member accessors/mutators
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((autopolicyid == null) ? 0 : autopolicyid.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Dimension other = (Dimension) obj;
if (autopolicyid == null) {
if (other.autopolicyid != null)
return false;
} else if (!autopolicyid.equals(other.autopolicyid))
return false;
return true;
}
}
Sample Driver code (exception handling is incomplete. Naturally, this is not the actual code I’m using, but—for simplicity—I’ve written this small block of code for this thread; it exhibits the same problematic behavior as described above):
// this is the so called "unit of work" that would be executed by threads concurrently
public void testPersist()
{
Dimension dim = new Dimension();
// set dim's fields based on XML processing.
// dim.setAutopolicyid(autopolicyid) is a GeneratedValue.
FactPK factPK = new FactPK();
factPK.setDimension( dim );
Fact fact = new Fact();
fact.setPk( factPK );
// set fact's fields based on XML processing
try
{
EntityManager em = PersistCxt.getEntityManager();
EntityTransaction userTransaction = em.getTransaction();
userTransaction.begin();
em.merge( fact );
userTransaction.commit();
} catch( Exception e )
{
System.err.println( e.getMessage() );
}
}
If you need any additional information, please let me know. I would provide the console output on my server, but the only output pertaining to JPA/Toplink are the lines confirming its initialization (there are no errors):
[1/16/11 10:06:16:340 EST] 0000000b JPAComponentI I CWWJP0026I: The Java Persistence API (JPA) component is initializing.
[1/16/11 10:06:16:340 EST] 0000000b JPAComponentI I CWWJP0006I: The oracle.toplink.essentials.PersistenceProvider class is loaded as the default Java Persistence API (JPA) provider.
[1/16/11 10:06:16:340 EST] 0000000b JPAComponentI I CWWJP0027I: The Java Persistence API (JPA) component has initialized.
In the mean time, I am going to configure my local server and see if I can get any helpful log output from Toplink.
Thanks in advance