对springBoot事务的一些初步了解
springboot事务初步了解
什么是事务:
事务由单独单元的一个或者多个sql语句组成,在这个单元中,每个sql语句时相互依赖的。而整个单独单元作为一个不可分割的整体,如果单元中某条sql语句一旦执行失败或者产生错误,整个单元将会回滚,也就是所有受到影响的数据将会返回到事务开始以前的状态;如果单元中的所有sql语句均执行成功,则事务被顺利执行
事务的属性:
- 原子性:一个事务不可在分割,要么都执行要么都不执行。
- 一致性:一个事务的执行会使数据从一个一致状态切换到另一个一致的状态。
- 隔离性:一个事务的执行不受其他事物的干扰
- 持久性: 一个事务一旦提交,则会永久的改变数据库的数据
事务的两种管理方式
注解式事务管理
在SpringBoot中声明式事务最常见,就是把需要事务的方法用@Transactional标注一下就行了,这个一般用在Service层。标注后该方法就具备了事务的能力,出错了会自动回滚。
在大部分场景下,该方法已经够用了。
- 在入口类使用注解
@EnableTransactionManagement
开启事务 - 在访问数据库的service方法上添加注解
@Transactional
即可
@SpringBootApplication
@EnableTransactionManagement
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Service
public class TestServiceImpl implements TestService {
@Autowired
private UserMapper userMapper;
@Override
@Transactional
public void updataUser(int id,String name) {
//sql相关接口实现
}
}
编程式事务管理
在有些场景下,我们需要获取事务的状态,是执行成功了还是失败回滚了,那么使用声明式事务就不够用了,需要编程式事务。
在SpringBoot中,可以使用两种编程式事务。
- TransactionTemplate
@Service
public class TestServiceImpl implements TestService {
@Autowired
private UserMapper userMapper;
@Autowired
private TransactionTemplate transactionTemplate;
@Override
public void updataUser(int id,String name) {
User user = new User();
user.setName(name);
user.setId(id);
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
try {
//在doIntransaction里做逻辑处理即可
boolean res = userMapper.updateUser(user);
}catch (Exception e){
//如果出异常了,就执行isRollbackOnly方法进行回滚
transactionStatus.isRollbackOnly();
e.printStackTrace();
}
return null;
}
});
}
}
- TransactionManager
@Service
public class TestServiceImpl implements TestService {
@Autowired
private UserMapper userMapper;
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public void updataUser(int id,String name) {
User user = new User();
user.setName(name);
user.setId(id);
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try{
//进行sql逻辑处理
boolean res = userMapper.updateUser(user);
transactionManager.commit(status);
}catch (Exception e){
transactionManager.rollback(status);
e.printStackTrace();
}
}
}
使用这个就可以把事务结果同步返回给调用端了,出异常了就返回false,成功了就true。
业务中相关的应用
业务场景
在项目中,往往需要执行数据库操作后,发送消息或事件来异步调用其他组件执行相应的操作,例如:
用户注册后发送激活码;
配置修改后发送更新事件等。
但是,数据库的操作如果还未完成,此时异步调用的方法查询数据库发现没有数据,这就会出现问题
解决方案
- 使用注解
@TransactionalEventListener
- 使用
TransactionSynchronizationManager
方法
- TransactionalEventListener
@Service
public class TransactionEventTestService {
@Resource
private TestMapper mapper;
@Resource
private ApplicationEventPublisher publisher;
@Transactional
public void addTestModel() {
TestModel model = new TestModel();
model.setName("haogrgr");
mapper.insert(model);
//对于@TransactionalEventListener, 会在事务提交后才执行Listener处理逻辑.
//发布事件, 事务提交后, 记录日志, 或发送消息等操作
publisher.publishEvent(model);
}
//当事务提交后, 才会真正的执行@TransactionalEventListener配置的Listener, 如果Listener抛异常, 方法返回失败, 但事务不会回滚.
}
@Component
public class TransactionEventListener {
@TransactionalEventListener
public void handle(PayloadApplicationEvent<TestModel> event) {
System.out.println(event.getPayload().getName());
//这里可以记录日志, 发送消息等操作.
//这里抛出异常, 会导致addTestModel方法异常, 但不会回滚事务.
}
}
- TransactionSynchronizationManager
//线程池异步调用提高性能
private final ExecutorService executorService = Executors.newFixedThreadPool(5);
public void insert(TechBook techBook){
bookMapper.insert(techBook);
// send after tx commit but is async
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("send email after transaction commit...");
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("complete send email after transaction commit...");
}
});
}
}
);
// async work but tx not work, execute even when tx is rollback
// asyncService.executeAfterTxComplete();
ThreadLocalRandom random = ThreadLocalRandom.current();
if(random.nextInt() % 2 ==0){
throw new RuntimeException("test email transaction");
}
System.out.println("service end");
}