spring事物管理
1.数据库的隔离级别
数据库事务的隔离级别有4个,由低到高依次为Read uncommitted 、Read committed 、Repeatable read 、Serializable ,这四个级别可以逐个解决脏读 、不可重复读 、幻读这几类问题。
-
READ_UNCOMMITTED(未授权读取): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
-
READ_COMMITTED(授权读取): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
-
REPEATABLE_READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-
SERIALIZABLE(串行): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
2. 事物特性ACID -
原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
-
一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
-
隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
-
持久性(durability):持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
2 . 数据库的脏读,幻读,与不可重复读
-
脏读
脏读发生在一个事务A读取了被另一个事务B修改,但是还未提交的数据。假如B回退,则事务A读取的是无效的数据。这跟不可重复读类似,但是第二个事务不需要执行提交。 -
不可重复读
不可重复读,是指在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。
有两个策略可以防止这个问题的发生:
(1) 推迟事务2的执行,直至事务1提交或者回退。这种策略在使用锁时应用。
(2) 而在多版本并行控制中,事务2可以被先提交。而事务1,继续执行在旧版本的数据上。当事务1终于尝试提交时,数据库会检验它的结果是否和事务1、事务2顺序执行时一样。如果是,则事务1提交成功。如果不是,事务1会被回退。
- 幻读
幻读发生在当两个完全相同的查询执行时,第二次查询所返回的结果集跟第一个查询不相同。
发生的情况:没有范围锁。
幻读 与 不可重复读 区别
不可重复读: 同时操作,事务一分别读取事务二操作时和提交后的数据,读取的记录内容不一致
幻读:和可重复读类似,但是事务二的数据操作仅仅是插入和删除,不是修改数据,读取的记录数量前后不一致
例如:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入(注意时插入或者删除,不是修改))了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样。这就叫幻读。
spring事物隔离级别
(1)read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
(2)read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
(3)repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
(4)serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读
事务传播行为
事务传播行为就是多个事务方法调用时,如何定义方法间事务的传播。Spring定义了7中传播行为:
(1)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是Spring默认的选择。
(2)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
(3)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
(4)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
(5)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
(6)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
(7)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作。
在这七种传播行为中,前6种都是从EJB中引入的,和EJB中的传播行为概念相同。最后一个时Spring独有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,内部事务的提交只能通过外部的事务提交来引发而不能单独提交。另外外部事务的回滚也会导致嵌套子事务的回滚。
事务超时
一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition中以int类型定义,单位是秒。
只读
指定对事务性资源(被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等)进行只读操作或者是读写操作。boolean类型。
回滚规则
一般而言,如果在事务中抛出了未检查异常,则事务将回滚。如果没有抛出任何异常或者抛出的异常已被检查,则仍然提交事务(EJB 中的默认处理方式)。但是,我们可以根据需要人为控制事务在抛出某些未检查异常时任然提交事务,或者在抛出某些已检查异常时回滚事务(_)。
支持的策略
传统Java EE有两种策略:全局事务和局部事务。在使用Spring的事务管理策略时,可以有效的解耦,开发者在切换不同的事务策略时,无需修改代码,只需要改变相应的相应的配置。
本地事务 –基于JDBC事务管理器
他和所采用的持久化技术有关,不同的持久化技术有不同的对象来完成事务操作。
全局事务 –基于JTA事务管理器
全局事务需要服务器的底层JTA支持,从JNDI中获取数据源,然后进行全局事务管理(什么是JNDI?请戳,JNDI如何配置?请戳)。JTA的事务管理在企业应用中用的相对较多,我们再此不做深层的研究,在后续的博客中再做详细的解释和应用,请谅解!
管理方式
编程式事务管理
编程式事务管理需要显式的编写事务管理的代码,例如调用一些所使用持久化框架中提交事务或回滚事务的一些方法。Spring提供了事务管理的API,可以在应用代码中使用,而在底层,Spring仍将事务操作的技术交给对应的持久化框架来完成。
基于底层API
Spring的事务管理有三个核心接口,分别为PlatformTransactionManager,TransactionDefinition 和TransactionStatus。我们来分析以下:
... ...
public interface PlatformTransactionManager {
//获得事务
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;
//提交事务
void commit(TransactionStatus var1) throws TransactionException;
//回滚事务
void rollback(TransactionStatus var1) throws TransactionException;
}
这三个方法均和平台无关。他和所有的事务策略都分离开来,随着底层不同事务策略的切换,他会采用不同的实现类,并且无需和任何的事务性资源进行绑定。所以他可以应用与任何事务策略(在使用时可以结合Ioc来注入相关的平台特性)。采用不同配置,Spring会自动匹配相应的实现类,将实务操作任务交给对应的持久化框架来完成。
接下来上一段使用编程式事务管理的例子来感受下(编程式事务管理建议单独使用JDBC配合Spring来写一个Demo而不是在SSM框架上,我会在文末提供案例):
public class UserServiceImpl{
private IUserDao userDao;
private TransactionDefinition txDefinition;
private PlatformTransactionManager txManager;
//省略getter和setter
public int addUser(User user)
{
TransactionStatus txStatus = txManager.getTransaction(txDefinition);
//这里的result是在插入用户时所插入的数目,若为0,则插入失败
int result=0;
try {
result= userDao.insertSelective(user);
txManager.commit(txStatus);
} catch (Exception e) {
result = 0;
txManager.rollback(txStatus);
System.out.println("插入失败");
}
return result;
}
}
配置:
<bean id="userService" class="com.javafeng.service.impl_by_config.UserServiceImpl">
<property name="userDao" ref="IUserDao"/>
<property name="txManager" ref="transactionManager"/>
<property name="txDefinition">
<bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
<!--事务传播行为-->
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
如上,在代码中添加TransactionDefinition对象来定义一个事务,PlatformTransactionManager对象用于执行事务操作。如果某个方法需要进行事务管理操作,需要调用PlatformTransactionManager对象的getTransaction(…)方法启动一个事务,创建并启动事务后,进行逻辑代码的编写,然后在适当的位置添加提交事务(commit(…)方法)和回滚事务(rollback(…)方法)的代码。
基于TransactionTemplate
基于底层API的方式非常好理解,但是随着需要进行事务管理的地方越来越多,每个模块都会有类似的事务管理的代码存在,代码就会变得越来越繁杂,耦合度很高,极不利于后期维护。因此,Spring提供了一种简化的方法,即模板回调模式。上代码:
public class UserServiceImpl {
private IUserDao userDao;
private TransactionTemplate transactionTemplate;
//省略getter和setter
public int addUser(final User user)
{
//执行事务获取事务的执行结果对象
return (int) transactionTemplate.execute(new TransactionCallback(){
public Object doInTransaction(TransactionStatus status) {
Object result;
try {
result = userDao.insertSelective(user);
} catch (Exception e) {
status.setRollbackOnly();
result = false;
System.out.println("插入错误!");
}
return result;
}
});
}
}
配置:
<bean id="userService" class="com.javafeng.service.impl_by_config.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
<property name="transactionTemplate">
<bean class="org.springframework.transaction.support.TransactionTemplate">
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
</property>
</bean>
TransactionTemplate 的 execute() 方法有一个 TransactionCallback 类型的参数,该接口中定义了 doInTransaction() 方法,我们以匿名内部类(可以戳我)的方式实现 TransactionCallback 接口,然后在doInTransaction()中编写逻辑代码。采用这种形式时可以采用默认的提交和回滚规则,在逻辑代码中无需显式调用任何事务管理的API,要想回滚此事务,可以在doInTransaction()中调用其TransactionStatus 参数的setRollbackOnly()方法,设置事务为回滚的,来让事务进行回滚。
此时事务行为:
条件 | 行为 |
---|---|
显式调用TransacationStatus.setRollbackOnly() | 回滚事务 |
执行回调方法的过程中抛出了未检查异常 | 回滚事务 |
事务执行完成 | 提交事务 |
抛出了checked类型异常 | 提交事务 |
TransactionCallback 接口有一个子接口 TransactionCallbackWithoutResult,该接口中定义了一个 doInTransactionWithoutResult() 方法。对于不需要返回值的情况我们就有两种选择:
使用 TransactionCallback 接口,并在方法中返回任意值。
使用TransactionCallbackWithoutResult 接口。代码如下:
public void addUser(final User user)
{
transactionTemplate.execute(new TransactionCallbackWithoutResult(){
public void doInTransactionWithoutResult(TransactionStatus status) {
try {
userDao.insertSelective(user);
} catch (Exception e) {
status.setRollbackOnly();
System.out.println("插入错误!");
}
}
});
}
声明式事务管理
区别于编程式事务管理,它不需要在代码中显示的调用有关事务处理的代码只需在配置文件中相关的声明或者使用注解的方式来实现。
本质上它对方法前后进行拦截,使目标方法执行之前常见或加入事务,在目标方法执行后根据情况回滚或提交事务。这样就形成了一个横切逻辑,因此可以将AOP运用于此。当然Spring开发者也已经做足了相应的工作。
我们在后续的开发中,使用的正是声明式事务管理,并且推荐大家在以后开发中使用。
当然它也有缺点,那就是它进行事务管理最小的单位是方法,而不是代码块。当然这个问题可以通过将需要进行事务管理的代码提取为一个单独的方法。
基于 TransactionInter
从代码入手进行分析:
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<bean id="userServiceTarget"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<bean id="userService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userServiceTarget"/>
<property name="interceptorNames">
<list>
<idref bean="transactionInterceptor"/>
</list>
</property>
</bean>
我们来分析一下这个配置:
配置一个 TransactionInterceptor
它主要来定义相关的事务规则。它的transactionManager属性用来指定一个事务管理器并且将相关事务操作委托给它;另一个是Properties 类型的transactionAttributes 属性,用来定义事务规则,key就是方法名,可以使用通配符,值表示该方法所应用的事务属性。事务属性的取值规则如下
传播行为 [,隔离级别] [,只读属性] [,超时属性] [不影响提交的异常] [,导致回滚的异常]
1
其中隔离级别和传播行为的取值应和上面的两个表中的属性值完全相同,例如传播行为PROPAGATION_MANDATORY和隔离级别ISOLATION_DEFAULT。
如果事务是只读的,使用“readOnly”指定只读属性。否则不需要设置该属性。
超时属性的取值必须以“TIMEOUT_”开头,后面跟一个int类型的值,表示超时时间,单位是秒。
不影响提交的异常:即使事务中抛出了这些类型的异常,事务也会正常提交。须在每一个异常的名字前面加上“+”,异常的名字可以只是类名的一部分,例如“+Exception”,“+ption”。
导致回滚的异常:事务中抛出这些异常时事务回滚。须在每一个异常的名字前面加上“-”。同样,异常的名字可以只是类名的一部分,例如“-Exception”,“-ption”。
拓展一下上面的配置:
PROPAGATION_REQUIRED,readOnly,ISOLATION_READ_COMMITTED,TIMEOUT_10,+AException,-BException,-CException
1
如上表示事务管理针对所有结尾是User的方法,传播行为为PROPAGATION_REQUIRED,事务为只读,隔离级别是已提交读ISOLATION_READ_COMMITTED,超时时间10s,事务抛出AException异常时正常提交,抛出BException异常和CException时事务回滚。
2. 配置一个 ProxyFactoryBean 来组装 target 和advice。这是一个Spring的AOP行为。
缺点:配置太多。
必须为每一个目标对象配置一个 ProxyFactoryBean,导致在有很多业务类时出现大量的配置。为了解决这个问题,就出现了下面得基于 TransactionProxy的方式。
基于 TransactionProxy
Spring提供的TransactionProxyFactoryBean将TransactionInterceptor 和 ProxyFactoryBean 的配置合二为一。来看一下配置:
PROPAGATION_REQUIRED
基于 命名空间
在前两种方式的基础上,Spring 2.x 中开始引入 命名空间,配合支持切点表达式的 命名空间,实现了基于 命名空间的声明式事务管理。同样分析配置:
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource" />
</bean>
<!--业务逻辑Bean-->
<bean id="userService"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<!--事务增强-->
<tx:advice id="userAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="user" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="userPointcut" expression="execution(* *.user(..))"/>
<aop:advisor advice-ref="userAdvice" pointcut-ref="user Pointcut"/>
</aop:config>
若使用默认的事务属性,可简化为:
<!--此处我们使用JDBC数据源的局部事务,注意需要依赖注入DataSource-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入dataSource-->
<property name="dataSource" ref="dataSource" />
</bean>
<!--业务逻辑Bean-->
<bean id="userService"
class="com.javafeng.service.impl.UserServiceImpl">
<property name="userDao" ref="UserDao"/>
</bean>
<!--事务增强-->
<tx:advice id="userAdvice" transaction-manager="transactionManager" />
<aop:config>
<aop:pointcut id="userPointcut" expression="execution(**.User(..))"/>
<aop:advisor advice-ref="userAdvice" pointcut-ref="userPointcut"/>
</aop:config>
<tx:advice />用来配置事务增强管理,其中的<tx:attributes />元素用来指定事务的属性,在<tx:attributes />内可以定义多个子元素来批量的为不同的方法指定不同的事务属性,支持通配符的使用。
此处使用了命名空间和切点表达式。不再需要每一个业务类都创建一个代理对象。
<aop:advisor >可以将所配置的事务增强和切入点(通过pointcut-ref指定已有切入点或通过aop:pointcut来指定切点表达式)绑定,从而将所配置的事务增强织入到相应的切入点。
如果配置的事务管理器 Bean 的名字取值为“transactionManager”,则可以省略 tx:advice 的 transaction-manager 属性,因为该属性的默认值即为“transactionManager”。
使用这种声明式事务管理时,还可以定义多个tx:advice和多个切入点,来分别为不同的业务逻辑方法指定不同的事务属性。
属性,其中某些属性与上节的分析类似,可进行参考:
属性 | 作用 |
---|---|
name | 需要使用事务管理的方法名。支持通配符。如“list*”。 |
isolation | 指定事务隔离级别。默认isolation.DEFAULT |
propagation | 指定事务传播行为。默认propagation.REQUIRED |
timeout | 指定事务超时时间。单位秒,默认值-1(代表不超时)C |
read-only | 指定事务是否为只读。默认false |
rollback-for | 指定引起事务回滚的异常。使用全限定类名,可指定多个并用“,”隔开(半角逗号)。 |
no-rollback-for | 指定不引起事务回滚的异常。使用全限定类名,可指定多个并用“,”隔开(半角逗号)。 |
基于 @Transactional
基于 @Transactional的方式是最简单,最容易上手的一种事务实现方式,它也是Spring 2.x引入的基于注解(Annotation)的方式。他的作用范围为接口,接口方法,类,类方法。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,如果再方法级别使用该注解会覆盖类级别的事务属性定义。看一下配置和代码:
@Transactional(propagation = Propagation.REQUIRED)
public void insertSelective(User user) {
userDao.insertSelective(user);
}
配置(启动事务注解扫描):
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
与上相似,transaction-manager 属性的默认值是 transactionManager,如果事务管理器 Bean 的名字为该值,则可以省略该属性。可以通过设置@Transactional的属性来赋予相应的事务处理属性。
@Transactional具体属性如下:
属性 | 作用 |
---|---|
isolation | 指定事务隔离级别。默认为底层持久化框架的事务隔离级别 |
propagation | 指定事务传播行为 |
readOnly | 指定事务是否为只读 |
timeout | 指定事务超时时间 |
noRollbackFor | 指定抛出或捕获特定的异常时强制不回滚事务 |
noRollbackForClassName | 指定抛出或捕获多个特定的异常时强制不回滚事务。可以指定多个异常类名 |
RollbackFor | 指定抛出或捕获特定的异常时强制回滚事务 |
RollbackForClassName | 指定抛出或捕获特定的异常时强制回滚事务。可以指定多个异常类名 |
@Transactional 注解用于接口或接口方法时,只有在使用基于接口的代理时它才会生效(有实现类)。
@Transactional 注解应该只能被应用到 public 方法上。这是由AOP本质决定的。如果在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,都将被忽略,而且不会抛出任何异常。