First of all, thanks Tim for your reply.
Tim Holloway wrote:Just to be clear, I'm taking it as a given that you're NOT attempting to do any sort of threading here, that these are, in fact, multiple concurrent client requests. Because although it may not be apparent, most of the business logic in a Spring Boot app runs under a Servlet or JSP and both of those are absolutely forbidden to spawn threads.
Correct, I'm not starting manual threads anywhere: anyway, concurrent requests handling very same object may happen. The whole scenario is that
before I optimized @Service code responsible to handle the business transaction, an user had to wait about 1.5 minutes (!!) before the transaction completes. Somehow, an user managed to execute the very same request twice in a really short time (we actually think that the web UI allowed them to execute some kind of double click). The result was: whole transaction executed twice.
Tim Holloway wrote:
As far as database locking goes, my understanding of Optimistic Locking in JPA is that before an update is posted the JPA infrasstructure does a fetch of the affected record(s) and checks to make sure no one has modified them elsewhere, throwing an Exception if they have. Been there/done that.
My own database logic, which I've often mentioned elsewhere on the Ranch has essentially 3 layers.
* The Entity Layer, which (surprise!) defines the table and View Entity classes.
* The DAO Layer, which is primarily concerned with CRUD and finders for individual tables, or tightly-bound parent-child table pairs. Lately Spring Data's repository feature has supplanted a lot of the brute-force logic for me.
* The Service Layer. This layer works on a "working set" of related records (a directed graph). It detaches the graph from the JPA system before returning a fetched set to the higher levels of the app and it re-attaches (merges) when the business levels request a Service method to update the datastore. The Service layer delegates its dirty work to the DAOs.
Both the Service Layer and DAO layer classes are marked @Transactional,wwhere the DAO transactions get adopted into the transaction of the service that uses them (I forget the exact name of that option, though).
I'd say it's a textbook solution...
Tim Holloway wrote:
Once a Service is bound in a Transaction, it will either queue behind other Transactions or throw a locking Exception if it cannot otherwise resolve matters. In the case of a lock exception, the service is going to have to re-think its own changes to whatever Entities had conflicting requests and resolve them before trying again. Which sounds intimidating, but isn't something I've had many problems with.
Well, this should depend actually on isolation level of the transaction, but i don't think it it's enough to prevent lost updates without some kind of checking on data. Suppose two distinct transactions are trying to perform some kind of action on a given entity at the same time: a possible scenario is the following:
a) Tx A tries a SELECT FOR UPDATE on entity and gets the lock;
b) Tx B tries a SELECT FOR UPDATE on the same entity and waits for some time before getting a Locking Timeout or something similar;
c) Tx A commits the transaction and releases the lock
d) Tx B is granted the lock on the entity, and commits, overriding data set by transaction A.
At the very end, one needs to
thread some business transactions as not-repeatable, and to use some property (a status of the entity, for example, to mark it as "processed"), but this not a job that a framework may accomplish by its own. A solution may be to throwing a LockingException immediately, and rollback the transaction.
Anyway my question was about a pattern to follow whenever you cannot add a @Version attribute to an entity for any reason. The approach I followed was to use an "OptimisticLock" entity which unique key is derived by the actual entity being persisted, and work on @Version field of the wrapping an OptimisticLock.
To be honest, I think that is the entity design is flawed and it would be wise to fix it before it's too late.