How to rollback transaction from service class? - spring

I am trying to rollback for some condition by throwing exception. But i can not find proper way to do this. Here is my service class
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
And here is my BasicEM class
#Component
#Transactional(value = "transactionManager", rollbackFor = Exception.class)
public class BasicEM {
#PersistenceContext(unitName = "db1")
private EntityManager em;
public void update(Object target) {
em.merge(target);
}
}
So what i want is to call that update method, then if the condition is true undo the update.
My intention is that when i throw the exception, the transaction ends and does not commit the update. But i am wrong and the update is already done. Please help me to achieve my goal.

In order to achieve what you want you will need to have a transaction already in the Service method. The default propagation type for #Transactional(value = "transactionManager", rollbackFor = Exception.class) is Propagation.REQUIRED which means that if your Service is already included in a transaction, the basicEM.Update(entity) will also be included in such transaction.
#Service
public class UserManager implements IUserManager {
private final IBasicEM basicEM;
public ApplicantInfoManager(IBasicEM basicEM) {
this.basicEM = basicEM;
}
#Override
#Transactional(value = "transactionManager",
propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class)
public boolean purgeUser(Long id) throws Exception {
//business logic
basicEM.Update(entity)
if(//condition) {
throw New RollBackException("//message")
}
//business logic
}
}
If RollBackException is a RuntimeException you don't need to explicitly configure that the transaction should rollback when it is thrown. If it is not, then you need to configure it as follows: #Transactional(value = "transactionManager", propagation = Propagation.REQUIRES_NEW, rollbackFor = RollBackException.class).

Related

Why is a transaction full rollback?

The innerInvoke method is called in the outerInvoke method, and propagation = Propagation.REQUIRES_NEW is used in the innerInvoke method to prevent any problem in the transaction applied to the outerInvoke method even if there is a problem in the transaction applied to the innerInvoke method.
#Slf4j
#Service
#RequiredArgsConstructor
public class OuterService {
private final InnerService innerService;
private final FooRepository fooRepository;
#Transactional
public void outerInvoke() {
fooRepository.save(new Foo("foo"));
innerService.innerInvoke();
}
}
#Slf4j
#Service
#RequiredArgsConstructor
public class InnerService {
private final BarRepository barRepository;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerInvoke() {
try {
barRepository.save(new Bar("bar"));
} catch (Exception e) {
log.error("innerInvoke Exception : {}", e);
}
}
}
#SpringBootTest
class TransactionPropagationTestApplicationTests {
#Autowired
private OuterService outerService;
#Test
#Rollback(value = false)
void saveTest() {
outerService.outerInvoke();
}
}
However, if an IdentifierGenerationException, a subtype of DataAccessException, occurs in outerInvoke, all transactions are rolled back.
In my opinion, since the transactions are separated, the transaction of the outerInvoke method should be committed and the transaction of the innerInvoke method should be rolled back, so why are they all rolled back?

Propagation.REQUIRES_NEW not working properly

I have the following scenario.
I have one transaction method which calls another transaction method which having REQUIRED_NEW Propagation. if the first method gets exception then the second method (REQUIRED_NEW Propagation) also rollbacks.
I am using JPA, Spring-boot and chainedKakfkaTransactionManager
I have tried with changing chainedKakfkaTransactionManager to default one still no luck
here is my code :
#Service
#Transactional(readOnly = false)
public class ComponentServiceImpl implements ComponentService {
#Autowired
private UserRepository userRepository ;
#Override
#Transactional
public boolean validateName(String name) {
try{
retrun userRepository.validate(name);
}catch(Exception e){
handleError(name);
throw new Exception("user not valid");
}
}
#Override
#Transactional(propagation=Propagation.REQUIRES_NEW)
public boolean handleError(String name) {
userRepository.update(name);
}
}
Rollback is happening in the handleError method too. is there any code mistake?
Thanks #DarrenForsythe,
By Creating an autowire object for the same class (self-reference) its worked for me
#Service
#Transactional(readOnly = false)
public class ComponentServiceImpl implements ComponentService {
#Autowired
private UserRepository userRepository ;
// CREATE SELF REFRENCE
#Autowired
private ComponentService componentService;
#Override
#Transactional
public boolean validateName(String name) {
try{
retrun userRepository.validate(name);
}catch(Exception e){
componentService.handleError(name);
throw new Exception("user not valid");
}
}
#Override
#Transactional(propagation=Propagation.REQUIRES_NEW)
public boolean handleError(String name) {
userRepository.update(name);
}
}

Spring transaction doesn't rollback when checked exception is thrown

I'm trying to execute sample of transaction code. I throw checked exception from transactional method and set rollbackFor parameter for the class of this exception. However this transaction doesn't rollback. So my question is what may be the reason of that behaviour. Here is a sample of my code:
MyService:
#Service
#Transactional
#EnableTransactionManagement
public class MyService implements IMyService {
#Autowired
private IMyDao myDao;
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = BreakException.class)
public void test() throws BreakException {
myDao.updateMethodA("64", "'N'", "'2015.01.01/0008661/0008'");
if(true) throw new BreakException();
myDao.updateMethodB("15", "'N'", "'2015.01.01/0008661/0008'");
}
}
MyDao:
#Repository
public class MyDao implements IMyDao {
#Override
#Transactional(propagation=Propagation.MANDATORY)
public void updateOperationA(String x, String y, String z) {
String sql = "UPDATE ...."; //masked for example
jdbcTemplate.update(sql);
}
#Override
#Transactional(propagation=Propagation.MANDATORY, rollbackFor = Exception.class)
public void updateOperationB(String saldoPo, String saldo_status, String unikalne_id) throws BreakException {
String sql = "UPDATE ...."; //masked for example
//if(true) throw new BreakException();
jdbcTemplate.update(sql);
}
}
1.) So when I call test() on MyService the update from method updateMethodA is not rolledback after BreakException() is thrown.
2.) BreakException is my class that extends Exception
3.) When I throw BreakException from updateOperationB(commented in example) it does rollback correctly.
4.) When I change BreakException to extend TransactionException it works as expected so it seems the problem is only when I extend Exception.

Unittest a transactional method with REQUIRES_NEW and always force rollback

I want to test a service-method that inserts data into a table by calling a DAO in a loop. The service-method is annotated with
#Transactional(propagation = Propagation.REQUIRES_NEW)
The unit-test calls the service-method and is annotated with
#Transactional
Now I would like to tell the transaction that it always should do a rollback at the end. I don't want to clean up the db manually after the testruns.
#Rollback and EntityManager.getTransaction().setRollbackOnly() does'nt work. I think the reason is that the annotation and setRollbackOnly() are only applied on the Transaction that is created by the test-method and not on the transaction that is created by the service-method.
Does anyone know how to solve that?
I don't think it's possible to easily rollback a REQUIRES_NEW transaction. SpringTest starts a transaction and it can rollback the transaction that it started. But not the transactions started inside.
So you either may fall back to REQUIRED or write your tests to work fine even if they commit. If you choose the latter, you can achieve test isolation via randomization.
You can probably create a controllable mock implementation of org.springframework.transaction.PlatformTransactionManager delegating to the real one with only single difference that it would optionally mark a new transaction as read-only. Something like that if using java-based context configuration:
...
public class DaoTest {
...
#Autowired
Dao _dao;
#Autowired
MockPlatformTransactionManager _transactionManager;
...
#Test
#Transactional
public void testSomeAction() throws Exception
{
try (AutoCloseable ignored = _transactionManager.withRollBack())
{
_dao.someAction();
}
}
...
interface MockPlatformTransactionManager extends PlatformTransactionManager
{
AutoCloseable withRollBack();
}
...
// Somewhere in the #Configuration class
#Bean
MockPlatformTransactionManager transactionManager()
{
// an instance of real TM is adapted to become a MockPlatformTransactionManager
return new MockPlatformTransactionManager()
{
private final PlatformTransactionManager _delegate =
// TODO: same transaction manager as before
new DataSourceTransactionManager(dataSource());
private boolean shouldRollBack = false;
#Override
public TransactionStatus getTransaction(final TransactionDefinition definition)
throws TransactionException
{
final TransactionStatus transaction = _delegate.getTransaction(definition);
if (shouldRollBack)
transaction.setRollbackOnly();
return transaction;
}
#Override
public void commit(final TransactionStatus status) throws TransactionException
{
_delegate.commit(status);
}
#Override
public void rollback(final TransactionStatus status) throws TransactionException
{
_delegate.rollback(status);
}
#Override
public AutoCloseable withRollBack()
{
shouldRollBack = true;
return new AutoCloseable()
{
#Override
public void close() throws Exception
{
shouldRollBack = false;
}
};
}
};
}
If you use a PlatformTransactionManager implementation that permits to set the UserTransaction (like org.springframework.transaction.jta.JtaTransactionManager) you can instantiate an UserTransaction implementation which commit method does a rollback.
import com.atomikos.icatch.jta.UserTransactionImp;
#Bean
public UserTransaction myUserTransaction() {
final UserTransactionImp userTransactionImp = new UserTransactionImp() {
#Override
public void commit() throws javax.transaction.RollbackException, javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException, javax.transaction.SystemException, java.lang.IllegalStateException,
java.lang.SecurityException {
rollback();
}
};
return userTransactionImp;
}
And then, in your PlatformTransactionManager bean:
#Bean
public PlatformTransactionManager transactionManager(
#Qualifier("myUserTransaction") UserTransaction myUserTransaction,
#Qualifier("myTransactionManager") TransactionManager myTransactionManager
) {
final JtaTransactionManager jtaTm = new JtaTransactionManager();
jtaTm.setTransactionManager(myTransactionManager);
jtaTm.setUserTransaction(myUserTransaction);
return jtaTm;
}
I've also made my dao does not make any flush.

How to rollback transaction in spring

I am facing a problem in transaction rollback using the #Transactional annotation.
I have the following methods in backingbean, service and dao classes:
public class ItemBackingBean {
public void saveUpdate() {
try {
ItemService.executeTransaction();
}
catch(Exception e) {
}
}
}
public class ItemService {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void executeTransaction() {
deleteItem();
createOrder();
}
private void deleteItem() {
persist();
}
private void createOrder() {
persist();
}
private void persist() {
JpaDaoImpl.persist(object);
JpaDaoImpl.update(object);
}
}
public class JpaDaoImpl implements JpaDao {
#Transactional(readOnly = true)
public persist(Object object) {
getEm().persist(object);
}
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void update(Object object) {
getEm().merge(object);
}
#Transactional(readOnly = true)
public void remove(Object object) {
getEm().remove(object);
}
}
If any exception occurs in createOrder(), all transactions should rollback but it is not happening. Can any body tell me the problem?
What is the impact of #Transactional in JpaDaoImpl.java? The persist() and update() methods have different radOnly values. This Dao is existing code in our project and we don't want to change it. Can anybody help?
Regards,
Bandu
For those who don't want to throw exception (transaction should NOT be only rollbacked when exception happen), use this: TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
If any exception occurs in createOrder(), all transactions should rollback but it is not happening. Can any body tell me the problem?
Rollback occurs only for RuntimeExceptions (see http://docs.spring.io/spring/docs/2.0.8/reference/transaction.html "please note that the Spring Framework's transaction infrastructure code will, by default, only mark a transaction for rollback in the case of runtime, unchecked exceptions;") but it is a customizable behaviour
You can keep the default transaction propagation that is PROPAGATION_REQUIRED without affecting the existing code if you want a ALL or NOTHING behaviour

Resources