Under multi threading, I keep getting old result from repository.
#Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateScore(int score, Long userId) {
logger.info(RegularLock.getInstance().getLock().toString());
synchronized (RegularLock.getInstance().getLock()) {
Customer customer = customerDao.findOne(userId);
System.out.println("start:": customer.getScore());
customer.setScore(customer.getScore().subtract(score));
customerDao.saveAndFlush(customer);
}
}
And CustomerDao looks like
#Transactional
public T saveAndFlush(T model, Long id) {
T res = repository.saveAndFlush(model);
EntityManager manager = jpaContext.getEntityManagerByManagedType(model.getClass());
manager.refresh(manager.find(model.getClass(), id));
return res;
}
saveAndFlush() from JpaRepository is used in order to save the change instantly and the entire code is locked. But I still keep getting old result.
java.util.concurrent.locks.ReentrantLock#10a9598d[Unlocked]
start:710
java.util.concurrent.locks.ReentrantLock#10a9598d[Unlocked]
start:710
I'm using springboot with spring data jpa.
I put all code in a test controller, and the problem remains
#RestController
#RequestMapping(value = "/test", produces = "application/json")
public class TestController {
private static Long testId;
private final CustomerBalanceRepository repository;
#Autowired
public TestController(CustomerBalanceRepository repository) {
this.repository = repository;
}
#PostConstruct
public void init() {
// CustomerBalance customer = new CustomerBalance();
// repository.save(customer);
// testId = customer.getId();
}
#SystemControllerLog(description = "updateScore")
#RequestMapping(method = RequestMethod.GET)
#Transactional(isolation = Isolation.REPEATABLE_READ)
public CustomerBalance updateScore() {
CustomerBalance customerBalance = repository.findOne(70L);
System.out.println("start:" + customerBalance.getInvestFreezen());
customerBalance.setInvestFreezen(customerBalance.getInvestFreezen().subtract(new BigDecimal(5)));
saveAndFlush(customerBalance);
System.out.println("end:" + customerBalance.getInvestFreezen());
return customerBalance;
}
#Transactional
public CustomerBalance saveAndFlush(CustomerBalance customerBalance) {
return repository.saveAndFlush(customerBalance);
}
}
and the results are
start:-110.00
end:-115.00
start:-110.00
end:-115.00
start:-115.00
end:-120.00
start:-120.00
end:-125.00
start:-125.00
end:-130.00
start:-130.00
end:-135.00
start:-130.00
end:-135.00
start:-135.00
end:-140.00
start:-140.00
end:-145.00
start:-145.00
end:-150.00
I tried to reproduce the problem and failed. I put your code, with very little changes into a Controller and executed it, by requestion localhost:8080/test and could see in the logs, that the score gets reduced as expected. Note: it actually produces an exception because I don't have a view resulution configured, but that should be irrelevant.
I therefore recommend the following course of action:
Take my controller from below, add it to your code with as little changes as possible. Verify that it actually works. Then modify it step by step until it is identical with your current code. Note the change that starts producing your current behavor. This will probably make the cause really obvious. If not update the question with what you have found.
#Controller
public class CustomerController {
private static String testId;
private final CustomerRepository repository;
private final JpaContext context;
public CustomerController(CustomerRepository repository, JpaContext context) {
this.repository = repository;
this.context = context;
}
#PostConstruct
public void init() {
Customer customer = new Customer();
repository.save(customer);
testId = customer.id;
}
#RequestMapping(path = "/test")
#Transactional(isolation = Isolation.REPEATABLE_READ)
public Customer updateScore() {
Customer customer = repository.findOne(testId);
System.out.println("start:" + customer.getScore());
customer.setScore(customer.getScore() - 23);
saveAndFlush(customer);
System.out.println("end:" + customer.getScore());
return customer;
}
#Transactional
public Customer saveAndFlush(Customer customer) {
return repository.saveAndFlush(customer);
}
}
After update from OP and a little discussion we seemed to have it pinned down:
The problem occurs ONLY with multiple threads (OP used JMeter to do this thing 10times/second).
Also Transaction level serializable seemed to fix the problem.
Diagnosis
It seems to be a lost update problem, which causes effects like the following:
Thread 1: reads the customer score=10
Thread 2: reads the customer score= 10
Thread 1: updates the customer to score 10-4 =6
Thread 2: updates the customer to score 10-3 =7 // update from Thread 1 is gone.
Why isn't this prevented by the synchronization?
The problem here is most likely that the read happens before the code shown in the question, since the EntityManager is a first level cache.
How to fix it
This should get caught by optimistic locking of JPA, for this one needs a column annotated with #Version.
Transaction Level Serializable might be the better choice if this happens often.
This looks like the case for pessimistic locking. Create in your repository method findOneWithLock lke this:
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
import javax.persistence.LockModeType;
public interface CustomerRepository extends JpaRepository<Customer, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query("select c from Customer c where c.id = :id")
Customer findOneWithLock(#Param("id") long id);
}
and use it to obtain db level lock which will be held till the end of transaction:
#Transactional
public void updateScore(int score, Long userId) {
Customer customer = customerDao.findOneWithLock(userId);
customer.setScore(customer.getScore().subtract(score));
}
There is no need to use application level locks like RegularLock in your code.
The problem seems to be eventhough you call,
customerDao.saveAndFlush(customer);
The Commit will not take place until the end of the method is reached and a commit is made because your code inside of a
#Transactional(isolation = Isolation.REPEATABLE_READ)
What you can do is either change the propagation of the Transactional to Propagation.REQUIRES_NEW like below.
#Transactional(isolation = Isolation.REPEATABLE_READ, propagation=Propagation.REQUIRES_NEW)
This will result in a New Transaction being created and committed at the end of the method. And at the end of Transaction the changes will be committed.
With this line
manager.refresh(manager.find(model.getClass(), id));
you are telling JPA to undo all your changes. From the documentation of the refresh method
Refresh the state of the instance from the database, overwriting changes made to the entity, if any.
Remove it and your code should run as expected.
Related
I have some uncatchable bug in my work.
For example, I have code that looks like this:
#Entity
public class Message {
#Id
#GeneratedValue(strategy = SEQUENCE, generator = "message_generator")
private long id;
private long massMessageId;
}
public class MessageDTO {
public final long id;
public final long massMessageId;
}
#Transactional
#Service
public class ExtendedMessageService {
private MessageService messageService;
public MessageDTO createMessage(MessageCreateDTO createDTO) {
var messageDTO = messageService.create();
return messageService.linkMassMessage(messageDTO.id, createDTO.massMessageId);
}
}
#Transactional
#Service
public class MessageService {
private final MessageRepository repository;
private final ObjectMapper mapper;
public MessageDTO create() {
var message = new Message();
var savedMessage = repository.save(message);
return mapper.map(savedMessage, MessageDTO.class);
}
public MessageDTO linkMassMessage(long messageId, long massMessageId) {
var message = repository.findById(messageId)
.orElseThrow(() -> new ObjectNotFoundException("Message with id " + id + " was not found"));
return mapper.map(repository.save(message.setMassMessageId(massMessageId)), MessageDTO.class);
}
}
What will happen in this situation? I have some bugs, when repository.findById(id) can't find entity and throws exception.
And i have no reason, why this bug is only on prod (i tried to repeat it on dev and nothing succeeded)
And when i try to find the reason of it, i get a question:
"Can i save entity and get it in one transaction in Spring?"
How saving works
repository.save() doesn't save anything to database, this method puts entity to the session (persistent context) in memory.
flush step — on this step actual SQL insert happens. It can be invoked manually repository.saveAndFlush(), repository.flush(). Hibernate can do flush in the background, before operations that can use saved to the database value, like JPQL statements.
Also flush happens when the end of #Transactional boundary is reached.
What can be an issue
You are using incorrect method. This method from the old version of Spring data and it doesn't perform search in the database. You have to use findById() method instead.
Hibernate: findById vs getbyId
The most simple way, if you want to use id after save — flush the data immediately.
Entity entity = new Entity(some_information);
repository.saveAndFlush(entity);
Entity findedEntity = repository.findById(entity.getId())
.orElseThrow(() -> new RuntimeException("Can't find id=" + entity.getId()));
Hibernate will not necessary perform SQL select to get findedEntity. It can get it from the session, if it happens in the same #Transactional boundaries.
So if the above code resides in the method with #Transaction SQL will not performed. if there is not #Transaction SQL will be performed.
About this question
"Can Spring or Hibernate find not flushed entity in transaction context? Or there are some other ways to do it?"
Hibernate can't find not flushed entity. if id is autogenerated, Hibernate needs to perform SQL INSERT (flush) to get the id from a database. Another option to set up an id manually. Probably in this case it will be possible to get an entity from the persistent context.
I am trying to update the RUN_STATUS using native query as below:
#Modifying
#Transactional
#Query(value = "update rerun_scheduler set RUN_STATUS=:runStatus where SCHED_NAME = :scheduleName and STEP_NAME = :stepName and MODEL_ID = :modelId and SUBMITTED_TIME = :submittedTime and START_TIME = :startTime", nativeQuery = true)
int updateManualRun(#Param("scheduleName") String scheduleName, #Param("stepName") String stepName, #Param("modelId") String modelId, #Param("submittedTime") long submittedTime, #Param("startTime") long startTime, #Param("runStatus") String runStatus);
I am able to see the value being updated in the table using Mysqlworkbench. But in my code, when i try to read the status of the job as below:
#Query(value = "Select * from rerun_scheduler where SCHED_NAME = :scheduleName and MODEL_ID=:modelId and SUBMITTED_TIME=:submittedTime AND RUN_NUMBER = :runNumber", nativeQuery = true)
RerunDTO getJobStatus(#Param("scheduleName") String scheduleName, #Param("modelId") String modelId, #Param("submittedTime") Long submittedTime, #Param("runNumber") int runNumber);
Old value in RUN_STATUS was "TO_DO" when I am updating it, I am changing it to "COMPLETE". but when I am making the Select * query, I am still getting the RUN_STATUS as "TO_DO". Do I have to do any save operation after the update? if so, what is the command to save?
You may have two different transactions:
Some method marked with #Transactional is run. This code will call getJobStatus later.
After that updateManualRun is called from another thread, so another transaction is created and committed (note, that method above is still run and another transaction is still opened)
Since transaction for getJobStatus is still opened - it can't see updates which happened after it's start.
If it's the case - you need to call getJobStatus in separate transaction each time.
If you have service
#Service
class MyService {
#Autowired
JobStatusDao dao;
#Transactional
public void checkStatus() {
Boolean isFound=false;
while (!isFound) {
isFound=dao.getJobStatus() != null;
}
}
}
You may want to create another service (note, that there is temptation to just create another method with #Transactional in the same service - but this won't work, check this https://docs.spring.io/spring/docs/3.2.4.RELEASE/spring-framework-reference/html/aop.html#aop-proxying ).
#Service
class JobStatusService{
#Autowired
JobStatusDao dao;
#Transactional
public boolean checkStatus() {
return dao.getJobStatus() != null;
}
And then you need to rewrite your original service:
#Service
class MyService {
#Autowired
JobStatusService service;
public void checkStatus() {
Boolean isFound=false;
while (!isFound) {
isFound=service.checkStatus();
}
}
}
Btw, note that querying DB in while loop will create a decent load.
You should add some delay between checking.
I'm using Spring Rest. I have an Entity called Operator that goes like this:
#Entity
#Table(name = "operators")
public class Operator {
//various properties
private List<OperatorRole> operatorRoles;
//various getters and setters
#LazyCollection(LazyCollectionOption.TRUE)
#OneToMany(mappedBy = "operator", cascade = CascadeType.ALL)
public List<OperatorRole> getOperatorRoles() {
return operatorRoles;
}
public void setOperatorRoles(List<OperatorRole> operatorRoles) {
this.operatorRoles = operatorRoles;
}
}
I also have the corresponding OperatorRepository extends JpaRepository
I defined a controller that exposes this API:
#RestController
#RequestMapping("/api/operators")
public class OperatorController{
private final OperatorRepository operatorRepository;
#Autowired
public OperatorController(OperatorRepository operatorRepository) {
this.operatorRepository = operatorRepository;
}
#GetMapping(value = "/myApi")
#Transactional(readOnly = true)
public MyResponseBody myApi(#ApiIgnore #AuthorizedConsumer Operator operator){
if(operator.getOperatorRoles()!=null) {
for (OperatorRole current : operator.getOperatorRoles()) {
//do things
}
}
}
}
This used to work before I made the OperatorRoles list lazy; now if I try to iterate through the list it throws LazyInitializationException.
The Operator parameter is fetched from the DB by a filter that extends Spring's BasicAuthenticationFilter, and is then somehow autowired into the API call.
I can get other, non-lazy initialized, properties without problem. If i do something like operator = operatorRepository.getOne(operator.getId());, everything works, but I would need to change this in too many points in the code.
From what I understand, the problem is that the session used to fetch the Operator in the BasicAuthenticationFilter is no longer open by the time i reach the actual API in OperatorController.
I managed to wrap everything in a OpenSessionInViewFilter, but it still doesn't work.
Anyone has any ideas?
I was having this very same problem for a long time and was using FetchType.EAGER but today something has clicked in my head ...
#Transactional didn't work so I thought "if declarative transactions don't work? Maybe programmatically do" And they do!
Based on Spring Programmatic Transactions docs:
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private final TransactionTemplate transactionTemplate;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager,
PlatformTransactionManager transactionManager) {
super(authenticationManager);
this.transactionTemplate = new TransactionTemplate(transactionManager);
// Set your desired propagation behavior, isolation level, readOnly, etc.
this.transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
}
private void doSomething() {
transactionTemplate.execute(transactionStatus -> {
// execute your queries
});
}
}
It could be late for you, but I hope it helps others.
Consider the following situation:
We receive a request from a web service which updates our entity. Sometimes we might get two requests at (almost) the same time. We had situations in which our entity looked completely wrong, because of concurrent updates. The idea is to lock the entity pessimistic so that whenever the first request comes it instantly locks the entity and the second request can't touch it (Optimistic locking is no alternative for us). I wrote an integration test to check this behaviour.
I got an integration test which looks like the following:
protected static TestRemoteFacade testFacade;
#BeforeClass
public static void setup() {
testFacade = BeanLocator.lookupRemote(TestRemoteFacade.class, TestRemoteFacade.REMOTE_JNDI_NAME, TestRemoteFacade.NAMESPACE);
}
#Test
public void testPessimisticLock() throws Exception {
testFacade.readPessimisticTwice();
}
which calls the bean
#Stateless
#Clustered
#SecurityDomain("myDomain")
#RolesAllowed({ Roles.ACCESS })
public class TestFacadeBean extends FacadeBean implements TestRemoteFacade {
#EJB
private FiolaProduktLocalFacade produkt;
#Override
public void readPessimisticTwice() {
produkt.readPessimisticTwice();
}
}
with produkt being a bean itself
#Stateless
#Clustered
#SecurityDomain("myDomain")
#RolesAllowed({ Roles.ACCESS })
public class ProduktFacadeBean implements ProduktLocalFacade {
#Override
public void readPessimisticTwice() {
EntityManager entityManager = MyService.getCrudService().getEntityManager();
System.out.println("Before first try.");
entityManager.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE);
System.out.println("Before second try.");
entityManager.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE);
System.out.println("After second try.");
}
}
with
public class MyService {
public static CrudServiceLocalFacade getCrudService() {
return CrudServiceLookup.getCrudService();
}
}
public final class CrudServiceLookup {
private static CrudServiceLocalFacade crudService;
private CrudServiceLookup(){
}
public static CrudServiceLocalFacade getCrudService() {
if (crudService == null)
crudService = BeanLocator.lookup(CrudServiceLocalFacade.class, CrudServiceLocalFacade.LOCAL_JNDI_NAME);
return crudService;
}
public static void setCrudService(CrudServiceLocalFacade crudService) {
CrudServiceLookup.crudService = crudService;
}
}
#Stateless
#Local(CrudServiceLocalFacade.class)
#TransactionAttribute(TransactionAttributeType.MANDATORY)
#Interceptors(OracleDataBaseInterceptor.class)
public class CrudServiceFacadeBean implements CrudServiceLocalFacade {
private EntityManager em;
#Override
#PersistenceContext(unitName = "persistence_unit")
public void setEntityManager(EntityManager entityManager) {
em = entityManager;
}
#Override
public EntityManager getEntityManager() {
return em;
}
}
The problem that arises now is: If I start the integration test once with a breakpoint at System.out.println("Before second try."); and then start the integration test a second time, the latter one can still read MyEntity. Remarkable is that they were different instances (I made this observation on the instanceId in debug mode). This suggests that the entityManager didn't share his hibernate context.
I made the following observations:
Whenever I call a setter on entity and save it to the db, the lock is aquired. But this is not what I need. I need the lock without having modified the entity.
I tried the method entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE) as well, but the behaviour was the same.
I found Transaction settings in DBVisualizer. At the moment it is set to TRANSACTION_NONE. I tried all the others (TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE) as well, without any success.
Let the first thread read the entity, then the second thread read the same entity. Let the first tread modify the entity and then the second modify it. Then let both save the entity and whoever saves the entity last wins and no exceptions will be thrown.
How can I read an object pessimistic, that means: Whenever I load an entity from the db I want it to be locked immediately (even if there was no modification).
Both ways you describe ie.
em.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE)
em.lock(entity, LockModeType.PESSIMISTIC_WRITE)
hold a lock on the related row in database but only for the the entityManager lifespan, ie. for the time of the enclosing transaction, the lock will be so automatically released once you've reached the end of the transaction
#Transactional()
public void doSomething() {
em.lock(entity, LockModeType.PESSIMISTIC_WRITE); // entity is locked
// any other thread trying to update the entity until this method finishes will raise an error
}
...
object.doSomething();
object.doSomethingElse(); // lock is already released here
Have you tried to set the isolation level in your application server?
To get a lock on a row no matter what you are trying to do afterwards (read/write), you need to set the isolation level to TRANSACTION_SERIALIZABLE.
Lock fails only if another thread is already holding the lock. You can take two FOR UPDATE locks on single row in DB, so it's not JPA-specific thing.
In my unit tests I want to persist some entities and test their retrieval from the database. They were not being saved and I figured out that when the test method was also annotated with #Transaction, anything that happened inside it did not get persisted, even though the method finished without an error.
I had previously encountered a LazyInitializationException when messing with a many-to-many lazy-loaded association and annotating the method with #Transaction seemed to fix the issue, that's why I have been using it.
What could be the cause why the entities don't get saved? There is no reason for the transaction to be rolled back, since it does not fail.
Code of related classes:
#Test
#Transactional
public void plainPersistence() throws NullParameterException {
User user = userHelper.createUser("User1", "password", null, null);
Assert.assertNotNull(userDAO.findByUsername("User1"));
}
userHelper:
#Service
public class UserHelper {
#Autowired
private UserDAO userDAO;
public User createUser(...) throws NullParameterException {
User newUser = new User(username, ...);
userDAO.save(newUser);
return newUser;
}
UserDAO's save() method subsequently calls save() on UserRepository:
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
public User findByUsername(String username);
}
Since you're likely using Spring test, you should note that they are configured so that the default behaviour is to rollback the changes. To change this you should annotate your test classes with, if you are using Spring < 4.2
#TransactionConfiguration(defaultRollback = false)
otherwise, annotate the class with #Rollback(value = false)