Spring 中實現事務的方式

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章