相关文章:
- Spring参考手册 1 Spring Framework简介和典型的Web应用程序
- Spring参考手册 2 核心技术
- Spring参考手册 3 校验,数据绑定和类型转换
- Spring参考手册 4 面向切面编程
目录:
[TOC]
一、事务管理
事务主要是用来保证数据一致性的。
1.1 Spring Framework事务管理简介
综合的事务管理是吸引用户使用Spring Framework的原因之一。
Spring解决了全局和本地事务的缺点。它允许应用程序开发者在任何环境里使用一致的编程模型。Spring Framework提供了声明式和编程式事务管理。大多数用户倾向于使用声明式事务管理,这也是大多数场景下推荐的。
使用编程式事务管理,开发者需要使用到Spring Framework事务抽象,这可以运行任何底层的事务基础框架。编程式的比较复杂,这里重点翻译声明式事务管理。
1.2 声明式事务管理
Spring Framework声明式事务管理之所以能实现主要是通过Spring AOP,尽管事务切面总是被当做AOP的样板,但是使用声明式事务管理一般不需要理解AOP。
Spring Framework声明式事务管理与EJB CMT是相似的,允许你在单独方法级别指定事务行为。
1.3 声明式事务管理示例
考虑如下接口和它的实现类。DefaultFooService
的方法中抛出的UnsupportedOperationException
的异常是为了演示事务回滚。
// the service interface that we want to make transactional
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
// an implementation of the above interface
package x.y.service;
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
throw new UnsupportedOperationException();
}
public Foo getFoo(String fooName, String barName) {
throw new UnsupportedOperationException();
}
public void insertFoo(Foo foo) {
throw new UnsupportedOperationException();
}
public void updateFoo(Foo foo) {
throw new UnsupportedOperationException();
}
}
假设FooService
接口中的getFoo(String)
和getFoo(String, String)
必须运行在read-only事务内。其他的方法运行在read-write事务内。下面的配置有详细的解释:
<!-- spring主配置文件context.xml(也可以是别的名字) -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 需要事务管理的service实现类 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 事务切面 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 所有get开头的方法都是read-only -->
<tx:method name="get*" read-only="true"/>
<!-- 其他参数使用默认的设置 -->
<tx:method name=""/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 定义切点 -->
<aop:pointcut id="fooServiceOperation" expression="execution( x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
一个通用的需求是整个业务层都是需要事务管理的,最好的办法就是改变切点表达式。例如:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service..(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
这里假设你的业务层接口都在x.y.service
包下。
我们看过了配置文件,现在你可能会问自己“好吧,这些配置文件实际上做了什么?”
上面的配置文件将会被用来创建一个事务代理环绕着从fooService
bean里创建的对象。这个代理由事务通知所配置,这样当一个合适的方法被调用时,事务代理就会启动一个事务,挂起,标记为只读等等,取决于对那个方法的事务配置。
下面测试下之前创建的配置:
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
FooService fooService = (FooService) ctx.getBean("fooService");
fooService.insertFoo (new Foo());
}
}
输出的结果将会类似如下内容(Log4J从UnsupportedOperationException输出了栈的信息,为了清晰,内容已经被缩短):
<!-- Spring容器正在启动... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean fooService with 0 common interceptors and 1 specific interceptors
<!-- DefaultFooServic被代理 -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- .insertFoo(..)方法开始在代理上执行-->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- 开始事务的通知(advice) -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!-- insertFoo(..)方法抛出一个异常-->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]
<!-- 事务回滚(默认的RuntimeException的实例都会触发回滚) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource
Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- 为了简洁AOP 基础框架的栈信息被省略-->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
1.4 回滚一个声明式事务
之前的章节概述了基本的声明式事务配置,包括指定某个类,整个业务层。这章描述了如何用简单的声明式风格控制事务回滚。
要想展示事务回滚推荐的方式是在事务的方法里抛出一个异常。Spring Framework的事务框架代码会捕获任何未处理的异常并决定是否标记事务来进行回滚。
在默认的配置,事务框架只会标记那些产生运行时(runtime),未检查(unchecked)异常的事务进行回滚。当抛出的异常是RuntimeException
的实例或者子类时就会触发事务回滚。被处理的异常在默认配置下不会触发事务回滚。
你可以指定一个类型的异常,一旦产生(包括已处理的异常)就会进行回滚。下面的XML片段展示了针对已处理异常和自定义异常如何配置回滚策略:
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
你也可以指定遇到哪些异常不回滚:
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
当事务框架决定是否回滚时,任何比InstrumentNotFoundException
范围大的异常还是会回滚。
1.5 为不同的beans配置不同的事务
假设你的业务层都在x.y.service
包下。只将以Service结尾的类配置默认的事务,其他的类将不会有事务管理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<!--切点表达式-->
<aop:pointcut id="serviceOperation"
expression="execution(* x.y.service..Service.(..))"/>
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
</aop:config>
<!-- 这两个beans有事务管理 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
<!-- 这两个beans没有事务管理 -->
<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (没有在正确的包) -->
<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (不是以Service结尾) -->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 其他配置省略 -->
</beans>
上面这种方式是通过切点表达式来区分那些有事务管理,下面使用不同的切点不同的<aop:advisor>
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:config>
<aop:pointcut id="defaultServiceOperation"
expression="execution(* x.y.service..Service.(..))"/>
<aop:pointcut id="noTxServiceOperation"
expression="execution(* x.y.service.ddl.DefaultDdlManager.(..))"/>
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
</aop:config>
<!-- 有事务管理 -->
<bean id="fooService" class="x.y.service.DefaultFooService"/>
<!-- 这个也会有事务管理,但是不同配置 -->
<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
<tx:advice id="defaultTxAdvice">
<tx:attributes>
<tx:method name="get" read-only="true"/>
<tx:method name=""/>
</tx:attributes>
</tx:advice>
<tx:advice id="noTxAdvice">
<tx:attributes>
<tx:method name="" propagation="NEVER"/>
</tx:attributes>
</tx:advice>
<!-- 其他配置省略 -->
</beans>
1.6 <tx:advice/>配置
本章节总结了<tx:advice/>
的多种事务配置。默认的<tx:advice/>
设置如下:
- 传播(Propagation)设置为REQUIRED
- 隔离级别DEFAULT
- 事务是read/write
- 任何
RuntimeException
触发回滚,任何已检查的Exception
不会回滚
你可以改变这些默认设置。总结如下:
<tx:method/>设置
Attribute | Required? | Default | Description |
---|---|---|---|
name |
Yes | 事务关联的方法的名字,可以使用通配符*来批量设置,例如:get* ,on*Event
|
|
propagation |
No | REQUIRED | 事务传播行为 |
isolation |
No | DEFAULT | 事务隔离级别 |
timeout |
No | -1 | 事务超时时间(秒) |
read-only |
No | false | 事务是否是只读的? |
rollback-for |
No | 触发回滚的异常 | |
no-rollback-for |
No | 哪些异常不回滚 |
1.7 使用@Transactional
除了基于XML声明来实现事务配置,你还可以使用注解方式来实现。
使用@Transactional
是最轻松的方式:
@Transactional
@Service
public class DefaultFooService implements FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
开启注解式事务配置仅仅只需要一句话:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 启用注解式事务管理 -->
<tx:annotation-driven transaction-manager="txManager"/>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源(配置在这里省略) -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注解式配置bean已省略 -->
</beans>
注意:@Transactional
注解只能用在public方法上,protected、private 或者 package的访问修饰符都不行。
你可以放置@Transactional
注解在接口上,接口的一个方法上,一个类上或者类中一个公共方法上。但是仅仅有一个@Transactional
注解还不足以激活事务行为。@Transactional
只是简单元数据,可以被一些运行时框架使用,并且可以使用事务行为来配置这些beans。<tx:annotation-driven/>
元素真正开启事务行为。
Spring推荐你只在非抽象类和非抽象类的公共方法上放置@Transactional
注解,而不是在接口上。你当然可以在接口上或者接口方法上放置@Transactional
注解,但是它只在你是用基于接口代理时才有效。实际上Java注解没有从接口继承,这意味着如果你使用的是基于类代理(proxy-target-class="true"
)或者weaving-based切面(mode="aspectj"
)时,事务设置不会被代理识别,对象也不会被事务代理包装,这显然很不好。
注解驱动的事务设置
主要是设置<tx:annotation-driven>
这个元素的一些属性。
XML Attribute | Default | Description |
---|---|---|
transaction-manager |
transactionManager | 事务管理器的名称 |
mode |
proxy | 默认的模式"proxy"使用Spring AOP框架处理被注解的bean |
proxy-target-class |
false | 只适用于"proxy"模式,如果是true那么基于类的事务代理模式将会被创建,如果是false或者这个属性被忽略,那么标准JDK基于接口的代理将被创建 |
order |
Ordered.LOWEST_PRECEDENCE | 定义被@Transactional 注解的bean的事务通知的顺序 |
<tx:annotation-driven/>
只会在它们定义的应用程序上下文中查询@Transactional
注解的bean,这意味着如果你把这个元素放到DispatcherServlet
的上下文中,那么只会在你的controller里查询@Transactional
注解的bean,而不是你的service里。
当@Transactional
注解在方法上时比注解在类上有优先权。请看下面的例子:
@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
public Foo getFoo(String fooName) {
// do something
}
// 这里的配置优先级比类上的高
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {
// do something
}
}
1.8 配置@Transactional
默认的@Transactional
配置如下:
- 传播特性是
PROPAGATION_REQUIRED
- 隔离级别
ISOLATION_DEFAULT
- 事务是read/write
- 任何
RuntimeException
异常都会触发回滚,任何已检查的异常都不会触发回滚
@Transactional
可用参数如下:
Property | Type | Description |
---|---|---|
value |
String | 可选的修饰符,指定使用哪个事务管理器 |
propagation |
enum: Propagation
|
可选的传播特性设置 |
isolation |
enum: Isolation
|
可选的隔离级别 |
readOnly |
boolean | true为只读,false读写 |
timeout |
int(秒) | 事务超时时间 |
rollbackFor |
Class 对象的数组,必须派生自Throwable
|
数组里是需要回滚的异常对象 |
rollbackForClassName |
Class 名称的数组,必须派生自Throwable
|
数组里是需要回滚的异常对象的名称 |
noRollbackFor |
Class 对象的数组,必须派生自Throwable
|
数组里是不会回滚的异常对象 |
noRollbackForClassName |
Class 名称的数组,必须派生自Throwable
|
数组里是不会回滚的异常对象的名称 |
@Transactional与多事务管理器
大多数Spring应用程序只需要一个事务管理器,但是可能有些情况下需要配置多个独立的事务管理器。
<tx:annotation-driven/>
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="order"/>
</bean>
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
...
<qualifier value="account"/>
</bean>
public class TransactionalService {
@Transactional("order")
public void setSomething(String name) { ... }
@Transactional("account")
public void doSomething() { ... }
}
1.9 事务传播特性
不同种类的事务传播特性:
Required
当传播特性设置为PROPAGATION_REQUIRED
时,会为每个方法创建一个逻辑事务范围。每个这样的逻辑事务可以单独的决定回滚状态,外部的事务范围逻辑上独立于内部事务范围。在一个标准的PROPAGATION_REQUIRED
传播行为,所有这些范围将会被映射到同一个物理事务。这样一个内部事务产生的回滚将会影响到外部事务的提交。
但是,当一个内部事务范围设置了回滚标记,外部的事务并没有决定回滚,那么回滚将会异常。一个相应的UnexpectedRollbackException
会在那个点抛出。这是可预见的行为,这样外部事务的调用器将不会被误导认为一次提交被执行了。
RequiresNew
PROPAGATION_REQUIRES_NEW
相对于PROPAGATION_REQUIRED
,它为每个事务范围使用的是一个完全独立的事务。在这种请客,底层的物理事务是不同的因此可以独立的提交或者回滚。外部的事务不会被内部事务的回滚状态影响。
Nested
PROPAGATION_NESTED
使用一个单独的带有多个保存点的物理事务,保存点可以用来回滚。这样局部的回滚允许一个内部事务在其范围内触发回滚,外部事务仍然可以继续物理事务尽管一些操作已经被回滚。这个设置典型的被映射为JDBC保存点,所以只会在JDBC元事务下有作用。
未完,待续...
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.