Spring中“说明式”事务管理

在Spring中,说明式事务管理被大多数 Spring使用者使用,这种方法可以使用你的逻辑代码与事务管理代码分离,达到解偶合的目的。它可以和AOP一起使用,在这里你可以自定义事务相关行为。 当然这里对回滚的理解也是很重要的,在Spring的事务管理中,只要RuntimeException子类的异常抛出,事务就会回滚。
1、对Spring中的说明式事务实现的理解
这里面的实现机制其实是通过AOP代理来实现的。当用户通过一个事务代理来调用一个方法时,首先在一个事务通知器中会创建一个事务,这个事务可能会被提交或回滚,当然在这个通知器后也可能有一些自定义的通知器对相关方法的调用进行拦截,如下图所示。
这里写图片描述

在本例中我们使用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);
}

FooService接口的实现类:

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();
    }
}

这时我们要为这个实现类中的相关方法添加事务控制,配置文件如下:

<!-- from the file '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">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <tx:advice id="txAdvice" transaction-manager="txManager">

    <tx:attributes>

    <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>
    <!-- 其他bean的定义 -->
</beans>

其中事务控制定义在<tx:advice/>元素中,上述定义指定FooService类中的所有方法名以get开头的方法在只读的事务上下文中执行。

然后可以用一个主方法对以上配置进行测试

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());
    }
}

输出结果如下:

<!-- 创建一个代理... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean
'fooService' with 0 common interceptors and 1 specific interceptors
<!-- DefaultFooService 类的实例被代理 -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... insertFoo(..)方法通过代理调用 -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- 在这里事务通知启动... -->
[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)
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

2、对指定异常进行事务回滚

Spring中,在默认情况下,RuntimeException类及子类异常的抛出会触发事务回滚,而Exception类及子类的实例异常抛出不会触发事务回滚。我们可以通过<method/>元素的rollback-forno-rollback-for属性来自定义这个特征。例如:

<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>

这里配置当相关方法抛出NoProductInStockException异常实例时,会触发事务回滚。

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

这里指定,抛出InstrumentNotFoundException异常实例不会触发事务回滚。

3、对不同的bean配置不同的事务控制

要实现这个功能,我们只需配置不同的pointcut及让<aop:advisor/>中的advice-ref属性引用不同的值即可。例如:

<?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>
    <!-- 这两个bean会被添加事务控制 .. -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>
    <!-- ... 这两个bean不会被添加事务控制  -->
    <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>

4、使用@Transactional

对一个类中的方法添加事务控制的另一种方法是使用@Transactional例如:

@Transactional
public class DefaultFooService implements FooService {
    Foo getFoo(String fooName);
    Foo getFoo(String fooName, String barName);
    void insertFoo(Foo foo);
    void updateFoo(Foo foo);
}

不过我们还要在XML配置文件中加<tx:annotation-driven/>来驱动事务控制。

!-- from the file '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">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <tx:annotation-driven transaction-manager="txManager"/><!-- 驱动事务控制-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

<tx:annotation-driven/>元素有如下属性:
A、transactionmanager:指定事务管理器
B、mode:指定处理这个bean时,亿使用的代理模式,默认值为proxy,即在调用一个类中的方法时,要触发相关事务,就必须 通过代理来调用,想要实现类中方法的相互调用也触发事务可使用织入方式,这时,就要设置mode属性值为aspectj
C、proxy-target-class:指定代理的类型(基于类代理还是基于标准JDK接口的代理),当值 为true时,创建一个基于类的代理,为false或不指定,创建一个基于标准JDK接口的代理。
D、order:指定事务通知的执行顺序。
在这里需要注意的是,方法级别的@Transactional事务设置会覆盖类级别的@Transactional
例如:

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {
    public Foo getFoo(String fooName) {

    }
    // 这里的设置是优先的
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {

    }
}

5、事务的传播性
事务的传播性可分为三种:PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEWPROPAGATION_NESTED
在这之前,我们要了解Spring中,存在那么一个“实际存在”的事务以及“逻辑上的”事务的概念。
5.1、PROPAGATION_REQUIRED特性
这个特性的事务其流程如下所示:
这里写图片描述
在这种配置中,每个方法都会存在一个逻辑上的事务作用域,这里的每个事务作用域独立定义它的回滚行为,同时存在“内部事务作用域”和“外部事务作用域”之分。由于这些作用域同时映射到同一个“真实存在的作用域”,所以“内部事务作用域”会影响“外部事务作用域”的提交行为。
5.2、PROPAGATION_REQUIRES_NEW特性
这个和PROPAGATION_REQUIRED配置相反,由于每个受影响的事务作用域都会存在一个与之对应的“真实存在的作用域”,所以外部事务并不会受内部事务回滚的影响。
执行流程图如下 :
这里写图片描述
5.3、PROPAGATION_NESTED特性
这个设置带有事务的局部回滚特性。这可以在内部事务在回滚后,外部事务仍然可以继续进行下去。

6、监听一个事务的操作

下面主要是举一个例子:定义一个切面来监听一个事务的执行持续时间。
自定义的切面JAVA代码如下 :

package x.y;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;
public class SimpleProfiler implements Ordered {
    private int order;
    // 控制通知的执行顺序
    public int getOrder() {
    return this.order;
    }
    public void setOrder(int order) {
    this.order = order;
    }
    // 这个方法是around advice
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}

然后在XML配置文件中对DefaultFooService业务对象做事务配置

<?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">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <bean id="profiler" class="x.y.SimpleProfiler">
    <!-- 在事务通知之前执行 (所以定义一个较小的值) -->
        <property name="order" value="1"/>
    </bean>
    <tx:annotation-driven transaction-manager="txManager" order="200"/>
    <aop:config>
    <!--  -->
        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
            expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>
    </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>

还有第二种配置方法适用于纯XML的事务声明配置,即相关的业务对象不用@Transactional注解,而是通过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">
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <bean id="profiler" class="x.y.SimpleProfiler">

    <property name="order" value="1"/>
    </bean>
    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
            expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        </aop:aspect>
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true"/>
    <tx:method name="*"/>
    </tx:attributes>
    </tx:advice>
</beans>

猜你喜欢

转载自blog.csdn.net/yangkaige111/article/details/80375572
今日推荐