在使用Springboot的注解事務的時候,需要考慮到事務的傳播行為、遇到什麼類型的異常時,事務才起作用、事務方法之間的嵌套調用時,怎麼樣才生效等等諸多問題。
開啟事務管理
@EnableTransactionManagement // 啟注解事務管理,等同于xml配置方式的 <tx:annotation-driven />
事務注解詳解
預設遇到throw new RuntimeException("…");會復原
需要捕獲的throw new Exception("…");不會復原
指定復原
@Transactional(rollbackFor=Exception.class)
指定不復原
@Transactional(noRollbackFor=Exception.class)
加入事務
@Transactional(propagation=Propagation.REQUIRED)
不為這個方法開啟事務
@Transactional(propagation=Propagation.NOT_SUPPORTED)
不管是否存在事務,都建立一個新的事務,原來的挂起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.REQUIRES_NEW)
必須在一個已有的事務中執行,否則抛出異常
@Transactional(propagation=Propagation.MANDATORY)
必須在一個沒有的事務中執行,否則抛出異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.NEVER)
如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務
@Transactional(propagation=Propagation.SUPPORTS)
readOnly=true隻讀,不能更新,删除
@Transactional (propagation = Propagation.REQUIRED,readOnly=true)
設定逾時時間
@Transactional (propagation = Propagation.REQUIRED,timeout=30)
設定資料庫隔離級别
@Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)
指定事務管理器
Springboot使用事務非常簡單,首先使用注解 @EnableTransactionManagement 開啟事務支援後,然後在通路資料庫的Service方法上添加注解 @Transactional 便可。
關于事務管理器,不管是JPA還是JDBC等都實作自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依賴,架構會預設注入 DataSourceTransactionManager 執行個體。如果你添加的是 spring-boot-starter-data-jpa 依賴,架構會預設注入 JpaTransactionManager 執行個體。
如果項目做的比較大,添加的持久化依賴比較多,需要人為的指定使用哪個事務管理器。
指定事務管理器
@EnableTransactionManagement
@SpringBootApplication
public class ProfiledemoApplication {
// 其中 dataSource 架構會自動為我們注入
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public Object testBean(PlatformTransactionManager platformTransactionManager) {
System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
return new Object();
}
public static void main(String[] args) {
SpringApplication.run(ProfiledemoApplication.class, args);
}
}
在Spring容器中,我們手工注解@Bean 将被優先加載,架構不會重新執行個體化其他的 PlatformTransactionManager 實作類。
然後在Service中,被 @Transactional 注解的方法,将支援事務。如果注解在類上,則整個類的所有方法都預設支援事務。
對于同一個工程中存在多個事務管理器要怎麼處理,請看下面的執行個體,具體說明請看代碼中的注釋。
使用指定事務管理器
@EnableTransactionManagement // 開啟注解事務管理,等同于xml配置檔案中的 <tx:annotation-driven />
@SpringBootApplication
public class ProfiledemoApplication implements TransactionManagementConfigurer {
@Resource(name="txManager2")
private PlatformTransactionManager txManager2;
// 建立事務管理器1
@Bean(name = "txManager1")
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 建立事務管理器2
@Bean(name = "txManager2")
public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
// 實作接口 TransactionManagementConfigurer 方法,其傳回值代表在擁有多個事務管理器的情況下預設使用的事務管理器
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return txManager2;
}
public static void main(String[] args) {
SpringApplication.run(ProfiledemoApplication.class, args);
}
}
@Component
public class DevSendMessage implements SendMessage {
// 使用value具體指定使用哪個事務管理器
@Transactional(value="txManager1")
@Override
public void send() {
System.out.println(">>>>>>>>Dev Send()<<<<<<<<");
send2();
}
// 在存在多個事務管理器的情況下,如果使用value具體指定
// 則預設使用方法 annotationDrivenTransactionManager() 傳回的事務管理器
@Transactional
public void send2() {
System.out.println(">>>>>>>>Dev Send2()<<<<<<<<");
}
}
問題1:
業務:新增使用者時,需要對使用者詳細資訊進行儲存USER表,還要對使用者的角色進行儲存USER_ROLE表(一對多)。
此時就需要對新增使用者進行事務控制,避免二者不能同時更新成功。
項目采用SpringBoot+Mybatis
SpringBoot不用單獨的去配置事務管理,使用@Transactional即可,但是在service層方法上加上@Transactional後發現事務并沒有生效。代碼如下:
@Transactional (rollbackFor = Exception. class)
public ResponseVo addUser (CreatUserEntity user) {
try {
userMapper.addUser(user);
userMapper.addRoleById(user.getRoles());
return ResponseVo. success(true);
} catch (RuntimeException e) {
log. error("Fail to addUser data:", e);
return ResponseVo. fail(ResponseCode.ERROR_ACCESS_DB);
}
}
此時新增使用者成功,新增角色時抛出異常,但是使用者成功被添加了。這是不允許的,在一對多的資料操作中,肯定是需要全部資料操作都成功才行,否則業務會出現極大的損失。
後來發現時由于@Transactional在抛出異常時進行復原,但是try catch已經把異常捕獲了,@Transactional沒辦法擷取異常,也不知道在哪發生的異常,是以失效了。對代碼進行修改,如下:
Controller層:
@RequestMapping ( method = RequestMethod.POST)
public ResponseVo addUser (@RequestBody CreatUserEntity user) {
try {
return userService.addUser(user);
} catch (RuntimeException e) {
log. error("Fail to addUser data:", e);
return ResponseVo.fail(ResponseCode.ERROR_ACCESS_DB);
}
Service層:
@Transactional (rollbackFor = Exception. class)
public ResponseVo addUser (CreatUserEntity user) {
userMapper.addUser (user) ;
userMapper.addRoleById(user.getRoles());
return ResponseVo.success(true) ;
}
問題2:
像問題1中,如果需要啟用事務復原,必須要在Service層中添加事務注解。但是如果我就想在Controller層中加上事務呢?
可以這樣做:
@Transactional(rollbackFor = Exception.class)
public void yourMethodName(HttpServletRequest request, HttpServletResponse response) {
try{
int status1=mapper1.insert(step1);
if(status1==1){
int status2=mapper2.update(step2);
if(status2==1){
int status3=mapper3.delete(step3);
if(status3==1){
logger.info("成功!")
}else{
int e=1/0;
logger.error("失敗!啟用事務復原")
}
}else{
int e=1/0;
logger.error("失敗!啟用事務復原")
}
}else{
int e=1/0;
logger.error("失敗!啟用事務復原")
}
}catch(Exception e){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手動啟動復原事務機制
e.printStackTrace();
}
}
重點的語句是:
@Transactional(rollbackFor = Exception.class)
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//手動啟動復原事務機制
隻要在try catch中,如果發現insert、update、delete方法傳回的狀态碼不是1,都是mapper處理資料不成功的狀态碼,是以可以在不成功的else中手動啟用int e=1/0;的事務復原,1/0一定會報被除數不能為0的異常,是以手動進入復原事務,這樣就不會産生髒資料。