I'm developing a java web application that uses spring like application container. Now while I'm was using the transaction support to spring, I noticed that the time processing of the my annotated method is doubled. Try to descibe the method in a better way:
#Service
public class MyServiceImpl implements MyService{
#Autowired
UtilService utilService;
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void loadContracts(File fileToProcess,UtilDTO dto){
List<MyObject> objects = utilService.readSomethings("xxx","yyy")
//I modify Them
//I save or update them
}
}
#Service
public class UtilServiceImpl implements UtilService{
#PersistenceContext
EntityManager entityManager;
public List<MyObject> readSomethings(String p1,String p2){
String queryString = "from MyObject o where o.param1 = :param1 "
+ " and o.param2 = :param2 ";
Query q = entityManager.createQuery(queryString);
q.setParameter("param1", p1);
q.setParameter("param2", p2);
return q.getResultList();
}
}
For example:
The method readSomething is too late while If I remove the annotation its time processing improves.
Why is there this difference?
The performance is likely due to you creating a new transaction and suspending the existing transactions every time that method is invoked.
From the documentation on Propagation.REQUIRES_NEW, emphasis mine:
Create a new transaction, and suspend the current transaction if one exists. Analogous to the EJB transaction attribute of the same name.
Unless you have specific requirements about creating a new transaction for this method, I would recommend letting it fall through to default behavior - that is, Propagation.REQUIRED.
Related
I am using spring data rest and Spring JPA. I am having one method which update one database table.
#Autowired InvoiceClient;
#Override
#Transactional
public String doBilling(String x){
//get date from TableOne
Bill bill = billsRepository.getBill(x);
if(bill.isPaid()){
generateInvoice();
}
bill.setPaymentDate(new Date());
return "SUCCESS";
}
generateInvoice is non Transactional method which calls #Transactional method from other service.
public void generateInvoice(){
invoiceClient.generateInvoice();//this is #Transactional, make changes in TableTwo
}
In case of any exception in generateInvoice method whole transaction is rolled back.
Now I want to add one more method which will have list of bill numbers. I call doBilling method in loop to do billing for all the bills.
#Override
#Transactional(readOnly = false, rollbackFor = {Throwable.class}, propagation = Propagation.REQUIRED)
public String doBillingForAll(List<String> tx){
for(String x: tx){
doBilling(x);
}
}
But now in case of any exceptions in doBilling method, all the setPayment methods are getting rolled back but generateInvoice is persisted.
I want to rollback generateInvoice also. How can I do it?
You don't need to define a rollbackFor = {Throwable.class}.
By default all RuntimeException do a rollback when using #Transactional.
It can be that because you are using and intermediate non #Transactional annotated method, the main Transaction is suspended and a nested one is created.
Try to put #Transactional in your public void generateInvoice() then Propagation.REQUIRED should be applied with rollback of your invoices
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.
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)
Below is a quick outline of what I'm trying to do. I want to push a record to two different tables in the database from one method call. If anything fails, I want everything to roll back. So if insertIntoB fails, I want anything that would be committed in insertIntoA to be rolled back.
public class Service {
MyDAO dao;
public void insertRecords(List<Record> records){
for (Record record : records){
insertIntoAAndB(record);
}
}
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
Where MyDAO dao is an interface that is mapped to the database using mybatis and is set using Spring injections.
Right now if insertIntoB fails, everything from insertIntoA still gets pushed to the database. How can I correct this behavior?
EDIT:
I modified the class to give a more accurate description of what I'm trying to achieve. If I run insertIntoAAndB directly, the roll back works if there are any issues, but if I call insertIntoAAndB from insertRecords, the roll back doesn't work if any issues arise.
I found the solution!
Apparently Spring can't intercept internal method calls to transactional methods. So I took out the method calling the transactional method, and put it into a separate class, and the rollback works just fine. Below is a rough example of the fix.
public class Foo {
public void insertRecords(List<Record> records){
Service myService = new Service();
for (Record record : records){
myService.insertIntoAAndB(record);
}
}
}
public class Service {
MyDAO dao;
#Transactional (rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void insertIntoAAndB(Record record){
insertIntoA(record);
insertIntoB(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoA(Record record){
dao.insertIntoA(record);
}
#Transactional(propagation = Propagation.REQUIRED)
public void insertIntoB(Record record){
dao.insertIntoB(record);
}
public void setMyDAO(final MyDAO dao) {
this.dao = dao;
}
}
I think the behavior you encounter is dependent on what ORM / persistence provider and database you're using. I tested your case using hibernate & mysql and all my transactions rolled back alright.
If you do use hibernate enable SQL and transaction logging to see what it's doing:
log4j.logger.org.hibernate.SQL=DEBUG
log4j.logger.org.hibernate.transaction=DEBUG
// for hibernate 4.2.2
// log4j.logger.org.hibernate.engine.transaction=DEBUG
If you're on plain jdbc (using spring JdbcTemplate), you can also debug SQL & transaction on Spring level
log4j.logger.org.springframework.jdbc.core=DEBUG
log4j.logger.org.springframework.transaction=DEBUG
Double check your autocommit settings and database specific peciular (eg: most DDL will be comitted right away, you won't be able to roll it back although spring/hibernate did so)
Just because jdk parses aop annotation not only with the method, also parse annotation with the target class.
For example, you have method A with #transactional, and method B which calls method A but without #transactional, When you invoke the method B with reflection, Spring AOP will check the B method with the target class has any annotations.
So if your calling method in this class is not with the #transactional, it will not parse any other method in this method.
At last, show you the source code:
org.springframework.aop.framework.jdkDynamicAopProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
......
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping orfancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
}
I have code.
#Repository
public class ArticlesDao {
#Autowired
private SessionFactory sessionFactory;
/**
* #param count Specifited how many article get from DB
* #param start Start offset. Default 0
* #return all get article
*/
#Transactional
public List<Article> getLastArticles(Integer count, Integer start) {
if (start == null) {
start = 0;
}
final Session currentSession = sessionFactory.getCurrentSession();
final Criteria criteria = currentSession.createCriteria(Article.class);
criteria.addOrder(Order.desc("publishedDate"));
criteria.setFirstResult(count + start);
criteria.setMaxResults(count);
return criteria.list();
}
}
And Controler
#Autowired
ArticlesDao dao;
#RequestMapping(value = "/")
public ModelAndView getHome(#RequestParam("page") int page) {
dao.getLastArticles("STH args");
}
My question is whether Handler getHome() should be annotated #Transactional?
No, you should not use #Transactional over the controller/ controller method.
#Transactional is better/correct to use in service layer or DAO.
Normally I use #Transactional in the service layer and not in the DAO, since I want the transaction to hold an operation of business value and not an elementary operation.
In controllers, as in your example there is no real code, but just delegation to a service method, where the transaction is started, so you don't need to start another transaction in the Controller.
It is a good point to start your declarative transactions on your service/bussiness layer.
Anyway I think that #Transactional should be used in the service layer, and, at the same time, in the integration layer. You can use
#Transactional(propagation=Propagation.REQUIRES_NEW)
in your service layer, and, at the same time,
#Transactional(propagation=Propagation.REQUIRED)
in your DAO. Your integration layer will be more independent, and more easily testeable in a transactional enviroment.
Only the service layer and not the DAO should know the transaction behaviour because this is part of the business logic.
Further in your case you could make your transaction read only with #Transactional(readOnly = true).