Java教程

JAVA面试:数据库事务

本文主要是介绍JAVA面试:数据库事务,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

事务(2021.4.9)

MySQL中事务只有innoDB引擎支持,MyISAM不支持事务

事务是什么

事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

人话:事务就是你CURD的一行代码

事务四大特性(AICD):原子性、隔离性、一致性、持久性


特性底层实现原理——AICD

**A(原子性 Atomicity):**要么全部完成,要么完全不起作用。

底层实现原理undo log(当这个事务对数据库进行修改的时候,innodb生成对应undo log,他会记录这个SQL执行的相关信息,如果SQL执行失败发生这个回滚,innodb根据这个undo log内容去做相反的工作,比如说我执行了一个insert操作,那么回滚的时候,就会执行一个相反的操作,就是delete,对应update,回滚的时候也是执行相反的update)


**I(隔离性 Isolation):**多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

底层实现原理写-写操作(和java里面的锁机制是一样的)。写-读操作:MVCC(多版本并发控制,可以通过乐观锁和悲观锁实现,只在读已提交可重复读二个隔离级别,事务的排它锁形式修改数据,修改之前把数据放到undo log里面,通过回滚指针关联,成功了什么都不做,失败了,从undo log回滚数据。)


C(一致性 Consistency):一旦事务完成(不管成功还是失败),业务处于一致的状态,而不会是部分完成,部分失败。事务执行前后,数据库的完整约束没有遭受破坏,事务执行前后都是合法的一个数据状态

底层实现原理:一致性可以归纳为数据的完整性。 数据的完整性是通过其他三个特性来保证的,包括原子性、隔离性、持久性,而这三个特性,又是通过 Redo/Undo 来保证的 ,为了保证数据的完整性,提出来三个特性,这三个特性又是由同一个技术来实现的,所以理解 Redo/Undo 才能理解数据库的本质。


D(持久性 Durability ):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,事务的结果被写到持久化存储器中。

底层实现原理redo log(mysql的数据是存放在这个磁盘上的,但是每次去读数据都需要通过这个磁盘io,效率就很低,使用innodb提供了一个缓存buffer,这个buffer中包含了磁盘部分数据页的一个映射,作为访问数据库的一个缓冲,当从这个数据库读取一个数据,就会先从这个buffer中获取,如果buffer中没有,就从这个磁盘中获取,读取完再放到这个buffer缓冲中,当数据库写入数据的时候,也会首先向这个buffer中写入数据,定期将buffer中的数据刷新到磁盘中,进行持久化的一个操作。如果buffer中的数据还没来得及同步到这个磁盘上,这个时候MySQL宕机了,buffer里面的数据就会丢失,造成数据丢失的情况,持久性就无法保证了。使用redo log解决这个问题,当数据库的数据要进行新增或者是修改的时候,除了修改这个buffer中的数据,还会把这次的操作写入到这个redo log中,如果msyql宕机了,就可以通过redo log去恢复数据,redo log是预写式日志(会先将所有的修改写入到日志里面,然后再更新到buffer里面,保证了这个数据不会丢失,保证了数据的持久性)


数据库事务隔离级别

MySQL 的事务隔离是在 MySQL. ini 配置文件里添加的,在文件的最后添加

transaction-isolation = REPEATABLE-READ

可用的配置值:READ-UNCOMMITTED、READ-COMMITTED、REPEATABLE-READ、SERIALIZABLE。

Spring的事务隔离是在注解上声明

propagation = Propagation.REQUIRED

可用的配置值:DEFAULT、其他四个跟上面数据库的一样

Default 默认

这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别

数据库默认事务隔离级别
MySqlRepeatable read可重复读
大多数数据库,如Sql Server , OracleRead committed读提交

Read uncommitted 读未提交
公司发工资了,领导把20000元打到廖志伟的账号上,但是该事务并未提交,而廖志伟正好去查看账户,发现工资已经到账,是20000元整,非常高兴。可是不幸的是,领导发现发给廖志伟的工资金额不对,是16000元,于是迅速修改金额,将事务提交,最后廖志伟实际的工资只有16000元,廖志伟空欢喜一场。

出现上述情况,即我们所说的脏读,两个并发的事务,“事务A:领导给廖志伟发工资”、“事务B:廖志伟查询工资账户”,事务B读取了事务A尚未提交的数据。当隔离级别设置为Read uncommitted时,就可能出现脏读,如何避免脏读,请看下一个隔离级别。

这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。

Read committed 读提交/不可重复读
廖志伟拿着工资卡去消费,系统读取到卡里确实有2000元,而此时她的老婆也正好在网上转账,把廖志伟工资卡的2000元转到另一账户,并在廖志伟之前提交了事务,当廖志伟扣款时,系统检查到廖志伟的工资卡已经没有钱,扣款失败,廖志伟十分纳闷,明明卡里有钱,为何…

出现上述情况,即我们所说的不可重复读,两个并发的事务,“事务A:廖志伟消费”、“事务B:廖志伟的老婆网上转账”,事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。当隔离级别设置为Read committed时,避免了脏读,但是可能会造成不可重复读大多数数据库的默认级别就是Read committed,比如Sql Server , Oracle。如何解决不可重复读这一问题,请看下一个隔离级别。

保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。

Repeatable read 可重复读
当廖志伟拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),廖志伟的老婆就不可能对该记录进行修改,也就是廖志伟的老婆不能在此时转账。这就避免了不可重复读。廖志伟的老婆工作在银行部门,她时常通过银行内部系统查看廖志伟的信用卡消费记录。有一天,她正在查询到廖志伟当月信用卡的总消费金额(select sum(amount) from transaction where month = 本月)为80元,而廖志伟此时正好在外面胡吃海喝后在收银台买单,消费1000元,即新增了一条1000元的消费记录(insert transaction … ),并提交了事务,随后廖志伟的老婆将廖志伟当月信用卡消费的明细打印到A4纸上,却发现消费总额为1080元,廖志伟的老婆很诧异,以为出现了幻觉,幻读就这样产生了。当隔离级别设置为Repeatable read时,可以避免不可重复读,但会出现幻读。注:MySQL的默认隔离级别就是Repeatable read

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

Serializable 序列化
Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。

这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。

除了防止脏读,不可重复读外,还避免了幻像读。

脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。

不可重复读 :是指在一个事务内,多次读同一数据。

幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所有数据行的记录就变多或者变少了。


spring 事务实现方式有哪些?

声明式事务:

基于 xml 配置文件的方式注解方式(在类上添加 @Transactional 注解)

Spring在配置文件applicationContext.xml开启事务和注解

注解方式详见下面

编码方式:

提供编码的形式管理和维护事务(直接写在代码中)

@Autowired
private PlatformTransactionManager transactionManager;

public void testTX(){
   	DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
   	TransactionStatus status = transactionManager.getTransaction(definition);
   	try {
           // 业务逻辑
           // ...
       
           // 提交事务
           transactionManager.commit(status);
       }catch (Exception e){
           // 发生异常,事务回滚
           transactionManager.rollback(status);
       }
}

讲解@Transactional注解之前我们需要回顾一下java的异常体系:

Throwable 是 Java 语言中所有错误或异常的超类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。
实例分为 Error 和 Exception 两种。

Error 类是指 java 运行时系统的内部错误和资源耗尽错误。应用程序不会抛出该类对象。如果
出现了这样的错误,除了告知用户,剩下的就是尽力使程序安全的终止。

Exception 又有两个分支 ,一个是运行时异常 RuntimeException,一 个是检查异常 CheckedException。

RuntimeException 如 :NullPointerException 、 ClassCastException ;
CheckedException 如: I/O 错误导致的 IOException、SQLException。

RuntimeException 是那些可能在 Java 虚拟机正常运行期间抛出的异常的超类。 如果出现 RuntimeException,那么一定是程序员代码书写导致的错误

CheckedException:一般是外部错误,这种异常都发生在编译阶段,Java 编译器会
程序去捕获此类异常,即会出现要求你把这段可能出现异常的程序进行 try catch,不然无法编译运行


@Transactional注解的使用方式

详解:https://www.cnblogs.com/tangjiang-code/p/13141559.html

注解方式:

1)事务方法需要标记为public

2)@Transactional注解只能写在Service中,不能在Controller中,否则会报404错误

3)如果在使用事务的情况下,所有操作都是读操作,那建议把事务设置成只读事务,当事务被标识为只读事务时,Spring可以对某些可以针对只读事务进行优化的资源就可以执行相应的优化措施,需要手动设置成true。但是方法再执行增删改会抛异常。

@Transactional(
    transactionManager = "",//当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
    readOnly = false, //指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
    timeout = -1 ,     //事务的超时时间,-1为无限制。如果超过该时间限制但事务还没有完成,则自动回滚事务。
    rollBackFor = {},//用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
    noRollbackFor = ArithmeticException.class, //遇到指定的异常不回滚
    isolation = Isolation.DEFAULT, //事务的隔离级别,此处使用后端数据库的默认隔离级别
    propagation = Propagation.REQUIRED //事务的传播行为
)

使用方式:

//正常场景
//用在Service层的impl类上,调用dao层
//@Transactional注解只能写在service中,不能在controller中,否则会报404错误 
@Transactional
public void testSHIWU(){
	Comment comment = new Comment(23,2,null,"测试事务",1);
	String s = JSON.toJSONString(comment);
	commentDao.addComment(comment);
}
//结果:插入成功

如果遇到RuntimeException 会回滚,遇到CheckedException不会回滚。

如果也想检查时错误也回滚,可以这样配置**@Transactional(rollbackFor = Exception.class),这样的话就会跟运行时错误**一样了

//运行时错误
@Transactional
public void testSHIWU(){
    Comment comment = new Comment(23,2,null,"测试事务",1);
    String s = JSON.toJSONString(comment);
    commentDao.addComment(comment);

    if (true) {
        throw new NullPointerException();
    }

    commentDao.deleteCommentById(25);//如果事务中间发生了运行时错误,会因为原子性而全部不执行,错误放在最后就全部执行
    //throw new NullPointerException();//放在最后不会出现原子性问题,因为事务全部执行完了
}

//结果:回滚,不执行插入,删除失败

//检查时错误
@Transactional
public void testSHIWU() throws FileNotFoundException{
    Comment comment = new Comment(23,2,null,"测试事务",1);
    String s = JSON.toJSONString(comment);
    commentDao.addComment(comment);

    if (true){
        throw new FileNotFoundException();//放在中间会导致后面的事务无法执行,但是前面的事务不受影响
    }

    commentDao.deleteCommentById(25);
   
}

//结果:不回滚前面的事务,后面的错误无法执行,插入成功,删除失败

遇到异常捕获(try_catch_finally):如果我们在事务方法中,手动捕获了异常,并没有让事务抛出去,也没有手动指定需要回滚,那么事务方法即使出现异常,也会提交事务。

@Transactional
public void testSHIWU(){
    Comment comment = new Comment(23,2,null,"测试事务",1);
    String s = JSON.toJSONString(comment);
    commentDao.addComment(comment);

    try {
        throw new FileNotFoundException();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        //设置手动回滚,如下
      //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }

    commentDao.deleteCommentById(23);
}

有这句代码就不需要再手动抛出运行时异常了,但是不建议这样做因为这样做代码中会多出很多事务回滚的代码,不利于维护,还是交得spring去处理更妥当。

**如何处理异常回滚:**自定义全局异常类,我们可以把catch到的异常统一按照一定的格式进行抛出。再进行处理。


事务传播的过程

事务传播:启用一个事务方法,调用另一个事务方法时,会进行事务传播。注解@Transactional默认的传播机制是PROPAGATION_REQUIRED

我们假设有两个事务方法A,B,然后A调用B。

1)根据上面我们讲过的required的特性,我们知道spring对于这种事务方法间的调用,会默认把它们当做一个事务来处理;我们假设如果B中抛出了NullpointException(运行时错误),并且A,B都没有做异常的处理,那么由A,B组成的整个事务肯定都会进行回滚,这是毋庸置疑的。

2)如果B出现异常,A catch了异常,并且没有抛出去,就像我们上面例子讲的只是记录了日志,我们会发现这个异常,那就要考虑是不是设置了手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

解决这个问题可以设置传播方式为requeired_NEW,这样A,B方法就是两个不同的事务,互不影响。


Spring事务传播的七种行为:

Spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,Spring的事务是没有作用的。所以说Spring事务的底层依赖MySQL的事务,Spring是在代码层面利用AOP实现执行事务的时候使用TransactionInceptor进行拦截,然后处理。本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚

*Spring支持7种事务传播行为(常用)

一个场景:假设外围方法里面包含二个新增用户和新增角色的方法后面还会抛一个异常。

*propagation_required(需要传播):当前没有事务则新建事务,有则加入当前事务。

  • 外围方法未开启事务,插入用户表和用户角色表的方法在自己的事务中独立运行,外围方法异常不影响内部插入,所以两条记录都新增成功。
  • 外围方法开启事务,内部方法加入外围方法事务(在最后加入),外围方法回滚,内部方法也要回滚,所以两个记录都插入失败。
如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

propagation_supports(支持传播):支持当前事务,如果当前没有事务则以非事务方式执行

  • 外围方法未开启事务,插入用户表和用户角色表的方法以非事务的方式独立运行,外围方法异常不影响内部插入,所以两条记录都新增成功。
  • 外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚,所以两个记录都插入失败。
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行

propagation_mandatory(强制传播):使用当前事务,如果没有则抛出异常

  • 外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚,所以两个记录都插入失败。
  • 外围方法没有开启事务,两张表数据都为空,在调用用户新增方法时候已经报错了,所以两个表都没有新增数据。
支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

propagation_nested(嵌套传播):如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行需要传播行为。

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按 propagation_required属性执行

propagation_never(绝不传播):以非事务的方式执行,如果当前有事务则抛出异常。

  • 外层方法没有事务会以非事务的方式运行,两个表新增成功;
  • 外围方法有事务则抛出异常,两个表都都没有新增数据
以非事务方式执行,如果当前存在事务,则抛出异常。 

*propagation_requires_new(传播需要新的):新建事务,如果当前有事务则把当前事务挂起,创建新的事务。

  • 无论当前存不存在事务,都创建新事务,所以两个数据新增成功。
新建事务,如果当前存在事务,把当前事务挂起。

propagation_not_supported(不支持传播):以非事务的方式执行,如果当前有事务则把当前事务挂起。

  • 外围方法未开启事务,插入用户表和用户角色表的方法在自己的事务中独立运行,外围方法异常不影响内部插入,所以两条记录都新增成功。
  • 外围方法开启事务,两个数据新增成功。
以非事务方式执行,如果当前存在事务,则抛出异常。 

当传播行为设置了

propagation_not_supports(不支持传播)

propagation_never(绝不传播)

propagation_supports(支持传播)

这三种时,就有可能存在事务不生效

解决方法:尽量避免使用上面三种传播行为。


更多可以了解一下隔离级别的具体使用场景,以及优化的地方https://www.cnblogs.com/limuzi1994/p/9684083.html


补充:

1、事务隔离级别为读提交时,写数据只会锁住相应的行

2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读

3、事务隔离级别为串行化时,读写数据都会锁住整张表

4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

READ UNCOMMITTED (未提交读) :隔离级别:0. 哪个问题都不能解决

原理: 事务A和事务B,事务B可以读取事务A未提交的记录。会出现脏读,因为事务A可能会回滚操作,导致数据发生变化。

READ COMMITTED (提交读) :隔离级别:1. 可以解决脏读 。

原理: 事务中只能看到已提交的修改,提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免了脏读,但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据

REPEATABLE READ (可重复读) :隔离级别:2. 可以解决脏读和不可重复读,实现不幻读,需要加锁

**原理:在InnoDB中是这样的:RR隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),因此不存在幻读现象。但是标准的RR只能保证在同一事务中多次读取同样记录的结果是一致的,而无法解决幻读问题。**InnoDB的幻读解决是依靠MVCC的实现机制做到的。Mysql默认的隔离级别是RR。

InnoDB的幻读解决是依靠MVCC的实现机制: (增加系统版本号,每次事务操作,会比较系统版本号)

​ InnoDB为每行记录添加了一个版本号(系统版本号),每当修改数据时,版本号加一。在读取事务开始时,系统会给事务一个当前版本号事务会读取版本号<=当前版本号的数据,这时就算另一个事务插入一个数据,并立马提交,新插入这条数据的版本号会比读取事务的版本号高,因此读取事务读的数据还是不会变。例如:此时books表中有5条数据,版本号为1 事务A,系统版本号2:select * from books;因为1<=2所以此时会读取5条数据。 事务B,系统版本号3:insert into books …,插入一条数据,新插入的数据版本号为3,而其他的数据的版本号仍然是2,插入完成之后commit,事务结束。 事务A,系统版本号2:再次select * from books;只能读取<=2的数据,事务B新插入的那条数据版本号为3,因此读不出来,解决了幻读的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1nUIK9R2-1625550438853)(C:\Users\Amamiya\Desktop\面试知识\MVCC实现机制.png)]

强烈建议看:https://blog.csdn.net/promisessh/article/details/115385685,了解MVCC的底层版本判断,以及为什么MVCC能解决不可重复读的问题

SERIALIZABLE (可串行化):隔离级别:3.

**原理:**该隔离级别会在读取的每一行数据上都加上锁,退化为基于锁的并发控制,即LBCC。可以解决脏读不可重复读和幻读—相当于锁表

需要注意的是,MVCC只在RC (提交读) 和RR(可重复读) 两个隔离级别下工作,其他两个隔离级别都和MVCC不兼容。

下面的不要求


容器事务

Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。

Spring事务管理涉及的接口及其联系:

img

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

Public interface PlatformTransactionManager{  
       // 由TransactionDefinition得到TransactionStatus对象
       TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
       // 提交
       Void commit(TransactionStatus status) throws TransactionException;  
       // 回滚
       Void rollback(TransactionStatus status) throws TransactionException;  
}

1)Spring JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用 DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

     <property name="dataSource" ref="dataSource" />

</bean>

实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

2)Hibernate事务

如果应用程序的持久化是通过Hibernate实现的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的**<bean>**声明:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory" />
</bean>

sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

3)Java持久化API事务(JPA)

Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
     <property name="sessionFactory" ref="sessionFactory" />
</bean>

JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

基本的事务属性的定义:

事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。

事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。

事务属性包含了5个方面:

传播行为、隔离规则、回滚规则、事务超时、是否只读

TransactionDefinition:

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事务的传播行为
    int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
    int getTimeout();  // 返回事务必须在多少秒内完成
    boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}

事务的属性可同通过注解方式或配置文件配置:

注解方式:

@Transactional只能被应用到public方法上,对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
默认情况下,一个有事务方法, 遇到RuntimeException 时会回滚 . 遇到 受检查的异常 是不会回滚 的. 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常})

@Transactional(
    readOnly = false, //读写事务
    timeout = -1 ,     //事务的超时时间,-1为无限制
    noRollbackFor = ArithmeticException.class, //遇到指定的异常不回滚
    isolation = Isolation.DEFAULT, //事务的隔离级别,此处使用后端数据库的默认隔离级别
    propagation = Propagation.REQUIRED //事务的传播行为
)

配置文件( aop拦截器方式):

<tx:advice id="advice" transaction-manager="txManager">
<tx:attributes>
    <!-- tx:method的属性:
    * name 是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。通配符 
    (*)可以用来指定一批关联到相同的事务属性的方法。
    如:'get*'、'handle*'、'on*Event'等等.
    * propagation:不是必须的,默认值是REQUIRED表示事务传播行为,
    包括REQUIRED,SUPPORTS,MANDATORY,REQUIRES_NEW,NOT_SUPPORTED,NEVER,NESTED
    * isolation:不是必须的 默认值DEFAULT ,表示事务隔离级别(数据库的隔离级别)
    * timeout:不是必须的 默认值-1(永不超时),表示事务超时的时间(以秒为单位)
    * read-only:不是必须的 默认值false不是只读的表示事务是否只读?
    * rollback-for: 不是必须的表示将被触发进行回滚的 Exception(s);以逗号分开。
    如:'com.foo.MyBusinessException,ServletException'
    * no-rollback-for:不是必须的表示不被触发进行回滚的 Exception(s),以逗号分开。         如:'com.foo.MyBusinessException,ServletException'   
    任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚     -->
    <tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
    <tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
    <tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"  rollback-for="Exception"/>
    <!-- 其他的方法之只读的 -->
    <tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
    如:'com.foo.MyBusinessException,ServletException'   
任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚     -->
<tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"  rollback-for="Exception"/>
<!-- 其他的方法之只读的 -->
<tx:method name="*" read-only="true"/>

</tx:attributes>
</tx:advice>



这篇关于JAVA面试:数据库事务的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!