We're using a service layer class for executing hibernate entitymanager operations, like find or persist entities to our mysql database. We run an integration test (10000 times), which performs a persist to the database and in the second step, we want to find this entity and update a column ('state') of the entity. The problem is, that both operations are independent, what means:
- Create entity and persist
- Find this particular entity through a named query and update
Sometimes, the entity is not found by the named query after random repeats (1000, 3500, 6000), but we see the entity is in database while debugging and getting a NoResultException.
Here is our sample code:
persistence.xml
<persistence>
<persistence-unit name="GDCT_PU" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- the JNDI data source -->
<!--<non-jta-data-source>java:comp/env/jdbc/testDS</non-jta-data-source>-->
<class>com.ikea.hrms.gdct.persistence.config.domain.Sequence</class>
<class>com.ikea.hrms.gdct.persistence.config.domain.Journal</class>
<properties>
<!-- if this is true, hibernate will print (to stdout) the SQL it executes,
so you can check it to ensure it's not doing anything crazy -->
<!-- since most database servers have slightly different versions of the
SQL, Hibernate needs you to choose a dialect so it knows the subtleties of
talking to that server -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/DB_RU_PT"/>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.username" value="dbru_pt"/>
<property name="hibernate.connection.password" value="peterkloeppel1234"/>
<property name="hibernate.connection.useUnicode" value="true" />
<property name="hibernate.connection.characterEncoding" value="UTF-8" />
<property name="hibernate.connection.release_mode" value="on_close"/>
<!-- JDBC connection pool (use the built-in) -->
<property name="hibernate.connection.pool_size" value="100" />
<property name="org.hibernate.flushMode" value="COMMIT"/>
<!-- this tell Hibernate to update the DDL when it starts, very useful
for development, dangerous in production -->
<property name="hibernate.hbm2ddl.auto" value="create"/>
</properties>
</persistence-unit>
</persistence>
SequenceService.java
public static synchronized Sequence findOne(String personnelAreaCode, String documentType, String documentCreationDate, int seqNumber) {
logger.info("Find one specific Sequence.");
Sequence sequence = null;
try {
checkEM();
if (EMFGlobal.getInstance().getLock().tryLock(10, TimeUnit.SECONDS)) {
logger.info("Find one: " + personnelAreaCode + "-" + documentCreationDate + "-" + seqNumber + "-" + documentType);
Query query = em.createNamedQuery(
"Sequence.findOne", Sequence.class);
sequence = (Sequence) query
.setParameter("personalAreaCode", personnelAreaCode)
.setParameter("documentType", documentType)
.setParameter("documentCreationDate", documentCreationDate)
.setParameter("seqNumber", seqNumber)
.getSingleResult();
}
}
catch(NoResultException nre) {
logger.error(nre);
}
catch (InterruptedException e) {
e.printStackTrace();
}
catch (JDBCConnectionException jdbcConnectionExc) {
logger.debug("Could not prepare Statement Sequence.findOne");
jdbcConnectionExc.printStackTrace();
}
catch (Exception e) {
logger.error(e);
}
finally {
disconnectEntityManager();
EMFGlobal.getInstance().getLock().unlock();
}
return sequence;
}
public static synchronized void persistSequence(Sequence sequence) {
// persist with state "Created"
try {
Sequence tmpSequence = SequenceService.findOne(sequence.getPersonalAreaCode(), sequence.getDocumentType(), sequence.getDocumentCreationDate(), sequence.getSeqNumber());
if (EMFGlobal.getInstance().getLock().tryLock(10, TimeUnit.SECONDS)) {
checkEM();
em.getTransaction().begin();
if (tmpSequence == null) {
em.persist(sequence);
} else {
em.merge(sequence);
}
em.flush();
em.getTransaction().commit();
}
} catch (org.hibernate.PersistentObjectException e) {
logger.debug("Persisting sequence number went wrong. Probably problems with EntityManager.", e);
} catch (InterruptedException e) {
logger.error(e);
} finally {
disconnectEntityManager();
EMFGlobal.getInstance().getLock().unlock();
}
}
private synchronized static void checkEM() {
logger.info("Init Entity Manager.");
if (em == null || !em.isOpen()) {
em = EMFGlobal.getInstance().getEm();
} else {
logger.info("EntityManager checked. EM is open and not null!");
}
}
/**
* Disconnecting EntityManager used by this class.
*/
private static synchronized void disconnectEntityManager() {
logger.info("Disconnecting the EntityManager.");
if (em != null) {
em.clear();
if (em.isOpen()) {
em.close();
}
em = null;
}
}
SequenceTest.java
public void testGenerateApprove() {
Sequence s = null;
for (int i = 1; i < 10000; i++) {
try {
s = SequenceNumberManager.generateNewSequence("\\021", "002", "141123");
logger.error("During generateNewSequence for: \\021, 002, 141123");
} catch (Exception e) {
e.printStackTrace();
}
try {
SequenceNumberManager.approveSequence(s);
} catch (Exception e) {
logger.error("During approveSequences for sequence: " + s.toString());
e.printStackTrace();
}
}
}
SequenceNumberManager.java
public static synchronized Sequence generateNewSequence(final String personalArea, final String form, final Date date) throws Exception {
String personnelAreaCode = Constants.getPersonnelAreaCode().get(personalArea);
String documentType = Constants.getformIdToDocType().get(form);
if (documentType == null) {
documentType = "";
}
SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
final char separator1 = '-';
final String documentCreationDate = sdf.format(date);
final char separator2 = separator1;
logger.info("Getting next highest sequence number.");
final int sequenceNumber = getNextSequenceNumber(personnelAreaCode, documentType, documentCreationDate);
final char separator3 = separator1;
Sequence sequence = new Sequence(personnelAreaCode, separator1, documentCreationDate, separator2,
sequenceNumber, separator3, documentType);
sequence.setFormNumber(form);
Sequence seq = SequenceService.findOne(sequence.getPersonalAreaCode(), sequence.getDocumentType(), sequence.getDocumentCreationDate(), sequenceNumber);
if (seq == null) {
SequenceService.persistSequence(sequence);
} else {
logger.error("TAKING AN OLD SEQUENCE NUMBER!!!");
return seq;
}
return sequence;
}
public static synchronized void approveSequence(Sequence sequence) throws Exception {
String sequenceStr = sequence.toString();
// check if approved
if (sequence.getState().equalsIgnoreCase("Created")) {
// set state to approved
sequence.setState("Approved");
java.util.Date persistingDate = new java.util.Date();
java.sql.Date sqlDate = new java.sql.Date(persistingDate.getTime());
sequence.setPersistingDate(sqlDate);
SequenceService.persistSequence(sequence);
} else if (sequence.getState().equalsIgnoreCase("Approved")) {
logger.error("Sequence: " + sequenceStr + " is already approved. Please generate a new Sequence number.");
throw new Exception("Sequence: " + sequenceStr
+ " is already approved. Please generate a new Sequence number.");
}
}
EMFGlobal.java
public class EMFGlobal {
private static Logger logger = Logger.getLogger(ReportingManager.class);
private static EntityManagerFactory emf;
private EntityManager em;
private ReentrantLock lock;
public void initEntityManagerFactory() {
logger.info("Initializing the EntityManagerFactory for persistence unit GDCT_PU");
if(emf == null || !emf.isOpen()) {
emf = Persistence.createEntityManagerFactory("GDCT_PU");
}
}
public void destroyEntityManagerFactory() {
if(emf != null) {
if(emf.isOpen()) {
emf.close();
}
emf = null;
}
}
private static EMFGlobal instance = null;
private EMFGlobal() {
initEntityManagerFactory();
}
public static EMFGlobal getInstance() {
if (EMFGlobal.instance == null) {
EMFGlobal.instance = new EMFGlobal();
}
return EMFGlobal.instance;
}
public EntityManagerFactory getEmf() {
initEntityManagerFactory();
return emf;
}
public EntityManager getEm() {
if(emf == null) {
initEntityManagerFactory();
}
if(em == null || !em.isOpen()) {
em = emf.createEntityManager();
}
return em;
}
public Lock getLock() {
if(lock == null) {
lock = new ReentrantLock();
}
return lock;
}
}
Sequence.java
@Entity(name = "sequence")
@NamedQueries(value = {
@NamedQuery(name = "Sequence.findByPersonalAreaCodeAndDocumentTypeAndDocumentCreationDate",
query = "SELECT s FROM sequence s WHERE s.personalAreaCode = :personalAreaCode AND s.documentType = :documentType AND s.documentCreationDate = :documentCreationDate"),
@NamedQuery(name = "Sequence.findAll",
query = "SELECT s FROM sequence s"),
@NamedQuery(name = "Sequence.findOne",
query = "SELECT s FROM sequence s WHERE s.personalAreaCode = :personalAreaCode AND s.documentType = :documentType AND s.documentCreationDate = :documentCreationDate AND s.seqNumber = :seqNumber"),
@NamedQuery(name = "Sequence.getNextSequenceNumber",
query = "SELECT s FROM sequence s WHERE s.seqNumber=(SELECT max(s.seqNumber) FROM sequence s WHERE s.personalAreaCode = :personalAreaCode AND s.documentType = :documentType AND s.documentCreationDate = :documentCreationDate) AND s.personalAreaCode = :personalAreaCode AND s.documentType = :documentType AND s.documentCreationDate = :documentCreationDate")
})
public class Sequence implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int seqId;
private String personalAreaCode;
@Column(nullable = false, length = 1)
private char separator1;
@Column(nullable = false, length = 6)
private String documentCreationDate; // format: yyMMdd
@Column(nullable = false, length = 1)
private char separator2;
@Column(nullable = false)
private int seqNumber;
@Column(nullable = false, length = 1)
private char separator3;
@Column(nullable = false)
private String documentType;
@Column(nullable = false)
private String state;
private Date persistingDate;
@Column(nullable = false)
private String formNumber;
public Sequence() {
// no-args constructor required by JPA spec
// this one is protected since it shouldn't be used directly
}
/**
* Constructs Sequence with state "Created".
*
* @param personalAreaCode Personnel Area Code (PAC, e.g. RSO).
* @param separator1 Separator between PAC and DCD.
* @param documentCreationDate Document Creation Date (DCD).
* @param separator2 Separator between DCD and SN.
* @param seqNumber Sequence count Number (SN).
* @param separator3 Separator between SN and DT.
* @param documentType Document Type (DT).
*/
public Sequence(String personalAreaCode, char separator1, String documentCreationDate, char separator2, int seqNumber, char separator3, String documentType) {
this.personalAreaCode = personalAreaCode;
this.separator1 = separator1;
this.separator2 = separator2;
this.seqNumber = seqNumber;
this.separator3 = separator3;
this.documentType = documentType;
this.documentCreationDate = documentCreationDate;
this.state = "Created";
}
public int getSeqId() {
return seqId;
}
public void setSeqId(int seqId) {
this.seqId = seqId;
}
public String getPersonalAreaCode() {
return personalAreaCode;
}
public void setPersonalAreaCode(String personalAreaCode) {
this.personalAreaCode = personalAreaCode;
}
public char getSeparator1() {
return separator1;
}
public void setSeparator1(char separator1) {
this.separator1 = separator1;
}
public char getSeparator2() {
return separator2;
}
public void setSeparator2(char separator2) {
this.separator2 = separator2;
}
public int getSeqNumber() {
return seqNumber;
}
public void setSeqNumber(int seqNumber) {
this.seqNumber = seqNumber;
}
public char getSeparator3() {
return separator3;
}
public void setSeparator3(char separator3) {
this.separator3 = separator3;
}
public String getDocumentType() {
return documentType;
}
public void setDocumentType(String documentType) {
this.documentType = documentType;
}
public String getDocumentCreationDate() {
return documentCreationDate;
}
public void setDocumentCreationDate(String documentCreationDate) {
this.documentCreationDate = documentCreationDate;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Date getPersistingDate() {
return persistingDate;
}
public void setPersistingDate(Date persistingDate) {
this.persistingDate = persistingDate;
}
public String getFormNumber() {
return formNumber;
}
public void setFormNumber(String formNumber) {
this.formNumber = formNumber;
}
@Override
public String toString() {
return this.getPersonalAreaCode() + this.getSeparator1() + this.getDocumentCreationDate() + this.getSeparator2()
+ this.getSeqNumber() + this.getSeparator3() + this.getDocumentType();
}
@Override
public boolean equals(Object o) {
if(o instanceof Sequence) {
Sequence seq = (Sequence) o;
return this.toString().equals(seq.toString());
}
else {
return false;
}
}
}
We hope someone can help us. We also tried a lot of stuff like creating the EntityManagerFactory and the EntityManager before every transaction and close it after, but it got even worse. Thanks for any help or advice.