What is the difference between method with #Transactional(propagation = Propagation.SUPPORTS) vs having no #Transactional annotation at all?
#Transactional(propagation = Propagation.SUPPORTS)
public void MyMethod()
vs
public void MyMethod()
Wouldn't the one without the annotation also use a transaction if one is already open, otherwise continue without any transaction?
There is a small difference. suppose we have two methods a() and b(), and a() is going to call b(). a itself is transnational and its propagation level is Propagation.REQUIRED but b one time is annotated with #Transactional(propagation = Propagation.SUPPORTS) and one time is not with annotation.
case 1:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
in case 1 we have aspect() -> a() -> b() and you are able to prevent RuntimeException to reach aspect and aspect inspects the transaction to see whether it is marked to rollback, so aspect consider this transaction successful and you can see we have this result in our database
0,product 0
1,product 1
2,product 2
3,product 3
4,product 4
5,product 5
even though multiple exceptions were thrown but we were able to commit the operations have been done so far.
now consider case 2:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
#Transactional(propagation = Propagation.SUPPORTS)
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
with propagation = Propagation.SUPPORTS if a transaction already exists it's going to use it. so if you throw an exception in b it is going to mark the same transaction to rollback and even if you use the try/catch block to prevent the RuntimeException to reach aspect in a() the transaction is already marked to rollback in b and you see no result in your database. aspect() -> a() -> aspect() -> b()
credit goes to Laurentiu Spilca, see this and read the comment section
From your link, it states that Propagation.SUPPORTS might have impact on
synchronization:
SUPPORTS is slightly different from no transaction at all, as it defines a transaction scope that synchronization will apply for. As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) will be shared for the entire specified scope
Yes one without any annotation will use existing transaction if one is already open but what if there is not any won't you want to revert the changes if your transaction fails.
Propagation has also other attribute has well in case you method is not dependant of parent transaction and runs in individual transaction scope, probably you can go for REQUIRES_NEW this way you can have independent transaction for your method.
Related
I have a job method in a class-annotated #Transactional. This job method calls inner methods for persistence of individual records. If I simulate an error in the following inner update() method somewhere in the middle of my result set processing, I see that all successful records before/after this exception do not get saved after job completion. Why is that? All outside persistence should remain, with the exception of the individual record that failed. The inner update alone has rollbackFor.
#Service("mailService")
#Transactional
#EnableScheduling
public class MailServiceImpl implements MailService {
#Override
#Scheduled(cron = "${mail.cron.pubmed.autosynch.job}")
public void autoSynchPubMedJob() {
//... Fetch result set
for (Result r: resultset) {
try {
pubService.updatePublication(r);
} catch (Exception e) {
// Silently log and continue
log.error("Error on record: ", e);
}
}
}
The updatePublication method, this is the one with rollbackFor:
#Override
#Transactional(readOnly = false, rollbackFor = Exception.class)
public void updatePublication(Publication publication) throws Exception {
dao.update1(..);
dao.update2(..);
// Simulate exception for a specific record for testing
if (publication.getId() == 123) {
throw new Exception("Test Exception");
}
}
Result: no successful data persisted at all at the end of job completion. There should be partial persistence (for other successful records).
When I remove this Exception simulation, all data is successfully persisted at the end. Also, all data is persisted if I remove the inner call's rollbackFor.
Probaby because it uses existing transaction. Try opening a new one with propagation = REQUIRES_NEW.
Note: New transaction won't be opened if you call the method from the same service. You should use either self-reference call or extract logic to another #Service.
#GetMapping("trans")
#Transactional()
public String primaryTrans() {
User u1 = new User(0,"test","test#email.com");
us.save(u1);
User u2 = new User(0,"test1","test1#email.com");
us.save(u2);
secondaryTrans();
return "index";
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void secondaryTrans() {
// TODO Auto-generated method stub
User u2 = new User(0,"test2","test3#email.com".repeat(300));
us.save(u2);
}
Here i am manually raising DATA TOO LONG exception from secondary transaction, But it causes primary transaction also rolled back. How can we make sure that primary transaction to be committed irrespective of secondary transaction
In this case, since the second method is called from the same class, the second transaction is most likely not created. Springs transactional support uses AOP proxies to create transactions. The docs contain a description on why this will not work.
The simplest way is to catch the exception thrown from secondaryTrans() method, so just wrap secondaryTrans() into try-catch block:
try {
secondaryTrans();
} catch (Exception e) {
//...
}
I use Spring + Hibernate + JPA
I need to handle the list of customers by inserting their orders.
Here is the Unit of work :
for(Customer customer: CustomerList) {
List<Order> orderList = customer.getOrders();
for(Order order: OrderList) {
//1. Insert order into ORDER table
//If insert fails due to Duplicate key then no rollback and I follow steps 2 & 3.
//If insert fails due to any reason except duplicate key then rollback all the previous transactions
//2. select the order record (If any issue during selection then rollbackall the previous transactions)
//3. Update the order If status of the order is different from that in DB (If any issue during update then rollback all the previous transactions)
}
// 4. Update Customer record into CUSTOMER table (If any issue during update then rollback all the previous transactions)
}
Commit is required when all the orders and customer db processes are ok.
Insert order
1.a If duplicate order do not roll back. But select that order from table and update if the status of the order is different in the req compared to the one in db
1.b If any other error during inserting of ORDER then roll back
1.c If no error then proceed inserting orders of the particular Customer
Once orders of the particular Customer is done, then update Customer table
Loop continues..
While handling point 1, 2 and 3 If everything is ok, then commit is required.
If any issues in the middle then all transactions are rolled back
Controller --> Facade layer --> Service --> Repository/Dao
Facade:
#Autowired
MyServiceBean serviceBean;
#Transactional(noRollbackFor = {EntityExistsException.class, PersistException.class, ConstraintViolationException.class, DataIntegrityViolationException.class})
#override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
String dbAction = "";
try {
dbAction = serviceBean.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
}
if("handleDupl".equalsTo(dbAction) {
serviceBean.handleDuplOrder(order);
}
}
myService.updateCustomer(customer);
}
}
Service:
#Autowired
MyRepository repo;
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public String inserOrder() {
String dbAction = "";
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
dbAction = "handleDuplOrder";
} catch(all other Exception except duplicate key e) {
// roll back and throw exception to Facade layer
}
return dbAction;
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public void handleDuplOrder(Order order) {
try {
repo.selectOrderDao(order);
repo.updateOrder(order);
} catch(Exception e) {
// roll back and throw exception to Facade layer
}
}
Repository:
#PersistentContext(unitNmae = "MY_SHCEMA")
EntityManager entityManager;
#Override
public void insertOrderDao(Order order) {
entityManager.persist(order);
entityManager.flush();
}
Problem:
When I send req with One customer who has single order, where the order is duplicate, I see PersistException is caught inside Service method and when it exists from Service method it also throws TransactionSystemException(nested exception is RollbackException: Transaction is marked as rollback only, could not commit JPA transaction) is thrown to Facade layer irrespective of the how I suppress the exception in inner transaction.
Kindly advice If I can achieve Unit of work commit or rollback in this way.
Expected:
I want Spring's #Transactional to ignore Duplicate key exceptions by not rolling back and not affecting the next transaction.
Your outer transaction still fail because you throw ApplicationDuplOrderException() inside your service.
You should setup your services like below:
#Transactional
#Override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
try {
myService.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
throw e; // important, you must rethrow
}
}
myService.updateCustomer(customer);
}
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#Override
public void inserOrder() {
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
log.error(xxx); // instead of throwing
} catch(all other Exception except duplicate key e) {
throw e;
}
}
I have configured an application to work with MyBatis-Spring and I would like to connect to multiple databases.
For this purpose, in my applicationContext.xml I have defined one datasource, one Transaction Manager (org.springframework.jdbc.datasource.DataSourceTransactionManager), one Sql Session Factory (org.mybatis.spring.SqlSessionFactoryBean) and one MapperScannerConfigurer (org.mybatis.spring.mapper.MapperScannerConfigurer) for each one of them.
Then, inside my service class I would like to perform CRUD operations with multiple databases inside the same method. As I must point to the correct transaction manager I have done what is commented below:
#Service("myServiceDB")
public class MyServiceDB implements MyService {
[...]
#Transactional(value = "TransactionManager1", rollbackFor = MyCustomException.class)
public MyUser multipleMethod(final int idUser) throws MyCustomException {
MyUser myUser = null;
int rowsAffected1 = -1;
int rowsAffected2 = -1;
try {
myUser = this.mapperOne.getById(idUser);
if (myObject != null) {
rowsAffected1 = this.mapperOne.deleteByIdUser(idUser);
}
if (rowsAffected1 == 1) {
insertUser(myUser);
}
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
if ((myUser == null) || (rowsAffected1 != 1)) {
throw new MyCustomException(TRANSACTION_ERROR);
}
return myUser;
}
#Transactional(value = "TransactionManager2", rollbackFor = MyCustomException.class)
public void insertUser(final MyUser myUser) throws MyCustomException{
int rowsAffected = -1;
try {
rowsAffected = this.mapperTwo.insert(myUser);
**throw new MyCustomException();**
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
//if (rowsAffected != 1) {
// throw new MyCustomException(TRANSACTION_ERROR);
//}
}
[...]
}
So each method points to its corresponding transaction manager.
If I throw the custom exception in the second method after the insert, I get the delete made in the first method correctly rolled back. However, the insert performed by the second Transaction Manager is not rolled back properly as I would desire. (i.e. the user is inserted in the second database but not deleted in the first one).
My questions are:
Is it possible to achieve what I want?
How should I configure the #Transactional annotation?
Thanks in advance.
I found the solution here by #RisingDragon:
"If you are calling it from another local method then it will not work, because spring has no way of know that it is called and to start the transaction.
If you are calling it from a method of another class by using autowired object of the class which contains insertNotes() method, then it should work."
In my case, I created a second class (e.g. RisingDragom´s NoteClass) with some #Transactional methods (e.g. insertUser in my code) and then, the rollback worked!! This second class appeared in the debugger with the tail "$$EnhancedByCGLib".
However, if you need a method with several steps in different databases another "custom" rollback should be applied...The rollback is just applied method by method, not for the full process, so surely some data should be restored "by hand" in case of failure in any of the steps.
Is that possible to perform commit in the method that is marked as Spring's #Transactional?
#PersistenceContext
private EntityManager em;
#Transactional(propagation = Propagation.REQUIRED)
public void saveMembersWithMultipleCommits(List<Member> members)
throws HibernateException
{
Iterator<Member> it = members.iterator();
while (it.hasNext())
{
while (it.hasNext())
{
Member wsBean = it.next();
em.persist(wsBean); // overall commit will be made after method exit
log.info("Webservices record " + wsBean + " saved. " + i++);
}
}
}
I would like to have commit to DB after say each 500 items. Is that possible with aforementioned context?
No, you need to do it programatically using, for instance, the TransactionTemplate API. Read more here.
It would look something like
while (it.hasNext())
{
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
int counter = 0;
while (it.hasNext() && counter++ < 500) {
Member wsBean = it.next();
em.persist(wsBean);
log.info("Webservices record " + wsBean + " saved. " + i++);
}
}
);
}
Your question suggests that you have misplaced your transaction boundary.
You can move the persist call into a private method and make that method transactional instead of the outer one. This method could accept 500 members at a time and then will commit when it exits.
If you are looking forward to committing transactionally inside your other transaction, you might need to use #Transactional (propagation = Propagation.REQUIRES_NEW)
Alternate strategy is you create a method in DAO and mark it #Transactional. This method will do bulk update(for eg 500 nos). So you can have a method with code
#Transactional
public void mybatchUpdateMethod(){
StatelessSession session = this.hibernateTemplate.getSessionFactory()
.openStatelessSession();
Transaction transaction = null;
Long entryCounter = 0L;
PreparedStatement batchUpdate = null;
try {
transaction = session.beginTransaction();
batchUpdate = session.connection().prepareStatement(insertSql);
for (BatchSnapshotEntry entry : entries) {
entry.addEntry(batchUpdate);
batchUpdate.addBatch();
if (++entryCounter == 500) {
// Reached limit for uncommitted entries, so commit
batchUpdate.executeBatch();
}
}
batchUpdate.executeBatch();
batchUpdate.close();
batchUpdate = null;
}
catch (HibernateException ex) {
transaction.rollback();
transaction = null;
}
}
Every time you call this method, it will commit after 500 inserts/updates