Spring 並不直接支持事務,只有當數據庫支持事務時,Spring 才支持事務,Spring 只不過簡化了開發人員實現事務的步驟。 Spring 提供了兩種方式實現事務。
聲明式和編程式。
如何選擇
當需要用到事務操作的地方很少的時候,那麼就可以使用編程方式 TransactionTemplate,它不會建立很多事務代理。但是,如果程序中用到大力的事務操作,聲明式事務方式更適合,它使得事務管理和業務邏輯分離。
聲明式事務管理
聲明式事務管理只需要用到@Transactional 註解和@EnableTransactionManagement。它是基於 Spring AOP 實現的,並且通過註解實現,實現起來簡單,對原有代碼沒有入侵性。
例子
使用 JDBCTemplate 的方式操作 Mysql,實現事務演示。
1,Mave 中需要引入相關的包。
XHTML
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.12.RELEASE</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> </dependency>
123456789101112131415 | <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.12.RELEASE</version> </dependency> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.44</version> </dependency> |
---|
2. 配置類
Java
package com.learn; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.springframework.context.annotation.*; @Configuration @ComponentScan(value = "com.learn") @EnableTransactionManagement public class Config { @Bean public DataSource dataSource() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("112233"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/blog?useSSL=false"); return dataSource; } @Bean public JdbcTemplate jdbcTemplate() throws Exception{ //Spring對@Configuration類會特殊處理;給容器中加組件的方法,多次調用都只是從容器中找組件 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource()); return jdbcTemplate; } //註冊事務管理器在容器中 @Bean public PlatformTransactionManager transactionManager() throws Exception{ return new DataSourceTransactionManager(dataSource()); } }
1234567891011121314151617181920212223242526272829303132333435363738 | package com.learn; import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;import com.mchange.v2.c3p0.ComboPooledDataSource;import org.springframework.context.annotation.*; @Configuration@ComponentScan(value = "com.learn")@EnableTransactionManagementpublic class Config { @Bean public DataSource dataSource() throws Exception{ ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("112233"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/blog?useSSL=false"); return dataSource; } @Bean public JdbcTemplate jdbcTemplate() throws Exception{ //Spring對@Configuration類會特殊處理;給容器中加組件的方法,多次調用都只是從容器中找組件 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource()); return jdbcTemplate; } //註冊事務管理器在容器中 @Bean public PlatformTransactionManager transactionManager() throws Exception{ return new DataSourceTransactionManager(dataSource()); } } |
---|
配置文件中有一個要注意的點:
Spring 對@Configuration 類會特殊處理;dataSource() 方法雖然在 jdbcTemplate 方法和 transactionManager 方法中調用,但是實際返回的都是容器中的那個對象,都是同一個對象,這就保證了 JDBCTemplate 和事務操作所用的數據源是同一個。
3.Dao 層代碼,實現往 blog_article 插數據的功能
Java
package com.learn.entity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class BlogDao { @Autowired private JdbcTemplate jdbcTemplate; public void insert(String title,String content){ String sql = "INSERT INTO `blog_article`(title,content) VALUES(?,?)"; jdbcTemplate.update(sql, title,content); } }
12345678910111213141516 | package com.learn.entity; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository;@Repositorypublic class BlogDao { @Autowired private JdbcTemplate jdbcTemplate; public void insert(String title,String content){ String sql = "INSERT INTO `blog_article`(title,content) VALUES(?,?)"; jdbcTemplate.update(sql, title,content); } } |
---|
4.Service 層,調用 dao 方法,事務註解在這一層中。
Java
package com.learn.entity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.interceptor.TransactionAspectSupport; @Service public class BlogService { @Autowired private BlogDao blogDao; @Autowired private PlatformTransactionManager transactionManager; @Transactional public void insertBlog(String title, String content) { blogDao.insert(title, content); System.out.println("插入完成..."); //transactionManager.rollback(TransactionAspectSupport.currentTransactionStatus()); // 手動開啓事務回滾 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 手動開啓事務回滾 } }
1234567891011121314151617181920212223242526 | package com.learn.entity; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.Transactional;import org.springframework.transaction.interceptor.TransactionAspectSupport; @Servicepublic class BlogService { @Autowired private BlogDao blogDao; @Autowired private PlatformTransactionManager transactionManager; @Transactional public void insertBlog(String title, String content) { blogDao.insert(title, content); System.out.println("插入完成..."); //transactionManager.rollback(TransactionAspectSupport.currentTransactionStatus()); // 手動開啓事務回滾 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 手動開啓事務回滾 } } |
---|
上面兩句 transactionManager.rollback(TransactionAspectSupport.currentTransactionStatus()) 和 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 都能實現手動回滾事務。實際上,當方法出現任何異常的時候,就會自動回滾操作。
5.Main 方法,入口
Java
public class Main { public static void main(String[] args) { // 使用Config.class這個配置類 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class); BlogService blogService = applicationContext.getBean(BlogService.class); blogService.insertBlog("my news", "Hello world"); applicationContext.close(); } }
123456789101112 | public class Main { public static void main(String[] args) { // 使用Config.class這個配置類 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class); BlogService blogService = applicationContext.getBean(BlogService.class); blogService.insertBlog("my news", "Hello world"); applicationContext.close(); }} |
---|
細說
Maven 中 C3P0 是一個開源的 JDBC 連接池, 是對 JDBC 驅動管理的一個封裝。
配置類中有一個註解@EnableTransactionManagement,作用很簡單,就是開啓事務管理功能;
配置類中的 DataSource 接口類型的 Bean 是一個數據源
配置類中,PlatformTransactionManager 接口類型的 Bean,是一個事務管理器,此接口是事務管理的核心,用來控制事務的,比如回滾事務。該接口有如下幾個方法需要實現:
Java
commit(TransactionStatus status) ; getTransaction(TransactionDefinition definition) ; rollback(TransactionStatus status) ;
123 | commit(TransactionStatus status) ; getTransaction(TransactionDefinition definition) ; rollback(TransactionStatus status) ; |
---|
這個接口有很多具體的實現類。如圖:
比較常用的就是 DataSourceTransactionManager,HibernateTransactionManager,JpaTransactionManager 等。
DataSourceTransactionManager 事務管理器,是基於 JDBC 連接提供的事務處理器實現。jdbcTemplate,MyBatis 這些框架都是基於 JDBC 的,因此對於這些技術實現的數據庫操作,都是可以使用 DataSourceTransactionManager 作爲事務管理器。JpaTransactionManager 用於 JPA 操作數據庫, HibernateTransactionManager 則用於 Hibernate 操作數據庫。每個具體的實現類,都是基於不同的數據庫操作方式實現的。
編程式實現方式
對於編程式實現的事務管理方式,Spring 也提供兩種方法實現: 使用 TransactionTemplate 和使用 PlatformTransactionManager。 Spring 團隊推薦使用 TransactionTemplate。
TransactionTemplate 採用了和模範化的方式,類似使用 JDBCTemplate 那樣,減少了大量的樣板代碼,使得開發人員可以轉註於業務代碼的實現。
代碼舉例:
1. 配置類
Config配置類
Java
@Configuration @ComponentScan(value = "com.learn") public class Config { @Bean public DataSource dataSource() throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("112233"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/blog?useSSL=false"); return dataSource; } @Bean public JdbcTemplate jdbcTemplate() throws Exception { System.out.println(dataSource().hashCode()); // Spring對@Configuration類會特殊處理;給容器中加組件的方法,多次調用都只是從容器中找組件 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource()); return jdbcTemplate; } // 註冊事務管理器在容器中 @Bean public PlatformTransactionManager transactionManager() throws Exception { System.out.println(dataSource().hashCode()); return new DataSourceTransactionManager(dataSource()); } @Bean public TransactionTemplate transactionTemplate() throws Exception { TransactionTemplate t = new TransactionTemplate(); t.setTransactionManager(transactionManager()); return t; } }
123456789101112131415161718192021222324252627282930313233343536373839 | @Configuration@ComponentScan(value = "com.learn")public class Config { @Bean public DataSource dataSource() throws Exception { ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setUser("root"); dataSource.setPassword("112233"); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/blog?useSSL=false"); return dataSource; } @Bean public JdbcTemplate jdbcTemplate() throws Exception { System.out.println(dataSource().hashCode()); // Spring對@Configuration類會特殊處理;給容器中加組件的方法,多次調用都只是從容器中找組件 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource()); return jdbcTemplate; } // 註冊事務管理器在容器中 @Bean public PlatformTransactionManager transactionManager() throws Exception { System.out.println(dataSource().hashCode()); return new DataSourceTransactionManager(dataSource()); } @Bean public TransactionTemplate transactionTemplate() throws Exception { TransactionTemplate t = new TransactionTemplate(); t.setTransactionManager(transactionManager()); return t; }} |
---|
注意,這裏使用到了 TransactionTemplate 類,但仍然是依賴於一個 PlatformTransactionManager 。
2.Dao 層代碼,和上面的代碼一樣,沒有什麼變化,實現往 blog_article 插數據的功能。
Java
package com.learn.entity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class BlogDao { @Autowired private JdbcTemplate jdbcTemplate; public void insert(String title,String content){ String sql = "INSERT INTO `blog_article`(title,content) VALUES(?,?)"; jdbcTemplate.update(sql, title,content); } }
12345678910111213141516 | package com.learn.entity; import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.stereotype.Repository;@Repositorypublic class BlogDao { @Autowired private JdbcTemplate jdbcTemplate; public void insert(String title,String content){ String sql = "INSERT INTO `blog_article`(title,content) VALUES(?,?)"; jdbcTemplate.update(sql, title,content); } } |
---|
3.Service 層的代碼
Service層的代碼
Java
@Service public class BlogService { @Autowired private BlogDao blogDao; @Autowired private TransactionTemplate transactionTemplate; public void insertBlog(final String title, final String content) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { blogDao.insert(title, content); blogDao.insert(title + "2", content + "2"); if (new Date().getMinutes() > 30) { status.setRollbackOnly(); } } }); } }
12345678910111213141516171819202122 | @Servicepublic class BlogService { @Autowired private BlogDao blogDao; @Autowired private TransactionTemplate transactionTemplate; public void insertBlog(final String title, final String content) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { blogDao.insert(title, content); blogDao.insert(title + "2", content + "2"); if (new Date().getMinutes() > 30) { status.setRollbackOnly(); } } }); }} |
---|
4.Main 方法入口,同上例。
上述代碼,通過 TransactionTemplate 執行數據庫操作邏輯,邏輯實際包含在 doInTransactionWithoutResult 方法中,該方法有異常的時候,事務會回滾,也可以通過代碼判斷手動回滾,比如,判斷當前時間分鐘數大於 30 的時候,手動回滾。
另一種採用 PlatformTransactionManager 的實現方式不舉例了。
參考:https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction