Spring transaction doesn't rollback when checked exception is thrown - spring

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.

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?

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

Cannot activate transaction for junit test

I want to test a transactional method. I added the #transactional over test method but I got an exception IllegalStateException: Failed to retrieve PlatformTransactionManager for #Transactional test. I don't know how to activate the transaction. I tried to add TestTransaction.start() in the first line of the test body but it throws an exception that the transaction is not active. I don't know what's wrong with my test.
If I remove classes in #SpringbootTest the test randomly throws NoSuchBeanException for my repositories.
My test method:
#ExtendWith(SpringExtension.class)
#DataJpaTest
#AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
class PersistenceHelperTest {
#Autowired
private ReturnRepository returnRepository;
#SpyBean
private PersistenceHelper persistenceHelper;
#Test
void removeFromDbOnWriteExceptionForNotWritableFile(#TempDir Path tempDir) {
Path testFile = tempDir.resolve("testFile.txt");
try {
String content = "This is a test";
testFile.toFile().createNewFile();
boolean writable = testFile.toFile().setWritable(false);
assertTrue("File should not be writable", writable);
persistenceHelper.saveToFileAndDB(content.getBytes(), testFile.toFile().getAbsolutePath(), returnFile, returnRepository);
fail();
} catch (Exception e) {
assertTrue("Should be instance of IOException", e instanceof IOException);
assertTrue("Should exists", Files.exists(testFile));
assertSame(0, returnRepository.findAll().size());
}
}
The class under the test:
#Component
public class PersistenceHelper {
#Transactional(rollbackFor = {IOException.class}, propagation = Propagation.REQUIRES_NEW)
public <T extends BaseEntity> void saveToFileAndDB(byte[] fileContent, String fileAbsolutePath, T entity, JpaRepository<T, Long> jpaRepository) throws IOException {
FileTransactionListener transactionListener = new FileTransactionListener(new FileDeleter(), fileAbsolutePath);
TransactionSynchronizationManager.registerSynchronization(transactionListener);
jpaRepository.save(entity);
FileUtil.writeToFile(fileAbsolutePath, fileContent);
}
}
my Springboot class:
#SpringBootApplication
#EnableTransactionManagement
#EnableJpaRepositories("packageAddress")
public class MyApplication {

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);
}
}

JdbcTemplate Mockito ClassCastException

I am trying to mock a method of the Spring framework's JdbcTemplate class. The method has the following signature:
public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException {...}
The mocking is being done as mentioned below:
when(jdbcTemplate.queryForObject(anyString(), eq(String.class))).thenReturn("data");
However, this call throws the following exception
java.lang.ClassCastException: org.mockito.internal.creation.jmock.ClassImposterizer$ClassWithSuperclassToWorkAroundCglibBug$$EnhancerByMockitoWithCGLIB$$ce187d66 cannot be cast to java.lang.String
at test.DaoJdbcTest.setUp(DaoJdbcTest.java:81)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
Any idea as to what I am doing wrong?
Thanks in advance~
EDIT:
Here's the full code:
public class DaoJdbc extends NamedParameterJdbcSupport {
public String query(String sql) {
return getJdbcTemplate().queryForObject(sql, String.class);
}
}
public class DaoJdbcTest {
#Mock(answer = Answers.RETURNS_SMART_NULLS)
private JdbcTemplate jdbcTemplate;
private DaoJdbc dao;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
dao = new DaoJdbc();
dao.setJdbcTemplate(jdbcTemplate);
Mockito.when(jdbcTemplate.queryForObject(anyString(), Matchers.eq(String.class))).thenReturn("data");
}
#Test
public void testQuery() {
String ret = dao.query("select 'test' from dual");
assertEquals("data", ret);
verify(jdbcTemplate).queryForObject(anyString(), eq(String.class));
}
}
Remove the answer = Answers.RETURNS_SMART_NULLS. Test passes when I remove that. What does that feature do? The default null behavior works fine for me.
As a bonus, you can also use the MockitoJunitRunner to clean up the code a bit...
#RunWith(MockitoJUnitRunner.class)
public class DaoJdbcTest {
#Mock
private JdbcTemplate jdbcTemplate;
#InjectMocks
private DaoJdbc dao;
#Before
public void setUp() throws Exception {
Mockito.when(jdbcTemplate.queryForObject(anyString(), Matchers.eq(String.class))).thenReturn("data");
}
#Test
public void testQuery() {
String ret = dao.query("select 'test' from dual");
assertEquals("data", ret);
verify(jdbcTemplate).queryForObject(anyString(), eq(String.class));
}
}

Resources