Why is a transaction full rollback? - spring

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?

Related

How to rollback transaction from service class?

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).

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 test transaction thread

I have moved a synchronous process to asynchronous, and now I have some troubles to maintain integration tests. It seems related to the fact that when you create a new thread inside a #Transactional method, then call a new #Transactional, Spring create a new transaction.
During integration tests the problem occurs with #Transactional tests. It seems that the thread transaction is rollbacked before the test finishes because of TransactionalTestExecutionListener in test configuration.
I'have tried many things like
- autowiring EntityManager and manually flushing after thread was finished
- using #Rollback instead of #Transactional in test methods
- managing transactions with TestTransaction
- using #Rollback and TestTransaction together
Here is the simplified source code :
public interface MyService{
public void doThing(someArgs...);
public void updateThings(someArgs...);
}
#Service
public class MyServiceImpl implements MyService{
#Autowired
private AsynchronousFutureHandlerService futureService;
#Autowired
#Qualifier("myExecutorService")
private ScheduledExecutorService myExecutorService;
#Transactional
#Override
public void doThing(someArgs...){
doThingAsync(someArgs...);
}
private void doThingAsync(someArgs...){
AsynchronousHandler runnable = applicationContext.getBean(
AsynchronousHandler.class, someArgs...);
//as we are executing some treatment in a new Thread, a new transaction is automatically created
Future<?> future = myExecutorService.submit(runnable);
//keep track of thread execution
futureService.addFutures(future);
}
#Override
#Transactional
public void updateThings(someArgs...){
//do udpate stuff
}
}
/**
* very basic solution to improve later to consult thread state
*/
#Service
public class AsynchronousFutureHandlerService {
//TODO : pass to ThreadSafe collection
private List<Future<?>> futures = new ArrayList<>();
public void addTheoreticalApplicationFuture(Future<?> future){
futures.add(future);
this.deleteJobsDone();
}
public boolean isThreadStillRunning(){
boolean stillRunning = false;
for(Future<?> f : futures){
if(!f.isDone()){
stillRunning = true;
break;
}
}
return stillRunning;
}
public void deleteJobsDone(){
this.futures.removeIf(f -> f.isDone());
}
}
#Component
#Scope("prototype")
public class AsynchronousHandler implements Runnable {
#Autowired
private MyService myService;
#Override
public void run() {
myService.updateThings(...); //updates data in DB
...
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfiguration.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DataSetTestExecutionListener.class,
TransactionalTestExecutionListener.class })
#DataSet(dbType = DBType.H2, locations = { "classpath:dataset.xml" })
public class MyServiceTest{
#Autowired
private MyService myService;
#Autowired
private AsynchronousFutureHandlerService futureService;
#Test
#Transactional
public void test_doThings(){
myService.doThings(someArgs...);
waitUpdateFinish();
Assert.assertEquals(...); //fails here because Thread transaction has been rollbacked
}
private void waitUpdateFinish() throws InterruptedException{
while(futureService.isThreadStillRunning()){
Thread.sleep(500);
}
}
}

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.

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