How to prevent non-repeatable query results using persistence API in Java SE? - jpa

I am using Java SE and learning about the use of a persistence API (toplink-essentials) to manage entities in a Derby DB. Note: this is (distance learning) university work, but it is not 'homework' this issue crops up in the course materials.
I have two threads operating on the same set of entities. My problem is that every way I have tried, the entities within a query result set (query performed within a transaction) in one thread can be modified so that the result set is no longer valid for the rest of the transaction.
e.g. from one thread this operation is performed:
static void updatePrices(EntityManager manager, double percentage) {
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
Query query = manager.createQuery("SELECT i FROM Instrument i where i.sold = 'no'");
List<Instrument> results = (List<Instrument>) query.getResultList();
// force thread interruption here (testing non-repeatable read)
try { Thread.sleep(2000); } catch (Exception e) { }
for (Instrument i : results) {
i.updatePrice(percentage);
}
transaction.commit();
System.out.println("Price update commited");
}
And if it is interrupted from another thread with this method:
private static void sellInstrument(EntityManager manager, int id)
{
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
Instrument instrument = manager.find(Instrument.class, id);
System.out.println("Selling: " + instrument.toFullString());
instrument.setSold(true);
transaction.commit();
System.out.println("Instrument sale commited");
}
What can happen is that when the thread within updatePrices() resumes it's query resultSet is invalid, and the price of a sold item ends up being updated to different price to that at which it was sold. (The shop wishes to keep records of items that were sold in the DB). Since there are concurrent transactions occuring I am using a different EntityManager for each thread (from the same factory).
Is it possible (through locking or some kind of context propagation) to prevent the results of a query becoming 'invalid' during a (interrupted) transaction? I have an idea that this kind of scenario is what Java EE is for, but what I want to know is whether its doable in Java SE.
Edit:
Taking Vineet and Pascal's advice: using the #Version annotation in the entity's Class (with an additional DB column) causes the large transaction ( updatePrices() ) to fail with OptimisticLockException. This is very expensive if it happens at the end of a large set of query results though. Is there any way to cause my query (inside updatePrices() ) to lock the relevant rows causing the thread inside sellInstrument() to either block or abort throw an exception (then abort)? This would be much cheaper. (From what I understand I do not have pessimistic locking in Toplink Essentials).

Thread safety
I have a doubt about the way you manage your EntityManager. While a EntityManagerFactory is thread-safe (and should be created once at the application startup), an EntityManager is not and you should typically use one EntityManager per thread (or synchronize accesses to it but I would use one per thread).
Concurrency
JPA 1.0 supports (only) optimistic locking (if you use a Version attribute) and two lock modes allowing to avoid dirty read and non repeatable read through the EntityManager.lock() API. I recommend to read Read and Write Locking and/or the whole section 3.4 Optimistic Locking and Concurrency of the JPA 1.0 spec for full details.
PS: Note that Pessimistic locking is not supported in JPA 1.0 or only through provider specific extensions (it has been added to JPA 2.0, as well as other locking options). Just in case, Toplink supports it through the eclipselink.pessimistic-lock query hint.
As written in the JPA wiki, TopLink Essentials is supposed to support pessimistic locking in JPA 1.0 via a query hint:
// eclipselink.pessimistic-lock
Query Query = em.createQuery("select f from Foo f where f.bar=:bar");
query.setParameter("bar", "foobar");
query.setHint("eclipselink.pessimistic-lock", "Lock");
query.getResultList();
I don't use TopLink so I can't confirm this hint is supported in all versions. If it isn't, then you'll have to use a native SQL query if you want to generate a "FOR UPDATE".

You might want to take a look at the EntityManager.lock() method, which allows you to obtain an optimistic or a pessimistic lock on an entity once a transaction has been initialized.
Going by your description of the problem, you wish to lock the database record once it has been 'selected' from the database. This can be achieved via a pessimistic lock, which is more or less equivalent to a SELECT ... FROM tbl FOR UPDATE statement.

Related

Spring data jpa - how to guarantee flush order?

I have a quite complex save process of Spring data JPA repos in one Transaction:
mainRepo.save();
relatedRepo1.save();
relatedRepoOfRelatedRepo1.save();
...
And in the end I call (on mainRepo):
#Modifying
#Query("update mainEntity set finished = true where id = :id")
void setFinishedTrue(#Param("id") UUID id);
I want to guarantee that when setFinishedTrue(id) is called, all the related data are actually on the database because it will start an integration process that requires all needed data is available.
If you are using standard settings JPA will flush data before executing queries. So you are fine.
If you want to be really really really sure you can add an explicit flush operation.
You can do this by either using the JpaRepository.flush operation or by injecting the EntityManager and call flush on it explicitly.

How to achieve consistent transactions in ORMs?

My question is of a conceptual nature.
Let's assume we use an ORM (like Entity Framework, Hibernate, ...) and model our domain objects in those ORMs. Since the ORM will abstract away most SQL / do transactions for us, how can we make sure our transactions will be consistent?
Example:
We have an online shop that sells books. If we sell a book, we would basically use something similar to this:
var book = books.Single(b => b.Id == 42);
book.Quantity -= 1;
books.SaveChanges();
But if I understood ORMs correctly, they will only wrap the change of book in an UPDATE query. Meaning you could get basic concurrency issues like this:
concurrency issue with orms
If you would use SQL, you would just wrap the .Quantity -= 1 in a transaction and be safe.
How would you usually deal with this in a proper way in ORMs, or is this handled somehow automatically?
You have to do a select for update before you change the quantity. This will lock the record.
It depends on the ORM you are using. With Hibernate you can set the LockModeType when doing the query.
In Hibernate this would be LockModeType.PESSIMISTIC_WRITE
Read more about locking in the Hibernate documentation:
https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#locking
Entity Framework uses Optimistic Concurrency for this. And most RDBMS systems won't prevent this update anomaly using a transaction.
UPDATE T SET Quantity = Quantity - 1 WHERE ID = :ID
This single statement will run atomically, and while using a single statement will prevent lost updates, it won't guarantee that Quantity doesn't get reduced below zero.
A pessimistic lock requires not just a transaction, but either locking hints or an elevated transaction isolation level.

Optimistic locking with Kundera JPA

I'm trying to use Kundera as a JPA implementation over cassandra (namely for the transaction management capabilities).
2 questions:
FIRST QUESTION
is it true that transaction management does not support native queries? i.e. if I do:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("piccandra", getCassandraProperties());
EntityManager em = emf.createEntityManager();
em.setFlushMode(FlushModeType.COMMIT);
em.getTransaction().begin();
Query q = em.createNativeQuery("insert into photographer (photographer_id, photographer_name) values ('id1', 'name1')");
q.executeUpdate();
em.getTransaction().rollback();
the rollback will not work?
SECOND QUESTION
how do I perform optimistic locking (aka CAS) on persist?
In cassandra I can run:
UPDATE photographer SET name = 'x' WHERE id = 1 AND update_date <= some-timestamp
When using Kundera I want to persist an entity only if it hasn't changed by someone else on the base store. can I do that?
EDIT:
I see that JPA does support optimistic locking, but Kundera does not imeplement it.
Rollbacks will not work in case of native queries, as there is no entity object mapped to the query is available in persistence context (required for maintaining the state of the object in transactions).
Kundera does not support optimistic locking as of now. If you are looking for a workaround you can use native querying on Cassandra.

LockMode in EJB3 Persistence NamedQuery

How do we specify LockMode in EJB3 Persistence NamedQuery? I want to add Pessimistic LockMode to my existing select so that I can update if necessary but surprisingly Query object doesnot have setLockMode(xxx) method ( My understanding was if JPA, asubset of EJB3 persistence, exposes setLockMode, EJB3 persistence should have the method available too).
Query query = em.createNamedQuery("findOptoutStudent");
query.setParameter("optoutIndicator", optoutIndicator);
List<Student> students = query.getResultList();
return students.get(0);
I would assume I dont have to change the query manually to "select for update".
Thanks
Kevin
Pessimistic Lock Mode :
PESSIMISTIC_READ which represents a shared lock. Lock request fails when another entity manager has acquired PESSIMISTIC_WRITE.
PESSIMISTIC_WRITE which represents an exclusive lock. Lock request fails when another entity manager has acquired either of the locks on the object.
Applying lock on the object
entityManager.lock(object, LockModeType.PESSIMISTIC_READ)
Releasing the lock afterwards
entityManager.lock(object, LockModeType.NONE)

Create new or update existing entity at one go with JPA

A have a JPA entity that has timestamp field and is distinguished by a complex identifier field. What I need is to update timestamp in an entity that has already been stored, otherwise create and store new entity with the current timestamp.
As it turns out the task is not as simple as it seems from the first sight. The problem is that in concurrent environment I get nasty "Unique index or primary key violation" exception. Here's my code:
// Load existing entity, if any.
Entity e = entityManager.find(Entity.class, id);
if (e == null) {
// Could not find entity with the specified id in the database, so create new one.
e = entityManager.merge(new Entity(id));
}
// Set current time...
e.setTimestamp(new Date());
// ...and finally save entity.
entityManager.flush();
Please note that in this example entity identifier is not generated on insert, it is known in advance.
When two or more of threads run this block of code in parallel, they may simultaneously get null from entityManager.find(Entity.class, id) method call, so they will attempt to save two or more entities at the same time, with the same identifier resulting in error.
I think that there are few solutions to the problem.
Sure I could synchronize this code block with a global lock to prevent concurrent access to the database, but would it be the most efficient way?
Some databases support very handy MERGE statement that updates existing or creates new row if none exists. But I doubt that OpenJPA (JPA implementation of my choice) supports it.
Event if JPA does not support SQL MERGE, I can always fall back to plain old JDBC and do whatever I want with the database. But I don't want to leave comfortable API and mess with hairy JDBC+SQL combination.
There is a magic trick to fix it using standard JPA API only, but I don't know it yet.
Please help.
You are referring to the transaction isolation of JPA transactions. I.e. what is the behaviour of transactions when they access other transactions' resources.
According to this article:
READ_COMMITTED is the expected default Transaction Isolation level for using [..] EJB3 JPA
This means that - yes, you will have problems with the above code.
But JPA doesn't support custom isolation levels.
This thread discusses the topic more extensively. Depending on whether you use Spring or EJB, I think you can make use of the proper transaction strategy.