事务控制介绍

FDP开发平台基础spring+mybatis,事务是由spring来管理的,采用基于注解的事务。

@Transactional 注解可以被写在接口定义、接口方法、类定义、类的 public 方法上。

如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,但是这个事务不生效。

FDP平台基础@Transactional 注解方案

在Service类上的注解

@Transactional(propagation=Propagation.SUPPORTS)(表示不主动开启事务)

这是后期性能调优时,决定使用SUPPORTS传播行为的。查询类的方法(select)不需要开事务,修改类型的方法(insert\update)在方法上单独写事务注解。

是为了优化mybatis的二级缓存,这个缓存是在dao层。若上层的service层开启了事务,但下层的dao层命中了缓存,这个事务就白白浪费了,影响了查询性能。

在查询类型的方法上(采用)

不写事务注解,表示使用类上的事务注解(表示不主动开启事务)

查询类型的方法上(未采用)

这么写,表示使用只读事务(与普通事务区别很小),若数据库支持readOnly的话,会得到“可重复读”能力

@Transactional(readOnly = true)   (readOnly属性是否起作用取决于数据库是否支持,oralce是支持的)

修改、增加、删除方法上

这么写,表示开启事务,任何异常都回滚
@Transactional(rollbackFor=Exception.class)

不需要事务的方法上

这么写,表示不主动开启事务

@Transactional(propagation=Propagation.SUPPORTS)

Transactional 注解

Spring的  @Transactional 事务注解的属性

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

Spring的事务传播 行为

各种属性的意义:

REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。是默认值。

NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。

SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。

NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

示例:

@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

事务回滚与异常类型

Java的异常分两种
1、被检查的异常(Exception)
2、不被检查的异常(RunTimeException)
Spring的事务默认情况下会对RunTimeException进行事务回滚,如果遇到被检查的异常(Exception)默认不不回滚。

若想让让被检查的异常(Exception)也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)

只读事务

readOnly

该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)

readOnly这个东西没有什么大的用途,使用面儿太小,FDP平台计划不使用这个属性。

原理请看文章: readonly事务深入挖掘

当我们需要“可重复读”时,请开启readonly=true。但好不好使还取决于数据库和jdbc驱动的支持情况。 实际意义不大。

“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在“只读事务”里面修改数据,也并非不可以(我试了oracle可成功修改提交也可回滚),只不过对于数据一致性的保护不像“读写事务”那样保险而已。
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。
有些数据库jdbc驱动不支持readonly属性 ,但Oracle是支持的。

超时

事务超时设置:@Transactional(timeout=30) //默认是30秒

Spring事务的隔离级别

1. ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别, 使用数据库默认的事务隔离级别.
2. ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它允许令外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。
3. ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据
4. ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
5. ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。

示例:

@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化