#Transactional annotation on a method inside the Application class is not rolling back insertions. However, #Transactional annotation on the service class(EmpService.java) method("insertEmp(Emp emp)") is working as expected.
Could someone please let me know why #Transactional working differently?
Spring Boot version - 2.1.3.RELEASE with the h2 database.
Please let me know if any additional information required.
#SpringBootApplication
#ComponentScan("org.saheb")
#EnableJpaRepositories("org.saheb.repo")
#EntityScan("org.saheb.vo")
#EnableTransactionManagement
public class SpringJpaTransactionApplication implements CommandLineRunner {
#Autowired
private EmpService empService;
public static void main(String[] args) {
SpringApplication.run(SpringJpaTransactionApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
insertSingleBatch();
}
#Transactional(rollbackFor=RuntimeException.class, propagation=Propagation.REQUIRES_NEW)
public void insertSingleBatch() {
try {
Set<Emp> empSet = new LinkedHashSet<>();
Dept dept = new Dept();
dept.setDeptNo(10);
empSet.add(new Emp("abc", "abc", dept));
empSet.add(new Emp("xyz", "xyz", dept));
empSet.add(new Emp("def", "def", dept));
empSet.add(new Emp("pqrstu", "pqr", dept));// This will fail as max character allowed in 5 and should rollback all the insertion. But, first three records are getting saved in h2 database.
empService.insertEmp(empSet);
} catch (RuntimeException e) {
System.out.println("Exception in batch1.." + e.getMessage());
}
}
}
#Service
public class EmpService {
#Autowired
private EmpRepository empRepository;
//#Transactional(rollbackFor=RuntimeException.class, propagation=Propagation.REQUIRES_NEW)//This is working as expected as all the insertions are rolling back after failure of 4th insertion
public void insertEmp(Set<Emp> empSet) {
System.out.println("Inside insert");
for (Emp temp : empSet) {
Emp temp2 =empRepository.save(temp);
System.out.println("inserted-->"+temp2.getFirstName());
}
}
}
You are "self-invocation" a #Transactional method from the same bean which will not work .This behaviour is well explained in the docs at here and here (Search the keyword "self-invocation")
You can simply move the #Transactional method to another bean.Then inject this bean to its client bean and invoke this #Transactional method.
Or use the TransactionTemplate to execute it within a transaction:
#Autowired
private TransactionTemplate txTemplate;
#Override
public void run(String... args) throws Exception {
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
txTemplate.execute(status->{
insertSingleBatch();
return null;
});
}
Please note that TransactionTemplate will ignore the setting on the #Transactional and you have to configure it programatically.
Related
I'm unable to rollback a transaction in Spring Boot 2.1.4 involving two DAOs (JDBC). The DAOs implement a parameterized interface. The first DAO inserts a record into three tables, and the last DAO inserts a record into one table. I'm forcing the last DAO insert to fail.
Database is DB2.
Some code:
public interface FooDao<T extends Foo> {
int insert(T foo) throws SQLException;
}
#Repository
public class FooDaoJdbc implements FooDao {
#Override
public int insert(Foo foo) throws SQLException {
insertFoo(foo);
insertFooDescriptions(foo);
insertFooActivity(foo);
return 0;
}
}
#Repository
public class BazDaoJdbc implements FooDao<Baz> {
#Override
public int insert(Baz baz) throws SQLException {
public interface FooService<T extends Foo> {
void add(T foo) throws SQLException;
}
#Service
public class BazServiceImpl implements FooService<Baz> {
private FooDao<Baz> bazDaoJdbc;
private FooDao<Foo> fooDaoJdbc;
#Transactional(rollbackFor = Exception.class)
public void add(Baz baz) throws SQLException {
fooDaoJdbc.insert(foo);
bazDaoJdbc.insert(baz);
}
}
It sure seems the FooDaoJdbc and BazDaoJdbc are in the same transaction when I debug TransactionAspectSupport, but the rollback seems to ignore FooDaoJdbc. I don't have a record in the "baz" table, but I would expect the three "foo" tables to rollback, too.
My eyes are crossed at this point. Do I have a misplaced annotation? Am I making things too "fancy" with generics? Thanks in advance for the help!
A #Transactional method only rollback when unchecked exception is thrown. So you have to throw RuntimeException in add method when an exception is thrown.
#Service
public class BazServiceImpl implements FooService<Baz> {
private FooDao<Baz> bazDaoJdbc;
private FooDao<Foo> fooDaoJdbc;
#Transactional
public void add(Baz baz) {
try {
fooDaoJdbc.insert(foo);
bazDaoJdbc.insert(baz);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
More information:
https://www.catalysts.cc/wissenswertes/spring-transactional-rollback-on-checked-exceptions/
I'm trying to use dbunit to test my DAOs. We use Spring in a version that is not compatible with spring-test-dbunit. I can't autowire my dao beans into my test class, because then I would have to use #RunWith(SpringJUnit4ClassRunner.class) which regards one parameterless constructor. My class looks like following:
public class DbUnitExample extends DBTestCase {
#Autowired
public MyDAO myDAO;
public DbUnitExample(String name) {
super(name);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "...");
}
#Override
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSetBuilder().build(new FileInputStream("target/partial.xml"));
}
#Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.REFRESH;
}
#Override
protected DatabaseOperation getTearDownOperation() throws Exception {
return DatabaseOperation.NONE;
}
#Test
public void testSometing() throws Exception {
myDAO.deleteById(12662);
}
}
Of course I get an NPE because my dao bean can't be found. When I use #RunWith(SpringJUnit4ClassRunner.class) I need to provide one parameterless constructor and have to delete my "dbunit"-constructor. Is there a standard way or workaround to use dbunit with spring without the use of spring-test-dbunit
EDIT
My class now looks like following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/test-application.xml")
#DirtiesContext
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class })
public class DbUnitExample extends DBTestCase {
#Autowired
public MyDAO myDAO;
public DbUnitExample() {
super("target/partial.xml");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, "...");
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, "...");
}
#Override
protected IDataSet getDataSet() throws Exception {
return new FlatXmlDataSetBuilder().build(new FileInputStream("target/partial.xml"));
}
#Override
protected DatabaseOperation getSetUpOperation() throws Exception {
return DatabaseOperation.REFRESH;
}
#Override
protected DatabaseOperation getTearDownOperation() throws Exception {
// return DatabaseOperation.NONE;
// return DatabaseOperation.REFRESH;
return DatabaseOperation.CLEAN_INSERT;
}
#Test
public void testSometing() throws Exception {
myDAO.deleteById(12662);
}
}
It compiles now, but has no dbunt-functionality, which means if I delete a row it doesn't get restored to it's previous state (inserted again).
Since you are using Spring, I suggest autowiring the dbUnit instances into the test. The dbUnit Test Cases page has "Configuration Example Using Spring" for the PrepAndExpectedTestCase, but just copy the code and change it to DBTestCase and adjust accordingly.
I am using Spring boot and Mockito for testing. I have been able to write test cases for Service layer and they re working fine. But, the test cases for DAO layer do not. The jdbcTemplate object that is mocked and autowired gives null pointer when executing the test case. Below are the details:
My DAOTest class:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = EcommerceApplication.class)
public classEcommerceDaoTest {
#InjectMocks
private IEcommerceDao ecommerceDao = new EcommerceDaoImpl();
#Mock
#Autowired
private JdbcTemplate as400JdbcTemplate;
#Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
}
#Test
public void checkOrderExistsTest() throws EcommerceException{
Mockito.when(as400JdbcTemplate.queryForObject(queryForOrder,new Object[]
{"1000"}, int.class)).thenReturn(1);
boolean exists =
ecommerceDao.checkOrderExists("1000");
assertTrue(exists);
}
}
EcommerceDAOImpl.java:
#Override
public boolean checkOrderExists(String orderNo)throws EcommerceException{
boolean doesExist = false;
int count = 0;
try{
count= as400JdbcTemplate.queryForObject(queryForOrder, new Object[]{orderNo}, int.class);
if(count >0){
doesExist = true;
}
}
catch(Exception e){
}
return doesExist;
}
AS400Config.java:
#Bean
#Autowired
public JdbcTemplate as400JdbcTemplate(#Qualifier("as400DataSource")DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
ECommerceApplication.java
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
#EnableTransactionManagement
#Import(As400Configuration.class)
public class EcommerceApplication {
public static void main(String[] args) {
SpringApplication.run(EcommerceApplication.class, args);
}
}
When I am running the test case, I am getting NullPointerException for as400JdbcTemplate. The functionality works fine as is. Its just the test cases for DAO layer that fail because of the inability of the jdbcTemplate to get mocked/autowired.
Please let me know where I am going wrong.
You don't need to use #Mock and #Autowired at the same time. Use only #Mock:
#Mock
private JdbcTemplate as400JdbcTemplate;
Use instead of #RunWith(SpringRunner.class) --> #RunWith(MockitoJUnitRunner.class)
Also to inject mock into DAO you can use ReflectionTestUtils from spring test.
public static void setField(Class targetClass, String name, Object value)
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(ecommerceDao ,"as400JdbcTemplate" ,
as400JdbcTemplate);
}
#Mock
private JdbcTemplate as400JdbcTemplate;
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.
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.