请看下面的接口和它的实现。这个例子的意图是介绍概念,使用 Foo
和 Bar
这样的名字只是为了让你关注于事务的用法,而不是领域模型。
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);
}
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();
}
}
(对该例的目的来说,上例中实现类(DefaultFooService
)的每个方法在其方法体中抛出 UnsupportedOperationException
的做法是恰当的,我们可以看到,事务被创建出来,响应 UnsupportedOperationException
的抛出,然后回滚。)
我们假定,FooService
的前两个方法(getFoo(String)
和getFoo(String, String)
)必须执行在只读事务上下文中,其余方法(insertFoo(Foo)
和updateFoo(Foo)
)必须执行在读写事务上下文中。
使用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-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd" >
<!-- 这是我们将要配置并使它具有事务性的Service对象 -->
< bean id ="fooService" class ="x.y.service.DefaultFooService" />
<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below) -->
< tx:advice id ="txAdvice" transaction-manager ="txManager" >
<!-- the transactional semantics... -->
< tx:attributes >
<!-- all methods starting with 'get' are read-only -->
< tx:method name ="get*" read-only ="true" />
<!-- other methods use the default transaction settings (see below) -->
< tx:method name ="*" />
</ tx:attributes >
</ tx:advice >
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
< aop:config >
< aop:pointcut id ="fooServiceOperation" expression ="execution(* x.y.service.FooService.*(..))" />
< aop:advisor advice-ref ="txAdvice" pointcut-ref ="fooServiceOperation" />
</ aop:config >
<!-- don't forget the DataSource -->
< 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 >
<!-- similarly, don't forget the (particular) PlatformTransactionManager -->
< bean id ="txManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
< property name ="dataSource" ref ="dataSource" />
</ bean >
<!-- other <bean/> definitions here -->
</ beans >
我们来分析一下上面的配置。我们要把一个服务对象('fooService'
bean)做成事务性的。我们想施加的事务语义封装在<tx:advice/>
定义中。<tx:advice/>
“把所有以 'get'
开头的方法看做执行在只读事务上下文中,其余的方法执行在默认语义的事务上下文中”。 其中的 'transaction-manager'
属性被设置为一个指向 PlatformTransactionManager
bean的名字(这里指 'txManager'
),该bean将实际上实施事务管理。
提示
事实上,如果 PlatformTransactionManager
bean的名字是 'transactionManager'
的话,你的事务通知(<tx:advice/>
)中的 'transaction-manager'
属性可以忽略。否则你则需要像上例那样明确指定。
配置中最后一段是 <aop:config/>
的定义,它确保由 'txAdvice'
bean定义的事务通知在应用中合适的点被执行。首先我们定义了 一个切面,它匹配 FooService
接口定义的所有操作,我们把该切面叫做 'fooServiceOperation'
。然后我们用一个通知器(advisor)把这个切面与 'txAdvice'
绑定在一起,表示当 'fooServiceOperation'
执行时,'txAdvice'
定义的通知逻辑将被执行。
一个普遍性的需求是让整个服务层成为事务性的。满足该需求的最好方式是让切面表达式匹配服务层的所有操作方法。例如:
< aop:pointcut id ="fooServiceMethods" expression ="execution(* x.y.service.*.*(..))" />
< aop:advisor advice-ref ="txAdvice" pointcut-ref ="fooServiceMethods" />
</ aop:config >
现在,既然我们已经分析了整个配置,你可能会问了,“好吧,但是所有这些配置做了什么?”。
上面的配置将为由 'fooService'
定义的bean创建一个代理对象,这个代理对象被装配了事务通知,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。
我们来看看下面的例子,测试一下上面的配置。
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的消息和从 DefaultFooService
的 insertFoo(..)
方法抛出的 UnsupportedOperationException
异常堆栈信息被省略了)。
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy
for bean ' fooService ' with 0 common interceptors and 1 specific interceptors
<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection
[org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[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]
<!-- and the transaction is rolled back (by default , RuntimeException instances cause rollback) -->
[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 infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java: 11 )