以往我们需要事务控制的时候,通常需要引入笨重的EJB,Spring的出现改变了这种状况,我们可以用Spring的轻量级容器来管理事务。Spring对事务的控制有几种方式:
1.编码的方式
...{
public void enrollStudentInCourse()
...{
transactionTemplate.execute(
new TransactionCallback()
...{
public Object doInTransaction(TransactionStatus ts)
...{
try...{
// do staff
}catch(Exception e)...{
ts.setRollbackOnly();
}
return null;//如果成功,事务被提交
}
}
);
}
}
transactionTemplate实例是从那里来的呢?他将被注入到CourseServiceImpl,代码如下:
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
</bean>
<bean id="courseService" class="com.springinaction.training.service.CourseServiceImpl">
<property name="transactionTemplate">
<ref bean="transactionTemplate"/>
</property>
</bean>
通常,你的事务的需求并没有要求在事务的边界上进行如此精确的控制,这就是我们一般选择在应用代码之外声明事务的原因。
2.声名的方式
在Spring里,事务属性是对事务策略如何应用到方法的描述,这个描述包括下列一个或多个参数:
l 传播行为
l 隔离级别
l 只读提示
l 事务超时间隔
事务的传播行为主要包含以下几个:
Ø PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
Ø PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
Ø PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
Ø PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
Ø PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
Ø PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
关于事务的传播行为,具体可以参照http://fhjxp.javaeye.com/blog/124978,写的比较详细。
关于事务的隔离级别:
Isolation Level(事务隔离等级):
1、Serializable:最严格的级别,事务串行执行,资源消耗最大;
2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
4、Read Uncommitted:保证了读取过程中不会读取到非法数据。
隔离级别在于处理多事务的并发问题。
我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,这需要查看数据库教材的可串行化条件判断了。
这里就不阐述。
我们首先说并发中可能发生的3中不讨人喜欢的事情
1: Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。
2: non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。
3: phantom reads--幻象读数据,这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。
Dirty reads non-repeatable reads phantom reads
Serializable 不会 不会 不会
REPEATABLE READ 不会 不会 会
READ COMMITTED 不会 会 会
Read Uncommitted 会 会 会
readOnly:
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。
Timeout :
在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。
以上所阐述的为基础部分,下面我们看看Spring声明事务的策略有那几种!
l TransactionProxyFactoryBean参照一个方法的事务属性,决定如何在那个方法上执行事务策略,但是TransactionProxyFactoryBean从哪里得到一个方法的事务特性呢?TransactionProxyFactoryBean有一个transactionAttributeSource属性,这个属性被设置成一个transactionAttributeSource的实例,transactionAttributeSource是作为在方法上查找事务属性的一个参考。
<bean id="transactionAttributeSource" class="org.springframework.transaction.inteceptor.MatchAlwaysTransactionAttributeSource">
</bean>
MatchAlwaysTransactionAttributeSource可能是最简单的TransactionAttributeSource的实现,它每一次调用它的getTransactionAttribute()方法被调用过,它总是简单的返回相同的TransactionAttribtue,而不管这个事务包含了什么方法(PROPAGATION_REQUIRED和ISOLATION_DEFAULT)。那就是MatchAlwaysTransactionAttributeSource永远匹配在起作用;同时你也可以改变默认的事务属性:
<bean id="myTransactionAttribute" class="org.springframework.transaction.interceptor.DefaultTransactionAttribute">
<property name="propagationBehaviorName">
<value>PROPAGATION_REQUIRED_NEW</value>
</property>
<property name="isolationLevelName">
<value>ISOLATION_REPEATABLE_READ</value>
</property>
</bean>
<bean id="transactionAttributeSource" class="org.springframework.transaction.inteceptor.MatchAlwaysTransactionAttributeSource">
<property name="transactionAttribute">
<ref bean="myTransactionAttribute"/>
</property>
</bean>
l 通过方法名声名事务
类似与EJB中的CMT,也就是CMT等价物。
<bean id="transactionAttributeSource" class="org.springframework.transaction.inteceptor.NameMatchTransactionAttributeSource">
<properties name="properties">
<props>
<prop key="get*">
PROPAGATION_REQUIRED_NEW
</prop>
</props>
</properties>
</bean>
3. 用元数据声明事务(少用)
实际在项目中用到的配置为:
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED,-JbssAppException</prop>
<prop key="remove*">PROPAGATION_REQUIRED,-JbssAppException</prop>
<prop key="update*">PROPAGATION_REQUIRED,-JbssAppException</prop>
<prop key="create*">PROPAGATION_REQUIRED,-JbssAppException</prop>
<prop key="delete*">PROPAGATION_REQUIRED,-JbssAppException</prop>
<prop key="add*">PROPAGATION_REQUIRED,-JbssAppException</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly,-JbssAppException</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly,-JbssAppException</prop>
</props>
</property>
</bean>
<bean id="manager" parent="txProxyTemplate">
<property name="target">
<bean class="com.jbss.service.base.impl.BaseManagerImpl">
<property name="dao" ref="dao" />
</bean>
</property>
</bean>
<bean id="log" class="com.jbss.util.LogAdvice"/>
<bean id="txProxyTemplate" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<list>
<value>log</value>
<value>transactionInterceptor</value>
</list>
</property>
</bean>