SpringBoot事務簡單操作及手動回滾


一、引入依賴

<!-- 核心啟動器, 包括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則不需要。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM