Spring参考手册 5 数据访问

翻译自Spring官方文档 4.1.2版本

相关文章:

目录:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容

  • 这部分的参考文档涉及数据访问和数据访问层和业务或服务层之间的交互。 Spring的综合事务管理支持覆盖很多细节,然...
    竹天亮阅读 1,033评论 0 0
  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,773评论 6 342
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 很多人喜欢这篇文章,特此同步过来 由浅入深谈论spring事务 前言 这篇其实也要归纳到《常识》系列中,但这重点又...
    码农戏码阅读 4,722评论 2 59
  • 事务有四个特性:ACID 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作...
    jiangmo阅读 1,227评论 0 7