一、事务在什么背景下产生的
事务的出现是为了确保数据的完整性和一致性。
二、什么是事务
作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
三、事务的特性(ACID)
原子性:一个事务是一个不可分割的工作单位。
一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。
隔离性:一个事务的执行不能被其他事务干扰。
持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
四、并发事务产生的问题
1、脏读:一 个事务读取了另外一个事务要修改但没有修改成功的数据。
例如:事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
2、不可重复读:当前事务先进行了一次数据读取,然后再次读取到的数据是别的事务修改成功的数据,导致两次读取到的数据不匹配。
例如:事务A首先读取了一条数据,然后执行逻辑的时候,事务B将这条数据改变了,然后事务A再次读取的时候,发现数据不匹配了。
3、幻读:一个事务根据相同的查询条件,重新执行查询,返回的记录中包含与前一次执行查询返回的记录不同的行。
例如:事务A首先根据条件索引得到N条数据,然后事务B改变了这N条数据之外的M条或者增添了M条符合事务A搜索条件的数据,导致事务A再次搜索发现有N+M条数据了,就产生了幻读。
这三种情况都是针对同时进行的几个事务对相同的数据进行读取时造成的。
五、事务的隔离级别(Isolation Level)
1、读未提交(Read Uncommitted):会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )。
2、读已提交(Read Committed):避免了“脏读”,会出现不可重复读、幻读问题(锁定正在读取的行)。大部分数据库默认的事务隔离级别,不包括MySql。
3、重复读(Repeatable Read):避免了“脏读”和“不可重复读”。会出现幻读(锁定所读取的所有行)。这是MySql默认的。
4、可串行化(Serializable):三种都能避免。保证所有的情况不会发生(锁表)。所有事务串行,而不是并行。
六、对应的spring中的隔离级别
1、READ_UNCOMMITTED
2、READ_COMMITTED
3、REPEATABLE_READ
4、SERIALIZABLE
七、spring中事务的传播类型(Propagation)
REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
举例说明:
serviceA{
methodA();
serviceB.methodB();
}
1、ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB就加入ServiceA.methodA的事务内部,就不再起新的事务。
在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。
即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。
ServiceA.methodA没有在事务中,这时调用ServiceB.methodB, ServiceB.methodB就会为自己分配一个事务。
2、 ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW
当调用ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务。
等待ServiceB.methodB的事务完成以后,ServiceA.methodA才继续执行。
ServiceB.methodB和ServiceA.methodA两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。
如果ServiceB.methodB失败回滚并且抛出的异常被ServiceA.methodA捕获的话,ServiceA.methodA事务仍然可能提交。
3、ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED
调用ServiceB.methodB时,ServiceA.methodA的事务挂起,而以非事务的状态运行完,再继续ServiceA.methodA的事务。
4、ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER
ServiceB.methodB会抛出异常。
总结:Spring的事务传播策略在内部方法调用时将不起作用。
换句话说,你在某个方法上声明它需要事务的时候,如果这个类还有其他开发者,你将不能保证这个方法真的会在事务环境中。
可以使用以下方式来避免这个缺陷:分开两个方法调用,即serviceA.methodA不配置事务,调用配置上事务的serviceB.methodA;方法内使用编程式事务。
八、spring管理事务的方式
1、编程式事务管理
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
2、声明式事务管理
声明式事务管理建立在AOP之上的。
其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理的方式有五种:每个bean都有一个代理;所有bean共享一个代理基类;拦截器;基于tx的拦截器;全注解。(后两种最常用,具体配置如下)。
基于tx和aop的xml配置文件
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="create*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="find*" propagation="SUPPORTS" read-only="true" />
<tx:method name="select*" propagation="SUPPORTS" read-only="true" />
<tx:method name="get*" propagation="SUPPORTS" read-only="true" />
</tx:attributes> </tx:advice>
<!-- 切面 -->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.jeenotes.ssm.service.*.*(..))" />
</aop:config>
基于@Transactional注解的
<!-- 定义事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务控制的注解支持,配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
通过以上配置后在类、或者方法上就可以使用@Transactional注解了。
@Transactional的常用属性说明:
value:可选的限定描述符,指定使用的事务管理器。
propagation:可选的事务传播行为设置。
isolation:可选的事务隔离级别设置。
readOnly:读写或只读事务,默认读写。
timeout:事务超时时间设置。