事务
事务的传播机制
1、PROPAGATION_REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择。
A方法配有这个事务,B方法也配有这个事务,A方法调用B方法的时候,B方法会加入到A方法的事务当中;如果是B方法单独调用就新建一个事务
2、 PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
在方法A和方法B都有事务的情况下和required的时候运行效果是一样的;
区别在于方法B单独运行的时候,会以非事务的方式运行
3、PROPAGATION_MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
同required,在A和B方法都有事务的情况下运行效果是一样的;
在方法B单独运行的时候,如果没有事务就抛出异常,异常名为:
IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
意为:非法事务状态异常
4、PROPAGATION_REQUIRES_NEW:总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
如,方法A中调用了A1,A2,B方法,AB都有事务,
我们把A的事务称为外层事务,B的事务称为内层事务,
A执行过程中遇到了B,新起了一个内层事务,
无论两个事务互不关联,外层回滚不影响内层事务的提交,
内层回滚也影响不了A1和A2方法
5、 PROPAGATION_NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作为事务管理器。(代码示例同上,可同理推出)
这个事务传播机制就很简单了,A方法有事务就挂起,
等B方法以非事务方式执行完之后再恢复A的事务,并完成提交,
A事务回滚影响不了B方法
6、PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。
只允许非事务的情况下执行
7、PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行。这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。有一些JTA的事务管理器实现可能也提供了同样的功能。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true;
而 nestedTransactionAllowed属性值默认为false。
当B方法单独执行的时候,也是有事务就支持,
没事务就开启一个事务;
但当A方法调用B方法的时候,
在B事务执行前,会设置一个savepoint点,
一旦方法B回滚就回滚到savepoint点的状态,
如果B成功提交了,但是A后面的方法执行失败,
回滚A事务的时候会连带B事务一起回滚。
关于nested和required-new的区别:
其实required-new完全就是两个事务,这两个事务之间互不影响,并不是一个真正的嵌套事务,一旦内部事务提交了,就是真正提交了,外部事务的回滚不会引起内部事务也回滚,不依赖于外部事务;
而nested配置的事务是存在于嵌套事务中外部事务下的一个子事务,当子事务开始执行时,我们会保存一个savepoint,如果子事务执行失败,则回滚到savepoint的状态,只有外部事务提交了,子事务才会提交
事务的隔离级别
事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。
脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
不可重复读与幻读的区别
不可重复读的重点是修改:
同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
例如:在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
con1 = getConnection();
select salary from employee empId ="Mary";
在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务
con2 = getConnection();
update employee set salary = 2000;
con2.commit();
在事务1中,Mary 再次读取自己的工资时,工资变为了2000
//con1
select salary from employee empId ="Mary";
在一个事务中前后两次读取的结果并不一致,导致了不可重复读。
幻读的重点在于新增或者删除:
同样的条件, 第1次和第2次读出来的记录数不一样
例如:目前工资为1000的员工有10人。事务1,读取所有工资为1000的员工。
con1 = getConnection();
Select * from employee where salary =1000;
共读取10条记录
这时另一个事务向employee表插入了一条员工记录,工资也为1000
con2 = getConnection();
Insert into employee(empId,salary) values("Lili",1000);
con2.commit();
事务1再次读取所有工资为1000的员工
//con1
select * from employee where salary =1000;
共读取到了11条记录,这就产生了幻像读。
从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。
对于前者, 只需要锁住满足条件的记录。
对于后者, 要锁住满足条件及其相近的记录。
隔离级别
ISOLATION_DEFAULT
使用后端数据库默认的隔离级别
ISOLATION_READ_UNCOMMITTED
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
ISOLATION_READ_COMMITTED
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
ISOLATION_REPEATABLE_READ
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
ISOLATION_SERIALIZABLE
最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
只读
事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
占用资源呗
回滚规则
事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
Tips:在Java中所有不是RuntimeException派生的Exception都是检查型异常。当函数中存在抛出检查型异常的操作时该函数的函数声明中必须包含throws语句。调用改函数的函数也必须对该异常进行处理,如不进行处理则必须在调用函数上声明throws语句。
检查型异常是JAVA首创的,在编译期对异常的处理有强制性的要求。在JDK代码中大量的异常属于检查型异常,包括IOException,SQLException等等。
声明式事务
一直以来,我所使用的事务都是声明式事务,即在spring配置文件中配置了事务的属性,切面等。
编程式事务
嘿嘿,明天要好好复习咯,为了进老白的笨鸟项目,所以今天就到这里吧,关于剩下的内容以后一定会更新完毕的,再会!