@Transactional事务处理解决方案的看法
- 前言
- 一.声明式事务
- 二.编程式事务
- 三.事务粒度优化方法
- 四.缓存和事务的一致性
- 五.介绍--延时双删
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
本文就是了解一下声明式事务和编程式事务的优缺点和事务一致性的一些方案
提示:以下是本篇文章正文内容,下面案例可供参考
一.声明式事务
官方:所谓声明式事务,就是通过配置的方式,比如通过配置文件(xml)或者注解的方式,告诉spring,哪些方法需要spring帮忙管理事务,然后开发者只用关注业务代码,而事务的事情spring自动帮我们控制。
声明式事务就是通过@Transactional注解然后基于AOP实现 ,原理就是spring容器生命周期启动的时候发现有EnableTransactionManagement注解,就会扫描看一下bean上是否有@Transaction注解(类、或者父类、或者接口、或者方法中有这个注解都可以),如果有这个注解,spring会通过aop的方式给bean生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截bean中public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务。
参考链接
@Transaction
public void insert(String userName){
//业务操作
}
//配置类加上EnableTransactionManagement
@EnableTransactionManagement
public class config {
}
注意点:
-
@Transaction放在接口上,那么接口的实现类中所有public都被spring自动加上事务
-
@Transaction放在类上,那么当前类以及其下无限级子类中所有pubilc方法将被spring自动加上事务
-
@Transaction放在public方法上,那么该方法将被spring自动加上事务
-
@Transaction只对public方法有效
12种失效场景
二.编程式事务
基于底层的API,如PlatformTransactionManager、TransactionDefinition 和 TransactionTemplate 等核心接口,开发者完全可以通过编程的方式来进行事务管理
官方建议使用TransactionTemplate
//依赖注入
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(User user) {
//业务代码
dosth();
transactionTemplate.execute((status) => {
//数据库操作
insert(user);
return Boolean.TRUE;
})
}
手动提交示例
public void test() {
TransactionDefinition def = new DefaultTransactionDefinition();
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 业务操作
// 事务提交
transactionManager.commit(status);
} catch (DataAccessException e) {
// 事务提交
transactionManager.rollback(status);
throw e;
}
}
三.事务粒度优化方法
声明式事务就是会有事务包裹太大的问题
1.分开方法
@Service
public class BaseTestServiceImpl implements BaseTestService {
@Resource
private EmployeeMapper employeeMapper;
@Autowired
private BaseTestService baseTestService;
public void addData(){
Employee employee = new Employee();
//业务代码
//注意不可以用this,要用代理类,这边简单注入自己展示一下
baseTestService.insertData(employee);
}
@Transactional(rollbackFor = Exception.class)
public int insertData(Employee employee){
//数据库操作
employeeMapper.insert(employee);
return 1;
}
2.上面的方式不优雅,每个方法都要新写一个public,这里展示第二种函数式编程的方式
函数类
@FunctionalInterface
public interface DbOperateHandler {
public int insertData();
}
service层代码
public void addData2(){
Employee employee = new Employee();
//业务代码
//复写insertData,加Transactional
DbOperateHandler dbOperateHandler = new DbOperateHandler() {
@Override
@Transactional
public int insertData() {
employeeMapper.insert(employee);
return 0;
}
};
}
这样就可以复用了
四.缓存和事务的一致性
大家应该都使用Cache Aside模式:先写DB再删Cache, 如果数据在缓存不存在,则从数据库查询,并将数据写入缓存。
还有一种常用方法就是倒过来,先删Cache再写DB。
还有很多实现模式,双删缓存,延迟双删缓存,同步双写等等。
博主这边介绍一下先写DB再删Cache的模版
@Transactional
public void demo(){
//数据库操作
int flag = demoService.deleteById(1);
if(flag ==1 ){
//删除缓存
}
}
参考链接
五.介绍–延时双删
1.在修改数据库数据前,需要先删除一次redis:此时是为了保证在数据库数据修改和redis数据被删除的间隔时间内,如有命中,保证此数据也不存在redis中。如果没有这一次删除,当数据库数据已经被修改了,但是还是可以从redis中读出旧数据,导致数据不一致。
2.第二次删除则是在修改数据库数据后,此时需要再次删除redis中对应数据一次,这一次是为了删除 第一次redis删除和数据库数据修改之间,如果有请求,那么旧数据又会重新缓存到redis中,然而数据在数据库中在接下来就会被修改,如果没有这一次删除,redis中则会存在数据库中旧的数据。
3.那么第二次为什么需要在数据库修改后延迟一定时间再删除redis呢?
为了等待之前的一次读取数据库,并等待其数据写入到缓存,最后删除这次脏数据,所以是一次数据从数据库中发到服务器+缓存写入的时间
参考文档
总结
以上就是今天要讲的内容,希望对大家有所帮助。