#Service
public class TransactionClass{
#AutoWire
TransactionClass tranClass;
#Autowire
TransactionRepository transRepo;
public void methodA(Data data){
try{
methodB(data)
}catch(Exception e){
//some logic
}
}
public void methodB(Data data){
//some logic
tranClass.methodC(data)
}
#Transactional
public void methodC(Data data){
//some logic
transRepo.save(data);
throw new RuntimeException();
}
}
The problem is that the methodC() isn't getting rolled back even though an unchecked exception is thrown.
To check how transactions work using logs, just add this to the application.yaml
logging.level.org.springframework.transaction.interceptor: TRACE
logging.level.org.springframework.orm.jpa.JpaTransactionManager: DEBUG
logging.level.org.hibernate.SQL: DEBUG
spring.jpa.properties.hibernate.use_sql_comments: true
Transactional Method not rolling back as it had multi DB connection. So #Transactional will roll back for the only primary configuration and we can have only one primary configuration as well in an application. The solution to this is to use a chained transaction.
Refer to this link for furthermore on the chained transaction:
https://blog.usejournal.com/springboot-jpa-rollback-transaction-with-multi-databases-53e6f2f143d6
Related
I am working on a spring boot app where I am using transactional and its not rolling back its changes when I throw a exception:
My method:
private BtoBWalletTransactionResponseModel doWalletOperation(BtoBWalletTransactionTypes transactionType, BtoBWalletTransactionRequestModel transactionRequest) {
// DB Operation
BtoBWalletTransaction savedTransaction = commonTransactionalService.finishWalletTransaction(userWallet, btoBWalletTransaction);
log.info("wallet {} txn of amount {} for user {}",transactionType.name(),txnAmount,userId);
// throwing a exception to rollback
throw new RuntimeException("Time to Rollback");
} catch(Exception e){
log.error(e.getMessage());
log.error("error while doing wallet operations for user {}",userId);
throw new WalletException(e.getMessage());
}
}
My common TransactionalService Interface:
public interface CommonTransactionalService {
BtoBWalletTransaction finishWalletTransaction(BtoBUserWallet userWallet,BtoBWalletTransaction btoBWalletTransaction);
}
My Interface Impl:
import javax.transaction.Transactional;
#Service
public class CommonTransactionalServiceImpl implements CommonTransactionalService {
#Autowired
private BtoBWalletTransactionRepo btoBWalletTransactionRepo;
#Transactional
#Override
public BtoBWalletTransaction finishWalletTransaction(BtoBUserWallet userWallet, BtoBWalletTransaction walletTransaction) {
BtoBWalletTransaction savedTransaction = btoBWalletTransactionRepo.save(walletTransaction);
btoBUserWalletRepo.save(userWallet);
return savedTransaction;
}
}
Now even when I am sending a RuntimeException the DB record is not getting rolled back.
Can someone help? stuck since hours here.
Transactional is scoped, if you anotate a method (or class) as #Transactional all the methods this class calls wil also be transactional. and if within this transaction an exception occurs things wil be rolled back.
If however like in you example a non-transactional method calls a transactional one and after that call throws an exception the previous transactioned function wil not be rolled back as it's outside of the transactions scope.
I am working on a spring-batch, where after reader and processor, writer is responsible to populate data to DB. Writer is calling Service which internally calls DAO layer. In method insertToDB() if some exception occurs the transaction is not being rolled back. PSB my code.
public class MyWriter{
#Autowired
private MyService myService;
#Override
public void write(List<? extends MyBO> list) {
try{
for(MyBO bo: list){
myService.insert(bo);
}
}
catch(Exception e){
log.error("Cant write to DB")
}
}
public class MyService{
#Autowired
private TableOneDAO tableOneDao;
#Autowired
private TableTwoDAO tableTwoDAO;
#Autowired
private TableThreeDAO tableThreeDAO;
public void insert(MyBO bo){
try{
// do other stuff of processing bo and create entity
MyEntityTableOne myentity1 = getEntityT1(bo);
MyEntityTableTwo myentity2 = getEntityT2(bo);
MyEntityTableThree myentity3 = getEntityT3(bo);
insertToDB(myEntity1,myEntity2,myEntity3);
}
catch(Exception e){
log.error("Error occured.");
throw new MyException("Error Blah blah occured");
}
}
#Transactional(value = "txn1")
public void insertToDB(MyEntityTableOne entity1, MyEntityTableTwo entity2, MyEntityTableThree entity3) {
try{
tableOneDao.insert(entity1);
tableTwoDAO.insert(entity2);
tableThreeDAO.insert(entity3);
}
catch(Exception e){
log.error("Error occured during insert to DB");
throw new MyException("Error Blah blah occured during DB insert");
}
}
The code goes to the catch block, but doesn't rollback records. If some error occurs during insert of Table2 then entry for Table1 is not rolled-back. And if occurs during table3 insertion then table1 and table2 records are not rolled-back.
If I move the #Transactional annotation to insert() method it works fine. What is root cause of this issue. What I have to do if I want to have transaction only on insertToDB() method.
I am trying to make it simple: To support #Transactional spring wraps the implementing class into a so called proxy and surrounds the method call / class with the transactional logic.
Now you are calling the #Transactional annotated method within the same class. Therefore the proxy is not invoked and the transactional does not work. When moving the annotation to your insert method you are invoking the method from outside of the class which means you invoke the method against the proxy.
Thats a limitation of Spring AOP (?) I think.
You can do something like following to achieve what you want:
public class MyService{
#Ressource
private MyService self;
...
self.insertToDB(myEntity1,myEntity2,myEntity3)
Your item writer will be already called in a transaction driven by Spring Batch and that you can configure at the step level by providing the transaction manager and transaction attributes. So there is no need to use #Transactional in the downstream service used by the writer.
You need to remove that annotation from MyService and it should work as expected.
I am facing a problem due which is unknown to me, can you one have faced this problem?
JSON mapping problem: <package>ApiResponse["data"]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: possible non-threadsafe access to the session (through reference chain: <package>.ApiResponse["data"])
I have a standard API response pojo. Which I return every time with ResponseEntity. Everything is working fine, but sometimes I got that above error. I don't why this error occurred .
I got the below log from console
an assertion failure occurred (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): org.hibernate.AssertionFailure: possible non-threadsafe access to the session
org.hibernate.AssertionFailure: possible non-threadsafe access to the session
I think you are trying to share same Hibernate session within multiple threads. That's illegal.
Hibernate Sessions are not thread-safe whereas Hibernate SessionFactory is thread-safe.
So, make a separate DAO layer. Create single sessionfactory object and share it among the DAO classes.
Get a session for a single-threaded DB operation and close the session in that thread.
For example :
#Repository
public class DAO {
#Autowired
private SessionFactory sessionFactory;
public class performDBOperation(Object obj) {
Session session = sessionFactory.currentSession();
session.save(obj);
session.close();
}
}
Now, I have looked at your github code.
I saw the code Exec.java
#Service
public interface Exec {
#Async
#Transactional
public void run();
}
This is incorrect.
Updated :
public interface Exec {
public void run();
}
Update ExecImpl to this :
#Service
public class ExecImpl implements Exec {
#Autowired
private ExecDAO execDAO;
#Override
#Async
#Transactional
public void run() {
// example : create an object to save it.
Object object = ...;
execDAO.saveItem(object);
}
}
Create DAO layer :
Suppose ExecDAO interface and implementation ExecDAOImpl :
public interface ExecDAO {
public void saveItem(Object obj);
// keep here abstract method to perform DB operation
}
#Repository
public class ExecDAOImpl implements ExecDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
public void saveItem(Object obj) {
Session session = sessionFactory.currentSession();
session.save(obj);
session.close();
}
}
Looking at the code at the link you shared in the comment, I think that
#Async
#Transactional
is a dangerous thing.
I would suggest you to extract a method to do the transactions and try
what I mean is that,
interface ExecImpl{
#Async
void run(){
someThingElse.doTransaction();
}
}
interface SomeThingElse{
#Transactional
void doTransaction();
}
I am still not convinced this will help you. But this is something you can try.
I would also suggest to use readonly transactions for getting data and not have a single transaction for all purposes.
This blog explains why its not good to use these two annotations together whether on a class or on an interface
I have two methods are shown below.
#Transactional
public void methodA(){
logger.trace("Executing methodA");
methodB()
logger.trace("Executing methodA completed");
}
public void methodB(){
//other codes here
try{
staffDao.queryById(1) //Fetch a record from database
}catch(EmptyResultDataAccessException e){
logger.trace("Staff does not exists")
}
//other codes here
}
When there occurs an EmptyResultDataAccessException within methodB()
, the entire transaction started on methodA() is rollbacked, by below exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
I know this is the default behaviour of spring #Transactional annotation.
For my case, I need to commit the transaction even when there is an EmptyResultDataAccessException. As EmptyResultDataAccessException is a RuntimeException, I can't use the noRollBackFor attribute of #Transactional annotation.
Can anyone suggest a solution ?
I have not looked closely at your code yet but if you just need a way to not rollback the transaction for a particular exception, you can mark that in #Transactional annotation.
#Transactional(noRollbackFor = {EmptyResultDataAccessException.class})
public void methodA(){
.
.
}
http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor--
Looking at the code of deleteById in SimpleJpaRepository
public void deleteById(ID id) {
Assert.notNull(id, "The given id must not be null!");
this.delete(this.findById(id).orElseThrow(() -> {
return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
}));
}
I thought this is just a convenience method. If I don't want the exception I just implement it the way I need it:
repo.findById(id).ifPresent(repo::delete);
Given this example code:
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Here is what I want to achieve:
Only processItem(anItem); should rollback if exception occurs inside it.
If exception occurs, myTransactionalMethod should continue, that means the for-each should end.
If exception occurs inside myTransactionalMethod but not in processItem(anItem), myTransactionalMethod should rollback completely.
Is there a solution that doesn't involve managing transactions manually (without annotations)?.
Edit: I was thinking of using #Transactional(PROPAGATION=REQUIRES_NEW), don't know if it will work within the same bean though.
This is a common misunderstanding. Spring Transactions are implemented through proxies. Proxies are a wrapper around your class. You are accessing the processItem method from the same class, i.e. you don't go through the proxy, so you don't get any transactions. I explained the mechanism in this answer some years ago.
Solution: you need two separate Spring beans if you want nested transactions, both of them must be proxied by #Transactional.
It looks like a case for NESTED transaction. NESTED transaction starts a subtransaction with in the outer transaction with savepoint, allowing it rollback to that savepoint. Since it is a nested transactions they committed at the end of outer transation.
public class MyServiceImpl implements MyService {
#Transactional
public void myTransactionalMethod() {
List<Item> itemList = itemService.findItems();
for (Item anItem : itemList) {
try {
// If you want to call this method directly configure your transaction use to aspectJ for transaction handling or refactor the code. Refer - [http://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo][1]
processItem(anItem);
catch (Exception e) {
// dont rollback here
// rollback just one item
}
}
}
#Transactional(PROPAGATION = PROPAGATION.NESTED)
// Throw some runtime exception to rollback or some checkedException with rollbackFor attribute set in the above annotation
public void processItem(Item anItem) {
anItem.setSomething(new Something);
anItem.applyBehaviour();
itemService.save(anItem);
}
}
Note that, I have not yet tried this below code see if that helps. You might have to tweak it, if needed. In fact I would love to give this code a try myself sometime soon.