目錄
一哮伟、訪問權(quán)限問題
二干花、方法用final修飾
三、方法內(nèi)部調(diào)用
四楞黄、末被spring管理
五池凄、多線程調(diào)用
六、表不支持事務(wù)
七鬼廓、未開啟事務(wù)
八肿仑、 錯(cuò)誤的傳播特性 / 異常
九、聲明式事務(wù)和編程式事務(wù)
一.訪問權(quán)限問題
例子
@Service
public class UserService {
@Transactional
private void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
分析
1.spring要求被代理方法必須是public的碎税。
2.java的訪問權(quán)限主要有四種:private尤慰、default、protected蚣录、public,它們的權(quán)限從左到右眷篇,依次變大
3.在AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個(gè)判斷萎河,如果目標(biāo)方法不是public,則TransactionAttribute返回null蕉饼,即不支持事務(wù)虐杯。
4.AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// 省略.................
二.方法用final修飾
例子
@Service
public class UserService {
@Transactional
public final void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
分析
1.add方法被定義成了final的,這樣會導(dǎo)致事務(wù)失效昧港。
2.spring事務(wù)底層使用了aop擎椰,也就是通過jdk動態(tài)代理或者cglib,幫我們生成了代理類创肥,在代理類中實(shí)現(xiàn)的事務(wù)功能达舒。如果某個(gè)方法用final修飾了,那么在它的代理類中叹侄,就無法重寫該方法巩搏,而添加事務(wù)功能。
3.注意:如果某個(gè)方法是static的趾代,同樣無法通過動態(tài)代理贯底,變成事務(wù)方法。
三.方法內(nèi)部調(diào)用
例子
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
分析
1.需要在某個(gè)Service類的某個(gè)方法中撒强,調(diào)用另外一個(gè)事務(wù)方法禽捆,這樣會導(dǎo)致事務(wù)失效。
2.updateStatus方法擁有事務(wù)的能力是因?yàn)閟pring aop生成代理了對象, 但是這種方法直接調(diào)用了this對象的方法, 所以updateStatus方法不會生成事務(wù)
3.如果想在同一個(gè)類某個(gè)方法調(diào)用自己另一個(gè)事務(wù)方法, 有什么辦法嗎?
3.1 新增一個(gè)Service類方法, 把@Transactional注解加到新Service方法上,具體代碼如下
@Servcie
public class ServiceA {
@Autowired
prvate ServiceB serviceB;
public void save(User user) {
queryData1();
queryData2();
serviceB.doSave(user);
}
}
@Servcie
public class ServiceB {
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
3.2 該Service類中注入自己
@Servcie
public class ServiceA {
@Autowired
prvate ServiceA serviceA;
public void save(User user) {
queryData1();
queryData2();
serviceA.doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
3.3 該Service類中使用AopContext.currentProxy()獲取代理對象
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
4.其實(shí)spring ioc內(nèi)部的三級緩存保證了它飘哨,不會出現(xiàn)循環(huán)依賴問題胚想。
spring:我是如何解決循環(huán)依賴的:
https://mp.weixin.qq.com/s__biz=MzkwNjMwMTgzMQ==&mid=2247490271&idx=1&sn=e4476b631c48882392bd4cd06d579ae9&source=41#wechat_redirect
四.末被spring管理
例子
//@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
分析
1.忘了加@Service注解, 使用spring事務(wù)的前提是:對象要被spring管理,需要創(chuàng)建bean實(shí)例芽隆。
五.多線程調(diào)用
例子
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表數(shù)據(jù)");
}
}
分析
1.事務(wù)方法add中顿仇,調(diào)用了事務(wù)方法doOtherThing淘正,但是事務(wù)方法doOtherThing是在另外一個(gè)線程中調(diào)用的
2.兩個(gè)方法不在同一個(gè)線程中,獲取到的數(shù)據(jù)庫連接不一樣,想doOtherThing方法中拋了異常臼闻,add方法也回滾是不可能的鸿吆。
3.spring的事務(wù)是通過數(shù)據(jù)庫連接實(shí)現(xiàn)的, 當(dāng)前線程保存了map, key是數(shù)據(jù)源,value是數(shù)據(jù)庫連接述呐。
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
六.表不支持事務(wù)
分析
1.在mysql5之前惩淳,默認(rèn)的數(shù)據(jù)庫引擎是myisam,myisam好用,但有個(gè)很致命的問題是:不支持事務(wù)乓搬。
2.myisam好處:索引文件和數(shù)據(jù)文件是分開存儲, 對于查多寫少的單表操作, 性能比innodb好
七.未開啟事務(wù)
分析
1.事務(wù)沒有生效的根本原因是沒有開啟事務(wù)思犁。
2.springboot項(xiàng)目: springboot通過DataSourceTransactionManagerAutoConfiguration類已經(jīng)幫你默默開啟了事務(wù),只需要配置spring.datasource相關(guān)參數(shù)
3.spring.datasource
<!-- 配置事務(wù)管理器 -->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="advice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 用切點(diǎn)把事務(wù)切進(jìn)去 -->
<aop:config>
<aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/>
<aop:advisor advice-ref="advice" pointcut-ref="pointcut"/>
</aop:config>
八.錯(cuò)誤的傳播特性 / 異常
例子
// case1
@Service
public class UserService {
@Transactional(propagation = Propagation.NEVER)
public void add(UserModel userModel) {
saveData(userModel);
updateData(userModel);
}
}
// case2
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
// case3
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
// 期望結(jié)果: 即使doOtherThing異常, 上面insertUser依然正常
// 錯(cuò)誤
roleService.doOtherThing();
// 正確
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表數(shù)據(jù)");
}
}
分析
1.事務(wù)的傳播特性
REQUIRED 如果當(dāng)前上下文中存在事務(wù),那么加入該事務(wù)进肯,如果不存在事務(wù)激蹲,創(chuàng)建一個(gè)事務(wù),這是默認(rèn)的傳播屬性值江掩。
SUPPORTS 如果當(dāng)前上下文存在事務(wù)学辱,則支持事務(wù)加入事務(wù),如果不存在事務(wù)环形,則使用非事務(wù)的方式執(zhí)行策泣。
MANDATORY 如果當(dāng)前上下文中存在事務(wù),否則拋出異常抬吟。
REQUIRES_NEW 每次都會新建一個(gè)事務(wù)萨咕,并且同時(shí)將上下文中的事務(wù)掛起,執(zhí)行當(dāng)前新建事務(wù)完成以后火本,上下文事務(wù)恢復(fù)再執(zhí)行危队。
NOT_SUPPORTED 如果當(dāng)前上下文中存在事務(wù),則掛起當(dāng)前事務(wù)钙畔,然后新的方法在沒有事務(wù)的環(huán)境中執(zhí)行交掏。
NEVER 如果當(dāng)前上下文中存在事務(wù),則拋出異常刃鳄,否則在無事務(wù)環(huán)境上執(zhí)行代碼盅弛。
NESTED 如果當(dāng)前上下文中存在事務(wù),則嵌套事務(wù)執(zhí)行叔锐,如果不存在事務(wù)挪鹏,則新建事務(wù)。
2.case1: add方法的事務(wù)傳播特性定義成了Propagation.NEVER愉烙,這種類型的傳播特性不支持事務(wù)讨盒,如果有事務(wù)則會拋異常。
3.case2: 自己手動try...catch吞了異常, 這種情況下spring事務(wù)當(dāng)然不會回滾
注意1: 默認(rèn)情況下只會回滾RuntimeException(運(yùn)行時(shí)異常)和Error(錯(cuò)誤)步责,對于普通的Exception(非運(yùn)行時(shí)異常)返顺,它不會回滾
注意2: 自定義了回滾異常,@Transactional(rollbackFor = BusinessException.class), 如果程序報(bào)錯(cuò)了禀苦,拋了SqlException、DuplicateKeyException等異常遂鹊。而BusinessException是我們自定義的異常振乏,報(bào)錯(cuò)的異常不屬于BusinessException,所以事務(wù)也不會回滾秉扑。建議一般情況下慧邮,將該參數(shù)設(shè)置成:Exception或Throwable。
4.case3:嵌套事務(wù)回滾多了
九.聲明式事務(wù)和編程式事務(wù)
例子
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}
分析
1.聲明式事務(wù)和編程式事務(wù)
聲明式事務(wù):基于@Transactional注解
編程式事務(wù):手動編寫代碼實(shí)現(xiàn)的事務(wù)
2.TransactionTemplate
(1)在spring中為了支持編程式事務(wù)舟陆,專門提供了一個(gè)類:TransactionTemplate误澳,在它的execute方法中,就實(shí)現(xiàn)了事務(wù)的功能
(2)好處:
--避免由于spring aop問題導(dǎo)致事務(wù)失效的問題
--可以能夠更小粒度控制事務(wù)的范圍