一、 场景

有一个定时器,做数据库表数据同步。

把数据库A的表table(DB_A_TABLE)同步到数据库B的表table(DB_B_TABLE)。

对于DB_A_TABLE的每行数据要做一定的逻辑处理转换到DB_B_TABLE,这转换过程中可能会出异常,所以此行记录无法同步,要记录这行数据到log文件或者保存到DB_B的某张表记录下来。但别的正确转换的数据,要正常的同步。

EX:

DB_A_TABLE 有8行记录[1,2,3,4,5,6,7,8],其中[3,6,7]无法被正确转换,[5]能正确转换但无法保存进DB_B_TABLE。

那么,[1,2,4,8]要正确的同步到DB_B_TABLE。 [3,5,6,7]要记录到DB_B_ERROR。

二、 事务

针对以上场景,个人的做法是:

针对每行记录,进行逻辑转换、数据保存,这一系列的操作都在一个事务。

即,对每行记录的同步,都用各自的事务去控制。

那么针对以上的ex,就是有8个事务。

(传播行为都默认是PROPAGATION_REQUIRED,可能更好的是用PROPAGATION_NESTED/ PROPAGATION_REQUIRES_NEW)

三、code demo

// 定时器 timmer.class
public class SyncDataTimmer {
@Log
private Logger log;
@Autowired
private DealService dealService;
// @Transactional(readOnly=false,rollbackFor=Exception.class)
@Scheduled(cron = "0 0 4 * * ?")
public void syncData() {
Map<String,Object> condition = new HashMap<String,Object>();
List<DbBtable> allData = dealService.getBankDefaultRepaymentInfo(condidtions);
if (ObjectHelper.isNotEmpty(allData)) {
for (DbBtable row : allData) {
row.setXXX(...);
try {
// 针对每行数据的操作
dealService.save(row);
} catch (Exception e) {
// 记录到log或者记录到数据库表
log.error("错误信息:", e);
}
}
}
}
}
// Service处理 DealService.class
public class DealService {
@Override
@Transactional(rollbackFor=Exception.class)
public void save(DbBtable dto ) throws Exception {
String table001Id = dto.getTable001Id(); if(ObjectHelper.isNotEmpty(table001Id)){
Table001 table001 = this.findById(table001Id);
if(ObjectHelper.isNotEmpty(table001)) this.syncDataTemplate(table001,dto);//逻辑处理,可能异常
else throw new Exception("根据id="+ table001Id +",未找到记录!");
}else{
throw new Exception("表001的Id为空");
}
} @Transactional(rollbackFor=Exception.class)
public void syncDataTemplate (Table001 table001,DbBtable dto ) throws Exception {
//可能进行表数据查询、保存等
//省略...
}
}

说明:

1、 Spring默认只会回滚RuntimeException运行时异常。而CheckedException不会回滚,所以要用rollbackFor指定。(若异常定义好,就不需要指定rollbackFor)

2、 Demo中是把要同步的数据获取写在了timmer.class中,针对timmer.class也是一个定时器,所以也可以用事务Transactional。

结合场景和demo,如果由timmer.class创建事务,又未指定事务的传播行为,那么默认是PROPAGATION_REQUIRED。则后面操作全部在一个事务中,无法达到想要的结果。

所以,(不知道怎么表达,现在理解也不深刻,先这么理解着)

针对场景和demo,不要在顶层方法创建事务,即SyncDataTimmer.class 的syncData()不用事务。而是在每行记录的顶层处理方法DealService.Save() 创建事务,然后传播行为默认ROPAGATION_REQUIRED,那么可以针对每行数据进行控制。

为什么不在synData()创建事务?

因为,我没有修改事务传播行为,都用的默认的ROPAGATION_REQUIRED。导致,DealService.Save()会用同一个事务,即[1,2,3,4,5,6,7,8]都在一个事务中,被一起提交/回滚。

更好的传播方式?

针对场景和demo,可能的传播行为可以是:

1) PROPAGATION_NESTED:和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,NESTED也要回滚的。

(hibernate并不支持NESTED!最好确认所用hibernate版本是否支持,我查的hibernate4说不支持。)

2) PROPAGATION_REQUIRES_NEW:另起一个事务,将会与他的父事务相互独立;

假设ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,

ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,

那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,

ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,ServiceA.methodA才继续执行。

(对code demo,可能timmer.syncData()需要事务,所以REQUIRES_NEW更适合。)

PROPAGATION_REQUIRES_NEW与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。

因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。

如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。

如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

针对deme的修改

Timmer.class可以启用事务, 而把调用的传播行为定义为REQUIRES_NEW

    @Transactional(rollbackFor=Exception.class,propagation=Propagation.REQUIRES_NEW)
public void save(DbBtable dto ) throws Exception {
...
}

此时,timmer与service是完全独立的。且service的事务在timmer之前提交。所以,REQUIRES_NEW 的话,当timmer方法错误时,无法全部回滚。

当用NESTED的话,service是timmer的子事务,在timmer提交时一起提交。

四、 扩展

1、事务传播行为

传播行为

含义

MANDATORY

表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常

NESTED

表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与 PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。

NEVER

表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常

NOT_SUPPORTED

表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

REQUIRED(默认)

表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务

REQUIRED_NEW

表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

SUPPORTS

表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行

2、事务的隔离级别ISOLATION

隔离级别

含义

DEFAULT

使用数据库默认的事务隔离级别

READ_UNCOMMITTED

事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。

会产生脏读,不可重复读和幻读

READ_COMMITTED

保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。

避免脏读,但会不可重复读和幻读

REPEATABLE_READ

这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。
它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

SERIALIZABLE

花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。
除了防止脏读,不可重复读外,还避免了幻像读

隔离级别

脏读

不可重复读

幻读

READ_UNCOMMITTED

Y

Y

Y

READ_COMMITTED

N

Y

Y

REPEATABLE_READ

N

N

Y

SERIALIZABLE

N

N

N

脏读: 指当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中(事务A未提交)。

这时,另外一个事务B也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据(A事务可能回滚), 那么事务B读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。

不可重复读: 指在一个事务A内,多次读同一数据。在这个事务A还没有结束时,另外一个事务B也访问该同一数据并修改。
    那么,事务A中的两次读数据之间,由于事务B的修改,那么事务A两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

幻读: 指当事务不是独立执行时发生的一种现象,例如事务A对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。

同时,事务B也修改这个表中的数据,这种修改是向表中插入一行新数据。

那么,可能就会发生操作事务A的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

不可重复读 与 幻读 的区别

不可重复读:可能是第一次读取total=100,第二次读取total=200。因为修改引起。

幻读:则是条数不一样,因为有新插入或者删除的记录。因为新增/删除引起。

针对oracle

Oracle数据库缺省的事物隔离级别已经保证了避免脏读和不可重复读。

可能会幻读,避免幻读 需要加表级锁,Oracle缺省行级锁。在基于Spring的事物配置中一定要慎重使用ISOLATION_SERIALIZABLE的事物隔离级别。

这种配置会使用表级锁,对性能影响巨大。

一般没有特殊需要的话,配置为使用数据库缺省的事物隔离级别便可。

参考

Spring事务异常回滚,捕获异常不抛出就不会回滚

Spring中的事务管理实例详解

浅谈Spring事务隔离级别

解惑 spring 嵌套事务

Spring事务的隔离级别

【Spring】事务(transactional)之初步理解的更多相关文章

  1. Spring事务Transactional和动态代理(一)-JDK代理实现

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  2. Spring事务Transactional和动态代理(二)-cglib动态代理

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  3. Spring事务Transactional和动态代理(三)-事务失效的场景

    系列文章索引: Spring事务Transactional和动态代理(一)-JDK代理实现 Spring事务Transactional和动态代理(二)-cglib动态代理 Spring事务Transa ...

  4. 对于spring中事务@Transactional注解的理解

    现在spring的配置都喜欢用注解,这边就说下@Transactional 一.如何开启@Transactional支持 要使用@Transactional,spring的配置文件applicatio ...

  5. Spring事务@Transactional标签深入学习

    事务管理是应用系统开发中必不可少的一部分.Spring为事务管理提供了丰富的功能支持.Spring事务管理分为编码式和声明式 两种方式.编码式事务指的是通过编码方式实现事务;声明式事务基于AOP,将具 ...

  6. 分析spring事务@Transactional注解在同一个类中的方法之间调用不生效的原因及解决方案

    问题: 在Spring管理的项目中,方法A使用了Transactional注解,试图实现事务性.但当同一个class中的方法B调用方法A时,会发现方法A中的异常不再导致回滚,也即事务失效了. 当这个方 ...

  7. 关于Spring事务<tx:annotation-driven/>的理解(Controller可以使用@Transactional)

    在使用SpringMvc的时候,配置文件中我们经常看到 annotation-driven 这样的注解,其含义就是支持注解,一般根据前缀 tx.mvc 等也能很直白的理解出来分别的作用.<tx: ...

  8. Spring事务<tx:annotation-driven/>的理解(Controller使用@Transactional)

    在使用Spring的时候,配置文件中我们经常看到 annotation-driven 这样的注解,其含义就是支持注解,一般根据前缀 tx.mvc 等也能很直白的理解出来分别的作用. <tx:an ...

  9. Spring事务<tx:annotation-driven/>的理解

    在使用Spring的时候,配置文件中我们经常看到 annotation-driven 这样的注解,其含义就是支持注解,一般根据前缀 tx.mvc 等也能很直白的理解出来分别的作用. <tx:an ...

  10. Spring事务控制和传递性理解

    1.在同一类方法间相互调用,如果调用方无事务控制,被调用方有事务控制,则被调用方也无事务 原因:外部经过spring容器调用service的方法事务才生效,service类内部方法间相互调用事务不生效 ...

随机推荐

  1. Go语言实现:【剑指offer】数组中出现次数超过一半的数字

    该题目来源于牛客网<剑指offer>专题. 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组 ...

  2. geo常见需求

    常见的地理位置相关需求有: 1.查找附近的人 2.显示两点距离 3.点是否在指定范围内(地理围栏) redis.MongoDB.mysql都已支持geo 几种geo方案对比 https://blog. ...

  3. Hexo搭建静态博客踩坑日记(一)

    前言 博客折腾一次就好, 找一个适合自己的博客平台, 专注于内容进行提升. 方式一: 自己买服务器, 域名, 写前端, 后端(前后分离最折腾, 不分离还好一点)... 方式二: 利用Hexo, Hug ...

  4. [MSSQL]xp_cmdshell 查看磁盘空间

    EXEC xp_cmdshell 'wmic logicaldisk get freespace,caption | findstr C'; <class 'pyodbc.Row'> (' ...

  5. win10打开自带wifi热点共享

    win10打开自带wifi热点共享 第一步,打开网络和Internet设置 二. 找到移动热点

  6. Winfrom 减少控件重绘闪烁的方法

    Winform控件的双缓冲.控件的双缓冲属性是隐藏的,可以通过反射改变其属性值. lv.GetType().GetProperty("DoubleBuffered", Bindin ...

  7. 学习笔记——python(继承)

    学习笔记(Python继承) 有几种叫法(父类-子类.基类-派生类)先拿代码演示一下: class father: def work(self): print("work>>&g ...

  8. Android Intent用法总结

    Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作.动作涉及数据.附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 ...

  9. Maven 阿里云仓库地址

    https://maven.aliyun.com/mvn/view 一般使用聚合仓库(group),path是仓库地址.可点击右上角“使用指南”: 附   目前阿里云仓库的地址 https://mav ...

  10. Winfom 使用 BackgroundWorker 实现进度条

    BackgroundWorker 简介(来自百度) BackgroundWorker是·net里用来执行多线程任务的控件,它允许编程者在一个单独的线程上执行一些操作.耗时的操作(如下载和数据库事务)在 ...