今日目標
能夠掌握Spring事務配置
Spring事務管理
1 Spring事務簡介【重點】
1.1 Spring事務作用
-
事務作用:在數據層保障一系列的數據庫操作同成功同失敗
-
Spring事務作用:在數據層或業務層保障一系列的數據庫操作同成功同失敗
1.2 案例分析Spring事務
-
需求:實現任意兩個賬戶間轉賬操作
-
需求微縮:A賬戶減錢,B賬戶加錢
-
分析: ①:數據層提供基礎操作,指定賬戶減錢(outMoney),指定賬戶加錢(inMoney) ②:業務層提供轉賬操作(transfer),調用減錢與加錢的操作 ③:提供2個賬號和操作金額執行轉賬操作 ④:基于Spring整合MyBatis環境搭建上述操作
-
結果分析: ①:程序正常執行時,賬戶金額A減B加,沒有問題 ②:程序出現異常后,轉賬失敗,但是異常之前操作成功,異常之后操作失敗,整體業務失敗
-
結構:
1.3 代碼實現
【前置工作】環境準備
創建數據庫和表
CREATE?DATABASE?IF?NOT?EXISTS?`spring_db2`?DEFAULT?CHARACTER?SET?utf8
USE?`spring_db2`;DROP?TABLE?IF?EXISTS?`tbl_account`;
CREATE?TABLE?`tbl_account`?(`id`?INT(11)?NOT?NULL?AUTO_INCREMENT,`name`?VARCHAR(20)?DEFAULT?NULL,`money`?DOUBLE?DEFAULT?NULL,PRIMARY?KEY?(`id`)
)?ENGINE=INNODB?AUTO_INCREMENT=3?DEFAULT?CHARSET=utf8;INSERT??INTO?`tbl_account`(`id`,`name`,`money`)?VALUES
(1,'Jack',1000),
(2,'Rose',1000);DROP?TABLE?IF?EXISTS?`tbl_log`;
CREATE?TABLE?`tbl_log`?(`id`?INT(11)?NOT?NULL?AUTO_INCREMENT,`info`?VARCHAR(255)?DEFAULT?NULL,`create_date`?DATETIME?DEFAULT?NULL,PRIMARY?KEY?(`id`)
)?ENGINE=INNODB?AUTO_INCREMENT=3?DEFAULT?CHARSET=utf8;INSERT??INTO?`tbl_log`(`id`,`info`,`create_date`)?VALUES
(2,'Jack向Rose轉賬520.0元','2021-11-04?17:19:18');
pom.xml添加依賴
????<dependencies><!--導入spring的坐標spring-context,對應版本是5.2.10.RELEASE--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.15</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.15</version></dependency>
<!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><!--?https://mvnrepository.com/artifact/org.mybatis/mybatis?--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.13</version></dependency><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version></dependency><!--?https://mvnrepository.com/artifact/com.alibaba/druid?--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.18</version></dependency><!--junit,spring對junit4的要求必須是4.12版本以上--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version><scope>test</scope></dependency><!--spring-test--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.3.22</version><scope>test</scope></dependency></dependencies>
Spring整合Mybatis相關代碼(依賴、JdbcConfig、MybatisConfig、SpringConfig) JdbcConfig
package?com.zbbmeta.config;import?com.alibaba.druid.pool.DruidDataSource;
import?org.springframework.beans.factory.annotation.Value;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.jdbc.datasource.DataSourceTransactionManager;
import?org.springframework.transaction.PlatformTransactionManager;import?javax.sql.DataSource;public?class?JdbcConfig?{@Value("${jdbc.driverClassName}")private?String?driverClassName;@Value("${jdbc.url}")private?String?url;@Value("${jdbc.username}")private?String?userName;@Value("${jdbc.password}")private?String?password;@Beanpublic?DataSource?dataSource(){DruidDataSource?ds?=?new?DruidDataSource();ds.setDriverClassName(driverClassName);ds.setUrl(url);ds.setUsername(userName);ds.setPassword(password);return?ds;}//spring提供的事務切面類:里面增強了事務管理功能,里面有事務提交和事務回滾功能@Beanpublic?PlatformTransactionManager?transactionManager(DataSource?dataSource){DataSourceTransactionManager?ptm?=?new?DataSourceTransactionManager();ptm.setDataSource(dataSource);return?ptm;}
}
MybatisConfig
package?com.zbbmeta.config;import?org.apache.ibatis.logging.stdout.StdOutImpl;
import?org.apache.ibatis.session.Configuration;
import?org.mybatis.spring.SqlSessionFactoryBean;
import?org.mybatis.spring.mapper.MapperScannerConfigurer;
import?org.springframework.context.annotation.Bean;import?javax.sql.DataSource;public?class?MybatisConfig?{@Bean??//不僅可以將返回值加入IOC容器,而且可以實現方法參數進行依賴注入,參數默認會根據類型從IOC容器中獲取對象自動注入public?SqlSessionFactoryBean?sqlSessionFactoryBean(DataSource?dataSource){SqlSessionFactoryBean?ssfb?=?new?SqlSessionFactoryBean();ssfb.setTypeAliasesPackage("com.zbbmeta.entity");?//告訴mybatis,設置實體類包別名ssfb.setDataSource(dataSource);?//將IOC容器中連接池給到Mybatis//注意:必須導入org.apache.ibatis.session.Configuration;Configuration?configuration?=?new?Configuration();//設置整合mybatis駝峰命名映射configuration.setMapUnderscoreToCamelCase(true);//設置打印日志configuration.setLogImpl(StdOutImpl.class);ssfb.setConfiguration(configuration);return?ssfb;}
}
SpringConfig
@Configuration
@ComponentScan("com.zbbmeta")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public?class?SpringConfig?{
}
Account
@ToString
@Data
public?class?Account?{private?Integer?id;private?String?name;private?Double?money;}
AccountDao
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);
}
public?interface?AccountService?{/***?轉賬操作*?@param?out?傳出方*?@param?in?轉入方*?@param?money?金額*/void?transfer(String?out,String?in,Double?money)?;
}
AccountServiceImpl
@Service
public?class?AccountServiceImpl?implements?AccountService?{@Autowiredprivate?AccountDao?accountDao;public?void?transfer(String?out,?String?in,?Double?money)?{//轉出accountDao.outMoney(out,?money);//模擬出現異常//System.out.println(1?/?0);//轉入accountDao.inMoney(in,?money);System.out.println("轉賬成功");}
}
【第一步】在業務層接口上添加Spring事務管理
在轉賬的方法上添加@Transactional注解
/***?業務接口*/
public?interface?AccountService?{/***?轉賬操作*?@param?out?傳出方*?@param?in?轉入方*?@param?money?金額*/@Transactionalvoid?transfer(String?out,String?in?,Double?money)?;
}
注意事項
-
Spring注解式事務通常添加在業務層接口中而不會添加到業務層實現類中,降低耦合
-
注解式事務可以添加到業務方法上表示當前方法開啟事務,也可以添加到接口上表示當前接口所有方法開啟事務
【第二步】設置事務管理器(將事務管理器添加到IOC容器中)
說明:可以在JdbcConfig中配置事務管理器
//配置事務管理器,mybatis使用的是jdbc事務
@Bean
public?PlatformTransactionManager?transactionManager(DataSource?dataSource){DataSourceTransactionManager?transactionManager?=?new?DataSourceTransactionManager();transactionManager.setDataSource(dataSource);return?transactionManager;
}
注意事項
-
事務管理器要根據實現技術進行選擇
-
MyBatis框架使用的是JDBC事務
【第三步】開啟注解式事務驅動
@EnableTransactionManagement
@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableTransactionManagement?//開啟事務注解掃描
public?class?SpringConfig?{
}
【第四步】運行測試類,查看結果
-
去掉@EnableTransactionManagement注解,即沒有進行事務管理,當轉賬出現問題時結果
-
加上@EnableTransactionManagement注解,賬戶錢都恢復成1000
結果
-
轉賬成功的時候
結果:
@RunWith(SpringJUnit4ClassRunner.class)??//指定第三方的運行器
@ContextConfiguration(classes?=?SpringConfig.class)??//讀取類配置文件
public?class?AccountDaoTest?{@Autowired??//自動注入業務對象private?AccountService?accountService;@Testpublic?void?testFindById()?throws?IOException?{//直接使用業務方法accountService.transfer("Jack","Rose",?100d);}
}
2 Spring事務角色【理解】
問題導入
什么是事務管理員,什么是事務協調員?
2.1 Spring事務角色
-
事務管理員:發起事務方,在Spring中通常指代業務層開啟事務的方法
-
事務協調員:加入事務方,在Spring中通常指數據層方法,也可以是業務層方法
3 Spring事務相關配置
問題導入
什么樣的異常,Spring事務默認是不進行回滾的?
3.1 @Transactional注解中與事務相關配置
屬性 | 作用 | 示例 | |
---|---|---|---|
readOnly | 設置是否為只讀事務 | readOnly=true 只讀事務 | |
timeout | 設置事務超時時間 | timeout = -1(永不超時) | |
rollbackFor | 設置事務回滾異常(class) | rollbackFor = {FileNotFoundException.class} | |
rollbackForClassName | 設置事務回滾異常(String) | 同上格式為字符串 | |
noRollbackFor | 設置事務不回滾異常(class) | noRollbackFor = {FileNotFoundException.class} | |
noRollbackForClassName | 設置事務不回滾異常(String) | 同上格式為字符串 | |
propagation | 設置事務傳播行為 | …… |
屬性 | 作用 | 示例 |
readOnly | 設置是否為只讀事務 | readOnly=true 只讀事務 |
timeout | 設置事務超時時間 | timeout = -1(永不超時) |
rollbackFor | 設置事務回滾異常(class) | rollbackFor = {FileNotFoundException.class} |
rollbackForClassName | 設置事務回滾異常(String) | 同上格式為字符串 |
noRollbackFor | 設置事務不回滾異常(class) | noRollbackFor = {FileNotFoundException.class} |
noRollbackForClassName | 設置事務不回滾異常(String) | 同上格式為字符串 |
propagation | 設置事務傳播行為 | …… |
說明:對于RuntimeException類型異常或者Error錯誤,Spring事務能夠進行回滾操作。但是對于非運行時異常,Spring事務是不進行回滾的,所以需要使用rollbackFor來設置要回滾的異常。
3.2 案例:轉賬業務追加日志
需求和分析
-
需求:實現任意兩個賬戶間轉賬操作,并對每次轉賬操作在數據庫進行記錄
-
需求微縮:A賬戶減錢,B賬戶加錢,數據庫記錄日志
-
分析: ①:基于轉賬操作案例添加日志模塊,實現數據庫中記錄日志 ②:業務層轉賬操作(transfer),調用減錢、加錢與記錄日志功能
-
實現效果預期: 無論轉賬操作是否成功,均進行轉賬操作的日志留痕
-
存在的問題: 日志的記錄與轉賬操作隸屬同一個事務,同成功同失敗
-
實現效果預期改進: 無論轉賬操作是否成功,日志必須保留
-
事務傳播行為:事務協調員對事務管理員所攜帶事務的處理態度
【準備工作】環境整備
創建新的LogDao接口
public?interface?LogDao?{@Insert("insert?into?tbl_log?(info,create_date)?values(#{info},now())")void?log(String?info);
}
創建業務接口使用事務
public?interface?LogService?{/***?記錄日志*?@param?out?轉出賬戶*?@param?in?轉入賬戶*?@param?money?金額*?設置事務屬性:傳播行為設置為需要新事務*/@Transactionalvoid?log(String?out,?String?in,?Double?money);
}
實現類
@Service
public?class?LogServiceImpl?implements?LogService?{@Autowiredprivate?LogDao?logDao;public?void?log(String?out,?String?in,?Double?money)?{logDao.log("轉賬操作由"?+?out?+?"到"?+?in?+?",金額:"?+?money);}
}
【第一步】在AccountServiceImpl中調用logService中添加日志的方法
因為無論成功與否,都需要記錄日志,所以日志放在finally語句塊中,但異常不需要捕獲,要拋出用來激活事務的處理
/***?業務接口*/
public?interface?AccountService?{/***?轉賬操作*?@param?out?傳出方*?@param?in?轉入方*?@param?money?金額*/@Transactionalvoid?transfer(String?out,String?in?,Double?money)?;
}
@Service
public?class?AccountServiceImpl?implements?AccountService?{@Autowiredprivate?AccountDao?accountDao;@Autowiredprivate?LogService?logService;public?void?transfer(String?out,String?in?,Double?money)?{try{accountDao.outMoney(out,money);//int?i?=?1/0;accountDao.inMoney(in,money);}?finally?{logService.log(out,in,money);}}
}
【第二步】在LogService的log()方法上設置事務的傳播行為
需求:無論有沒有異常日志都要記錄下來,不能被回滾
-
先只設置@Transactional,查看運行結果
-
設置成當前操作需要新事務再查看運行結果
public?interface?LogService?{//propagation設置事務屬性:傳播行為設置為當前操作需要新事務@Transactional(propagation?=?Propagation.REQUIRES_NEW)void?log(String?out,?String?in,?Double?money);
}
【第三步】運行測試類,查看結果
@RunWith(SpringJUnit4ClassRunner.class)??//指定第三方的運行器
@ContextConfiguration(classes?=?SpringConfig.class)??//讀取類配置文件
public?class?AccountServiceTest?{@Autowired??//自動注入業務對象private?AccountService?accountService;/***?轉賬的測試*/@Testpublic?void?testTransfer()?{accountService.transfer("Jack",?"Rose",?10d);}
}
思考:為什么 在LogService的方法上設置propagation = Propagation.REQUIRES_NEW接可以創建新的事務?
這就是事務傳播行為(經常會面試提問)
3.3 事務傳播行為
傳播屬性 | Method1 | Method2 |
REQUIRED | 開啟TI事務 | 加入T1事務 |
無 | 新建T2事務 | |
REQUIRES_NEW | 開啟TI事務 | 新建T2事務 |
無 | 新建T2事務 | |
SUPPORTS | 開啟TI事務 | 加入T1事務 |
無 | 無 | |
NOT_SUPPORTED | 開啟TI事務 | 無 |
無 | 無 | |
MANDATORY | 開啟TI事務 | 加入T1事務 |
無 | ERROR | |
NEVER | 開啟TI事務 | ERROR |
無 | 無 | |
NESTED | |
思考:事務會一直生效?有沒有事務失效的情況?為什么會失效,大家可以思考一下,這也是一個長問面試題