Spring data CrudRepository and pessimistic lock - java

I'm using
Spring Boot 1.4.2
Spring Data JPA 1.10.5
PostgreSQL 9.5 database
I want to have a findOne method with pessimistic lock in my Spring Data repository that is separate from the findOne method that is already provided.
Following this answer I wrote:
public interface RegistrationRepository extends CrudRepository<Registration, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query("select r from Registration r where r.id = ?1")
Registration findOnePessimistic(Long id);
}
This almost works.
Unfortunately, this does not refresh previous instance of my entity in the entity manager cache. I have two concurrent requests updating the status of my registration
the second one waits for the transaction of the first one to commit
the second one does not take into account the changes made by the first one.
Hence broken behavior.
Any clue why #Lock does not out of the box refresh the entity manager?
Update
Here is the requested example code:
public interface RegistrationRepository extends CrudRepository<Registration, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query("select r from registration_table r where r.id = ?1")
Registration findOnePessimistic(Long id);
}
public void RegistrationService {
#Transactional
public void doSomething(long id){
// Both threads read the same version of the data
Registration registrationQueriedTheFirstTime = registrationRepository.findOne(id);
// First thread gets the lock, second thread waits for the first thread to have committed
Registration registration = registrationRepository.findOnePessimistic(id);
// I need this to have this statement, otherwise, registration.getStatus() contains the value not yet updated by the first thread
entityManager.refresh(registration);
registration.setStatus(newStatus);
registrationRepository.save(registration);
}
}

You need to use the entityManger transaction that Spring creates for you :
#Transactional
public void doSomething(long id){
// Both threads read the same version of the data
Registration registrationQueriedTheFirstTime = registrationRepository.findOne(id);
// First thread gets the lock, second thread waits for the first thread to have committed
Registration registration = registrationRepository.findOnePessimistic(id);
// I need this to have this statement, otherwise, registration.getStatus() contains the value not yet updated by the first thread
entityManager.refresh(registration);
EntityManager em = EntityManagerFactoryUtils.getTransactionalEntityManager(<Your entity manager factory>);
em.refresh(registration);
registration.setStatus(newStatus);
registrationRepository.save(registration);
}
}

Related

Outer transaction can not retrieve changes from inner transaction if entity already loaded once

I have a #Transactional main service loading from repository an entity.
Then this service call a sub-service having #Transactional(propagation = Propagation.REQUIRES_NEW) loading the same entity to update one of its properties
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateEntity(Long id) {
repository.findById(id).get().setName("NEW NAME");
}
Unfortunately, the update done from the sub-service is not visible from the main service, even if I try to reload the entity from repository and no matter which isolation level I choose (READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE).
Here is the code of the main service :
#Transactional
public void transactional() {
MyEntity myEntity1 = repository.findById(1L).get();
log.info("{} - {}", myEntity1, myEntity1.getName()); // "OLD NAME"
subService.updateEntity(1L);
MyEntity myEntity2 = repository.findById(1L).get();
log.info("{} - {}", myEntity2, myEntity2.getName()); // "OLD NAME" again, and same reference as myEntity1
}
Nevertheless, If I don't load once the entity in the main service before calling the sub-service, I DO see changes from the inner transaction.
#Transactional
public void transactional() {
subService.updateEntity(1L);
MyEntity myEntity2 = repository.findById(1L).get();
log.info("{} - {}", myEntity2, myEntity2.getName()); // "NEW NAME"
}
I guess what I want to achieve is not a standard way of doing things, but I really want to understand why if an entity is loaded once in an outer transaction, I can not retrieve changes from inner transaction(s).
After edition from the inner transaction, the entity must be explicitly reloaded inside the outer transaction by calling EntityManager#refresh. (Thx to #M.Prokhorov for the clue)
entityManager.refresh(myEntity1);
As #Kayaman said in comment of my question:
It's the 1st level cache in action. It won't reload the entity (unless
explicitly told to), since you've just loaded it.

JPA not fetching data that reflects state of database

I have encountered a curious bug or feature while writing code. Here's the situation:
We are using a PostgreSQL database, EclipseLink in a JavaEE project.
What I am doing in this part of the project is fetching an entity from the database i.e.:
User user = userController.get(userId);
Which then goes to our controller and fetches the user via a TypedQuery:
#Stateless
#LocalBean
public class UserController {
private EntityManager em;
public User get(Integer userId){
User retval = null;
TypedQuery<User> = em.createNamedQuery("User.findByUserId", User.class);
q.setParameter("userId", userId);
retval = q.getSingleResult();
}
public User update(final User modified){...}
}
And in my User class I have:
#NamedQuery(name = "User.findByUserId", query = "SELECT u FROM User u WHERE u.id = :userId"),
So the call goes, I get my user object with its respective data from the database.
In the class where I called the userController.get method I continue to modify the data on this object, and call our controller again to update this data on the database
user.setServiceId(1); //any id (integer) pointing to an existing service, this is a ManyToOne relationship
userController.update(user);
And here is where it gets funny. In our update method inside the controller class I have my modified User object and using this object I get the primary key userId and fetch the data again from the database to get the original:
#Stateless
#LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
catch(Exception e){
}
return retval;
}
}
However, the fields in the original object do not reflect the state of the database but instead contains the same data as the modified object. In this case, the serviceId on the database is null, and in the modified I set it to an ID. The original has its serviceId set to the same value as the modified object even though it should contain the fetched data from the database, in this case null
My current solution is to construct a new User object, after fetching the user from the database, and modify the data on that new object:
User user = userController.get(userId);
User newUser = new User(user);
newUser.setService(service);
userController.update(newUser);
Now when I do the update method, the original reflects the state of the database.
Or maybe it reflects the state of the user object that already exists in the persistence context?
But why does this happen? Since I do make a new get call with a SELECT statement to the database in my update method.
You are using the same EntityManager for everything, both the read and the 'merge', which in this case is then a no-op. Everything read in through an EM is managed, so that if you read it back again, you get the same instance back. As long as the User isn't being serialized, it is 'managed' by the EntityManager it was read from, and so that same instance, and its changes, are visible on any get calls on that ID.
You didn't show how you are getting EntityManagers, but I would guess is isn't container managed, as they would inject a new one for these calls, and then close them for you when done. You haven't shown any transaction logic on how the update and the em context it is using are hooked up, but I would suggest you create a new EntityManager for these calls. Flush also seems unnecessary, as if update is wrapped in a transaction, should handle flushing the update statement to the database without this extra call.
If user.setServiceId(1); is called when the "user" entity is managed, the call is going to update the database row.
you can check the manage entity lifecycle
You need to refresh the data after saving it to the database and to get the latest state of the object, as em.refresh(retval)
You can find the code added below.
#Stateless
#LocalBean
public class userController {
private EntityManager em;
public User get(Integer userId){...}
public User update(final User modified){
User retval = null;
if(modified != null){
try {
User original = get(modified.getId()); //Here I fetch the current state of the DB
if(original != null){
Set<Modifications> modifications = apply(original, modified); //Method to apply modifications
retval = em.merge(original); //Merge changes into database
em.flush(); //Force data to be persisted
em.refresh(retval); // This will fetch the updated data from database
catch(Exception e){
}
return retval;
}
}

Create an database entity in one thread and try to retrieve the same entity in another thread gives different result

I am trying to create a scenario where I create an entity in main thread and then use another thread to query the same entity that was just created. However, if I execute the query to fetch the entity outside of the thread, it returns value. However, if I query it inside the thread, I get null. I have attached the source code with comments and tried to make it simple. I would appreciate if anyone can help me point out the issue. I am using JPA and Spring framework. For database, I am using postgresql. Thank you. Here is the sample program:
#Test
public void test() {
Employee generatedEmployee = employeeService.createEmployee(passEmployeeInfo()); // CREATE TEST EMPLOYEE.. passEmployeeInfo() just sets name, phone etc.
System.out.println(generatedEmployee.getReferenceId()); //Output is: 20
generatedEmployee.setReferenceId(30L); //update the referenceId here...
long refId = generatedEmployee.getReferenceId();
Employee employeeFromDB = employeeQuery.getEmployeeByReferenceId(refId); //Use refId to get Employee from table
System.out.println(employeeFromDB); //Output is: Employee [id=2, refId=30, ...]
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
Employee employee = employeeQuery.getEmployeeByReferenceId(refId);
System.out.println("The employee inside run method is: " + employee); //Output is NULL here.. This is what I am trying to understand.
}
});
thread.start();
}
Points to Note:
Spring will apply a transaction to your test method and will by default roll the transaction back when the method completes.
https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#rollback
Spring transactions do not propagate to new threads.
https://dzone.com/articles/spring-and-threads-transactions
So therefore the method completes and rolls back the transaction before the lookup in the new Thread executes.
If you add the #Commit annotation to your test method the record should be found.

Hibernate 5.2.9.Final cache not updated

I have a service (which I for some reason call controller) that is injected into the Jersey resource method.
#Named
#Transactional
public class DocCtrl {
...
public void changeDocState(List<String> uuids, EDocState state, String shreddingCode) throws DatabaseException, WebserviceException, RepositoryException, ExtensionException, LockException, AccessDeniedException, PathNotFoundException, UnknowException {
List<Document2> documents = doc2DAO.getManyByUUIDs(uuids);
for (Document2 doc : documents) {
if (EDocState.SOFT_DEL == state) {
computeShreddingFor(doc, shreddingCode); //here the state change happens and it is persisted to db
}
if (EDocState.ACTIVE == state)
unscheduleShredding(doc);
}
}
}
doc2DAO.getManyByUUIDs(uuids); gets an Entity object from the database.
#Repository
public class Doc2DAO {
#PersistenceContext(name = Vedantas.PU_NAME, type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
public List<Document2> getManyByUUIDs(List<String> uuids) {
if (uuids.isEmpty())
uuids.add("-3");
TypedQuery<Document2> query = entityManager.createNamedQuery("getManyByUUIDs", Document2.class);
query.setParameter("uuids", uuids);
return query.getResultList();
}
}
However When I do second request to my API, I see state of this entity object unchanged, that means the same as before the logic above occoured.
In DB there is still changed status.
After the api service restart, I will get the entity in the correct state.
As I understand it, Hibernate uses it's L2 cache for the managed objects.
So can you, please point me to what I am doing wrong here? Obviously, I need to get cached entity with the changed state without service restart and I would like to keep entities attached to the persistence context for the performance reasons.
Now, can you tell me what I am
In the logic I am making some changes to this object. After the completition of the changeDocState method, the state is properly changed and persisted in the database.
Thanks for the answers;

GAE Objectify load object created in transaction within same transaction

I try to use objectify transaction, but I have some issues when I need to reload an object created in the same transaction.
Take this sample code
#Entity
public class MyObject
{
#Parent
Key<ParentClass> parent;
#Index
String foo;
}
ofy().transact(new VoidWork()
{
#Override
public void vrun()
{
ParentClass parent = load();// load the parent
String fooValue = "bar";
Key<ParentClass> parentKey = Key.create(ParentClass.class, parent.getId())
MyObject myObject = new MyObject(parentKey);
myObject.setFoo(fooValue);
ofy().save().entity(myObject).now();
MyObject reloaded = ofy().load().type(MyObject.class).ancestor(parentKey).filter("foo", fooValue).first().now();
if(reloaded == null)
{
throw new RuntimeException("error");
}
}
});
My object reloaded is always null, maybe I miss something, but logically within a transaction I can query an object which was created in the same transaction?
Thanks
Cloud Datastore differs from relational databases in this particular case. The documentation states that -
Unlike with most databases, queries and gets inside a Cloud Datastore
transaction do not see the results of previous writes inside that
transaction. Specifically, if an entity is modified or deleted within
a transaction, a query or lookup returns the original version of the
entity as of the beginning of the transaction, or nothing if the
entity did not exist then.
https://cloud.google.com/datastore/docs/concepts/transactions#isolation_and_consistency

Categories