1. When MyBatis is used alone, use SqlSession to handle transactions:
public class MyBatisTxTest { private static SqlSessionFactory sqlSessionFactory; private static Reader reader; @BeforeClass public static void setUpBeforeClass() throws Exception { try { reader = Resources.getResourceAsReader("Configuration.xml"); sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } finally { if (reader != null) { reader.close(); } } } @Test public void updateUserTxTest() { SqlSession session = sqlSessionFactory.openSession(false); // Open the session and the transaction starts try { IUserMapper mapper = session.getMapper(IUserMapper.class); User user = new User(9, "Test transaction"); int affectedCount = mapper.updateUser(user); // The commit statement was not executed due to the subsequent exception User user = new User(10, "Test transaction continuously"); int affectedCount2 = mapper.updateUser(user2); // The commit statement int i = 2 / 0 is not executed due to the subsequent exception; // The runtime exception is triggered session.commit(); // Submit the session, that is, the transaction commit} finally { session.close(); // Close the session and release resources} } }
2. After integrating with Spring, use Spring's transaction management:
One of the main reasons for using MyBatis-Spring is that it allows MyBatis to participate in Spring's transaction management. Rather than creating a new specific transaction manager for MyBatis, MyBatis-Spring utilizes the DataSourceTransactionManager that exists in Spring.
Once the DataSourceTransactionManager is configured, you can configure transactions in Spring as you normally do. @Transactional annotation and AOP style configuration are supported. During transaction processing, a separate SqlSession object will be created and used. When the transaction is completed, this session will be committed or rolled back in the appropriate way.
Once the transaction is created, MyBatis-Spring will transparently manage transactions. There is no need for extra code in your DAO or Service class.
1. Standard configuration
To enable Spring's transaction processing, simply create a DataSourceTransactionManager object in Spring's XML configuration file:
<bean id="transactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
The specified DataSource can generally be any JDBC DataSource you use Spring. This includes the connection pool and the DataSource obtained through JNDI lookup.
Note that the DataSource specified for the transaction manager must be the same data source as the one used to create the SqlSessionFactoryBean, otherwise the transaction manager will not work.
2. Container management transactions
If you are using a JEE container and want Spring to participate in container management transactions, Spring should be configured using JtaTransactionManager or its container-specified subclass. The most convenient way to do this is to use Spring's transaction namespace:
<tx:jta-transaction-manager/>
In this configuration, MyBatis will be the same as other Spring transaction resources configured by container management transactions. Spring will automatically use any existing container transactions, attaching an SqlSession to it. If the transaction is not started, or if the transaction is required, Spring will enable a new container to manage transactions.
Note that if you want to manage transactions using containers and not Spring's transaction management, you must configure SqlSessionFactoryBean to use the basic MyBatis ManagedTransactionFactory instead of any other Spring transaction manager:
<bean id="sqlSessionFactory"> <property name="dataSource" ref="dataSource"/> <property name="transactionFactoryClass"> <value>org.apache.ibatis.transaction.managed.ManagedTransactionFactory"/> </property> </bean>
3. Programming transaction management
MyBatis' SqlSession provides a specified method to handle programmatic transactions. But when using MyBatis-Spring, the bean will be injected using Spring-managed SqlSession or mapper. That means Spring usually handles transactions. You cannot call the SqlSession.commit(), SqlSession.rollback(), or SqlSession.close() methods on Spring-managed SqlSession. If you do this, an UnsupportedOperationException will be thrown. Note that those methods cannot be accessed when using injected mappers. Regardless of whether the connection is set to autocommit or not, the execution of the SqlSession data method or any call to the mapper method outside of the Spring transaction will be automatically committed. Here is an example of a programming transaction:
DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try{ userMapper.insertUser(user); }catch(MyException ex){ throw ex; } txManager.commit(status);4.@Transactional method:
Create beans-da-tx.xml file under the classpath and add transaction configuration based on beans-da.xml (Series V):
<!-- Transaction Manager--> <bean id="txManager" > <property name="dataSource" ref="dataSource" /> </bean> <!-- Transaction annotation driver, classes and methods marked @Transactional will be transactional--> <tx:annotation-driven transaction-manager="txManager" /> <bean id="userService" />
Service category:
@Service("userService") public class UserService { @Autowired IUserMapper mapper; public int batchUpdateUsersWhenException() { // Non-transactional User user = new User(9, "Before exception"); int affectedCount = mapper.updateUser(user); // Execution successful User user2 = new User(10, "After exception"); int i = 1 / 0; // Throw runtime exception int affectedCount2 = mapper.updateUser(user2); // Not executed if (affectedCount == 1 && affectedCount2 == 1) { return 1; } return 0; } @Transactional public int txUpdateUsersWhenException() { // Transactional User user = new User(9, "Before exception"); int affectedCount = mapper.updateUser(user); // Rollback due to the subsequent exception User user2 = new User(10, "After exception"); int i = 1 / 0; // Throw a runtime exception and the transaction rollback int affectedCount2 = mappper.updateUser(user2); // Not executed if (affectedCount == 1 && affectedCount2 == 1) { return 1; } return 0; } }In the test class:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:beans-da-tx.xml" }) public class SpringIntegrateTxTest { @Resource UserService userService; @Test public void updateUsersExceptionTest() { userService.batchUpdateUsersWhenException(); } @Test public void txUpdateUsersExceptionTest() { userService.txUpdateUsersWhenException(); } }
5. TransactionTemplate method
Add in beans-da-tx.xml:
<bean id="txTemplate"> <constructor-arg type="org.springframework.transaction.PlatformTransactionManager" ref="transactionManager" /> </bean>
Join in the UserService class:
@Autowired(required = false) TransactionTemplate txTemplate; public int txUpdateUsersWhenExceptionViaTxTemplate() { int retVal = txTemplate.execute(new TransactionCallback<Integer>() { @Override public Integer doInTransaction(TransactionStatus status) { // Transaction operation User user = new User(9, "Before exception"); int affectedCount = mapper.updateUser(user); // Rollback due to the subsequent exception User user2 = new User(10, "After exception"); int i = 1 / 0; // Throw a runtime exception and roll back int affectedCount2 = mapper.updateUser(user2); // Not executed if (affectedCount == 1 && affectedCount2 == 1) { return 1; } return 0; } }); return returnVal; }Add to the SpringIntegrateTxTest class:
@Test public void updateUsersWhenExceptionViaTxTemplateTest() { userService.txUpdateUsersWhenExceptionViaTxTemplate(); // }Note: Cannot catch Exception or RuntimeException without throwing:
@Transactional public int txUpdateUsersWhenExceptionAndCatch() { // Transactional operation, but the peripheral framework cannot catch the exception and submit it if the execution is correct. try { User user = new User(9, "Before exception"); int affectedCount = mapper.updateUser(user); // Execution was successful User user2 = new User(10, "After exception"); int i = 1 / 0; // Throw runtime exception int affectedCount2 = mapper.updateUser(user2); // Not executed if (affectedCount == 1 && affectedCount2 == 1) { return 1; } } catch (Exception e) { // All exceptions are caught without throwing e.printStackTrace(); } return 0; }