EntityManager throws OptimisticLockException when try to delete locked entity in same transaction - java

Here is my code:
EntityManager em = JPAUtil.createEntityManager();
try {
EntityTransaction tx = em.getTransaction();
try {
//do some stuff here
tx.begin();
List es = em.createNamedQuery("getMyEntities", MyEntity.class).getResultList();
for (MyEntity e : es) {
em.lock(e, LockModeType.OPTIMISTIC);
}
if (es.size() != 0) {
em.remove(es.get(0));
}
tx.commit
} finally {
if (tx.isActive()) {
tx.rollback();
}
}
} finally {
em.close();
}
When I'm executing that code I get :
...
..........
Caused by: javax.persistence.OptimisticLockException: Newer version [null] of entity [[MyEntity#63]] found in database
at org.hibernate.ejb.AbstractEntityManagerImpl.wrapLockException(AbstractEntityManagerImpl.java:1427)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1324)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1300)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:80)
... 23 more
Can anybody explain me why that?

I suppose that you have added the #Version-annotated column after you already had some entries in database, so that some null-values were created for the already-existing records.
Now hibernate can't compare the versions.
I would try to set the version column to 1 for all null-versioned entities.

I think this error is thrown due to the fact that I try to delete a record that has a lock on it. Trying to delete this row, will set the version to null, but the version in the database still remains set to former number. It seems that hibernate core perceive a null value to be not reliable for this kind of operation.
If I have to do this kind of operation, I have to release the lock first on this entity.
Anyone with better knowledge on it has to clarify that issue.

Related

How to execute native query and get feedback?

How to execute native query (create table) in java and also in return get information about is operation was successfull or not. Every method i did try always work the same. Query is working but i am getting errors about "how really bad that query was" but as i said it works.
try{
session.createNativeQuery("create table test (id number)").getResultList()
}
catch(Exception e){
// I am getting error "Could not extract result set metadata"
// Is there any way to execute getResultList() only if operation was select?
}
Summarizing, I need execute CRUD.
1. If "select" was executed i need resultList.
2. If "create" i don't want to execute getResultSet()
3. If "insert" was executed i need information about numbers of row affected.
etc... ... ...
And most important i always need information about eventual errors! If query had missing syntax or something i always need to get that information.
Guys can someone help me? I am fighting with this from several days...
A simple example using Native Query. You can determine the result of query from the affected rows value.
EntityTransaction entityTransaction;
EntityManager entityManager;
try
{
entityTransaction = entityManager.getTransaction();
entityTransaction.begin();
// rowsUpdated - The number of "affected rows".
int rowsUpdated = entityManager.createNativeQuery("create table test (id nubmer)").executeUpdate();
entityTransaction.commit();
}
catch (HibernateException | IllegalStateException e)
{
// handle exceptions
if (entityTransaction != null && entityTransaction.isActive())
{
entityTransaction.rollback();
}
}

Prevent reading the same record with Hibernate

I use Hibernate 5 and Oracle 12.
With the below query I want to randomly select an Entity from a set of Entity's:
Query query = getSession().createQuery("SELECT e FROM Entity e ... <CONDITIONS> ... AND ROWNUM = 1");
Optional<Entity> entity = query.list().stream().findAny();
// Change the entity in some way. The changes will also make sure that the entity won't appear in the next query run based on <CONDITIONS>
...
This works but only if all the transactions that execute the code run sequentially. Thus I also want to make sure that the entity that has already been read won't be read in another transaction.
I tried it with locking:
Query query = getSession().createQuery("SELECT e FROM Entity e ... <CONDITIONS> ... AND ROWNUM = 1")
.setLockMode("this", LockMode.PESSIMISTIC_READ);
But it seems that Hibernate converts this construct to SELECT ... FOR UPDATE which doesn't prevent the other transaction from reading the entity, waiting till the other transactions using it commits and then applying their own changes on the entity.
Is it possible to set some kind of lock on the entity so that it disappears guaranteed from the query result in another transaction?
I've written some experimental code to understand how locking works in Hibernate. It simulates two transactions whose key steps (select and commit) can be executed in different order by adjusting the parameters of transaction() method. This time Field is used instead of Entity, but it doesn't matter. Each transaction reads the same Field, updates its description attribute and commits.
private static final LockMode lockMode = LockMode.PESSIMISTIC_WRITE;
enum Order {T1_READS_EARLIER_COMMITS_LATER, T2_READS_EARLIER_COMMITS_LATER};
#Test
public void firstReadsTheOtherRejected() {
ExecutorService es = Executors.newFixedThreadPool(3);
// It looks like the transaction that commits first is the only transaction that can make changes.
// The changes of the other one will be ignored.
final Order order = Order.T1_READS_EARLIER_COMMITS_LATER;
// final Order order = Order.T2_READS_EARLIER_COMMITS_LATER;
es.execute(() -> {
switch (order) {
case T1_READS_EARLIER_COMMITS_LATER:
transaction("T1", 1, 8);
break;
case T2_READS_EARLIER_COMMITS_LATER:
transaction("T1", 4, 1);
break;
}
});
es.execute(() -> {
switch (order) {
case T1_READS_EARLIER_COMMITS_LATER:
transaction("T2", 4, 1);
break;
case T2_READS_EARLIER_COMMITS_LATER:
transaction("T2", 1, 8);
break;
}
});
es.shutdown();
try {
es.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void transaction(String name, int delayBeforeRead, int delayBeforeCommit) {
Transaction tx = null;
Session session = null;
try {
session = factory.openSession();
tx = session.beginTransaction();
try {
TimeUnit.SECONDS.sleep(delayBeforeRead);
} catch (InterruptedException e) {
e.printStackTrace();
}
Query query = session.createQuery("SELECT f FROM Field f WHERE f.description=?1").setLockMode("this", lockMode);
query.setString("1", DESC);
Field field = (Field) query.uniqueResult();
String description1 = field.getDescription();
System.out.println(name + " : FIELD READ " + description1);
try {
TimeUnit.SECONDS.sleep(delayBeforeCommit);
} catch (InterruptedException e) {
e.printStackTrace();
}
field.setDescription(name);
session.update(field);
System.out.println(name + " : FIELD UPDATED");
tx.commit();
} catch (Exception e) {
fail();
if (tx != null) {
tx.rollback();
}
} finally {
session.close();
}
System.out.println(name + " : COMMITTED");
}
and the output:
T1 : FIELD READ This is a field for testing
апр 19, 2019 5:28:01 PM org.hibernate.loader.Loader determineFollowOnLockMode
WARN: HHH000445: Alias-specific lock modes requested, which is not currently supported with follow-on locking; all acquired locks will be [PESSIMISTIC_WRITE]
апр 19, 2019 5:28:01 PM org.hibernate.loader.Loader shouldUseFollowOnLocking
WARN: HHH000444: Encountered request for locking however dialect reports that database prefers locking be done in a separate select (follow-on locking); results will be locked after initial query executes
Hibernate: select field0_.ID as ID1_9_, field0_.DESCRIPTION as DESCRIPTION2_9_, field0_.NAME as NAME3_9_, field0_.TYPE as TYPE4_9_ from FIELD field0_ where field0_.DESCRIPTION=?
Hibernate: select ID from FIELD where ID =? for update
T1 : FIELD UPDATED
Hibernate: update FIELD set DESCRIPTION=?, NAME=?, TYPE=? where ID=?
T2 : FIELD READ This is a field for testing
T1 : COMMITTED
апр 19, 2019 5:28:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
T2 : FIELD UPDATED
Hibernate: update FIELD set DESCRIPTION=?, NAME=?, TYPE=? where ID=?
INFO: HHH000030: Cleaning up connection pool [jdbc:oracle:thin:#localhost:1521:oracle]
T2 : COMMITTED
Process finished with exit code 0
After the execution the column description contains T2. It looks like pessimistic_write mode works. The transaction who wrote first - won. And this was T2. But what happened with T1? T1 : COMMITTED is also seen in the output. As long as T1 doesn't change anything it's acceptable for me, but I need an indicator that T1 failed, so that I can retry the read/select.
I was wrong. I ran the code multiple times and with different results. Sometimes the column description contains T1, sometimes T2.
You say you want to make sure that other transactions will NOT READ the queries entities.
For that, you need LockMode.PESSIMISTIC_WRITE. This does not allow both READs and UPDATEs. LockMode.PESSIMISTIC_READ does not allow only UPDATEs.
A lock with LockModeType.PESSIMISTIC_WRITE can be obtained on an
entity instance to force serialization among transactions attempting
to update the entity data.
A lock with LockModeType.PESSIMISTIC_WRITE can be used when querying
data and there is a high likelihood of deadlock or update failure
among concurrent updating transactions.

Spring annotation transaction rollback done not working for two tables

I'm facing one problem, if there are two insert statement and there is exception in second insert query, rollback is not working properly.
Insert 1 for table PatientDemographics SUCCESS
Insert 2 for table EncounterHistory FAILS
Then result of insert 1 should not rollback (not revert from database automatically, it is to insert a record in the database).
#Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void addPatientDemographicsAndEncounterHistory(PatientDemographicsDTO patientDemographicsDTO, int count) throws Exception{
// save patient demographics data
PatientDemographics patientDemographics = new PatientDemographics();
patientDemographics.setFullName(patientDemographicsDTO.getFullName());
patientDemographics.setMedicalRecordNumber(patientDemographicsDTO.getMedicalRecordNumber());
mednetDAO.saveOrUpdate(patientDemographics);
if(count == 1){
throw new Exception(count+" Records Saved");
}
// save patient encounter history
EncounterHistory encounterHistory = new EncounterHistory();
encounterHistory.setCompanyID(patientDemographics.getDefaultCompanyID());
encounterHistory.setPatientID(patientDemographics.getPersonID());
encounterHistory.setDepartmentID(patientDemographics.getLastDepartmentID());
encounterHistory.setDoctorID(patientDemographics.getLastDoctorID());
encounterHistory.setLastOPDDate(patientDemographics.getLastOPDDate());
encounterHistory.setLastOPDFreeCount(patientDemographics.getLastOPDFreeCount());
encounterHistory.setLastNotesID(null);
encounterHistory.setLastIPDDate(null);
encounterHistory.setLastIPDFreeCount(patientDemographics.getLastIPDFreeCount());
mednetDAO.saveOrUpdate(encounterHistory);
}
In its default configuration, the Spring Framework’s transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException. Checked exceptions that are thrown from a transactional method do not result in rollback in the default
configuration.
Remove throws from your method signature and throw RuntimeException :
if(count == 1){
throw new RuntimeException(count+" Records Saved");
}

SELECT FOR UPDATE in Hibernate and Oracle with multiple threads

I am having trouble getting SELECT FOR UPDATE to work in Hibernate and Oracle.
When I have two threads with one EntityManager per thread, the second thread seems to be able to read the same row as the first. I can see this by adding traces which show that the second thread reads the same row while the first is in between query.getSingleResult() and entityManager.getTransaction().commit() My expectation was that once a SELECT FOR UPDATE has been issued no one else should be able to read the same row until it is committed by the first thread. But this is not happening.
I can resort to an alternative implementation. What I want to achieve is only one process being able to read and update a row in an Oracle table so that it behaves like a queue given that the consumer processes can be in different machines.
Here is the minimum example of my code:
public MyMessage getNextMessage() {
String sql = "SELECT * FROM MESSAGE WHERE MESSAGE_STATUS = 'Pending' AND rownum=1 FOR UPDATE OF MESSAGE_STATUS";
entityManager.getTransaction().begin();
Query query = entityManager.createNativeQuery(sql, MyMessage.class);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
MyMessage msg = null;
try {
msg = (MyMessage) query.getSingleResult();
} catch (NoResultException nodatafound) {
// Ignore when no data found, just return null
}
if (msg != null) {
msg.setMessageStatus("In Progress");
entityManager.persist(msg);
}
entityManager.getTransaction().commit();
return msg;
}

JPA Select Query with composite primary key leads an exception

I have tried the below code for getting the single NotificationSubType Object. But finding exception like " org.apache.openjpa.persistence.ArgumentException: Encountered "from" at character 1, but expected: ["DELETE", "SELECT", "UPDATE"]."
Please help me on given problem.
Code is given below :
...
...
try {
Query query = this.em.createQuery("from NotificationSubType conc WHERE conc.id.alertTyC = ?1 and conc.id.alertSubTyC = ?2");
query.setParameter(1, "MPPRINT");
query.setParameter(2, "REIN");
NotificationSubType trackIdResLst = (NotificationSubType)query.getSingleResult();
if (null != trackIdResLst) {
System.out.println("TRUE");
} else{
System.out.println("FALSE");
}
} catch (Exception e) {
NotificationLogger.errorWithThrowable(e);
throw new NotificationServiceException(e.getMessage());
}
...
Why not read the exception? and read the JPA spec ?
JPQL statements have to start with SELECT, UPDATE or DELETE. Anything other than those is illegal (and yours starts with FROM for some reason). Put a SELECT and the candidate alias in front of it for more chance of success
Yes Hibernate bastardises the JPQL standard with HQL (which allows you to omit SELECT plus the alias) but you are using OpenJPA so you need to use JPA compliant queries, and besides it would be good practice even when using Hibernate to obey the standard!

Categories