Java開發學習(二十一)----Spring事務簡介與事務角色解析

一、Spring事務簡介

1.1 相關概念介紹

  • 事務作用:在數據層保障一系列的數據庫操作同成功同失敗

  • Spring事務作用:在數據層或業務層保障一系列的數據庫操作同成功同失敗

數據層有事務我們可以理解,爲什麼業務層也需要處理事務呢?

舉個簡單的例子,

  • 轉賬業務會有兩次數據層的調用,一次是加錢一次是減錢

  • 把事務放在數據層,加錢和減錢就有兩個事務

  • 沒辦法保證加錢和減錢同時成功或者同時失敗

  • 這個時候就需要將事務放在業務層進行處理。

Spring爲了管理事務,提供了一個平臺事務管理器PlatformTransactionManager

commit是用來提交事務,rollback是用來回滾事務。

PlatformTransactionManager只是一個接口,Spring還爲其提供了一個具體的實現:

從名稱上可以看出,我們只需要給它一個DataSource對象,它就可以幫你去在業務層管理事務。其內部採用的是JDBC的事務。所以說如果你持久層採用的是JDBC相關的技術,就可以採用這個事務管理器來管理你的事務。而Mybatis內部採用的就是JDBC的事務。

1.2 轉賬案例-需求分析

接下來通過一個案例來分析下Spring是如何來管理事務的。

先來分析下需求:

需求: 實現任意兩個賬戶間轉賬操作

需求微縮: A賬戶減錢,B賬戶加錢

爲了實現上述的業務需求,我們可以按照下面步驟來實現下:

①:數據層提供基礎操作,指定賬戶減錢(outMoney),指定賬戶加錢(inMoney)

②:業務層提供轉賬操作(transfer),調用減錢與加錢的操作

③:提供2個賬號和操作金額執行轉賬操作

1.3 轉賬案例-環境搭建

步驟1:準備數據庫表
create database spring_db character set utf8;
use spring_db;
create table tbl_account(
    id int primary key auto_increment,
    name varchar(35),
    money double
);
insert into tbl_account values(1,'Tom',1000);
insert into tbl_account values(2,'Jerry',1000);

步驟2:創建項目導入jar包

項目的pom.xml添加相關依賴

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>
​
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>
​
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
​
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
​
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>
​
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
​
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
​
  </dependencies>
步驟3:根據表創建模型類
public class Account implements Serializable {
​
    private Integer id;
    private String name;
    private Double money;
    //setter...getter...toString...方法略    
}
步驟4:創建Dao接口
public interface AccountDao {
​
    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);
​
    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}
步驟5:創建Service接口和實現類
public interface AccountService {
    /**
     * 轉賬操作
     * @param out 傳出方
     * @param in 轉入方
     * @param money 金額
     */
    public void transfer(String out,String in ,Double money) ;
}
​
@Service
public class AccountServiceImpl implements AccountService {
​
    @Autowired
    private AccountDao accountDao;
​
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        accountDao.inMoney(in,money);
    }
​
}
步驟6:添加jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root
步驟7:創建JdbcConfig配置類
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
​
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}
步驟8:創建MybatisConfig配置類
public class MybatisConfig {
​
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
​
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}
步驟9:創建SpringConfig配置類
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}
​
步驟10:編寫測試類
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
​
    @Autowired
    private AccountService accountService;
​
    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",100D);
    }
​
}

最終創建好的項目結構如下:

1.4 事務管理

上述環境,運行單元測試類,會執行轉賬操作,Tom的賬戶會減少100,Jerry的賬戶會加100。

這是正常情況下的運行結果,但是如果在轉賬的過程中出現了異常,如:

@Service
public class AccountServiceImpl implements AccountService {
​
    @Autowired
    private AccountDao accountDao;
​
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }
​
}

這個時候就模擬了轉賬過程中出現異常的情況,正確的操作應該是轉賬出問題了,Tom應該還是900,Jerry應該還是1100,但是真正運行後會發現,並沒有像我們想象的那樣,Tom賬戶爲800而Jerry還是1100,100塊錢憑空消息了,銀行樂瘋了。如果把轉賬換個順序,銀行就該哭了。

不管哪種情況,都是不允許出現的,對剛纔的結果我們做一個分析:

①:程序正常執行時,賬戶金額A減B加,沒有問題

②:程序出現異常後,轉賬失敗,但是異常之前操作成功,異常之後操作失敗,整體業務失敗

當程序出問題後,我們需要讓事務進行回滾,而且這個事務應該是加在業務層上,而Spring的事務管理就是用來解決這類問題的。

Spring事務管理具體的實現步驟爲:

步驟1:在需要被事務管理的方法上添加註解
public interface AccountService {
    /**
     * 轉賬操作
     * @param out 傳出方
     * @param in 轉入方
     * @param money 金額
     */
    //配置當前接口方法具有事務
    public void transfer(String out,String in ,Double money) ;
}
​
@Service
public class AccountServiceImpl implements AccountService {
​
    @Autowired
    private AccountDao accountDao;
    @Transactional
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }
​
}

注意:

@Transactional可以寫在接口類上、接口方法上、實現類上和實現類方法上

  • 寫在接口類上,該接口的所有實現類的所有方法都會有事務

  • 寫在接口方法上,該接口的所有實現類的該方法都會有事務

  • 寫在實現類上,該類中的所有方法都會有事務

  • 寫在實現類方法上,該方法上有事務

  • 建議寫在接口類或接口類的方法上

步驟2:在JdbcConfig類中配置事務管理器
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;
​
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
​
    //配置事務管理器,mybatis使用的是jdbc事務
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

注意:事務管理器要根據使用技術進行選擇,Mybatis框架使用的是JDBC事務,可以直接使用DataSourceTransactionManager

步驟3:開啓事務註解

在SpringConfig的配置類中開啓

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//開啓註解式事務驅動
@EnableTransactionManagement
public class SpringConfig {
}
​
步驟4:運行測試類

會發現在轉換的業務出現錯誤後,事務就可以控制回顧,保證數據的正確性。

知識點1:@EnableTransactionManagement
名稱 @EnableTransactionManagement
類型 配置類註解
位置 配置類定義上方
作用 設置當前Spring環境中開啓註解式事務支持
知識點2:@Transactional
名稱 @Transactional
類型 接口註解 類註解 方法註解
位置 業務層接口上方 業務層實現類上方 業務方法上方
作用 爲當前業務層方法添加事務(如果設置在類或接口上方則類或接口中所有方法均添加事務)

二、Spring事務角色

Spring不是一個數據庫技術,那它是怎麼控制事務的呢?

主要理解兩個概念,分別是事務管理員事務協調員

  1. 未開啓Spring事務之前:

  • AccountDao的outMoney因爲是修改操作,會開啓一個事務T1

  • AccountDao的inMoney因爲是修改操作,會開啓一個事務T2

  • AccountService的transfer沒有事務,

    • 運行過程中如果沒有拋出異常,則T1和T2都正常提交,數據正確

    • 如果在兩個方法中間拋出異常,T1因爲執行成功提交事務,T2因爲拋異常不會被執行

    • 就會導致數據出現錯誤

  1. 開啓Spring的事務管理後

  • transfer上添加了@Transactional註解,在該方法上就會有一個事務T

  • AccountDao的outMoney方法的事務T1加入到transfer的事務T中

  • AccountDao的inMoney方法的事務T2加入到transfer的事務T中

  • 這樣就保證他們在同一個事務中,當業務層中出現異常,整個事務就會回滾,保證數據的準確性。

通過上面例子的分析,我們就可以得到如下概念:

  • 事務管理員:發起事務方,在Spring中通常指代業務層開啓事務的方法

  • 事務協調員:加入事務方,在Spring中通常指代數據層方法,也可以是業務層方法

注意:

目前的事務管理是基於DataSourceTransactionManagerSqlSessionFactoryBean,他們使用的是同一個數據源。

 

 

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