一、引入依賴
<!-- 核心啟動器, 包括auto-configuration、logging and YAML --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- using Spring Data JDBC, JdbcTemplate或NamedParameterJdbcTemplate都是由spring jdbc提供的 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <!-- 數據庫操作需要的mysql 驅動包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <!-- testing Spring Boot applications with libraries including JUnit, Hamcrest and Mockito --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
二、application.properties
spring.datasource.url=jdbc:mysql://192.168.178.5:12345/mydb?characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=com.zaxxer.hikari.HikariDataSource
三、dao和service代碼
1. dao
student接口
public interface StudentDao { void saveStudent(); }
student實現類
@Repository public class StudentDaoImpl implements StudentDao{ @Autowired private JdbcTemplate jdbcTemplate; public void saveStudent(){ String sql = "insert into student(name, s_class) values(?,?"; //這里會出現一個錯誤,少了右括號 List<Object> sqlParamList = new ArrayList<Object>(); sqlParamList.add("stn-"+Math.random()); sqlParamList.add(20); jdbcTemplate.update(sql, sqlParamList.toArray(new Object[sqlParamList.size()])); } }
user接口
public interface UserDao { void saveUser(); }
user實現類
@Repository public class UserDaoImpl implements UserDao{ @Autowired private JdbcTemplate jdbcTemplate; public void saveUser(){ String sql = "insert into user(name, age) values(?,?)"; List<Object> sqlParamList = new ArrayList<Object>(); sqlParamList.add("sn-"+Math.random()); sqlParamList.add(20); jdbcTemplate.update(sql, sqlParamList.toArray(new Object[sqlParamList.size()])); } }
2. service
接口:
public interface OperatorService { void saveEntity() throws Exception; }
接口實現類
@Service public class OperatorServiceImpl implements OperatorService{ @Autowired private StudentDao studentDao; @Autowired private UserDao userDao; @Override public void saveEntity() throws Exception{ userDao.saveUser(); studentDao.saveStudent(); } }
當執行以上saveEntity()代碼時,因StudentDaoImpl 插入語句的一個錯誤,會導致事務不一致,user表成功插入一條記錄,student表沒有。
為了使事務一致,在SpringBoot項目中,我們只需要在saveEntity()上添加@Transactional注解,,對@Transactional的注解可以查看 對注解@Transactional的解讀 一節。
這里為了適應更多的異常,我們提升了事務捕獲異常的范圍:@Transactional(rollbackFor = Exception.class)
四、手動回滾事務
有時我們需要捕獲一些錯誤信息,又需要進行事務回滾,這時我們就需要用到Spring提供的事務切面支持類TransactionAspectSupport。
@Transactional(rollbackFor = Exception.class) @Override public void saveEntity() throws Exception{ try { userDao.saveUser(); studentDao.saveStudent(); }catch (Exception e){ System.out.println("異常了=====" + e); //手動強制回滾事務,這里一定要第一時間處理 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
手動回滾事務一定要加上@Transactional,不然會報以下錯誤:
org.springframework.transaction.NoTransactionException: No transaction aspect-managed TransactionStatus in scope
想想也是,不開啟事務,何來手動回滾,所以@Transactional必不可少。
五、回滾部分異常
使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 設置回滾點。
使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); 回滾到savePoint。
@Transactional(rollbackFor = Exception.class) @Override public void saveEntity() throws Exception{ Object savePoint = null; try { userDao.saveUser(); //設置回滾點 savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); studentDao.saveStudent(); //執行成功 int a = 10/0; //這里因為除數0會報異常,進入catch塊 }catch (Exception e){ System.out.println("異常了=====" + e); //手工回滾異常 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint); } }
六、使用DataSourceTransactionManager
springboot 開啟事務以及手動提交事務,可以在服務類上加上兩個注解。
@Autowired DataSourceTransactionManager dataSourceTransactionManager; @Autowired TransactionDefinition transactionDefinition;
手動開啟事務
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
手動提交事務
dataSourceTransactionManager.commit(transactionStatus);//提交
手動回滾事務
dataSourceTransactionManager.rollback(transactionStatus);//最好是放在catch 里面,防止程序異常而事務一直卡在哪里未提交
七、spring boot controller設置 @Transactional 不回滾的解決辦法
默認spring事務只在發生未被捕獲的 RuntimeException 時才回滾。
spring aop 異常捕獲原理:被攔截的方法需顯式拋出異常,並不能經任何處理,這樣aop代理才能捕獲到方法的異常,才能進行回滾,默認情況下aop只捕獲 RuntimeException 的異常,但可以通過配置來捕獲特定的異常並回滾。
換句話說在service的方法中不使用try catch 或者在catch中最后加上throw new RuntimeException (),這樣程序異常時才能被aop捕獲進而回滾。
解決方案:
方案1:例如service層處理事務,那么service中的方法中不做異常捕獲,或者在catch語句中最后增加throw new RuntimeException()語句,以便讓aop捕獲異常再去回滾,並且在service上層(webservice客戶端,view層action)要繼續捕獲這個異常並處理。
方案2:在service層方法的catch語句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();語句,手動回滾,這樣上層就無需去處理異常。
注意:
1. 默認地,如果使用的數據源不是SpringBoot的默認配置(即是由自己定義的配置信息,自己解析創建的數據源),則需要手動創建事務管理器,因為SpringBoot無法識別配置信息,無法完成自動注入。
//DynamicDataSource 是自定義的數據源 @Bean public PlatformTransactionManager transactionManager(DynamicDataSource dataSource){ return new DataSourceTransactionManager(dataSource); }
2. SpringBoot1.x需要在啟動類上添加@EnableTransactionManagement,SpringBoot2.x則不需要。