the JPA optimistic locking doesn't throw an OptimisticLockException/StaleStateException where i would expect it.
Here is my setup:
i am using spring boot with spring data envers. So my repository are versioned, which should not influence the optimistic locking behaviour. In my entities the property version (Long) is annotated with #Version. My application consists of 3 layers:
persistence-layer
business-layer
transfer-layer
To map objects between the layers i use mapstruct.
When a request is received by the controller in the transfer-layer, the JSON-Payload is mapped to an business-layer object to process business rules to it. The version is always mapped through the whole lifecycle.
When i reach the persistence-layer, i use the ID of the object to find the corresponding entity in my database. The signature of my save-method looks like this:
#Transactional
public Entity saveEntity(BOEntity boEntity){
Entity e = entityRepository.findById(boEntity.getId());
entityMapper.updateEntity(boEntity, e);
entityRepository.save(e);
}
When the same entity is loaded by my clients, (e.g. two browser-tabs) each of them has the same version of the entity. Changes are made and saved in both clients.
The version is contained in the boEntity object and mapped into the entity.
Due to the findById call the entity is managed. The entitymanager will try to merge the entity and succeeds in both requests to do so.
The state of the entity of the first request is merged (with version 1). Hibernate calls the executeUpdate method and writes to the database. The version is increased to 2.
Now the second request delivers the entity in the former state with version 1. The save-method is called and the entity is retrieved from the persistence-context. It has the version 2, which is overwritten by the boEntity object with version 1.
When the entityManager now merges the entity, no exception is thrown.
My expectation is the second request to fail because of an old version.
Isn't it possible to overwrite the version of the entity?
I already read a lot of blog entries, but couldn't find any hint to do the trick.
The default JPA optimistic locking mechanism only works when a managed object is flushed but was changed in the meantime. What you want has to be coded manually. Just add the logic to your saveEntity method:
#Transactional
public Entity saveEntity(BOEntity boEntity){
Entity e = entityRepository.findById(boEntity.getId());
if (boEntity.getVersion() != e.getVersion()) {
throw new OptimisticLockException();
}
entityMapper.updateEntity(boEntity, e);
entityRepository.save(e);
}
Related
This question already has answers here:
Spring Data JPA Update #Query not updating?
(5 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question 1 year ago and left it closed:
Original close reason(s) were not resolved
Let's suppose to have this situation:
We have Spring Data configured in the standard way, there is a Respository object, an Entity object and all works well.
Now for some complex motivations I have to use EntityManager (or JdbcTemplate, whatever is at a lower level than Spring Data) directly to update the table associated to my Entity, with a native SQL query. So, I'm not using Entity object, but simply doing a database update manually on the table I use as entity (it's more correct to say the table from which I get values, see next rows).
The reason is that I had to bind my spring-data Entity to a MySQL view that makes UNION of multiple tables, not directly to the table I need to update.
What happens is:
In a functional test, I call the "manual" update method (on table from which the MySQL view is created) as previously described (through entity-manager) and if I make a simple Respository.findOne(objectId), I get the old object (not updated one). I have to call Entitymanager.refresh(object) to get the updated object.
Why?
Is there a way to "synchronize" (out of the box) objects (or force some refresh) in spring-data? Or am I asking for a miracle?
I'm not ironical, but maybe I'm not so expert, maybe (or probably) is my ignorance. If so please explain me why and (if you want) share some advanced knowledge about this amazing framework.
If I make a simple Respository.findOne(objectId) I get old object (not
updated one). I've to call Entitymanager.refresh(object) to get
updated object.
Why?
The first-level cache is active for the duration of a session. Any object entity previously retrieved in the context of a session will be retrieved from the first-level cache unless there is reason to go back to the database.
Is there a reason to go back to the database after your SQL update? Well, as the book Pro JPA 2 notes (p199) regarding bulk update statements (either via JPQL or SQL):
The first issue for developers to consider when using these [bulk update] statements
is that the persistence context is not updated to reflect the results
of the operation. Bulk operations are issued as SQL against the
database, bypassing the in-memory structures of the persistence
context.
which is what you are seeing. That is why you need to call refresh to force the entity to be reloaded from the database as the persistence context is not aware of any potential modifications.
The book also notes the following about using Native SQL statements (rather than JPQL bulk update):
■ CAUTION Native SQL update and delete operations should not be
executed on tables mapped by an entity. The JP QL operations tell the
provider what cached entity state must be invalidated in order to
remain consistent with the database. Native SQL operations bypass such
checks and can quickly lead to situations where the inmemory cache is
out of date with respect to the database.
Essentially then, should you have a 2nd level cache configured then updating any entity currently in the cache via a native SQL statement is likely to result in stale data in the cache.
In Spring Boot JpaRepository:
If our modifying query changes entities contained in the persistence context, then this context becomes outdated.
In order to fetch the entities from the database with latest record.
Use #Modifying(clearAutomatically = true)
#Modifying annotation has clearAutomatically attribute which defines whether it should clear the underlying persistence context after executing the modifying query.
Example:
#Modifying(clearAutomatically = true)
#Query("UPDATE NetworkEntity n SET n.network_status = :network_status WHERE n.network_id = :network_id")
int expireNetwork(#Param("network_id") Integer network_id, #Param("network_status") String network_status);
Based on the way you described your usage, fetching from the repo should retrieve the updated object without the need to refresh the object as long as the method which used the entity manager to merge has #transactional
here's a sample test
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = ApplicationConfig.class)
#EnableJpaRepositories(basePackages = "com.foo")
public class SampleSegmentTest {
#Resource
SampleJpaRepository segmentJpaRepository;
#PersistenceContext
private EntityManager entityManager;
#Transactional
#Test
public void test() {
Segment segment = new Segment();
ReflectionTestUtils.setField(segment, "value", "foo");
ReflectionTestUtils.setField(segment, "description", "bar");
segmentJpaRepository.save(segment);
assertNotNull(segment.getId());
assertEquals("foo", segment.getValue());
assertEquals("bar",segment.getDescription());
ReflectionTestUtils.setField(segment, "value", "foo2");
entityManager.merge(segment);
Segment updatedSegment = segmentJpaRepository.findOne(segment.getId());
assertEquals("foo2", updatedSegment.getValue());
}
}
I'm going to become mad with JPA...
I have a JAX-WS Webservice like that
#WebService
public class MyService
{
#EJB private MyDbService myDbService;
...
System.out.println(dmrService.read());
...
}
My EJB contains
#Stateless
public class MyDbService
{
#PersistenceContext(unitName="mypu")
private EntityManager entityManager;
public MyEntity read()
{
MyEntity myEntity;
String queryString = "SELECT ... WHERE e.name = :type";
TypedQuery<MyEntity> query = entityManager.createQuery(queryString,MyEntity.class);
query.setParameter("type","xyz");
try
{
myEntity= query.getSingleResult();
}
catch (Exception e)
{
myEntity= null;
}
return myEntity;
}
In my persistence.xml the mypu has transaction-type="JTA" and a jta-data-source
If I call the webservice, it's working. The entity is retrieved from the db.
Now, using an external tool, I'm changing the value of one field in my record.
I'm calling the webservice again and ... the entity displayed contains the old value.
If I'm deploying again, or if I'm adding a entityManager.refresh(myEntity) after the request, I have the good value again.
In #MyTwoCents answer, Option 2 is to NOT use your 'external' tool for changes, use your application instead. Caching is of more use if your application knows about all the changes going on, or has some way of being informed of them. This is the better option, but only if your application can be the single access point for the data.
Forcing a refresh, via EntityManager.refresh() or through provider specific query hints on specific queries, or by invalidating the cache as described here https://wiki.eclipse.org/EclipseLink/Examples/JPA/Caching#How_to_refresh_the_cache is another option. This forces JPA to go past the cache and access the database on the specific query. Problems with this are you must either know when the cache is stale and needs to be refreshed, or put it on queries that cannot tolerate stale data. If that is fairly frequent or on every query, then your application is going through all the work of maintaining a cache that isn't used.
The last option is to turn off the second level cache. This forces queries to always load entities into an EntityManager from the database data, not a second level cache. You reduce the risk of stale data (but not eliminate it, as the EntityManager is required to have its own first level cache for managed entities, representing a transactional cache), but at the cost of reloading and rebuilding entities, sometimes unnecessarily if they have been read before by other threads.
Which is best depends entirely on the application and its expected use cases.
Don't be mad its fine
Flow goes like this.
You fired a query saying where type="xyz"
Now Hibernate keeps this query or state in cache so that if you fire query again it will return same value if state is not changes.
Now you are updating detail from some external resource.
Hibernate doesnt have any clue about that
So when you fire query again it returns from catch
When you do refresh, hibernate gets detail from Database
Solution :
So you can either add refresh before calling get call
OR
Change the Table value using Hibernate methods in Application so that Hibernate is aware about changes.
OR
Disable Hibernate cache to query each time from DB (not recommended as it will slow down stuff)
I have a Spring Boot 1.3.M1 web application using Spring Data JPA. For optimistic locking, I am doing the following:
Annotate the version column in the entity: #Version private long version;. I confirmed, by looking at the database table, that this field is incrementing properly.
When a user requests an entity for editing, sending the version field as well.
When the user presses submit after editing, receiving the version field as a hidden field or something.
Server side, fetching a fresh copy of the entity, and then updating the desired fields, along with the version field. Like this:
User user = userRepository.findOne(id);
user.setName(updatedUser.getName());
user.setVersion(updatedUser.getVersion());
userRepository.save(user);
I was expecting this to throw exception when the versions wouldn't match. But it doesn't. Googling, I found some posts saying that we can't set the #Vesion property of an attached entity, like I'm doing in the third statement above.
So, I am guessing that I'll have to manually check for the version mismatch and throw the exception myself. Would that be the correct way, or I am missing something?
Unfortunately, (at least for Hibernate) changing the #Version field manually is not going to make it another "version". i.e. Optimistic concurrency checking is done against the version value retrieved when entity is read, not the version field of entity when it is updated.
e.g.
This will work
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
// Assume at this point of time someone else change the record in DB,
// and incrementing version in DB to 3
fooRepo.flush(); // forcing an update, then Optimistic Concurrency exception will be thrown
However this will not work
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
foo.setVersion(1);
fooRepo.flush(); // forcing an update, no optimistic concurrency exception
// Coz Hibernate is "smart" enough to use the original 2 for comparison
There are some way to workaround this. The most straight-forward way is probably by implementing optimistic concurrency check by yourself. I used to have a util to do the "DTO to Model" data population and I have put that version checking logic there. Another way is to put the logic in setVersion() which, instead of really setting the version, it do the version checking:
class User {
private int version = 0;
//.....
public void setVersion(int version) {
if (this.version != version) {
throw new YourOwnOptimisticConcurrencyException();
}
}
//.....
}
You can also detach entity after reading it from db, this will lead to version check as well.
User user = userRepository.findOne(id);
userRepository.detach(user);
user.setName(updatedUser.getName());
user.setVersion(updatedUser.getVersion());
userRepository.save(user);
Spring repositories don't have detach method, you must implement it. An example:
public class BaseRepositoryImpl<T, PK extends Serializable> extends QuerydslJpaRepository<T, PK> {
private final EntityManager entityManager;
public BaseRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
public void detach(T entity) {
entityManager.detach(entity);
}
...
}
Part of the #AdrianShum answer is correct.
The version comparing behavior follows basically this steps:
Retrieve the versioned entity with its version number, lets called V1.
Suppose you modify some entity's property, then Hibernate increments the version number to V2 "in memory". It doesn't touch the database.
You commit the changes or they are automatically commited by the environment, then Hibernate will try to update the entity including its version number with V2 value. The update query generated by Hibernate will modify the registry of the entity only if it match the ID and previous version number (V1).
After the entity registry is successfully modified, the entity takes V2 as its actual version value.
Now suppose that between steps 1 and 3 the entity was modified by another transaction so its version number at step 3 isn't V1. Then as the version number are different the update query won't modify any registry, hibernate realize that and throw the exception.
You can simply test this behavior and check that the exception is thrown altering the version number directly on your database between steps 1 and 3.
Edit.
Don't know which JPA persistence provider are you using with Spring Data JPA but for more details about optimistic locking with JPA+Hibernate I suggest you to read chapter 10, section Controlling concurrent access, of the book Java Persistence with Hibernate (Hibernate in Action)
In addition to #Adrian Shum answer, I want to show how I solved this problem. If you want to manually change a version of Entity and perform an update to cause OptimisticConcurrencyException you can simply copy Entity with all its field, thus causing an entity to leave its context (same as EntityManager.detach()). In this way, it behaves in a proper way.
Entity entityCopy = new Entity();
entityCopy.setId(id);
... //copy fields
entityCopy.setVersion(0L); //set invalid version
repository.saveAndFlush(entityCopy); //boom! OptimisticConcurrencyException
EDIT:
the assembled version works, only if hibernate cache does not contain entity with the same id. This will not work:
Entity entityCopy = new Entity();
entityCopy.setId(repository.findOne(id).getId()); //instance loaded and cached
... //copy fields
entityCopy.setVersion(0L); //will be ignored due to cache
repository.saveAndFlush(entityCopy); //no exception thrown
THe title can't definitely reflect my question, however I don't know how to express. I have a JPA entity (VatOperatorBalance which has a field saleBalance), lets say I retrieve the entity at the first time, and get a entity (VatOperatorBalance#3d6396f5), its saleBalance is 100.0. Now there are other operations which has modified the saleBalance to 200, now I query from database and get a new entity (VatOperatorBalance#10f8ed), sure the saleBalance of this entity is 200.0. However what make me confused it the saleBalance of the old entity (VatOperatorBalance#3d6396f5) is also 200.0.
All these queries and operations are in a single transaction, and the query isn't by EntityManager.find(java.lang.Class<T> entityClass, java.lang.Object primaryKey) which will return entity from cache.
Below is my code
#Rollback(true)
#Test
public void testSale_SingleBet_OK() throws Exception {
// prepare request ...
// query the VatOperatorBalance first
VatOperatorBalance oldBalance = this.getVatOperatorBalanceDao().findByOperator("OPERATOR-111");
//this.entityManager.detach(oldBalance);
logger.debug("------ oldBalance(" + oldBalance + ").");
// the operation which will modify the oldBalance
Context saleReqCtx = this.getDefaultContext(TransactionType.SELL_TICKET.getRequestType(),
clientTicket);
saleReqCtx.setGameTypeId(GameType.VAT.getType() + "");
Context saleRespCtx = doPost(this.mockRequest(saleReqCtx));
RaffleTicket respTicket = (RaffleTicket) saleRespCtx.getModel();
this.entityManager.flush();
this.entityManager.clear();
// assert vat sale balance
VatOperatorBalance newBalance = this.getVatOperatorBalanceDao().findByOperator("OPERATOR-111");
logger.debug("------ newBalance(" + newBalance + ").");
assertEquals(oldBalance.getSaleBalance().add(respTicket.getTotalAmount()).doubleValue(), newBalance
.getSaleBalance().doubleValue(), 0);
}
This testcase will fail, I don't understand why this will happen. JPA entity manager will update all entities of same entity type? The oldBalance entity and newBlance entity have same entityId, however they are different Java instance, what happened in JPA entity manager? If I detach the oldBalance entity from EntityManager, testcase will pass.
Note: my test is using Spring4.0.5 and JPA2.1
#piet.t since the entityManager would recognize it is the same entity by its primary key (feel free to try it). So all changes made to this entity through the same entityManager will all affect the same java instance
So in a entity manager, a given entity type with given primary key, there should be only one java instance or managed entity(if query from entity manager, no matter what query criteria, by id or not, the same java instance(managed entity) will be returned).
However in my test case, the entity 'oldBalance' will be updated by "the operation which will modify the oldBalance", and then the call of entityManager.clear() will detach all entities managed by this entity manager, that says 'oldBalance' is detached too.
And the 'newBalance' is managed entity then, that is why they have different java instance identifier. If 'oldBalance' is managed, for example by call entityManager.merge(), it will be the same instance of 'newBalance'.
I think most of your confusion does arise from the flush()-call in your code.
Calling flush will always store the changed value to the database - that's the whoe point of calling flush. When using transactions the changed value might still not be visible via other connections due to the databases transaction machanism but your entityManager will only see the changed value.
Without the clear-call your query - even though it is not using find - would still return the same instance that was previously created (VatOperatorBalance#3d6396f5) since the entityManager would recognize it is the same entity by its primary key (feel free to try it). So all changes made to this entity through the same entityManager will all affect the same java instance while modifications through another entity manager will most likely cause an exception because the entity was update from another transaction.
Some queries might cause an implicit flush, since the cached changes might influence the query-result, so all changes have to be written to the database before executing the query to get a correct result-set.
I hope that does help a bit.
Is it possible to stop hibernate from auto updating a persistent object?
#Transactional
public ResultTO updateRecord(RequestTO requestTO) {
Entity entity = dao.getEntityById(requestTO.getId());
// now update the entity based on the data in the requestTO
ValidationResult validationResult = runValidation(entity);
if(validationResult.hasErrors()) {
// return ResultTO with validation errors
} else {
dao.persist(entity);
}
}
Here is what happens in the code, I retrieve the entity which would be considered by hibernate to be in persistent state, then I update some of the fields in the entity, then pass the entity to validation. if validation fails, then don't udpate, if validation succeeds then persist the entity.
Here is the main issue with this flow: because I updated the entity for it to be used in the validation, it does not matter whether I call persist() method (on the DAO) or not, the record will always be updated because hibernate detects that the entity has been changed and flags it for update.
Keep im mind I can change the way i do validation and work around the issue, so I'm not interested in workarounds. I'm interested in knowing how i would be able to disable the hibernate feature where it automatically updates persistent objects.
Please keep in mind I'm using hibernates' implementation of JPA. so Hibernate specific answers dealing with hibernate specific API will not work for me.
I tried to look for hibernate configuration and see if I can set any configuration to stop this behavior but no luck.
Thanks
--EDIT ---
I couldn't find a solution to this, so I opted to rolling back the transaction without throwing any RuntimeException even though I'm in a declarative transaction using:
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
which works like a charm.
Configure FlushMode for your session.
http://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/FlushMode.html
You can use EntityManager.clear() method after getting object from database.
http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#clear()
You can call the following code:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
Throw an exception if validation fails and have the caller handle that.