网站建设丨找王科杰信誉,中国建筑集团有限公司校园招聘,环艺做网站,企业所得税优惠税率最近在工作中遇到了两个关于事务操作的问题#xff0c;顺便就着这两个问题又回顾了一遍Spring的事务相关的操作#xff0c;想着一次性把这个问题研究明白了#xff0c;后续使用事务的时候也能踏实点#xff0c;让事务发挥真实的作用
什么是事务#xff1f;什么是事务管理…最近在工作中遇到了两个关于事务操作的问题顺便就着这两个问题又回顾了一遍Spring的事务相关的操作想着一次性把这个问题研究明白了后续使用事务的时候也能踏实点让事务发挥真实的作用
什么是事务什么是事务管理什么是Spring事务
什么是事务事务就是把一系列的动作当成一个独立的工作单元这些动作要么全部完成要么全部不起作用关乎数据准确性的地方我们一定要用到事务防止业务逻辑出错。
什么是事务管理事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的即便出现了异常的访问情况也不至于破坏后台数据的完整性。就像银行的自助取款机通常都能正常为客户服务但是也难免遇到操作过程中机器突然出故障的情况此时事务就必须确保出故障前对账户的操作不生效就像用户刚才完全没有使用过取款机一样以保证用户和银行的利益都不受损失
关于事务的基本概念和定义可以参照我的另一篇Blog:【Spring学习笔记 九】Spring声明式事务管理实现机制。Sping事务简而言之就是一种JTA事务这里不再详细展开。
一个用来演示的例子
我们还是沿用:【Spring学习笔记 九】Spring声明式事务管理实现机制这篇文章中的例子只不过为了更贴近工作实战这里我重构了一下代码实现。 单元测试入口 package com.example.springboot;import com.example.springboot.model.Person;
import com.example.springboot.service.PersonAggService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import javax.annotation.Resource;SpringBootTest
class SpringbootApplicationTests {Resourceprivate PersonAggService personAggService;Testpublic void springTransTest() {Person person new Person();person.setUsername(wcong);person.setAge(30);person.setEmail(111111qq.com);person.setPassword(111111);person.setPhone(11111111);person.setHobby(跳远);personAggService.addPerson(person, 100086L);}
}聚合的Service方法 package com.example.springboot.service;import com.example.springboot.model.Person;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** author tianmaolin004* date 2023/8/6*/
Service
public class PersonAggService {Resourceprivate PersonService personService;Resourceprivate PersonMaintainService personMaintainService;public void addPerson(Person person, Long creatorId) {//本地新增人员personService.insert(person);//保存人员创建者personMaintainService.savePersonCreator(creatorId);}
} 数据服务方法 package com.example.springboot.service;import com.example.springboot.dao.PersonDao;
import com.example.springboot.model.Person;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;Service
public class PersonService {ResourcePersonDao personDao;public ListPerson getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}public void insert(Person person) {personDao.insert(person);}} 人员维护人添加方法 package com.example.springboot.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;/*** author tianmaolin004* date 2023/8/6*/
Service
public class PersonMaintainService {Transactional(rollbackFor Exception.class)public void savePersonCreator(Long userId) {System.out.println(保存人员创建者失败 userId);throw new RuntimeException();}
}数据表落库 不使用事务的情况 不使用事务的情况虽然单元测试报错了
但是数据库落库还是成功了
遇到的两个事务问题
依据以上的基本case示例模拟我遇到的两个问题和解决方案
Transaction rolled back because it has been marked as rollback-only
为了保证整体数据与预期一致可以回滚我使用了事务首先在外层加事务 Transactional(rollbackFor Exception.class)public void addPerson(Person person, Long creatorId) {//本地新增人员personService.insert(person);try {//发送人员同步到下游系统personMaintainService.savePersonCreator(creatorId);} catch (Exception e) {System.out.println(保存人员维护人异常但是被catch住了);}}同时呢人员创建人这块我认为这里不需要报错阻塞整体操作如果这里有问题只要有日志记录就行了我通过巡检检查关注到即可所以对这块代码加了try catch但是呢因为内部代码不知道是谁写的也加了事务
Service
public class PersonMaintainService {Transactional(rollbackFor Exception.class)public void savePersonCreator(Long userId) {System.out.println(保存人员创建者失败 userId);throw new RuntimeException();}
}因为它们用的都是默认的传播机制所以可以看做一个事务使用REQUIRED传播模式addAndSendPerson和savePersonCreator在同一个事务里面savePersonCreator抛出异常要回滚addAndSendPerson try Catch了异常正常执行commit同一个事务一个要回滚一个要提交会报read-only异常结果就是全部回滚而外层所以这里就会出现rollback-only 解决方法有两种一种是
干掉内层事务
把内层的savePersonCreator事务干掉这时数据也能落库成功了事实上因为JTA的事务是有非常强的业务含义的所以对于DAO层或简单的数据操作指令不要加事务否则对于较长的外部调用链路会在传播过程中导致意外情况发生
内层声明为新事务
还有一种解决思路就是内层的事务声明为新事务
package com.example.springboot.service;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;/*** author tianmaolin004* date 2023/8/6*/
Service
public class PersonMaintainService {Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)public void savePersonCreator(Long userId) {System.out.println(保存人员创建者失败 userId);throw new RuntimeException();}
}
声明后再跑单测 数据也落库成功了因为是两个独立事务所以内层事务遇到异常回滚外层事务捕获到了异常catch住了没有继续回滚
事务设置为什么不生效
还有个例子是方法设置了事务但是不生效我们再调整下以上的代码模拟一种场景savePerson要执行很多事项但是不希望saveDate的执行异常回滚影响整体回滚所以saveDate中的核心数据操作被try catch并且声明内部的savePersonCreator方法为新事务符合上边我们提到的那种场景这种情况下理论上savePersonCreator抛出异常后会使 personDao.insert(person);回滚数据不能写入
SpringBootTest
class SpringbootApplicationTests {Resourceprivate PersonAggService personAggService;Testpublic void springTransTest() {Person person new Person();person.setUsername(wcong);person.setAge(30);person.setEmail(111111qq.com);person.setPassword(111111);person.setPhone(11111111);person.setHobby(跳远);personAggService.savePerson(person, 100086L);}}Service
public class PersonAggService {ResourcePersonDao personDao;Transactional(rollbackFor Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println(执行其它事项);saveDate(person, creatorId);}Transactional(rollbackFor Exception.class)public void saveDate(Person person, Long creatorId) {//本地新增人员try {savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println(捕获到创建人员异常);}}Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println(保存人员创建者失败 userId);throw new RuntimeException();}
}
但事实上数据库写入数据能成功 数据库数据写入成功了 这是因为Spring中事务的默认实现使用的是AOP也就是代理的方式如果大家在使用代码测试时同一个Service类中的方法相互调用需要使用注入的对象来调用不要直接使用this.方法名来调用this.方法名调用是对象内部方法调用不会通过Spring代理也就是事务不会起作用所以实际上saveDate和savePersonCreator的事务都没有生效
把需要成为事务的方法单独抽出来
上述代码我们把需要有事务机制的savePersonCreator单独抽到一个方法中
SpringBootTest
class SpringbootApplicationTests {Resourceprivate PersonAggService personAggService;Testpublic void springTransTest() {Person person new Person();person.setUsername(wcong);person.setAge(30);person.setEmail(111111qq.com);person.setPassword(111111);person.setPhone(11111111);person.setHobby(跳远);personAggService.savePerson(person, 100086L);}}Service
public class PersonAggService {ResourcePersonService personService;Transactional(rollbackFor Exception.class)public void savePerson(Person person, Long creatorId) {System.out.println(执行其它事项);saveDate(person, creatorId);}Transactional(rollbackFor Exception.class)public void saveDate(Person person, Long creatorId) {//本地新增人员try {personService.savePersonCreator(person, creatorId);} catch (Exception e) {System.out.println(捕获到创建人员异常);}}
}Service
public class PersonService {ResourcePersonDao personDao;public ListPerson getPersonList() {return personDao.getPersonList();}public Person getPersonById(Integer id) {return personDao.getPersonById(id);}Transactional(rollbackFor Exception.class, propagation Propagation.REQUIRES_NEW)public void savePersonCreator(Person person, Long userId) {personDao.insert(person);System.out.println(保存人员创建者失败 userId);throw new RuntimeException();}
}
这样savePersonCreator的事务就生效了数据没有插入成功
Spring事务的更多传播机制
以上两个示例是真实工作中遇到的基于安全原则模拟了两个类似的case其实spring还有更多的花式的事务使用机制可以参照带你读懂Spring 事务——事务的传播机制
总结一下
照例总结一下在单一的数据操作方法不要加事务事务应该是一系列操作指令的聚合添加了细粒度的事务可能会导致上层使用者在方法添加事务时产生了非预期的传播机制。当然如果内外层的方法调用都很复杂则基于自己的预期进行考虑如果不希望内层方法影响外层方法可以使用外层方法异常捕获加内层事务的REQUIRES_NEW传播机制解决。需要注意的是Spring的事务是基于AOP实现的所以对象内部方法调用不会通过Spring代理也就是事务不会起作用这点非常重要。