spring5.1.3使用篇-数据访问

spring的事务管理支持及集成的各种数据库访问框架和技术

资源来自官网:
https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/data-access.html#spring-data-tier

1. Transaction Management

spring事务支持模型的优点:

  • 跨不同事务API的一致编程模型,例如Java Transaction API(JTA),JDBC,Hibernate和Java Persistence API(JPA)【JPA是比较流行的orm框架之一】
  • 支持声明式事务管理
  • 比复杂事务API(如JTA)更简单的编程事务管理API
  • 与Spring的数据访问抽象完美集成

1.1. Advantages of the Spring Framework’s Transaction Support Model

传统java ee开发有两种事务管理可选:全局或本地事务;这两种事务管理都存在局限性

1.1.1. Global Transactions

全局事务:您可以使用多个事务资源,通常是关系数据库和消息队列。应用程序服务器通过JTA(java Transaction API)管理全局事务,这是一个繁琐的API(部分原因是它的异常模型)。此外,JTA 用户事务通常需要从JNDI获取,这意味着您还需要使用JNDI才能使用JTA。全局事务的使用限制了应用程序代码的任何潜在重用,因为JTA通常仅在应用程序服务器环境中可用。

以前,使用全局事务的首选方法是通过EJB CMT(容器管理事务)。CMT是一种声明式事务管理(与程序化事务管理不同)。EJB CMT消除了与事务相关的JNDI查找的需要,尽管使用EJB本身需要使用JNDI。它消除了编写Java代码以控制事务的大部分但不是全部的需要。重要的缺点是CMT与JTA和应用服务器环境相关联。此外,仅当选择在EJB中(或至少在事务EJB外观之后)实现业务逻辑时,它才可用。一般来说,EJB的负面影响是如此之大,以至于这不是一个有吸引力的主张,特别是在面对声明式事务管理的令人信服的替代方案时。

1.1.2. Local Transactions

本地事务:是特定于资源的,例如与JDBC连接关联的事务。本地事务可能更容易使用,但具有明显的缺点:它们无法跨多个事务资源工作(大部分应用都是使用单事务处理)。另一个缺点是本地事务对编程模型是侵入性的

1.1.3. Spring Framework’s Consistent Programming Model

spring的一致性编程模型:Spring解决了全局和本地事务的缺点。它允许应用程序开发人员在任何环境中使用一致的编程模型 您编写一次代码,它可以从不同环境中的不同事务管理策略中受益。Spring Framework提供了声明式和编程式事务管理。大多数用户更喜欢声明式事务管理,我们建议在大多数情况下使用。

通过编程事务管理,开发人员可以使用Spring Framework事务抽象,它可以在任何底层事务基础结构上运行。使用首选的声明性模型,开发人员通常很少编写或不编写与事务管理相关的代码,因此不依赖于Spring Framework事务API或任何其他事务API。

通常,只有当应用程序需要处理跨多个资源的事务时,才需要应用程序服务器的JTA功能,这对许多应用程序来说并不是必需。

您是否需要应用程序服务器进行事务管理?
Spring Framework的事务管理支持改变了关于企业Java应用程序何时需要应用程序服务器的传统规则。

特别是,您不需要纯粹用于通过EJB进行声明式事务的应用程序服务器。实际上,即使您的应用程序服务器具有强大的JTA功能,您也可以决定Spring Framework的声明性事务提供比EJB CMT更强大的功能和更高效的编程模型。

通常,只有当应用程序需要处理跨多个资源的事务时,才需要应用程序服务器的JTA功能,这对许多应用程序来说并不是必需的。许多高端应用程序使用单个高度可伸缩的数据库(例如Oracle RAC)。独立的事务管理器(例如 Atomikos Transactions和JOTM)是其他选项。当然,您可能需要其他应用程序服务器功能,例如Java消息服务(JMS)和Java EE连接器体系结构(JCA)Java EE Connector Architecture 。

Spring Framework使您可以选择何时将应用程序扩展到完全加载的应用程序服务器。使用EJB CMT或JTA的唯一替代方法是使用本地事务(例如JDBC连接上的代码)编写代码,并且如果您需要在全局容器管理的事务中运行代码,则会面临大量的返工。使用Spring Framework,只需要更改配置文件中的一些bean定义(而不是代码)。

1.2. Understanding the Spring Framework Transaction Abstraction

Spring事务抽象的关键是事务策略的概念,事务策略由org.springframework.transaction.PlatformTransactionManager接口定义

这主要是一个服务提供者接口(SPI),尽管您可以从应用程序代码中以编程方式使用它。因为PlatformTransactionManager是一个接口,所以可以根据需要轻松地对其进行模拟或存根处理。它与查找策略(如JNDI)无关。PlatformTransactionManager实现的定义与SpringFrameworkIOC容器中的任何其他对象(或bean)类似。仅此好处就使得Spring框架事务成为一个有价值的抽象,即使在您使用JTA时也是如此。与直接使用JTA相比,您可以更容易地测试事务代码。

同样,根据Spring的理念,可以由任何PlatformTransactionManager接口的方法抛出的TransactionException未选中(即,它扩展了java.lang.RuntimeException类)。事务基础结构故障几乎总是致命的。在应用程序代码可以从事务失败中实际恢复的罕见情况下,应用程序开发人员仍然可以选择捕获和处理TransactionException。最突出的一点是,开发人员不必被迫这样做。

getTransaction(…)方法根据TransactionDefinition参数返回TransactionStatus对象。如果当前调用堆栈中存在匹配的事务,则返回的TransactionStatus可能表示新事务,也可能表示现有事务。后一种情况的含义是,与JavaEE事务上下文一样,事务状态与执行线程相关联。

TransactionDefinition接口特性:

  • 传播Propagation:通常,在事务范围内执行的所有代码都在该事务中运行。但是,如果事务上下文已经存在,则执行事务方法,则可以指定行为。例如,代码可以在现有事务(常见情况)中继续运行,或者可以挂起现有事务并创建新事务。Spring提供了EJB CMT中熟悉的所有事务传播选项。
  • 隔离Isolation:此事务与其他事务的工作隔离的程度。
  • 超时Timeout:在超时并被基础事务基础结构自动回滚之前,该事务运行多长时间。
  • 只读状态Read-only status:您可以在代码读取时使用只读事务,但不能修改数据。在某些情况下,只读事务可能是一种有用的优化,例如当您使用Hibernate时。

TransactionStatus接口为事务代码提供了一种简单的方法来控制事务执行和查询事务状态

无论您是在Spring中选择声明式还是程序化事务管理,定义正确的PlatformTransactionManager实现都是绝对必要的。您通常通过依赖注入来定义此实现。

PlatformTransactionManager实现通常需要了解它们工作的环境:JDBC,JTA,Hibernate等
以下示例显示了如何定义本地PlatformTransactionManager实现(在本例中,使用纯JDBC)

您可以通过DataSource创建类似于以下内容的bean 来定义JDBC :

//定义jdbc
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
bean>

//然后,相关的PlatformTransactionManager bean定义具有对DataSource定义的引用
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
bean>

//如果在Java EE容器中使用JTA,则使用DataSource通过JNDI获得的容器和Spring的容器JtaTransactionManager。以下示例显示了JTA和JNDI查找版本的接口


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        http://www.springframework.org/schema/jee/spring-jee.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

    

beans>

JtaTransactionManager并不需要了解DataSource(或任何其他特定资源),因为它使用容器的全局事务管理。

您还可以轻松使用Hibernate本地事务,如以下示例所示。在这种情况下,您需要定义一个Hibernate LocalSessionFactoryBean,您的应用程序代码可以使用它来获取Hibernate Session实例

如果DataSource(由任何非JTA事务管理器使用)通过JNDI查找并由Java EE容器管理,则它应该是非事务性的,因为Spring
Framework(而不是Java EE容器)管理事务。

Hibernate示例:以下示例声明sessionFactory和txManagerbean

//datesource使用前面jdbc的
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xmlvalue>
        list>
    property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        value>
    property>
bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
bean>

//如果使用Hibernate和Java EE容器管理的JTA事务,则应使用与JtaTransactionManager
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

如果您使用JTA,那么无论您使用何种数据访问技术,无论是JDBC,Hibernate JPA还是任何其他支持的技术,您的事务管理器定义都应该看起来相同。这是因为JTA事务是全局事务,可以登记任何事务资源。

在所有这些情况下,应用程序代码不需要更改。您可以仅通过更改配置来更改事务的管理方式,即使该更改意味着从本地事务转移到全局事务,反之亦然。

1.3. Synchronizing Resources with Transactions

资源与事务同步

如何创建不同的事务管理器,以及它们如何链接到需要同步到事务的相关资源(例如,DataSourceTransactionManager到JDBC数据源,HibernateTransactionManager到Hibernate会话工厂,等等),现在应该清楚了。本节描述应用程序代码(通过使用JDBC、Hibernate或JPA等持久性API直接或间接)如何确保正确创建、重用和清理这些资源。本节还讨论了如何通过相关的PlatformTransactionManager(可选)触发事务同步。

1.3.1. High-level Synchronization Approach

高级同步方法

首选方法是使用Spring最高级别的基于模板的持久性集成API,或者将本机ORM API与事务感知工厂bean或代理一起使用,以管理本机资源工厂。这些事务感知解决方案在内部处理资源创建和重用,清理,资源的可选事务同步以及异常映射。因此,用户数据访问代码不必解决这些任务,而是可以完全专注于非样板持久性逻辑。通常,您使用本机ORM API或使用模板方法进行JDBC访问JdbcTemplate。这些解决方案将在本参考文档的后续章节中详细介绍。

1.3.2. Low-level Synchronization Approach

低级同步方法

诸如DataSourceUtils(对于JDBC),EntityManagerFactoryUtils(对于JPA), SessionFactoryUtils(对于Hibernate)等的类存在于较低级别。当您希望应用程序代码直接处理本机持久性API的资源类型时,您可以使用这些类来确保获得正确的Spring Framework托管实例,(可选)同步事务,并且在此过程中发生的异常是正确映射到一致的API。

比如:在spring中使用jdbc,可以使用spring提供的类获取连接
org.springframework.jdbc.datasource.DataSourceUtils

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有事务已经有一个与之同步(链接)的连接,则返回该实例。否则,方法调用将触发新连接的创建,该连接(可选)与任何现有事务同步,并可在同一事务中供后续重用。如前所述,任何SQLException都包装在Spring框架canNotGetJDBCConnectionException中,这是Spring框架中未检查的DataAccessException类型的层次结构之一。这种方法提供的信息比从sqlException中轻松获得的信息更多,并确保跨数据库甚至跨不同的持久性技术的可移植性。

这种方法在没有Spring事务管理(事务同步是可选的)的情况下也可以工作,因此无论您是否使用Spring进行事务管理,都可以使用它。

当然,一旦您使用了Spring的JDBC支持,JPA支持或Hibernate支持,您通常不喜欢使用DataSourceUtils或其他帮助程序类,因为您通过Spring抽象工作比直接使用相关API更快乐。例如,如果您使用Spring JdbcTemplate或 jdbc.object包来简化JDBC的使用,则在幕后进行正确的连接检索,您无需编写任何特殊代码。

1.3.3. TransactionAwareDataSourceProxy

在最底层存在TransactionAwareDataSourceProxy 类。这是目标数据源的代理,它包装目标数据源以添加对Spring管理事务的感知。在这方面,它类似于由JavaEE服务器提供的事务性JNDI数据源。

除了必须调用现有代码并传递标准JDBC数据源接口实现时,您几乎不需要或不想使用此类。在这种情况下,此代码可能是可用的,但它参与了Spring管理的事务。您可以使用前面提到的高级抽象来编写新代码。

1.4. Declarative transaction management

大多数Spring Framework用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此与非侵入式轻量级容器的理想最为一致。

使用Spring面向方面编程(AOP),Spring Framework的声明式事务管理成为可能。但是,由于事务方面代码随Spring Framework发行版一起提供并且可能以样板方式使用,因此通常不必理解AOP概念以有效使用此代码。

Spring Framework的声明式事务管理类似于EJB CMT,因为您可以将事务行为(或缺少它)指定为单个方法级别。setRollbackOnly()如有必要,您可以在事务上下文中进行调用。两种类型的交易管理之间的区别是:

  • 与绑定到JTA的EJB CMT不同,Spring Framework的声明式事务管理适用于任何环境。它可以通过调整配置文件使用JDBC,JPA或Hibernate来处理JTA事务或本地事务。
  • 您可以将Spring Framework声明式事务管理应用于任何类,而不仅仅是EJB等特殊类。
  • Spring Framework提供了声明性 回滚规则,这是一个没有EJB等价的功能。提供了对回滚规则的编程和声明性支持。
  • Spring Framework允许您使用AOP自定义事务行为。例如,您可以在事务回滚的情况下插入自定义行为。您还可以添加任意建议以及事务性建议。使用EJB CMT,您无法影响容器的事务管理,除非 setRollbackOnly()。
  • Spring框架不支持跨远程调用传播事务上下文,就像高端应用程序服务器那样。如果您需要此功能,我们建议您使用EJB。但是,在使用此类功能之前请仔细考虑,因为通常情况下,人们不希望事务跨越远程调用。

Spring 2.0及更高版本中的声明式事务配置与以前的Spring版本有很大不同。主要区别在于不再需要配置TransactionProxyFactoryBeanbean。

Spring 2.0之前的配置样式仍然是100%有效的配置。将新视为TransactionProxyFactoryBean代表您的定义bean。

回滚规则的概念很重要。它们允许您指定哪些异常(和throwables)应该导致自动回滚。您可以在配置中以声明方式指定它,而不是在Java代码中。因此,尽管仍可以对TransactionStatus对象调用setRollbackOnly()以回滚当前事务,但通常可以指定一个规则,即MyApplicationException必须始终导致回滚。此选项的显著优势是业务对象不依赖于事务基础结构。例如,它们通常不需要导入Spring事务API或其他Spring API。

虽然EJB容器默认行为会自动回滚系统异常(通常是运行时异常)上的事务,但EJB CMT不会在应用程序异常(即除了之外的已检查异常java.rmi.RemoteException)上自动回滚事务。虽然声明式事务管理的Spring默认行为遵循EJB约定(回滚仅在未经检查的异常时自动进行),但定制此行为通常很有用。

1.4.1. Understanding the Spring Framework’s Declarative Transaction ### Implementation

关于Spring框架的声明性事务支持,需要掌握的最重要的概念是,通过AOP代理启用此支持,并且事务性建议由元数据(当前基于XML或注释)驱动。AOP与事务元数据的组合生成一个AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来围绕方法调用驱动事务。

以下图像显示了在事务代理上调用方法的概念视图:
spring5.1.3使用篇-数据访问_第1张图片

1.4.2. Example of Declarative Transaction Implementation

考虑下面的接口及其助理实现。这个例子使用foo和bar类作为占位符,这样您就可以集中精力处理事务的使用,而不必关注特定的域模型。就本例而言,DefaultFooService类在每个实现方法的主体中抛出UnsupportedOperationException实例这一事实是很好的。该行为允许您看到创建事务,然后回滚以响应UnsupportedOperationException实例。下表显示了fooservice接口:

// 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);

}

//以下示例显示了上述接口的实现:
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)必须在具有只读语义的事务上下文中执行,而其他方法insertfoo(foo)和updatefoo(foo)必须在具有读写语义的事务上下文中执行。以下几段将详细解释以下配置:



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

    

beans>

检查前面的配置。它假定您要使服务对象fooservice bean成为事务性的。要应用的事务语义封装在定义中。定义读取为“从get开始的所有方法都将在只读事务的上下文中执行,而所有其他方法都将使用默认事务语义执行”。标记的transaction manager属性被设置为要驱动事务的platformTransactionManager bean的名称(在本例中是txManager bean)。

如果要连接的PlatformTransactionManager的bean名称具有transactionManager名称,则可以省略事务通知()中的transactionManager属性。如果要连接的PlatformTransactionManager bean具有任何其他名称,则必须显式使用事务管理器属性,如前面的示例所示。

定义确保由txadvice bean定义的事务通知在程序中的适当点执行。首先,定义一个切点,该切点与fooservice接口(fooservice operation)中定义的任何操作的执行相匹配。然后使用顾问(advisor)将切入点与txadvice关联起来。结果表明,在执行fooServiceOperation时,将运行由txadvice定义的通知(advice)。

一个常见的要求是使整个服务层具有事务性。实现这一点的最佳方法是更改切入点表达式以匹配服务层中的任何操作。以下示例显示了如何执行此操作:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
aop:config>

前面显示的配置用于围绕从FooServiceBean定义创建的对象创建事务代理。代理被配置为具有事务性建议,以便在代理上调用适当的方法时,根据与该方法关联的事务配置,启动、挂起、标记为只读等事务。考虑测试驱动前面所示配置的以下程序:

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

运行上述程序的输出应类似于以下内容(为了清晰起见,已截断了DefaultFooService类的InsertFoo(…)方法引发的UnsupportedOperationException异常的log4j输出和堆栈跟踪):


[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors


[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]


[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


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


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

1.4.3. Rolling Back a Declarative Transaction

本节介绍如何以简单的声明方式控制事务回滚。

向Spring框架的事务基础结构指示事务的工作将回滚的建议方法是从当前在事务上下文中执行的代码中抛出异常。Spring框架的事务基础结构代码捕获任何未处理的异常,因为它使调用堆栈冒泡,并决定是否将事务标记为回滚。

在其默认配置中,Spring框架的事务基础结构代码仅在运行时未选中的异常情况下标记回滚事务。也就是说,当抛出的异常是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>

当Spring框架的事务基础结构捕获异常,并参考配置的回滚规则来确定是否将事务标记为回滚时,最强的匹配规则获胜。因此,在以下配置的情况下,除了InstrumentNotFoundException之外的任何异常都会导致助理事务回滚:

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

您还可以以编程方式指示所需的回滚。虽然很简单,但这个过程非常具有侵入性,并且会将您的代码紧密地耦合到Spring Framework的事务基础结构中。以下示例显示如何以编程方式指示所需的回滚:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

1.4.4. Configuring Different Transactional Semantics for Different ### Beans

为不同的bean配置不同的语义:
考虑具有多个服务层对象的情况,并且您希望对每个对象应用完全不同的事务配置。您可以通过定义具有不同pointcut和 advice-ref属性值的不同元素来实现。

作为比较点,首先假设所有服务层类都定义在根x.y.service包中。要使该包(或子包)中定义的类实例以及名称以服务结束的所有bean都具有默认事务配置,可以编写以下内容:


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

    
    <bean id="anotherService" class="org.xyz.SomeService"/> 
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> 

    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    

beans>

以下示例显示如何使用完全不同的事务设置配置两个不同的bean:


<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.4.5. Settings

本节总结了您可以使用标记指定的各种事务设置
默认设置为:

  • propagation setting is REQUIRED
  • 隔离级别是 DEFAULT
  • 事务默认为读写
  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则默认为none
  • 任何RuntimeException触发器都会回滚,而任何选中的异常都不会

表1. 设置

Attribute Required? Default Description
name Yes 与事务属性关联的方法名称。通配符()字符可以被用于相同的事务相关联的属性的设置有许多方法(例如,get,handle*,on*Event,等等)。
propagation No REQUIRED 事务传播行为
isolation No DEFAULT 事务隔离级别。仅适用于REQUIRED或的传播设置REQUIRES_NEW
timeout No -1 事务超时(秒)。仅适用于传播REQUIRED或REQUIRES_NEW
read-only No false 读写与只读事务。仅适用于REQUIRED或REQUIRES_NEW
rollback-for No 以逗号分隔的Exception触发回滚的实例列表。例如, com.foo.MyBusinessException,ServletException
no-rollback-for No 以逗号分隔的Exception不触发回滚的实例列表。例如, com.foo.MyBusinessException,ServletException.

1.4.6. Using @Transactional

基于注解的事务声明

// the service class that we want to make 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);
}

如上所述,注释用于类级别,指示声明类(及其子类)的所有方法的默认值。或者,每个方法都可以单独进行注释。注意,类级注释不适用于类层次结构上的祖先类;在这种情况下,需要在本地重新声明方法,以便参与子类级注释。

当POJO类(如上面的类)在Spring上下文中被定义为bean时,可以通过@configuration类中的@enableTransactionManagement注释使bean实例成为事务性的。

在XML配置中,标记提供了类似的便利:



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

如果要连接的PlatformTransactionManager的bean名称具有名称transactionManager,则可以省略标记中的transactionManager属性。如果要依赖项注入的PlatformTransactionManager bean有任何其他名称,则必须使用事务管理器属性,如前面的示例所示。

方法可见性和@Transactional
使用代理时,应仅将@transactional注释应用于具有公共可见性的方法。如果使用@transactional注释对受保护、私有或包可见的方法进行了注释,则不会引发任何错误,但带注释的方法不会显示配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ(稍后描述)。

可以将@transactional注释应用于接口定义、接口上的方法、类定义或类上的公共方法。但是,仅仅存在@transactional注释还不足以激活事务行为。@transactional annotation只是一些运行时基础结构可以使用的元数据,这些运行时基础结构具有@transactional意识,并且可以使用元数据配置具有事务行为的适当bean。在前面的示例中,元素打开事务行为。

Spring团队建议您只使用@Transactional注释来注释具体的类(和具体类的方法),而不是注释接口。当然,您可以将@transactional注释放在接口(或接口方法)上,但这仅在使用基于接口的代理时才起作用。Java注释不从接口继承的事实意味着,如果使用基于类的代理(代理目标类=“true”)或基于编织的方面(MODE=“AspectJ”),事务设置不会被代理和编织基础设施所识别,并且对象不会被封装在事务代理中。

在代理模式下(默认情况下),只截获通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)不会在运行时导致实际事务,即使被调用的方法被标记为@transactional。此外,代理必须完全初始化才能提供预期的行为,因此在初始化代码中不应依赖此功能(即@postconstruct)。

如果希望自己的调用也包含在事务中,那么考虑使用AspectJ模式(请参见下表中的模式属性)。在这种情况下,首先没有代理。相反,目标类被编织(也就是说,它的字节代码被修改)来在任何类型的方法上将@transactional转换为运行时行为。

注解驱动的事务配置:

XML Attribute Annotation Attribute Default Description
transaction-manager N/A (see TransactionManagementConfigurer javadoc) transactionManager 要使用的事务管理器的名称。仅当事务管理器的名称不是TransactionManager时才需要,如前面的示例所示。
mode mode proxy 默认模式(proxy)处理通过使用Spring的AOP框架(遵循前面讨论的代理语义,仅应用于通过代理传入的方法调用)来代理的注释bean。替代模式(aspectj)用spring的aspectj事务方面来编织受影响的类,修改目标类字节代码以应用于任何类型的方法调用。AspectJ编织需要类路径中的spring-aspects.jar,并且启用加载时编织(或编译时编织)。(有关如何设置加载时间交织的详细信息,请参见弹簧配置。)
proxy-target-class proxyTargetClass false 仅适用于代理模式。控制为用@transactional annotation注释的类创建的事务代理的类型。如果代理目标类属性设置为true,则创建基于类的代理。如果proxy-target类为false或者省略了属性,那么将创建基于标准JDK接口的代理。(有关不同代理类型的详细检查,请参阅代理机制。)
order order Ordered.LOWEST_PRECEDENCE 定义应用于用@transactional注释的bean的事务通知的顺序。(有关AOP通知排序规则的更多信息,请参阅通知排序。)没有指定的排序意味着AOP子系统决定通知的顺序。

处理@transactional annotations的默认建议模式是proxy,它只允许通过proxy拦截调用。同一类中的本地调用不能以这种方式被截获。对于更高级的拦截模式,考虑结合编译时或加载时编织切换到AspectJ模式。
proxy target class属性控制为用@transactional annotation注释的类创建什么类型的事务代理。如果代理目标类设置为true,则创建基于类的代理。如果proxy target class为false或省略属性,则创建基于标准JDK接口的代理。(有关不同代理类型的讨论,请参阅[AOP代理]。)
@enableTransactionManagement和tx:annotation-driven/仅在定义它们的同一应用程序上下文中的bean上查找@transactional。这意味着,如果在DispatcherServlet的WebApplicationContext中放置注释驱动的配置,它只在控制器中检查@Transactional bean,而不检查服务。有关更多信息,请参阅MVC。

在评估方法的事务设置时,最派生的位置优先。在下面的示例中,DefaultFooService类在类级别用只读事务的设置进行注释,但同一类中updateFoo(foo)方法上的@transactional注释优先于在类级别定义的事务设置。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional 设置
@transactional annotation是指定接口、类或方法必须具有事务语义的元数据(例如,“在调用此方法时启动全新的只读事务,挂起任何现有事务”)。默认的@transactional设置如下:

  • 传播设置是 PROPAGATION_REQUIRED.
  • 隔离级别是 ISOLATION_DEFAULT
  • 事务是读写的。
  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则默认为none
  • 任何RuntimeException触发器都会回滚,而任何选中的异常都不会

@Transactional设置:

Property Type Description
value String 指定要使用的事务管理器的可选限定符。
propagation enum: Propagation 可选传播设置。
isolation enum: Isolation 可选隔离级别。仅适用于“REQUIRED ”或“REQUIRES_NEW”的传播值。
timeout int (in seconds of granularity)
readOnly boolean 读写与只读事务。仅适用于“REQUIRED ”或“REQUIRES_NEW”的传播值
rollbackFor 类对象的数组,必须从throwable派生。 必须导致回滚的异常类的可选数组。
rollbackForClassName 类名数组。类必须从可丢弃的派生。 必须导致回滚的异常类的名称的可选数组。
noRollbackFor 类对象的数组,必须从throwable派生。 不能导致回滚的异常类的可选数组。
noRollbackForClassName 字符串类名数组,必须从throwable派生。 不可导致回滚的异常类名称的可选数组。

当前,您不能对事务名称进行显式控制,其中“name”表示出现在事务监视器(如果适用)(例如,WebLogic的事务监视器)和日志记录输出中的事务名称。对于声明性事务,事务名称始终是完全限定的类名+.+事务通知类的方法名。例如,如果BusinessService类的handlePayment(…)方法启动了一个事务,则该事务的名称为:com.example.BusinessService.handlePayment.

具有@transactional的多个事务管理器

大多数Spring应用程序只需要一个事务管理器,但在某些情况下,您可能希望在一个应用程序中有多个独立的事务管理器。您可以使用@transactional注释的value属性来选择指定要使用的PlatformTransactionManager的标识。这可以是事务管理器bean的bean名称或限定符值。例如,使用限定符表示法,可以将以下Java代码与应用程序上下文中的以下事务管理器bean声明相结合:

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}

下面的清单显示了bean声明:

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

在这种情况下,TransactionalService上的两个方法在单独的事务管理器下运行,由order和account限定符区分。如果找不到特定限定的PlatformTransactionManager bean,则仍然使用默认的目标bean名称transactionManager。

自定义快捷方式Annotations
如果您发现在许多不同的方法上重复使用@transactional的相同属性,那么Spring的元注释支持允许您为特定的用例定义自定义快捷注释。例如,考虑以下注释定义:

Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

前面的注释让我们可以编写前面部分的示例,如下所示:

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) { ... }

    @AccountTx
    public void doSomething() { ... }
}

在前面的示例中,我们使用语法来定义事务管理器限定符,但是我们也可以包括传播行为、回滚规则、超时和其他特性。

1.4.7. Transaction Propagation

在Spring管理的事务中,要注意物理和逻辑事务之间的区别,以及传播设置如何应用于这种差异。

理解 PROPAGATION_REQUIRED
spring5.1.3使用篇-数据访问_第2张图片

传播需要强制一个物理事务,如果还没有事务存在,则为当前作用域本地执行,或者参与为更大作用域定义的现有“外部”事务。在同一线程中的公共调用堆栈安排中,这是一个很好的默认值(例如,一个服务外观,它委托给多个存储库方法,其中所有基础资源都必须参与服务级别事务)。

默认情况下,参与的事务加入外部作用域的特征,无提示地忽略本地隔离级别、超时值或只读标志(如果有)。如果希望在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,请考虑在事务管理器上将validateExistingTransactions标志切换为true。这种非宽松模式还拒绝只读不匹配(即,尝试参与只读外部作用域的内部读写事务)。

当需要传播设置时,将为应用该设置的每个方法创建逻辑事务作用域。每个这样的逻辑事务作用域只能单独确定回滚状态,外部事务作用域在逻辑上独立于内部事务作用域。在标准传播所需行为的情况下,所有这些作用域都映射到同一物理事务。因此,在内部事务范围中设置的仅回滚标记确实会影响外部事务实际提交的机会。

但是,在内部事务作用域设置仅回滚标记的情况下,外部事务没有决定回滚本身,因此回滚(由内部事务作用域自动触发)是意外的。此时将引发相应的UnexpectedRollbackException。这是预期的行为,因此事务的调用者永远不会被误导,以为提交是在实际不执行的情况下执行的。因此,如果一个内部事务(外部调用者不知道的事务)静默地将事务标记为仅回滚,则外部调用者仍然调用commit。外部调用程序需要接收一个未预期的RollbackException,以清楚地指示执行了回滚。

理解PROPAGATION_REQUIRES_NEW
spring5.1.3使用篇-数据访问_第3张图片

传播需要新的,与所需的传播不同,总是为每个受影响的事务范围使用一个独立的物理事务,从不参与外部范围的现有事务。在这种安排中,基础资源事务是不同的,因此可以独立地提交或回滚,外部事务不受内部事务的回滚状态影响,内部事务的锁在完成后立即释放。这样一个独立的内部事务也可以声明自己的隔离级别、超时和只读设置,而不继承外部事务的特性。

理解PROPAGATION_NESTED
传播嵌套PROPAGATION_NESTED使用一个具有多个保存点的物理事务,它可以回滚到。这种部分回滚允许内部事务作用域触发其作用域的回滚,而外部事务能够继续物理事务,尽管某些操作已回滚。此设置通常映射到JDBC保存点,因此它仅适用于JDBC资源事务。请参见Spring的DataSourceTransactionManager。

1.4.8. Advising Transactional Operations

假设您希望同时执行事务操作和一些基本的分析建议。在tx:annotation-driven/的上下文中,您如何影响这一点?

调用updatefoo(foo)方法时,希望看到以下操作:

  • 配置的性能分析方面开始。
  • 执行事务建议
  • 执行建议对象上的方法。
  • 事务提交。
  • 分析方面报告了整个事务方法调用的确切持续时间。

以下代码显示了前面讨论的简单分析方面:

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;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    // this method is the 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;
    }
}

通知的排序通过已排序的接口进行控制

以下配置创建了一个fooservice bean,该bean按所需的顺序应用了分析和事务方面:


<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声明性方法:


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

前面的配置的结果是一个fooservice bean,它按照这个顺序应用了分析和事务方面。如果希望在传入事务建议之后和传出事务建议之前执行分析建议,可以交换分析方面bean的order属性的值,使其高于事务建议的order值。

您可以用类似的方式配置其他方面。

1.4.9. Using @Transactional with AspectJ

您还可以通过AspectJ方面在Spring容器之外使用Spring框架的@Transactional支持。为此,首先使用@transactional注释来注释类(以及可选的类方法),然后将应用程序与spring-aspects.jar文件中定义的org.springframework.transaction.aspectj.annotationTransactionaspect链接(编织)。还必须使用事务管理器配置方面。您可以使用Spring框架的IOC容器来处理注入方面的依赖关系。配置事务管理特性的最简单方法是使用tx:annotation-driven/元素,并将模式属性指定为aspectj,如使用@transactional中所述。因为我们在这里关注的是在Spring容器之外运行的应用程序,所以我们将向您展示如何以编程方式进行。

在继续之前,您可能希望分别使用@transactional和aop进行读取。

下面的示例演示如何创建事务管理器并将AnnotationTransactionAspect配置为使用它:

// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

使用此方面时,必须注释实现类(或该类中的方法或两者),而不是类实现的接口(如果有)。AspectJ遵循Java的规则,即接口上的注释不会被继承。

类上的@transactional注释指定执行类中任何公共方法的默认事务语义。

类内方法的@transactional注释将覆盖类注释(如果存在)给出的默认事务语义。您可以注释任何方法,而不考虑可见性。

要使用annotationTransactionspect编织应用程序,必须使用aspectj构建应用程序(请参阅aspectj开发指南)或使用加载时编织。关于使用aspectj进行加载时编织的讨论,请参见弹簧框架中的加载时编织与aspectj。LoadTimeWeaver

1.5. Programmatic Transaction Management

Spring Framework提供了两种程序化事务管理方法:

  1. TransactionTemplate
  2. 直接执行PlatformTransactionManager。

Spring团队通常为编程事务管理推荐TransactionTemplate。第二种方法类似于使用JTA用户事务API,尽管异常处理不那么麻烦。

1.5.1. Using the TransactionTemplate

TransactionTemplate与JdbcTemplate类似,它使用回调方法(使应用程序代码不必执行样板文件获取和发布事务性资源),并生成意图驱动的代码,因为您的代码只关注您想要做的事情。

如下面的示例所示,使用TransactionTemplate绝对会将您与Spring的事务基础结构和API结合起来。程序化事务管理是否适合您的开发需求是您必须自己做出的决定。

必须在事务上下文中执行并且显式使用TransactionTemplate的应用程序代码类似于下一个示例。作为应用程序开发人员,您可以编写TransactionCallback实现(通常表示为匿名内部类),其中包含在事务上下文中需要执行的代码。然后,可以将自定义TransactionCallback的实例传递给TransactionTemplate上公开的Execute(…)方法。以下示例显示了如何执行此操作:

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // the code in this method executes in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果没有返回值,可以使用方便的TransactionCallbackWithoutOutResult类和匿名类,如下所示:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

回调中的代码可以通过对提供的TransactionStatus对象调用setRollbackOnly()方法回滚事务,如下所示:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessException ex) {
            status.setRollbackOnly();
        }
    }
});

指定事务设置

您可以在TransactionTemplate上以编程方式或在配置中指定事务设置(例如传播模式、隔离级别、超时等等)。默认情况下,TransactionTemplate实例具有默认的事务设置。以下示例显示特定TransactionTemplate的事务设置的编程自定义:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // the transaction settings can be set here explicitly if so desired
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 seconds
        // and so forth...
    }
}

下面的示例使用SpringXML配置定义了一个具有一些自定义事务设置的TransactionTemplate:

<bean id="sharedTransactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
bean>"

然后,您可以将sharedTransactionTemplate注入到所需的任意多个服务中。

最后,TransactionTemplate类的实例是线程安全的,在这种情况下不维护任何会话状态。但是,TransactionTemplate实例会维护配置状态。因此,虽然许多类可以共享TransactionTemplate的单个实例,但是如果一个类需要使用具有不同设置的TransactionTemplate(例如,不同的隔离级别),则需要创建两个不同的TransactionTemplate实例。

1.5.2. Using the PlatformTransactionManage

您还可以直接使用org.springframework.transaction.platformTransactionManager来管理您的事务。为此,请通过bean引用将使用的PlatformTransactionManager的实现传递给bean。然后,通过使用TransactionDefinition和TransactionStatus对象,可以启动事务、回滚和提交。以下示例显示了如何执行此操作:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

1.6. Choosing Between Programmatic and Declarative Transaction ### Management

只有在有少量事务操作的情况下,程序化事务管理才是一个好主意。例如,如果您的Web应用程序只需要某些更新操作的事务,那么您可能不希望使用Spring或任何其他技术来设置事务代理。在这种情况下,使用TransactionTemplate可能是一种很好的方法。能够显式地设置事务名也是一件只能通过使用事务管理的编程方法来完成的事情。

另一方面,如果您的应用程序有许多事务操作,那么声明性事务管理通常是值得的。它使事务管理远离业务逻辑,并且不难配置。当使用Spring框架而不是EJBCmt时,声明性事务管理的配置成本大大降低。

1.7. Transaction-bound Events

从Spring4.2开始,事件的侦听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件。这样,当当前事务的结果实际上对侦听器很重要时,可以更灵活地使用事件。

您可以使用@event listener注释注册常规事件侦听器。如果需要将其绑定到事务,请使用@transactionaleventlistener。当您这样做时,默认情况下,监听器被绑定到事务的提交阶段。

下一个例子展示了这个概念。假设一个组件发布一个订单创建的事件,并且我们希望定义一个侦听器,该侦听器只应在成功提交发布该事件的事务后处理该事件。以下示例设置了这样的事件侦听器:

@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        ...
    }
}

@TransactionalEventListener注释公开了一个阶段属性,该属性允许您自定义侦听器应绑定到的事务的阶段。有效阶段是BEFORE_COMMIT, AFTER_COMMIT (default), AFTER_ROLLBACK, and AFTER_COMPLETION一个聚合事务完成后(无论是提交还是回滚)。

如果没有运行任何事务,则根本不会调用侦听器,因为我们无法满足所需的语义。但是,可以通过将注释的fallbackExecution属性设置为true来重写该行为。

1.8. Application server-specific integration

Spring的事务抽象通常不区分应用服务器。此外,Spring的JTatransactionManager类(可以选择对JTA用户事务和TransactionManager对象执行JNDI查找)自动检测后一个对象的位置,该位置随应用程序服务器而异。访问JTA TransactionManager允许增强事务语义,尤其是支持事务暂停。有关详细信息,请参阅JTatransactionManager JavaDoc。

Spring的JtaTransactionManager 是在JavaEE应用服务器上运行的标准选择,并被称为在所有公共服务器上工作。高级功能(如事务暂停)也可以在许多服务器上工作(包括Glassfish、JBoss和Geronimo),而不需要任何特殊的配置。但是,对于完全支持的事务挂起和进一步的高级集成,Spring包含用于WebLogic服务器和WebSphere的特殊适配器。下面的章节将讨论这些适配器。

对于标准方案(包括WebLogic Server和WebSphere),请考虑使用方便的tx:jta-transaction-manager/配置元素。配置后,此元素会自动检测基础服务器并选择可用于平台的最佳事务管理器。这意味着您无需显式配置特定于服务器的适配器类(如以下部分所述)。相反,它们是自动选择的,标准 JtaTransactionManager作为默认回退。

1.8.1. IBM WebSphere

在WebSphere6.1.0.9及更高版本上,建议使用的SpringJTA事务管理器是WebSphereUowTransactionManager。这个特殊的适配器使用IBM的UowManager API,它在WebSphereApplicationServer6.1.0.9及更高版本中提供。有了这个适配器,IBM正式支持Spring驱动的事务挂起(暂停和恢复启动)。

1.8.2. Oracle WebLogic Server

在WebLogic Server 9.0或更高版本上,通常使用WebLogicJtaTransactionManager 而不是JtaTransactionManager 类。普通JtaTransactionManager 的这个特定于WebLogic的特殊子类支持WebLogic管理的事务环境中Spring事务定义的全部功能,超出了标准JTA语义。功能包括事务名称、每个事务隔离级别,以及在所有情况下正确恢复事务。

1.9. Solutions to Common Problems

本节介绍一些常见问题的解决方案。

1.9.1. Using the Wrong Transaction Manager for a Specific DataSource

根据您选择的事务技术和要求,使用正确的PlatformTransactionManager 实现。如果使用得当,Spring框架只提供了一个简单且可移植的抽象。如果使用全局事务,则必须对所有事务操作使用org.springframework.transaction.jta.JtaTransactionManager类(或其特定于应用服务器的子类)。否则,事务基础结构将尝试对容器数据源实例等资源执行本地事务。这样的本地事务没有意义,一个好的应用服务器将它们视为错误。

1.10. Further Resources

有关Spring框架的事务支持的更多信息,请参见:

  • Spring中的分布式事务,有或没有XA是一个JavaWorld演示文稿,其中Spring的David Syer将引导您完成Spring应用程序中分布式事务的七种模式,其中三种使用XA,四种不使用。
  • Java Transaction Design Strategies是 InfoQ提供的一本书,它提供了对Java中事务的快速介绍。它还包括如何使用Spring Framework和EJB3配置和使用事务的并排示例。

2. DAO Support

Spring中的数据访问对象(DAO)支持旨在以一致的方式轻松地使用数据访问技术(如JDBC、Hibernate或JPA)。这使您可以很容易地在前面提到的持久性技术之间进行切换,并且还可以让您在代码中不必担心捕获特定于每种技术的异常。

2.1. Consistent Exception Hierarchy

一致的异常层次结构

Spring提供了从特定于技术的异常(如sqlException)到它自己的异常类层次结构(以dataAccessException为根异常)的方便转换。这些异常将原始异常包装起来,这样就不会有任何可能丢失有关可能发生错误的任何信息的风险。

除了JDBC异常,Spring还可以包装JPA和Hibernate特定的异常,将它们转换为一组集中的运行时异常。这允许您只在适当的层中处理大多数不可恢复的持久性异常,而不需要在DAO中使用烦人的样板捕获和抛出块以及异常声明。(您仍然可以在任何需要的地方捕获和处理异常。)如上所述,JDBC异常(包括特定于数据库的方言)也转换为相同的层次结构,这意味着您可以在一致的编程模型中使用JDBC执行一些操作。

前面的讨论适用于Spring对各种ORM框架的支持中的各种模板类。如果使用基于拦截器的类,则应用程序必须关注处理HibernateExceptions和PersistenceExceptions本身,最好分别委托给sessionFactoryUtils的ConvertHibernateAccessException(…)或ConvertjpaAccessException()方法。这些方法将异常转换为与org.springframework.dao异常层次结构中的异常兼容的异常。由于不检查PersistenceExceptions,它们也可能被抛出(不过,从异常的角度牺牲了一般的DAO抽象)。

下图显示了Spring提供的异常层次结构。(注意,图中详细描述的类层次结构只显示整个DataAccessException层次结构的一个子集。)
spring5.1.3使用篇-数据访问_第4张图片

2.2. Annotations Used to Configure DAO or Repository Classes

用于配置DAO或存储库类的注释

确保数据访问对象(DAO)或存储库提供异常转换的最佳方法是使用@repository注释。此注释还允许组件扫描支持查找和配置DAO和存储库,而不必为它们提供XML配置条目。下面的示例演示如何使用@repository注释:

@Repository 
public class SomeMovieFinder implements MovieFinder {
    // ...
}

任何DAO或存储库实现都需要访问持久性资源,这取决于所使用的持久性技术。例如,基于JDBC的存储库需要访问JDBC数据源,基于JPA的存储库需要访问EntityManager。最简单的方法是使用@autowired、@inject、@resource或@persistencecontext注释之一注入此资源依赖项。以下示例适用于JPA存储库:

@Repository
public class JpaMovieFinder implements MovieFinder {

    @PersistenceContext
    private EntityManager entityManager;

    // ...

}

如果使用经典的Hibernate API,则可以注入sessionFactory,如下示例所示:

Repository
public class HibernateMovieFinder implements MovieFinder {

    private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    // ...

}

这里最后一个例子是典型的JDBC支持。您可以将数据源注入到初始化方法中,在该方法中,您可以使用此数据源创建JDBCTemplate和其他数据访问支持类(如SimpleJDBCCall等)。以下示例自动连接数据源:

@Repository
public class JdbcMovieFinder implements MovieFinder {

    private JdbcTemplate jdbcTemplate;

    @Autowired
    public void init(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    // ...

}

有关如何配置应用程序上下文以利用这些注释的详细信息,请参阅每个持久性技术的特定覆盖范围。

3. Data Access with JDBC

使用jdbc进行数据访问
SpringFrameworkJDBC抽象提供的值最好通过下表中概述的操作序列来显示。该表显示了Spring负责哪些操作以及哪些操作是您的责任。

Spring JDBC - 谁做了什么?

Action Spring You
定义连接参数 X
打开连接 X
指定SQL语句 X
声明参数并提供参数值 X
准备并提交 statement X
设置循环以迭代结果(如果有) X
为每次迭代做好工作 X
处理任何异常 X
处理transactions X
关闭连接,语句和结果集 X

Spring Framework负责处理所有可能使JDBC成为繁琐API的低级细节

3.1. Choosing an Approach for JDBC Database Access

jdbc数据库访问方法的选择

您可以从几种方法中进行选择,以构成JDBC数据库访问的基础。除了三种风格的JDBCTemplate外,新的simpleJDBCInsert和simpleJDBCCall方法还优化了数据库元数据,RDBMS对象样式采用了与JDO查询设计类似的更面向对象的方法。一旦您开始使用其中一种方法,您仍然可以混合和匹配以包含来自不同方法的特性。所有方法都需要一个JDBC2.0兼容的驱动程序,而一些高级功能需要一个JDBC3.0驱动程序。

  • JDBCTemplate是经典且最流行的SpringJDBC方法。这种“最低级别”的方法和所有其他方法都在封面下使用JDBCTemplate。
  • NamedParameterJDBCTemplate是否包装JDBCTemplate以提供命名参数而不是传统的JDBC?占位符。当一个SQL语句有多个参数时,这种方法提供了更好的文档和易用性。
  • SimpleJDBCInsert和SimpleJDBCCall优化数据库元数据以限制必要的配置量。这种方法简化了编码,因此您只需要提供表或过程的名称,并提供与列名称匹配的参数映射。只有当数据库提供足够的元数据时,这才有效。如果数据库不提供此元数据,则必须提供参数的显式配置。
  • RDBMS对象,包括mappingsqlquery、sqlupdate和storedprocedure,要求您在数据访问层初始化期间创建可重用和线程安全的对象。这种方法是在JDO查询之后建模的,其中定义查询字符串、声明参数和编译查询。一旦您这样做了,可以使用各种参数值多次调用Execute方法。

3.2. Package Hierarchy

Spring Framework的JDBC抽象框架由四个不同的包组成:

  • core:org.springframework.jdbc.core包包含JDBCTemplate类及其各种回调接口,以及各种相关类。名为org.springframework.jdbc.core.simple的子包包含SimpleJDBCInsert和SimpleJDBCCall类。另一个子包org.springframework.jdbc.core.namedparam包含namedparameterjdbctemplate类和相关的支持类。请参见使用JDBC核心类来控制基本的JDBC处理和错误处理、JDBC批处理操作以及使用SimpleJDBC类简化JDBC操作。
  • datasource:org.springframework.jdbc.datasource包包含一个实用工具类,用于简单的数据源访问和各种简单的数据源实现,可以用于测试和运行JavaEE容器之外的未修改的JDBC代码。一个名为org.springfamework.jdbc.datasource.embedded的子包为使用Java数据库引擎(如HSQL、H2和DeBy)创建嵌入式数据库提供了支持。请参见控制数据库连接和嵌入式数据库支持。

object:org.springframework.jdbc.object包包含将RDBMS查询、更新和存储过程表示为线程安全、可重用对象的类。请参见将JDBC操作建模为Java对象。这种方法由JDO建模,尽管查询返回的对象自然与数据库断开连接。更高层次的JDBC抽象依赖于org.springframework.jdbc.core包中较低层次的抽象。

support:org.springframework.jdbc.support包提供了sqlException转换功能和一些实用程序类。JDBC处理期间抛出的异常被转换为org.springframework.dao包中定义的异常。这意味着使用SpringJDBC抽象层的代码不需要实现JDBC或RDBMS特定的错误处理。所有已翻译的异常都未选中,这使您可以选择捕获可以从中恢复的异常,同时允许将其他异常传播到调用方。请参见使用sqlExceptionTranslator。

3.3. Using the JDBC Core Classes to Control Basic JDBC Processing and ### Error Handling

使用JDBC核心类控制基本JDBC处理和错误处理

3.3.1. Using JdbcTemplate

JDBCTemplate是JDBC核心包中的中心类。它处理资源的创建和释放,这有助于避免常见错误,例如忘记关闭连接。它执行核心JDBC工作流的基本任务(如语句创建和执行),留下应用程序代码来提供SQL和提取结果。JDBCTemplate类:

  • 运行SQL查询
  • 更新语句和存储过程调用
  • 对结果集实例执行迭代,并提取返回的参数值。
  • 捕获JDBC异常并将其转换为在org.springframework.dao包中定义的通用、信息更丰富的异常层次结构。(请参见一致的异常层次结构。)

当您为代码使用JDBCTemplate时,只需要实现回调接口,为它们提供一个明确定义的契约。给定JDBCTemplate类提供的连接,PreparedStatementCreator回调接口创建一个准备好的语句,提供SQL和任何必要的参数。CallableStatementCreator接口也是如此,它创建了可调用语句。rowCallbackHandler接口从结果集的每一行中提取值。

您可以通过直接实例化数据源引用在DAO实现中使用JDBCTemplate,也可以在SpringIOC容器中配置它,并将其作为bean引用提供给DAO。

数据源应该始终配置为SpringIOC容器中的bean。在第一种情况下,bean直接提供给服务;在第二种情况下,它提供给准备好的模板。

该类发出的所有SQL都在debug级别记录在模板实例的完全限定类名(通常是JDBCTemplate,但如果使用JDBCTemplate类的自定义子类,则可能不同)对应的类别下。

以下部分提供了一些JDBCTemplate使用的示例。这些示例并不是JDBCTemplate所公开的所有功能的详尽列表

查询(SELECT)

//以下查询获取关系中的行数:
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
//以下查询使用绑定变量:
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
        "select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
//以下查询查找字符串:
String lastName = this.jdbcTemplate.queryForObject(
        "select last_name from t_actor where id = ?",
        new Object[]{1212L}, String.class);
//以下查询查找并填充单个域对象:
Actor actor = this.jdbcTemplate.queryForObject(
        "select first_name, last_name from t_actor where id = ?",
        new Object[]{1212L},
        new RowMapper<Actor>() {
            public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
                Actor actor = new Actor();
                actor.setFirstName(rs.getString("first_name"));
                actor.setLastName(rs.getString("last_name"));
                return actor;
            }
        });

//以下查询查找并填充许多域对象:
List<Actor> actors = this.jdbcTemplate.query(
        "select first_name, last_name from t_actor",
        new RowMapper<Actor>() {
            public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
                Actor actor = new Actor();
                actor.setFirstName(rs.getString("first_name"));
                actor.setLastName(rs.getString("last_name"));
                return actor;
            }
        });

如果最后两段代码实际上存在于同一个应用程序中,那么删除两个RowMapper匿名内部类中的重复部分并将其提取到一个类(通常是静态嵌套类)中是有意义的,然后DAO方法可以根据需要引用该类。例如,最好按以下方式编写前面的代码段:

public List<Actor> findAllActors() {
    return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());
}

private static final class ActorMapper implements RowMapper<Actor> {

    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
        Actor actor = new Actor();
        actor.setFirstName(rs.getString("first_name"));
        actor.setLastName(rs.getString("last_name"));
        return actor;
    }
}

更新(INSERT,UPDATE,和DELETE)与JdbcTemplate

可以使用update(…)方法执行插入、更新和删除操作。参数值通常作为变量参数提供,或者作为对象数组提供。

//以下示例插入新条目:
this.jdbcTemplate.update(
        "insert into t_actor (first_name, last_name) values (?, ?)",
        "Leonor", "Watling");
//以下示例更新现有条目:
this.jdbcTemplate.update(
        "update t_actor set last_name = ? where id = ?",
        "Banjo", 5276L);
//以下示例删除条目:
this.jdbcTemplate.update(
        "delete from actor where id = ?",
        Long.valueOf(actorId));

其他JdbcTemplate 操作

您可以使用该execute(…)方法运行任意SQL。因此,该方法通常用于DDL语句。它重载了带有回调接口,绑定变量数组等的变体。以下示例创建一个表:

this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
//以下示例调用存储过程:
this.jdbcTemplate.update(
        "call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
        Long.valueOf(unionId));

JdbcTemplate 最佳实践

jdbctemplate类的实例一旦配置,就具有线程安全性。这很重要,因为这意味着您可以配置JDBCTemplate的单个实例,然后将此共享引用安全地注入多个DAO(或存储库)。JDBCTemplate是有状态的,因为它维护对数据源的引用,但这种状态不是会话状态。

使用JDBCTemplate类(以及关联的NamedParameterJDBCTemplate类)时的一个常见做法是在Spring配置文件中配置一个数据源,然后依赖项将该共享数据源bean注入到DAO类中。JDBCTemplate是在数据源的setter中创建的。这会导致类似以下内容的DAO:

public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

以下示例显示了相应的XML配置:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
        <property name="dataSource" ref="dataSource"/>
    bean>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    <context:property-placeholder location="jdbc.properties"/>

beans>

显式配置的另一种选择是使用组件扫描和注释支持进行依赖项注入。在这种情况下,您可以用@repository注释类(这使它成为组件扫描的候选对象),并用@autowired注释数据源setter方法。以下示例显示了如何执行此操作:

@Repository 
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired 
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource); 
    }

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

以下示例显示了相应的XML配置:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:component-scan base-package="org.springframework.docs.test" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    <context:property-placeholder location="jdbc.properties"/>

beans>

显式配置的另一种选择是使用组件扫描和注释支持进行依赖项注入。在这种情况下,您可以用@repository注释类(这使它成为组件扫描的候选对象),并用@autowired注释数据源setter方法。以下示例显示了如何执行此操作:

@Repository 
public class JdbcCorporateEventDao implements CorporateEventDao {

    private JdbcTemplate jdbcTemplate;

    @Autowired 
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource); 
    }

    // JDBC-backed implementations of the methods on the CorporateEventDao follow...
}

以下示例显示了相应的XML配置:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    
    <context:component-scan base-package="org.springframework.docs.test" />

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="${jdbc.driverClassName}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    bean>

    <context:property-placeholder location="jdbc.properties"/>

beans>

如果使用Spring的JDBCDAOSupport类,并且扩展了各种支持JDBC的DAO类,则子类将继承JDBCDAOSupport类中的setDataSource(…)方法。您可以选择是否从此类继承。JDBCDAOSupport类只是为了方便起见而提供的。

无论您选择使用(或不使用)上述哪种模板初始化样式,都很少需要每次运行SQL时都创建JDBCTemplate类的新实例。配置后,JDBCTemplate实例是线程安全的。如果应用程序访问多个数据库,您可能需要多个JDBCTemplate实例,这需要多个数据源,随后需要多个不同配置的JDBCTemplate实例。

3.3.2. Using NamedParameterJdbcTemplate

NamedParameterJDBCTemplate类通过使用命名参数来添加对JDBC语句编程的支持,而不是仅使用经典占位符(“?”)来编程JDBC语句。争论。NamedParameterJDBCTemplate类包装一个JDBCTemplate,并委托给包装好的JDBCTemplate来完成它的大部分工作。本节仅描述与JDBCTemplate本身不同的NamedParameterJDBCTemplate类的那些区域,即使用命名参数对JDBC语句进行编程。以下示例显示如何使用NamedParameterJDBCTemplate:

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

    String sql = "select count(*) from T_ACTOR where first_name = :first_name";

    SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

请注意,在分配给SQL变量的值中使用了命名参数表示法,以及插入到namedparameters变量(属于mapsqlparametersource类型)中的相应值。

或者,可以使用基于映射的样式将命名参数及其对应的值传递给NamedParameterJDBCTemplate实例。由NamedParameterJDBCOperations公开并由NamedParameterJDBCTemplate类实现的其余方法遵循类似的模式,此处不介绍。

以下示例显示了Map-based的样式的使用:

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActorsByFirstName(String firstName) {

    String sql = "select count(*) from T_ACTOR where first_name = :first_name";

    Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters,  Integer.class);
}

与NealDealPrimeJDbC样板(并且存在于同一Java包中)相关的一个很好的特性是SqlParameterSource 源接口。您已经在前面的一个代码片段(mapsqlparametersource类)中看到了此接口的实现示例。SQLParameterSource是NamedParameterJDBCTemplate的命名参数值源。mapsqlparametersource类是一个简单的实现,它是围绕java.util.map的适配器,其中键是参数名,值是参数值。

另一个sqlparametersource实现是beanpropertysqlparametersource类。此类包装任意JavaBean(即,遵循JavaBean约定的类的实例),并使用包装的JavaBean的属性作为命名参数值的源。

下面的示例显示了一个典型的JavaBean:

public class Actor {

    private Long id;
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return this.firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public Long getId() {
        return this.id;
    }

    // setters omitted...

}

下面的示例使用NamedParameterJDBCTemplate返回前面示例中所示的类成员的计数:

// some JDBC-backed DAO class...
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}

public int countOfActors(Actor exampleActor) {

    // notice how the named parameters match the properties of the above 'Actor' class
    String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";

    SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);

    return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
}

记住,NamedParameterJDBCTemplate类包装了一个经典的JDBCTemplate模板。如果需要访问Wrapped JDBCTemplate实例以访问仅存在于JDBCTemplate类中的功能,可以使用getJDBCOperations()方法通过JDBCOperations接口访问Wrapped JDBCTemplate。

有关在应用程序上下文中使用NamedParameterJDBCTemplate类的指导原则,请参见JDBCTemplate最佳实践。

3.3.3. Using SQLExceptionTranslator

sqlExceptionTranslator是一个由类实现的接口,这些类可以在sqlExceptions和Spring自己的org.springframework.dao.dataaccessException之间进行转换,这与数据访问策略无关。实现可以是通用的(例如,使用JDBC的SQLState代码)或专有的(例如,使用Oracle错误代码)以获得更高的精度。

sqlErrorCodesqlExceptionTranslator是默认情况下使用的sqlExceptionTranslator的实现。此实现使用特定的供应商代码。它比sqlstate实现更精确。错误代码转换基于名为sqlErrorcodes的JavaBean类型类中保存的代码。这个类是由一个sqlErrorcodesFactory创建和填充的,它(顾名思义)是一个工厂,用于根据名为sql-error-codes.xml的配置文件的内容创建sqlErrorcodes。此文件由供应商代码填充,并基于从databasemetadata中获取的databaseProductName。使用实际数据库的代码。

sqlErrorCodesqlExceptionTranslator按以下顺序应用匹配规则:

  • 由子类实现的任何自定义翻译。通常使用提供的具体sqlErrorCodesqlExceptionTranslator,因此此规则不适用。只有当您实际提供了一个子类实现时,它才适用。
  • 作为sqlErrorCodes类的customsqlExceptionTranslator属性提供的sqlExceptionTranslator接口的任何自定义实现。
  • 搜索CustomSQLErrorCodesTranslation类的实例列表(为SQLErrorCodes类的CustomTranslations属性提供)以查找匹配项。
  • 应用了错误代码匹配。
  • 使用回退转换器。sqlExceptionSubClassTranslator是默认的回退转换器。如果此转换不可用,则下一个回退转换程序是sqlstateqlExceptionTranslator。

默认情况下,sqlErrorCodesFactory用于定义错误代码和自定义异常翻译。它们在类路径中名为sql-error-codes.xml的文件中查找,匹配的sqlErrorcodes实例基于所用数据库元数据中的数据库名称定位。

可以扩展sqlErrorcodesqlExceptionTranslator,如下示例所示:

public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {

    protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) {
        if (sqlex.getErrorCode() == -12345) {
            return new DeadlockLoserDataAccessException(task, sqlex);
        }
        return null;
    }
}

在前面的示例中,转换特定的错误代码(-12345),而其他错误则由默认的转换器实现进行转换。若要使用此自定义转换器,必须通过setExceptionTranslator方法将其传递给JDBCTemplate,并且必须在需要此转换器的所有数据访问处理中使用此JDBCTemplate。以下示例显示如何使用此自定义转换器:

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {

    // create a JdbcTemplate and set data source
    this.jdbcTemplate = new JdbcTemplate();
    this.jdbcTemplate.setDataSource(dataSource);

    // create a custom translator and set the DataSource for the default translation lookup
    CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
    tr.setDataSource(dataSource);
    this.jdbcTemplate.setExceptionTranslator(tr);

}

public void updateShippingCharge(long orderId, long pct) {
    // use the prepared JdbcTemplate for this update
    this.jdbcTemplate.update("update orders" +
        " set shipping_charge = shipping_charge * ? / 100" +
        " where id = ?", pct, orderId);
}

将向自定义转换器传递数据源,以便在sql-error-codes.xml中查找错误代码。

3.3.4. Running Statements

运行SQL语句只需要很少的代码。您需要一个数据源和一个JDBCTemplate,包括随JDBCTemplate提供的方便方法。下面的示例显示了创建新表的最小但完全功能化的类需要包含的内容:

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAStatement {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void doExecute() {
        this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
    }
}

3.3.5. Running Queries

某些查询方法返回单个值。要从一行中检索计数或特定值,请使用queryforobject(…)。后者将返回的JDBC类型转换为作为参数传递的Java类。如果类型转换无效,将引发InvalidDataAccessApiusGeException。以下示例包含两个查询方法,一个用于int,另一个用于查询字符串:

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class RunAQuery {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int getCount() {
        return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
    }

    public String getName() {
        return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
    }
}

除了单个结果查询方法外,还有几个方法返回一个列表,其中包含查询返回的每一行的条目。最通用的方法是queryforlist(…),它返回一个列表,其中每个元素都是一个映射,其中每个列都包含一个条目,使用列名作为键。如果在前面的示例中添加一个方法来检索所有行的列表,则可能如下所示:

private JdbcTemplate jdbcTemplate;

public void setDataSource(DataSource dataSource) {
    this.jdbcTemplate = new JdbcTemplate(dataSource);
}

public List<Map<String, Object>> getList() {
    return this.jdbcTemplate.queryForList("select * from mytable");
}

//返回的列表如下所示:
[{name=Bob, id=1}, {name=Mary, id=2}]

3.3.6. Updating the Database

以下示例更新某个主键的列:

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class ExecuteAnUpdate {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public void setName(int id, String name) {
        this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
    }
}

在前面的示例中,SQL语句具有行参数的占位符。您可以将参数值作为varargs传递,或者作为对象数组传递。因此,您应该显式地将原语包装在原语包装类中,或者使用自动装箱。

3.3.7. Retrieving Auto-generated Keys

检索自动生成的密钥
update()方便方法支持检索数据库生成的主键。此支持是JDBC3.0标准的一部分。详见本规范第13.6章。方法将PreparedStatementCreator作为第一个参数,这是指定必需的insert语句的方式。另一个参数是keyholder,它包含从更新成功返回时生成的密钥。没有标准的单一方法来创建适当的PreparedStatement(这就解释了方法签名为何如此)。以下示例适用于Oracle,但可能不适用于其他平台:

final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
    new PreparedStatementCreator() {
        public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
            PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] {"id"});
            ps.setString(1, name);
            return ps;
        }
    },
    keyHolder);

// keyHolder.getKey() now contains the generated key

3.4. Controlling Database Connections

控制数据库连接

3.4.1. Using DataSource

Spring通过数据源获取到数据库的连接。数据源是JDBC规范的一部分,是一个通用的连接工厂。它允许容器或框架从应用程序代码中隐藏连接池和事务管理问题。作为开发人员,您不需要知道如何连接到数据库的详细信息。这是设置数据源的管理员的责任。在开发和测试代码时,您很可能同时扮演这两个角色,但不必知道如何配置生产数据源。

使用Spring的JDBC层时,可以从JNDI获取数据源,也可以使用第三方提供的连接池实现配置自己的数据源。流行的实现是Apache Jakarta Commons DBCP和c3p0。Spring发行版中的实现仅用于测试目的,不提供池。

本节使用Spring的DriverManagerDataSource实现,稍后将介绍一些其他实现。

您应该仅将DriverManagerDataSource类用于测试目的,因为它不提供池,并且在进行多个连接请求时性能较差。

要配置DriverManagerDataSource,请执行以下操作:

  1. 获取与DriverManagerDataSource的连接,就像通常获取JDBC连接一样。
  2. 指定JDBC驱动程序的完全限定类名,以便DriverManager可以加载驱动程序类。
  3. 提供JDBC驱动程序之间不同的URL。(有关正确的值,请参阅驱动程序的文档。)
  4. 提供连接到数据库的用户名和密码。

下面的示例演示如何在Java中配置DriverManagerDataSource :

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");

以下示例显示了相应的XML配置:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
bean>

<context:property-placeholder location="jdbc.properties"/>

接下来的两个例子展示了dbcp和c3p0的基本连接和配置。要了解有助于控制池功能的更多选项,请参阅相应连接池实现的产品文档。

以下示例显示了DBCP配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
bean>

<context:property-placeholder location="jdbc.properties"/>

以下示例显示了c3p0配置:

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
bean>

<context:property-placeholder location="jdbc.properties"/>

3.4.2. Using DataSourceUtils

DataSourceUtils类是一个方便且功能强大的助手类,它提供静态方法从JNDI获取连接,并在必要时关闭连接。它支持与DataSourceTransactionManager等线程绑定的连接。

3.4.3. Implementing SmartDataSource

SmartDataSource接口应该由能够提供到关系数据库连接的类实现。它扩展了数据源接口,让使用它的类查询在给定操作之后是否应该关闭连接。当您知道需要重用连接时,这种用法是有效的。

3.4.4. Extending AbstractDataSource

AbstractDataSource是Spring数据源实现的抽象基类。它实现所有数据源实现通用的代码。如果编写自己的数据源实现,则应扩展AbstractDataSource类。

3.4.5. Using SingleConnectionDataSource

SingleConnectionDataSource类是SmartDataSource接口的一个实现,它包装了每次使用后都未关闭的单个连接。这不支持多线程。

如果任何客户端代码close在假设池化连接的情况下调用(如使用持久性工具时),则应将SuppressClose属性设置为true。此设置返回包装物理连接的关闭禁止代理。请注意,您不能再将其强制转换为本机Oracle连接或类似的对象。

singleConnectionDataSource主要是一个测试类。例如,它可以与一个简单的JNDI环境结合,方便地测试应用服务器外部的代码。与DriverManagerDataSource不同,它始终重用相同的连接,避免过度创建物理连接。

3.4.6. Using DriverManagerDataSource

DriverManagerDataSource类是标准数据源接口的一个实现,它通过bean属性配置普通的JDBC驱动程序,并每次返回一个新的连接。

此实现对于Java EE容器外部的测试和独立环境非常有用,可以作为DataSourceSpring IoC容器中的bean ,也可以与简单的JNDI环境结合使用。Pool-assuming Connection.close()调用关闭连接,因此任何支持数据源的持久性代码都可以工作。但是,即使在测试环境中,使用JavaBean风格的连接池(如commons dbcp)也非常容易,因此几乎总是首选使用DriverManagerDataSource上的连接池。

3.4.7. Using TransactionAwareDataSourceProxy

TransactionWaredataSourceProxy是目标数据源的代理。代理对目标数据源进行包装,以添加对Spring管理的事务的感知。在这方面,它类似于由JavaEE服务器提供的事务性JNDI数据源。

很少需要使用这个类,除非已经存在的代码必须被调用并传递标准的JDBC数据源接口实现。在这种情况下,您仍然可以使用此代码,同时让此代码参与Spring管理的事务。通常,最好使用更高级别的抽象来编写自己的新代码,用于资源管理,如JDBCTemplate或DataSourceUtils。

3.4.8. Using DataSourceTransactionMan

DataSourceTransactionManager类是单个JDBC数据源的PlatformTransactionManager实现。它将指定数据源的JDBC连接绑定到当前正在执行的线程,可能允许每个数据源有一个线程连接。

应用程序代码需要通过DataSourceUtils.getConnection(DataSource)检索JDBC连接,而不是Java EE的标准DataSource.getConnection。它抛出未检查的org.springframework.dao异常,而不是检查的sqlExceptions。所有框架类(如JDBCTemplate)都隐式使用此策略。如果不与此事务管理器一起使用,则查找策略的行为与普通策略完全相同。因此,它可以在任何情况下使用。

DataSourceTransactionManager类支持自定义隔离级别和超时,这些级别和超时将作为适当的JDBC语句查询超时应用。要支持后者,应用程序代码必须使用JDBCTemplate,或者为每个创建的语句调用datasourceutils.ApplyTransactionTimeout(…)方法。

您可以在单个资源案例中使用此实现而不是JTatransactionManager,因为它不需要容器来支持JTA。如果您坚持使用所需的连接查找模式,那么在两者之间切换只是配置问题。JTA不支持自定义隔离级别。

3.5. JDBC Batch Operations

如果您对同一个准备好的语句批处理多个调用,那么大多数JDBC驱动程序都可以提高性能。通过将更新分组到批次中,可以限制到数据库的往返次数。

3.5.1. Basic Batch Operations with JdbcTemplate

基本批处理操作JdbcTemplate

通过实现特殊接口batchPreparedStatementSetter的两个方法,并将该实现作为batchUpdate方法调用中的第二个参数传入,可以完成JDBCTemplate批处理。可以使用getbatchsize方法提供当前批的大小。您可以使用setValues方法设置准备好的语句的参数值。调用此方法的次数是在getbatchsize调用中指定的次数。以下示例根据列表中的条目更新actor表,整个列表用作批处理:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] batchUpdate(final List<Actor> actors) {
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                new BatchPreparedStatementSetter() {
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, actors.get(i).getFirstName());
                        ps.setString(2, actors.get(i).getLastName());
                        ps.setLong(3, actors.get(i).getId().longValue());
                    }
                    public int getBatchSize() {
                        return actors.size();
                    }
                });
    }

    // ... additional methods
}

如果处理更新流或从文件读取,则可能具有首选批处理大小,但最后一批可能没有该条目数。在这种情况下,您可以使用该InterruptibleBatchPreparedStatementSetter接口,该接口允许您在输入源耗尽时中断批处理。该isBatchExhausted方法可让您发出批次结束的信号。

3.5.2. Batch Operations with a List of Objects

使用对象列表进行批处理操作

JDBCTemplate和NamedParameterJDBCTemplate都提供了一种提供批更新的替代方法。您可以将调用中的所有参数值作为列表提供,而不是实现特殊的批处理接口。框架循环遍历这些值并使用内部预处理语句setter。根据是否使用命名参数,API会有所不同。对于命名参数,提供一个sqlparametersource数组,为批处理的每个成员提供一个条目。可以使用sqlparametersourceutils.createBatch方便方法创建此数组,传入bean样式对象的数组(使用与参数对应的getter方法)、字符串键控的映射实例(将相应的参数作为值包含)或两者的混合。

以下示例显示了使用命名参数的批更新:

public class JdbcActorDao implements ActorDao {

    private NamedParameterTemplate namedParameterJdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
    }

    public int[] batchUpdate(List<Actor> actors) {
        return this.namedParameterJdbcTemplate.batchUpdate(
                "update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
                SqlParameterSourceUtils.createBatch(actors));
    }

    // ... additional methods
}

对于使用Classic的SQL语句?占位符,传递一个包含具有更新值的对象数组的列表。对于SQL语句中的每个占位符,此对象数组必须有一个条目,并且它们的顺序必须与在SQL语句中定义的顺序相同。

下面的示例与前面的示例相同,只是它使用了经典的JDBC?占位符:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[] batchUpdate(final List<Actor> actors) {
        List<Object[]> batch = new ArrayList<Object[]>();
        for (Actor actor : actors) {
            Object[] values = new Object[] {
                    actor.getFirstName(), actor.getLastName(), actor.getId()};
            batch.add(values);
        }
        return this.jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                batch);
    }

    // ... additional methods
}

我们前面描述的所有批处理更新方法都返回一个int数组,其中包含每个批处理条目受影响的行数。此计数由JDBC驱动程序报告。如果计数不可用,JDBC驱动程序返回值-2。

在这样的场景中,在基础PreparedStatement中自动设置值,每个值的对应JDBC类型需要从给定的Java类型导出。虽然这通常工作得很好,但有可能出现问题(例如,映射包含空值)。在这种情况下,Spring默认调用parametermetadata.getParameterType,这在JDBC驱动程序中可能很昂贵。如果遇到性能问题–例如Oracle 12C(SPR-16139)上报告的,则应使用最新的驱动程序版本,并考虑将spring.jdbc.getParameterType.ignore属性设置为true(作为JVM系统属性或类路径根目录中的spring.properties文件)。

或者,您可以考虑通过“batchPreparedStatementSetter”(如前所示)、为基于“list”的调用提供的显式类型数组、对自定义“mapsqlparametersource”实例的“registersqltype”调用或通过“beanpropertysqlparametersource”显式指定相应的JDBC类型。它从Java声明的属性类型派生SQL类型,即使是空值。

3.5.3. Batch Operations with Multiple Batches

多批次批处理操作

前面的批更新示例处理的批太大,以至于您希望将它们分成几个较小的批。您可以通过对batchupdate方法进行多次调用来使用前面提到的方法,但是现在有了一个更方便的方法。除了SQL语句外,此方法还接受一组对象,这些对象包含参数、为每个批进行的更新数,以及一个参数化的PreparedStatementSetter,用于设置准备语句的参数值。框架循环提供的值,并将更新调用分成指定大小的批。

以下示例显示了使用批量大小为100的批更新:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public int[][] batchUpdate(final Collection<Actor> actors) {
        int[][] updateCounts = jdbcTemplate.batchUpdate(
                "update t_actor set first_name = ?, last_name = ? where id = ?",
                actors,
                100,
                new ParameterizedPreparedStatementSetter<Actor>() {
                    public void setValues(PreparedStatement ps, Actor argument) throws SQLException {
                        ps.setString(1, argument.getFirstName());
                        ps.setString(2, argument.getLastName());
                        ps.setLong(3, argument.getId().longValue());
                    }
                });
        return updateCounts;
    }

    // ... additional methods
}

此调用的批更新方法返回一个int数组,其中包含每个批的一个数组项,以及每个更新的受影响行数的数组。顶级数组的长度指示执行的批数,而第二级数组的长度指示该批中的更新数。根据提供的更新对象总数,每个批中的更新数应该是为所有批提供的批大小(可能小于最后一个批)。每个更新语句的更新计数是JDBC驱动程序报告的更新计数。如果计数不可用,JDBC驱动程序返回值-2。

3.6. Simplifying JDBC Operations with the SimpleJdbc Classes

使用SimpleJdbc类简化JDBC操作

SimpleJDBCInsert和SimpleJDBCCall类利用可以通过JDBC驱动程序检索的数据库元数据提供了简化的配置。这意味着,尽管您可以覆盖或关闭元数据处理(如果您更愿意在代码中提供所有细节),但预先配置的空间较小。

3.6.1. Inserting Data by Using SimpleJdbcInsert

我们从使用最少配置选项的simplejdbinsert类开始。您应该在数据访问层的初始化方法中实例化simplejdbinsert。对于这个例子,初始化方法是setdatasource方法。您不需要子类simplejdbinsert类。相反,您可以使用withTableName方法创建一个新实例并设置表名。此类的配置方法遵循返回simplejdbinsert实例的流体样式,该实例允许您链接所有配置方法。以下示例仅使用一个配置方法(稍后我们将显示多个方法的示例):

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(3);
        parameters.put("id", actor.getId());
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        insertActor.execute(parameters);
    }

    // ... additional methods
}

这里使用的execute方法将纯java.util.map作为其唯一参数。这里需要注意的重要一点是,用于映射的键必须与数据库中定义的表的列名匹配。这是因为我们读取元数据来构造实际的insert语句。

3.6.2. Retrieving Auto-generated Keys by Using SimpleJdbcInsert

使用simplejdbinsert检索自动生成的密钥

下一个示例与前面的示例使用相同的insert,但它不传递ID,而是检索自动生成的键并将其设置在新的actor对象上。当它创建simplejdbinsert时,除了指定表名之外,它还使用usingGeneratedKeyColumns方法指定生成的键列的名称。下面的列表显示了它的工作原理:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}

使用第二种方法运行插入时的主要区别是,不向映射中添加ID,而是调用ExecuteAndReturnkey方法。这将返回java.lang.number对象,您可以使用该对象创建域类中使用的数值类型的实例。这里不能依赖所有数据库返回特定的Java类。java.lang.number是您可以依赖的基类。如果有多个自动生成的列,或者生成的值不是数字,则可以使用从ExecuteAndReturnKeyholder方法返回的密钥持有人。

3.6.3. Specifying Columns for a SimpleJdbcInsert

为SimpleJdbcInsert指定列

可以通过使用usingColumns方法指定列名称列表来限制插入的列,如下例所示:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingColumns("first_name", "last_name")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        Map<String, Object> parameters = new HashMap<String, Object>(2);
        parameters.put("first_name", actor.getFirstName());
        parameters.put("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}

插入的执行与您依赖元数据确定要使用的列相同。

3.6.4. Using SqlParameterSource to Provide Parameter Values

使用SqlParameterSource提供参数值

使用map提供参数值很好,但它不是最方便使用的类。Spring提供了两个sqlparametersource接口的实现,您可以使用它们来代替。第一个是beanpropertysqlparametersource,如果您有一个JavaBean兼容的包含值的类,那么这是一个非常方便的类。它使用相应的getter方法提取参数值。下面的示例演示如何使用BeanPropertySQLParameterSource:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}

另一个选项是mapsqlparametersource,它类似于一个映射,但提供了一个更方便的可链接的addvalue方法。下面的示例演示如何使用它:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcInsert insertActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.insertActor = new SimpleJdbcInsert(dataSource)
                .withTableName("t_actor")
                .usingGeneratedKeyColumns("id");
    }

    public void add(Actor actor) {
        SqlParameterSource parameters = new MapSqlParameterSource()
                .addValue("first_name", actor.getFirstName())
                .addValue("last_name", actor.getLastName());
        Number newId = insertActor.executeAndReturnKey(parameters);
        actor.setId(newId.longValue());
    }

    // ... additional methods
}

如您所见,配置是相同的。只有执行代码必须更改才能使用这些可选的输入类。

3.6.5. Calling a Stored Procedure with SimpleJdbcCall

使用SimpleJdbcCall调用存储过程

SimpleJDBCCall类使用数据库中的元数据来查找输入和输出参数的名称,这样就不必显式地声明它们。如果您喜欢这样做,或者如果您没有对Java类进行自动映射的参数(如数组或结构),则可以声明参数。第一个示例显示了一个简单的过程,该过程只从MySQL数据库返回varchar和date格式的标量值。示例过程读取指定的actor条目,并以out参数的形式返回first_name、last_name和birth_date列。下面的列表显示了第一个示例:

CREATE PROCEDURE read_actor (
    IN in_id INTEGER,
    OUT out_first_name VARCHAR(100),
    OUT out_last_name VARCHAR(100),
    OUT out_birth_date DATE)
BEGIN
    SELECT first_name, last_name, birth_date
    INTO out_first_name, out_last_name, out_birth_date
    FROM t_actor where id = in_id;
END;

in_id参数包含正在查找的参与者的ID。out参数返回从表中读取的数据。

可以用与声明SimpleJDBCInsert类似的方式声明SimpleJDBCCall。您应该在数据访问层的初始化方法中实例化和配置类。与storedprocedure类相比,不需要创建子类,也不需要声明可以在数据库元数据中查找的参数。下面的SimpleJDBCCall配置示例使用前面的存储过程(除了数据源之外,唯一的配置选项是存储过程的名称):

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        this.procReadActor = new SimpleJdbcCall(dataSource)
                .withProcedureName("read_actor");
    }

    public Actor readActor(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        Map out = procReadActor.execute(in);
        Actor actor = new Actor();
        actor.setId(id);
        actor.setFirstName((String) out.get("out_first_name"));
        actor.setLastName((String) out.get("out_last_name"));
        actor.setBirthDate((Date) out.get("out_birth_date"));
        return actor;
    }

    // ... additional methods
}

为执行调用而编写的代码涉及创建SqlParameterSource 包含IN参数的代码。您必须将为输入值提供的名称与存储过程中声明的参数名称的名称相匹配。该案例不必匹配,因为您使用元数据来确定应如何在存储过程中引用数据库对象。存储过程的源中指定的内容不一定是它存储在数据库中的方式。某些数据库将名称转换为全部大写,而其他数据库使用小写或使用指定的大小写。

execute方法接受in参数并返回一个映射,该映射包含由存储过程中指定的名称键控的任何out参数。在这种情况下,它们是out_first_name, out_last_name, and out_birth_date。

execute方法的最后一部分创建一个actor实例,用于返回检索到的数据。同样,在存储过程中声明out参数时,必须使用这些参数的名称。另外,结果映射中存储的out参数的名称与数据库中的out参数的名称相匹配,这可能在数据库之间有所不同。为了使代码更易于移植,您应该进行不区分大小写的查找,或者指示Spring使用LinkedcaseInsensitiveMap。要执行后者,可以创建自己的JDBCTemplate,并将setResultsMaxAsPersistent属性设置为true。然后您可以将这个定制的JDBCTemplate实例传递到simpleJDBCCall的构造函数中。以下示例显示了此配置:

public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_actor");
    }

    // ... additional methods
}

通过执行此操作,可以避免在用于返回参数名称的情况下发生冲突。

3.6.6. Explicitly Declaring Parameters to Use for a SimpleJdbcCall

显式声明用于SimpleJDBCCall的参数

在本章的前面,我们描述了如何从元数据中推导参数,但是如果您愿意,可以显式地声明它们。您可以通过使用declareParameters方法创建和配置SimpleJDBCCall来实现这一点,declareParameters方法将数量可变的sqlparameter对象作为输入。有关如何定义sqlparameter的详细信息,请参阅下一节。

如果您使用的数据库不是支持Spring的数据库,则需要显式声明。目前,Spring支持对以下数据库的存储过程调用的元数据查找:ApacheDerby、DB2、MySQL、Microsoft SQL Server、Oracle和Sybase。我们还支持为MySQL、Microsoft SQL Server和Oracle查找存储函数的元数据。

您可以选择显式声明一个、一些或所有参数。参数元数据仍在不显式声明参数的情况下使用。若要绕过对潜在参数的元数据查找的所有处理,并且仅使用声明的参数,可以在声明的一部分调用带有OutprocedureColumnMetadataAccess的方法。假设为数据库函数声明了两个或多个不同的调用签名。在这种情况下,您可以调用useinParameternames来指定要为给定签名包含的In参数名称列表。

下面的示例显示完全声明的过程调用,并使用前面示例中的信息:

public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadActor;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_actor")
                .withoutProcedureColumnMetaDataAccess()
                .useInParameterNames("in_id")
                .declareParameters(
                        new SqlParameter("in_id", Types.NUMERIC),
                        new SqlOutParameter("out_first_name", Types.VARCHAR),
                        new SqlOutParameter("out_last_name", Types.VARCHAR),
                        new SqlOutParameter("out_birth_date", Types.DATE)
                );
    }

    // ... additional methods
}

两个示例的执行和最终结果相同。第二个示例显式地指定所有细节,而不是依赖元数据。

3.6.7. How to Define SqlParameters

如何定义SqlParameters

要为SimpleJdbc 类定义参数,也可以为RDBMS操作类定义一个参数(在JDBC操作中被建模为Java对象),可以使用Sql参数或它的子类之一。为此,通常在构造函数中指定参数名和SQL类型。SQL类型是通过使用java.sql.types常量指定的。在本章的前面,我们看到了类似于以下内容的声明:

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

带有sqlparameter的第一行声明了一个in参数。通过使用sqlquery及其子类(在理解sqlquery中介绍),可以在参数中同时用于存储过程调用和查询。

第二行(使用sqlOutParameter)声明要在存储过程调用中使用的out参数。还有一个用于inout参数的sqlinoutparameter(为过程提供in值并返回值的参数)。

只有声明为sqlparameter和sqlinoutparameter的参数用于提供输入值。这与StoredProcedure类不同,后者(出于向后兼容性原因)允许为声明为sqlOutParameter的参数提供输入值。

对于In参数,除了名称和SQL类型之外,还可以为数字数据指定比例,或者为自定义数据库类型指定类型名称。对于out参数,可以提供一个rowmapper来处理从引用光标返回的行的映射。另一个选项是指定一个sqlReturnType,它提供了一个定义返回值自定义处理的机会。

3.6.8. Calling a Stored Function by Using SimpleJdbcCall

使用SimpleJdbcCall调用存储函数

调用存储函数的方式与调用存储过程的方式几乎相同,只是提供了函数名而不是过程名。将WithFunctionName方法用作配置的一部分,以指示要调用函数,并生成函数调用的相应字符串。专门的执行调用(ExecuteFunction)用于执行函数,它将函数返回值作为指定类型的对象返回,这意味着您不必从结果映射中检索返回值。对于只有一个out参数的存储过程,也可以使用类似的方便方法(名为executeObject)。以下示例(对于MySQL)基于名为get_actor_name的存储函数,该函数返回actor的全名:

CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
    DECLARE out_name VARCHAR(200);
    SELECT concat(first_name, ' ', last_name)
        INTO out_name
        FROM t_actor where id = in_id;
    RETURN out_name;
END;

要调用此函数,我们再次在初始化方法中创建simplejdbcall,如下示例所示:

public class JdbcActorDao implements ActorDao {

    private JdbcTemplate jdbcTemplate;
    private SimpleJdbcCall funcGetActorName;

    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
                .withFunctionName("get_actor_name");
    }

    public String getActorName(Long id) {
        SqlParameterSource in = new MapSqlParameterSource()
                .addValue("in_id", id);
        String name = funcGetActorName.executeFunction(String.class, in);
        return name;
    }

    // ... additional methods
}

所用的executeFunction方法返回一个字符串,该字符串包含函数调用的返回值。

3.6.9. Returning a ResultSet or REF Cursor from a SimpleJdbcCall

从SimpleJdbcCall返回ResultSet或REF游标

调用返回结果集的存储过程或函数有点棘手。一些数据库在JDBC结果处理期间返回结果集,而其他数据库则需要显式注册特定类型的out参数。这两种方法都需要额外的处理来循环结果集并处理返回的行。使用simplejdbcall,可以使用returningresultset方法并声明要用于特定参数的rowmapper实现。如果在结果处理过程中返回结果集,则没有定义名称,因此返回的结果必须与您声明rowmapper实现的顺序匹配。指定的名称仍用于将已处理的结果列表存储在从execute语句返回的结果映射中。

下一个示例(对于mysql)使用一个存储过程,该过程不接受任何输入参数,并返回t_actor表中的所有行:

CREATE PROCEDURE read_all_actors()
BEGIN
 SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;

要调用此过程,可以声明rowmapper。因为要映射到的类遵循JavaBean规则,所以可以使用BeanPropertyRowMapper,它是通过在NewInstance方法中传入要映射到的所需类而创建的。以下示例显示了如何执行此操作:

public class JdbcActorDao implements ActorDao {

    private SimpleJdbcCall procReadAllActors;

    public void setDataSource(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        jdbcTemplate.setResultsMapCaseInsensitive(true);
        this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
                .withProcedureName("read_all_actors")
                .returningResultSet("actors",
                BeanPropertyRowMapper.newInstance(Actor.class));
    }

    public List getActorsList() {
        Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
        return (List) m.get("actors");
    }

    // ... additional methods
}

execute调用传入空映射,因为此调用不接受任何参数。然后从结果映射中检索参与者列表并返回给调用者。

3.7. Modeling JDBC Operations as Java Objects

将JDBC操作建模为Java对象

object包包含的类可以让您以更面向对象的方式访问数据库。例如,您可以执行查询并将结果作为包含业务对象的列表返回,其中关系列数据映射到业务对象的属性。还可以运行存储过程并运行update、delete和insert语句。

许多Spring开发人员相信下面描述的各种RDBMS操作类(storedProcedure类除外)通常可以用直接的JDBCTemplate调用替换。通常,编写直接在JDBCTemplate上调用方法的DAO方法比较简单(而不是将查询封装为完整的类)。

但是,如果您从使用RDBMS操作类中获得了可测量的价值,那么您应该继续使用这些类。

3.7.1. Understanding SqlQuery

SqlQuery 是一个可重用的线程安全类,它封装了一个SQL查询。子类必须实现NewRowMapper(…)方法以提供RowMapper实例,该实例可以为每行创建一个对象,该对象是通过在执行查询期间对结果集进行迭代获得的。SqlQuery 类很少直接使用,因为MappingSqlQuery 子类为将行映射到Java类提供了更为方便的实现。扩展sqlquery的其他实现包括mappingsqlquerywithparameters和UpdatableSqlQuery。

3.7.2. Using MappingSqlQuery

mappingsqlquery是一个可重用的查询,其中具体的子类必须实现抽象maprow(…)方法,以将所提供结果集的每一行转换为指定类型的对象。下面的示例显示了一个自定义查询,该查询将来自t参与者关系的数据映射到参与者类的实例:

public class ActorMappingQuery extends MappingSqlQuery<Actor> {

    public ActorMappingQuery(DataSource ds) {
        super(ds, "select id, first_name, last_name from t_actor where id = ?");
        declareParameter(new SqlParameter("id", Types.INTEGER));
        compile();
    }

    @Override
    protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
        Actor actor = new Actor();
        actor.setId(rs.getLong("id"));
        actor.setFirstName(rs.getString("first_name"));
        actor.setLastName(rs.getString("last_name"));
        return actor;
    }

}

类扩展了用actor类型参数化的mappingsqlquery。此客户查询的构造函数将数据源作为唯一参数。在这个构造函数中,您可以使用应该执行的数据源和SQL来调用超类的构造函数来检索这个查询的行。此SQL用于创建PreparedStatement,因此它可能包含要在执行期间传入的任何参数的占位符。必须使用传入sqlparameter的declareparameter方法声明每个参数。sqlparameter采用java.sql.types中定义的名称和JDBC类型。定义完所有参数后,可以调用compile()方法,以便可以准备语句并稍后运行。这个类在编译后是线程安全的,因此,只要这些实例是在初始化DAO时创建的,它们就可以作为实例变量保存并重用。下面的示例说明如何定义此类:

private ActorMappingQuery actorMappingQuery;

@Autowired
public void setDataSource(DataSource dataSource) {
    this.actorMappingQuery = new ActorMappingQuery(dataSource);
}

public Customer getCustomer(Long id) {
    return actorMappingQuery.findObject(id);
}

上一个示例中的方法检索具有作为唯一参数传入的ID的客户。因为我们只希望返回一个对象,所以我们使用id作为参数调用findObject便利方法。如果我们有一个返回对象列表并接受其他参数的查询,那么我们将使用一个execute方法,它接受作为varargs传入的参数值数组。下面的示例显示了这种方法:

public List<Actor> searchForActors(int age, String namePattern) {
    List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
    return actors;
}

3.7.3. Using SqlUpdate

sql update类封装了一个SQL更新。与查询一样,更新对象是可重用的,并且,与所有RDBMsoperation类一样,更新可以有参数,并在SQL中定义。此类提供了许多update(…)方法,类似于查询对象的Execute(…)方法。sqlupdate类是具体的。例如,可以将其子类-添加自定义更新方法。但是,您不必对sqlupdate类进行子类化,因为通过设置SQL和声明参数可以很容易地对其进行参数化。以下示例创建名为Execute的自定义更新方法:

import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;

public class UpdateCreditRating extends SqlUpdate {

    public UpdateCreditRating(DataSource ds) {
        setDataSource(ds);
        setSql("update customer set credit_rating = ? where id = ?");
        declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
        declareParameter(new SqlParameter("id", Types.NUMERIC));
        compile();
    }

    /**
     * @param id for the Customer to be updated
     * @param rating the new value for credit rating
     * @return number of rows updated
     */
    public int execute(int id, int rating) {
        return update(rating, id);
    }
}

3.7.4. Using StoredProcedure

storedprocedure类是RDBMS存储过程的对象抽象的超类。这个类是抽象的,它的各种execute(…)方法都有受保护的访问权限,除了通过提供更严格类型的子类之外,其他的方法都无法使用。

继承的SQL属性是RDBMS中存储过程的名称。

要为storedprocedure类定义参数,可以使用sqlparameter或其子类之一。必须在构造函数中指定参数名和SQL类型,如下代码段所示:

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),

SQL类型是使用java.sql.types常量指定的。

第一行(使用sqlparameter)声明了一个in参数。您可以在参数中同时用于存储过程调用和使用sqlquery及其子类(在理解sqlquery中介绍)的查询。

第二行(使用sqlOutParameter)声明要在存储过程调用中使用的out参数。还有一个用于inout参数的sqlinoutparameter(为过程提供in值并返回值的参数)。

对于In参数,除了名称和SQL类型之外,还可以为数字数据指定比例,或者为自定义数据库类型指定类型名称。对于out参数,可以提供一个rowmapper来处理从引用光标返回的行的映射。另一个选项是指定一个sqlReturnType,用于定义返回值的自定义处理。

简单DAO的下一个示例使用storedprocedure调用任何Oracle数据库附带的函数(sysdate())。要使用存储过程功能,必须创建一个扩展StoredProcedure的类。在这个例子中,storedprocedure类是一个内部类。但是,如果需要重用storedprocedure,可以将其声明为顶级类。此示例没有输入参数,但使用sqlOutParameter类将输出参数声明为日期类型。execute()方法运行该过程并从结果映射中提取返回的日期。结果映射通过使用参数名称作为键为每个声明的输出参数(在本例中,只有一个)提供一个条目。下面的列表显示了我们的自定义storedprocedure类:

import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class StoredProcedureDao {

    private GetSysdateProcedure getSysdate;

    @Autowired
    public void init(DataSource dataSource) {
        this.getSysdate = new GetSysdateProcedure(dataSource);
    }

    public Date getSysdate() {
        return getSysdate.execute();
    }

    private class GetSysdateProcedure extends StoredProcedure {

        private static final String SQL = "sysdate";

        public GetSysdateProcedure(DataSource dataSource) {
            setDataSource(dataSource);
            setFunction(true);
            setSql(SQL);
            declareParameter(new SqlOutParameter("date", Types.DATE));
            compile();
        }

        public Date execute() {
            // the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
            Map<String, Object> results = execute(new HashMap<String, Object>());
            Date sysdate = (Date) results.get("date");
            return sysdate;
        }
    }

}

下面的storedprocedure示例有两个输出参数(在本例中是Oracle Ref Cursors):

import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class TitlesAndGenresStoredProcedure extends StoredProcedure {

    private static final String SPROC_NAME = "AllTitlesAndGenres";

    public TitlesAndGenresStoredProcedure(DataSource dataSource) {
        super(dataSource, SPROC_NAME);
        declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
        declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
        compile();
    }

    public Map<String, Object> execute() {
        // again, this sproc has no input parameters, so an empty Map is supplied
        return super.execute(new HashMap<String, Object>());
    }
}

请注意,在titlesAndGenRestoredProcedure构造函数中使用的declareParameter(…)方法的重载变量是如何传递RowMapper实现实例的。这是一种非常方便和强大的重用现有功能的方法。接下来的两个例子为两个rowmapper实现提供了代码。

TitleMapper类为提供的结果集中的每一行将结果集映射到标题域对象,如下所示:

import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;

public final class TitleMapper implements RowMapper<Title> {

    public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
        Title title = new Title();
        title.setId(rs.getLong("id"));
        title.setName(rs.getString("name"));
        return title;
    }
}

genremapper类为提供的结果集中的每一行将结果集映射到一个流派域对象,如下所示:

import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;

public final class GenreMapper implements RowMapper<Genre> {

    public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
        return new Genre(rs.getString("name"));
    }
}

要将参数传递给在RDBMS中定义有一个或多个输入参数的存储过程,可以编写一个强类型的execute(…)方法,该方法将委托给超类中的非类型化execute(map)方法,如下例所示:

import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;

public class TitlesAfterDateStoredProcedure extends StoredProcedure {

    private static final String SPROC_NAME = "TitlesAfterDate";
    private static final String CUTOFF_DATE_PARAM = "cutoffDate";

    public TitlesAfterDateStoredProcedure(DataSource dataSource) {
        super(dataSource, SPROC_NAME);
        declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
        declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
        compile();
    }

    public Map<String, Object> execute(Date cutoffDate) {
        Map<String, Object> inputs = new HashMap<String, Object>();
        inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
        return super.execute(inputs);
    }
}

3.8. Common Problems with Parameter and Data Value Handling

参数和数据值处理的常见问题
Spring框架的JDBC支持提供的不同方法中存在参数和数据值的常见问题。本节介绍如何解决这些问题。

3.8.1. Providing SQL Type Information for Parameters

提供参数的SQL类型信息
通常,Spring根据传入的参数类型确定参数的SQL类型。可以显式提供设置参数值时要使用的SQL类型。这有时是正确设置空值所必需的。

您可以通过多种方式提供SQL类型信息:

  • JDBCTemplate的许多更新和查询方法都采用int数组形式的附加参数。此数组用于通过使用java.sql.types类中的常量值指示相应参数的SQL类型。为每个参数提供一个条目。
  • 可以使用sqlparameterValue类包装需要此附加信息的参数值。为此,请为每个值创建一个新实例,并传入SQL类型和构造函数中的参数值。还可以为数值提供可选的比例参数。
  • 对于使用命名参数的方法,可以使用sqlparametersource类、beanpropertysqlparametersource或mapsqlparametersource。它们都有为任何命名参数值注册SQL类型的方法。

3.8.2. Handling BLOB and CLOB objects

处理BLOB和CLOB对象

您可以在数据库中存储图像、其他二进制数据和大文本块。这些大对象称为二进制数据的BLOB(二进制大对象)和字符数据的CLOB(字符大对象)。在Spring中,您可以直接使用JDBCTemplate来处理这些大型对象,也可以在使用RDBMS对象和SimpleJDBC类提供的更高抽象时处理这些大型对象。所有这些方法都使用lobhandler接口的实现来实际管理lob(大对象)数据。LobHandler通过getLobCreator方法提供对LobCreator类的访问,该方法用于创建要插入的新Lob对象。

LobCreator和LobHandler为Lob输入和输出提供以下支持:

  • BLOB
    byte[]: getBlobAsBytes and setBlobAsBytes
    InputStream: getBlobAsBinaryStream and setBlobAsBinaryStream
  • CLOB
    String: getClobAsString and setClobAsString
    InputStream: getClobAsAsciiStream and setClobAsAsciiStream
    Reader: getClobAsCharacterStream and setClobAsCharacterStream

下一个示例演示如何创建和插入blob。稍后我们将演示如何从数据库中读取它。

此示例使用JDBCTemplate和AbstractLobCreatingPreparedStatementCallback的实现。它实现了一个方法,setvalues。此方法提供了一个lobcreator,我们使用它来设置SQL INSERT语句中lob列的值。

对于这个例子,我们假设有一个变量lobhandler,它已经设置为一个defaultlobhandler的实例。通常通过依赖注入来设置这个值。

下面的示例演示如何创建和插入blob:

final File blobIn = new File("spring2004.jpg");
final InputStream blobIs = new FileInputStream(blobIn);
final File clobIn = new File("large.txt");
final InputStream clobIs = new FileInputStream(clobIn);
final InputStreamReader clobReader = new InputStreamReader(clobIs);

jdbcTemplate.execute(
    "INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
    new AbstractLobCreatingPreparedStatementCallback(lobHandler) {  
        protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
            ps.setLong(1, 1L);
            lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length());  
            lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length());  
        }
    }
);

blobIs.close();
clobReader.close();

如果在从defaultLobHandler.getLobCreator()返回的LobCreator上调用setBlobAsBinaryStream、setClobAscIIstream或setClobAsCharacterstream方法,则可以选择为ContentLength参数指定负值。如果指定的内容长度为负数,则DefaultLobHandler使用不带长度参数的set-stream方法的JDBC 4.0变体。否则,它会将指定的长度传递给驱动程序。
请参阅用于验证它是否支持流式处理LOB而不提供内容长度的JDBC驱动程序的文档。

现在是时候从数据库中读取LOB数据了。同样,使用具有相同实例变量lobhandler和对defaultlobhandler的引用的JDBCTemplate。以下示例显示了如何执行此操作:

List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
    new RowMapper<Map<String, Object>>() {
        public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
            Map<String, Object> results = new HashMap<String, Object>();
            String clobText = lobHandler.getClobAsString(rs, "a_clob");  
            results.put("CLOB", clobText);
            byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob");  
            results.put("BLOB", blobBytes);
            return results;
        }
    });

3.8.3. Passing in Lists of Values for IN Clause

传入IN子句的值列表

SQL标准允许基于包含变量值列表的表达式选择行。一个典型的例子是select*from t_actor,其中id在(1,2,3)中。JDBC标准不直接支持此变量列表用于准备好的语句。不能声明数量可变的占位符。您需要根据所需的准备好的占位符数量进行一些变化,或者在知道需要多少个占位符后,需要动态生成SQL字符串。在NamedParameterJDBCTemplate和JDBCTemplate中提供的命名参数支持采用后一种方法。您可以将这些值作为基元对象的java.util.list传入。此列表用于在语句执行期间插入所需的占位符并传递值。

传递多个值时要小心。JDBC标准并不保证可以对表达式列表使用超过100个值。各种数据库都超过了这个数字,但它们通常对允许的值有一个硬限制。例如,Oracle的限制是1000。

除了值列表中的原语值之外,还可以创建对象数组的java.util.list。此列表可以支持为in子句定义的多个表达式,例如select * from T_ACTOR where (id, last_name) in ((1, ‘Johnson’), (2, ‘Harrop’))。当然,这要求数据库支持这种语法。

3.8.4. Handling Complex Types for Stored Procedure Calls

处理存储过程调用的复杂类型

调用存储过程时,有时可以使用特定于数据库的复杂类型。为了适应这些类型,Spring提供了一个sqlReturnType,用于在从存储过程调用返回它们时对它们进行处理,以及在将它们作为参数传入存储过程时对它们进行处理。

sqlReturnType接口只有一个必须实现的方法(名为gettypeValue)。此接口用作sqlOutParameter声明的一部分。以下示例显示返回用户声明类型item_type的Oracle结构对象的值:

public class TestItemStoredProcedure extends StoredProcedure {

    public TestItemStoredProcedure(DataSource dataSource) {
        ...
        declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
            new SqlReturnType() {
                public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType, String typeName) throws SQLException {
                    STRUCT struct = (STRUCT) cs.getObject(colIndx);
                    Object[] attr = struct.getAttributes();
                    TestItem item = new TestItem();
                    item.setId(((Number) attr[0]).longValue());
                    item.setDescription((String) attr[1]);
                    item.setExpirationDate((java.util.Date) attr[2]);
                    return item;
                }
            }));
        ...
    }

您可以使用SqlTypeValue将Java对象(例如TestItem)的值传递给存储过程。sqltypeValue接口有一个必须实现的方法(名为createTypeValue)。活动连接被传入,您可以使用它来创建特定于数据库的对象,例如structDescriptor实例或arrayDescriptor实例。下面的示例创建一个结构描述符实例:

final TestItem testItem = new TestItem(123L, "A test item",
        new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
        Struct item = new STRUCT(itemDescriptor, conn,
        new Object[] {
            testItem.getId(),
            testItem.getDescription(),
            new java.sql.Date(testItem.getExpirationDate().getTime())
        });
        return item;
    }
};

现在可以将此sqltypeValue添加到包含存储过程的执行调用输入参数的映射中。

sqltypeValue的另一个用途是将值数组传递给Oracle存储过程。Oracle有自己的内部数组类,在这种情况下必须使用,并且可以使用SqlTypeValue 创建Oracle数组的实例并用Java数组的值填充它,如下示例显示:

final Long[] ids = new Long[] {1L, 2L};

SqlTypeValue value = new AbstractSqlTypeValue() {
    protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
        ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
        ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
        return idArray;
    }
};

3.9. Embedded Database Support

嵌入式数据库支持
org.springframework.jdbc.datasource.embedded软件包为嵌入式Java数据库引擎提供支持。本机提供对HSQL, H2和Derby的支持。您还可以使用可扩展API来插入新的嵌入式数据库类型和 DataSource实现

3.9.1. Why Use an Embedded Database?

为何使用嵌入式数据库?
嵌入式数据库在项目的开发阶段非常有用,因为它具有轻量级特性。优点包括易于配置,快速启动时间,可测试性以及在开发过程中快速发展SQL的能力。

3.9.2. Creating an Embedded Database by Using Spring XML

使用Spring XML创建嵌入式数据库
如果要在SpringApplicationContext中将嵌入式数据库实例公开为bean,可以使用SpringJDBC命名空间中的嵌入式数据库标记:

<jdbc:embedded-database id="dataSource" generate-name="true">
    <jdbc:script location="classpath:schema.sql"/>
    <jdbc:script location="classpath:test-data.sql"/>
jdbc:embedded-database>

前面的配置创建了一个嵌入的hsql数据库,该数据库由类路径根目录中的schema.sql和test-data.sql资源中的sql填充。此外,作为最佳实践,嵌入式数据库被分配一个唯一生成的名称。嵌入式数据库作为javax.sql.datasource类型的bean提供给Spring容器,然后可以根据需要注入到数据访问对象中。

3.9.3. Creating an Embedded Database Programmatically

以编程方式创建嵌入式数据库
EmbeddedDatabaseBuilder类为以编程方式构建嵌入式数据库提供了一个流畅的API。当需要在独立环境或独立集成测试中创建嵌入式数据库时,可以使用此方法,如下例所示:

EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
        .generateUniqueName(true)
        .setType(H2)
        .setScriptEncoding("UTF-8")
        .ignoreFailedDrops(true)
        .addScript("schema.sql")
        .addScripts("user_data.sql", "country_data.sql")
        .build();

// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)

db.shutdown()

有关所有支持的选项的详细信息,请参阅JavaDoc for EmbeddedDatabaseBuilder。

还可以使用EmbeddedDatabaseBuilder通过使用Java配置创建嵌入式数据库,如下示例显示:

@Configuration
public class DataSourceConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(H2)
                .setScriptEncoding("UTF-8")
                .ignoreFailedDrops(true)
                .addScript("schema.sql")
                .addScripts("user_data.sql", "country_data.sql")
                .build();
    }
}

3.9.4. Selecting the Embedded Database Type

本节介绍如何选择Spring支持的三个嵌入式数据库之一。它包括以下主题:

  • 使用HSQL
    spring支持HSQL 1.8.0及以上。如果没有显式指定类型,那么hsql是默认的嵌入式数据库。要显式指定hsql,请将嵌入数据库标记的type属性设置为hsql。如果使用builder API,请使用embeddeddatabasetype.hsql调用settype(embeddeddatabasetype)方法。

  • 使用H2
    Spring支持h2数据库。要启用h2,请将嵌入数据库标记的type属性设置为h2。如果使用Builder API,请使用embeddedDatabaseType.h2调用setType(embeddedDatabaseType)方法。

  • 使用Derby
    Spring支持Apache Derby 10.5及更高版本。要启用Derby,请将嵌入数据库标记的type属性设置为Derby。如果使用Builder API,请使用EmbeddedDatabaseType.derby调用SetType(EmbeddedDatabaseType)方法。

3.9.5. Testing Data Access Logic with an Embedded Database

使用嵌入式数据库测试数据访问逻辑

嵌入式数据库提供了一种测试数据访问代码的轻量级方法。下一个示例是使用嵌入式数据库的数据访问集成测试模板。当嵌入的数据库不需要在测试类之间重用时,使用这样的模板可以一次性使用。但是,如果希望创建在测试套件中共享的嵌入式数据库,请考虑使用Spring TestContext框架,并在Spring应用程序上下文中将嵌入式数据库配置为bean,如使用Spring XML创建嵌入式数据库和以编程方式创建嵌入式数据库中所述。下面的列表显示了测试模板:

public class DataAccessIntegrationTestTemplate {

    private EmbeddedDatabase db;

    @Before
    public void setUp() {
        // creates an HSQL in-memory database populated from default scripts
        // classpath:schema.sql and classpath:data.sql
        db = new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .addDefaultScripts()
                .build();
    }

    @Test
    public void testDataAccess() {
        JdbcTemplate template = new JdbcTemplate(db);
        template.query( /* ... */ );
    }

    @After
    public void tearDown() {
        db.shutdown();
    }

}

3.9.6. Generating Unique Names for Embedded Databases

为嵌入式数据库生成唯一名称

如果开发团队的测试套件无意中尝试重新创建同一数据库的其他实例,则开发团队经常会遇到嵌入数据库的错误。如果XML配置文件或@configuration类负责创建嵌入式数据库,然后在同一个测试套件(即在同一个JVM进程中)内的多个测试场景中重用相应的配置,则很容易发生这种情况-例如,针对嵌入式数据库的集成测试hose-applicationContext配置仅在bean定义概要文件处于活动状态时有所不同。

这种错误的根本原因是Spring EmbeddedDatabaseFactory(由jdbc:embedded-databaseXML命名空间元素和EmbeddedDatabaseBuilderJava配置在 内部使用)设置嵌入式数据库的名称( testdb如果没有另外指定)。对于这种情况jdbc:embedded-database,嵌入式数据库通常被赋予一个等于bean的名称id(通常是类似的dataSource)。因此,后续创建嵌入式数据库的尝试不会产生新的数据库。相反,相同的JDBC连接URL被重用,并且尝试创建新的嵌入式数据库实际上指向从相同配置创建的现有嵌入式数据库。

为了解决这个常见问题,SpringFramework4.2支持为嵌入式数据库生成唯一的名称。要启用使用生成的名称,请使用以下选项之一。

  • EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()
  • EmbeddedDatabaseBuilder.generateUniqueName()

3.9.7. Extending the Embedded Database Support

扩展嵌入式数据库支持

您可以通过两种方式扩展SpringJDBC嵌入式数据库支持:

  • 实现EmbeddedDatabaseConfigurer以支持新的嵌入式数据库类型。
  • 实现DataSourceFactory以支持新的数据源实现,例如用于管理嵌入式数据库连接的连接池。

我们鼓励您在jira.spring.io为Spring社区提供扩展。

3.10. Initializing a DataSource

初始化DataSource
org.springframework.jdbc.datasource.init包提供对初始化现有数据源的支持。嵌入式数据库支持提供了一个为应用程序创建和初始化数据源的选项。但是,有时可能需要初始化在某个服务器上运行的实例。

3.10.1. Initializing a Database by Using Spring XML

使用Spring XML初始化数据库
如果要初始化数据库,并且可以提供对数据源bean的引用,则可以使用spring-jdbc命名空间中的initialize-database标记:

<jdbc:initialize-database data-source="dataSource">
    <jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
    <jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
jdbc:initialize-database>

前面的示例针对数据库运行两个指定的脚本。第一个脚本创建一个模式,第二个脚本用测试数据集填充表。脚本位置也可以是具有通配符的模式,这些通配符采用通常的Ant样式,用于Spring中的资源(例如,classpath*:/com/foo/**/sql/*-data.sql)。如果使用模式,脚本将按其URL或文件名的词法顺序运行。

数据库初始值设定项的默认行为是无条件地运行提供的脚本。例如,如果您针对一个已经包含测试数据的数据库运行脚本,那么这可能并不总是您想要的-。通过遵循先创建表然后插入数据的常见模式(如前所示),可以降低意外删除数据的可能性。如果表已经存在,第一步将失败。

但是,为了更好地控制现有数据的创建和删除,XML名称空间提供了一些额外的选项。第一个是打开和关闭初始化的标志。您可以根据环境(例如从系统属性或环境bean中提取布尔值)设置此值。以下示例从系统属性获取值:

<jdbc:initialize-database data-source="dataSource"
    enabled="#{systemProperties.INITIALIZE_DATABASE}"> 
    <jdbc:script location="..."/>
jdbc:initialize-database>

控制现有数据的第二个选择是对故障更加宽容。为此,可以控制初始值设定项忽略从脚本执行的SQL中某些错误的能力,如下示例所示:

<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
    <jdbc:script location="..."/>
jdbc:initialize-database>

在前面的示例中,我们说,我们希望有时脚本针对空数据库运行,因此脚本中有一些DROP语句会失败。因此,失败的SQL DROP语句将被忽略,但其他失败将导致异常。如果您的SQL方言不支持DROP…(如果存在(或类似),但您希望在重新创建之前无条件删除所有测试数据,则此功能非常有用。在这种情况下,第一个脚本通常是一组drop语句,后跟一组create语句。

ignore-failures选项可以设置为NONE (the default), DROPS (ignore failed drops), or ALL (ignore all failures).

每个语句应该用分隔;如果脚本中根本不存在;字符,则用新行分隔。您可以全局控制它,也可以通过脚本控制它,如下示例所示:

<jdbc:initialize-database data-source="dataSource" separator="@@"> 
    <jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> 
    <jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
    <jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
jdbc:initialize-database>

在本例中,两个测试数据脚本使用@@作为语句分隔符,只有db-schema.sql使用;。此配置指定默认分隔符为@@,并重写DB架构脚本的默认分隔符。

如果您需要比从XML命名空间获得更多的控件,那么可以直接使用DataSourceInitializer,并将其定义为应用程序中的组件。

依赖数据库的其他组件的初始化

一大类应用程序(在Spring上下文启动之前不使用数据库的应用程序)可以使用数据库初始值设定项,而不会有进一步的复杂。如果您的应用程序不是其中之一,您可能需要阅读本节的其余部分。

数据库初始值设定项依赖于数据源实例并运行其初始化回调中提供的脚本(类似于XML bean定义中的init方法、组件中的@postconstruct方法或实现initialingbean的组件中的afterpropertieset()方法)。如果其他be an依赖于同一个数据源并在初始化回调中使用该数据源,则可能存在问题,因为数据尚未初始化。这方面的一个常见例子是缓存,它在应用程序启动时急切地初始化并从数据库加载数据。

为了解决这个问题,您有两个选项:将缓存初始化策略更改为稍后的阶段,或者确保首先初始化数据库初始值设定项。

如果应用程序在您的控制范围内而不是其他方式,则更改缓存初始化策略可能很容易。关于如何实现这一点的一些建议包括:

  • 使缓存在第一次使用时进行延迟初始化,从而缩短应用程序启动时间。
  • 使用缓存或单独的组件初始化缓存实现生命周期或SmartLifecycle。当应用程序上下文启动时,可以通过设置SmartLifecycle的自动启动标志来自动启动它,并且可以通过在封闭上下文上调用configurableApplicationContext.start()来手动启动生命周期。
  • 使用SpringApplicationEvent或类似的自定义观察器机制触发缓存初始化。ContextRefreshedEvent总是在准备好使用时由上下文发布(在所有bean都已初始化之后),因此这通常是一个有用的钩子(默认情况下,这是SmartLifecycle的工作方式)。

确保首先初始化数据库初始值设定项也很容易。关于如何实现这一点的一些建议包括:

  • 依赖于SpringBeanFactory的默认行为,即按照注册顺序初始化bean。通过采用XML配置中的一组元素的常见做法,您可以很容易地安排这些元素,这些元素对应用程序模块进行排序,并确保首先列出数据库和数据库初始化。
  • 分离数据源和使用它的业务组件,并通过将它们放入单独的ApplicationContext实例(例如,父上下文包含数据源,子上下文包含业务组件)来控制它们的启动顺序。这种结构在SpringWeb应用程序中很常见,但可以更广泛地应用。

4. Object Relational Mapping (ORM) Data Access

对象关系映射(ORM)数据访问

4.1. Introduction to ORM with Spring

Spring框架支持与Java持久化API(JPA)的集成,并支持本地Hibernate用于资源管理、数据访问对象(DAO)实现和事务策略。例如,对于Hibernate,有一流的支持和几个方便的IOC功能,可以解决许多典型的Hibernate集成问题。您可以通过依赖注入为或(对象关系)映射工具配置所有支持的功能。它们可以参与Spring的资源和事务管理,并符合Spring的通用事务和DAO异常层次结构。建议的集成样式是针对纯Hibernate或JPA API编写DAO代码。

当您创建数据访问应用程序时,Spring为您选择的ORM层添加了重要的增强功能。您可以尽可能多地利用集成支持,并且应该将此集成工作与内部构建类似基础设施的成本和风险进行比较。无论使用何种技术,您都可以像使用库一样使用ORM支持,因为所有东西都是作为一组可重用的JavaBean设计的。SpringIOC容器中的ORM便于配置和部署。因此,本节中的大多数示例都显示了spring容器内的配置。

使用Spring框架创建ORM DAO的好处包括:

  • 更容易测试。Spring的IoC方法可以轻松交换Hibernate SessionFactory实例,JDBC DataSource 实例,事务管理器和映射对象实现(如果需要)的实现和配置位置。这反过来使得单独测试每个与持久性相关的代码变得更加容易。
  • 常见数据访问异常。Spring可以从ORM工具中包装异常,将它们从专有(可能已检查)的异常转换为公共运行时 DataAccessException层次结构。此功能允许您处理大多数不可恢复的持久性异常,仅在适当的层中处理,而不会产生令人讨厌的样板捕获,抛出和异常声明。您仍然可以根据需要捕获和处理异常。请记住,JDBC异常(包括特定于DB的方言)也会转换为相同的层次结构,这意味着您可以在一致的编程模型中使用JDBC执行某些操作。
  • 一般资源管理。Spring应用程序上下文可以处理Hibernate会话工厂实例、JPA EntityManagerFactory实例、JDBC数据源实例和其他相关资源的位置和配置。这使得这些值易于管理和更改。Spring提供了高效、简单和安全的持久性资源处理。例如,使用Hibernate的相关代码通常需要使用相同的Hibernate会话来确保效率和正确的事务处理。Spring通过Hibernate会话工厂公开当前会话,使创建会话和将会话透明地绑定到当前线程变得容易。因此,Spring为任何本地或JTA事务环境解决了典型的Hibernate使用的许多慢性问题。
  • 集成的事务管理。您可以通过@transactional注释或通过在XML配置文件中显式配置事务AOP建议,使用声明性面向方面编程(AOP)风格的方法拦截器包装ORM代码。在这两种情况下,事务语义和异常处理(回滚等)都是为您处理的。正如在资源和事务管理中讨论的,您还可以交换各种事务管理器,而不影响与ORM相关的代码。例如,您可以在本地事务和JTA之间进行交换,在这两个场景中都可以使用相同的完整服务(如声明性事务)。此外,与JDBC相关的代码可以与用于执行ORM的代码进行事务性的完全集成。这对于不适合ORM(如批处理和BLOB流)但仍需要与ORM操作共享公共事务的数据访问很有用。

要获得更全面的ORM支持,包括对其他数据库技术(如MongoDB)的支持,您可能需要查看项目的Spring数据套件。如果您是JPA用户,那么https://spring.io的jpa指南提供了一个很好的介绍。

4.2. General ORM Integration Considerations

一般ORM集成注意事项
本节重点介绍适用于所有ORM技术的注意事项。Hibernate部分提供了更多的细节,并在具体的上下文中显示了这些特性和配置。

Spring的ORM集成的主要目标是清晰的应用程序分层(使用任何数据访问和事务技术),以及应用程序对象的松散耦合-不再依赖于数据访问或事务策略的业务服务,不再进行硬编码的资源查找,不再难以替换单例,不再自定义服务注册。其目标是使用一种简单而一致的方法来连接应用程序对象,使它们尽可能地可重用并且不受容器依赖性的影响。所有单独的数据访问特性都可以单独使用,但是可以很好地与Spring的应用程序上下文概念集成,提供基于XML的配置和不需要感知Spring的纯JavaBean实例的交叉引用。在典型的Spring应用程序中,许多重要的对象是JavaBeans:数据访问模板、数据访问对象、事务管理器、使用数据访问对象和事务管理器的业务服务、Web视图解析器、使用业务服务的Web控制器等。

4.2.1. Resource and Transaction Management

典型的业务应用程序被重复的资源管理代码弄得一团糟。许多项目试图发明自己的解决方案,有时为了编程方便而牺牲对故障的正确处理。Spring提倡使用简单的解决方案来正确地处理资源,即IOC,在JDBC的情况下通过模板化和将AOP拦截器应用于ORM技术。

基础结构提供适当的资源处理和特定API异常到未检查的基础结构异常层次结构的适当转换。Spring引入了一个DAO异常层次结构,适用于任何数据访问策略。对于直接JDBC,前一节中提到的JDBCTemplate类提供连接处理和将sqlException正确转换为dataAccessException层次结构,包括将特定于数据库的SQL错误代码转换为有意义的异常类。对于ORM技术,请参阅下一节,了解如何获得相同的异常转换好处。

当涉及到事务管理时,JDBCTemplate类会挂接到Spring事务支持,并通过各自的Spring事务管理器支持JTA和JDBC事务。对于支持的ORM技术,Spring通过Hibernate和JPA事务管理器以及JTA支持提供Hibernate和JPA支持。有关事务支持的详细信息,请参阅事务管理章节。

4.2.2. Exception Translation

在DAO中使用Hibernate或JPA时,必须决定如何处理持久性技术的本机异常类。DAO根据技术抛出HibernateException或PersistenceException的子类。这些异常都是运行时异常,不必声明或捕获。您可能还必须处理IllegalArgumentException和IllegalStateException。这意味着调用方只能将异常视为一般致命的异常,除非他们希望依赖于持久性技术自己的异常结构。如果不将调用者与实现策略联系起来,就不可能捕获特定的原因(例如乐观锁定失败)。这种权衡对于那些基于ORM的应用程序或者不需要任何特殊的异常处理(或者两者都需要)的应用程序来说是可以接受的。但是,Spring允许通过@repository注释透明地应用异常转换。下面的示例(一个Java配置和一个XML配置)演示了如何做到这一点:

@Repository
public class ProductDaoImpl implements ProductDao {

    // class body here...

}

//xml
<beans>

    <!-- Exception translation bean post processor -->
    <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

</beans>

后处理器自动查找所有异常转换器(PersistenceExceptionTranslator接口的实现),并通知所有标记有@Repository注释的bean,以便发现的转换器可以截获并对引发的异常应用适当的转换。

总之,您可以基于普通持久性技术的API和注释实现DAO,同时还可以从Spring管理的事务、依赖项注入和透明异常转换(如果需要)到Spring的自定义异常层次结构中获益。

4.3. Hibernate

我们从Spring环境中的Hibernate5开始,用它来演示Spring集成或映射器的方法。本节详细介绍了许多问题,并展示了DAO实现和事务划分的不同变体。这些模式中的大多数可以直接转换为所有其他支持的ORM工具。本章后面的部分将介绍其他ORM技术,并给出简单的示例。

从SpringFramework5.0开始,Spring需要HibernateORM4.3或更高版本来支持JPA,甚至需要HibernateORM5.0+来针对本机HibernateSessionAPI编程。请注意,Hibernate团队不再维护5.1之前的任何版本,很可能很快就会专门关注5.3+。

4.3.1. SessionFactory Setup in a Spring Container

Spring容器中的SessionFactory设置
为了避免将应用程序对象绑定到硬编码的资源查找,可以将资源(如JDBC数据源或Hibernate会话工厂)定义为Spring容器中的bean。需要访问资源的应用程序对象通过bean引用接收对此类预定义实例的引用,如下一节中的DAO定义所示。

XML应用程序上下文定义的以下摘录显示了如何在其上设置JDBC数据源和Hibernate会话工厂:

<beans>

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
        <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    bean>

    <bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource"/>
        <property name="mappingResources">
            <list>
                <value>product.hbm.xmlvalue>
            list>
        property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            value>
        property>
    bean>

beans>

从本地Jakarta Commons DBCP切换BasicDataSource到位于JNDI DataSource(通常由应用程序服务器管理)只是配置问题,如下例所示:

<beans>
    <jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
beans>

You can also access a JNDI-located SessionFactory, using Spring’s JndiObjectFactoryBean / 来检索和公开位于JNDI的sessionFactory。但是,这在EJB上下文之外通常不常见。

pring还提供了localsessionfactorybuilder变体,与@bean风格的配置和编程设置无缝集成(不涉及factorybean)。

localsessionfactorybean和localsessionfactorybuilder都支持后台引导,其中Hibernate初始化与给定引导执行器(如simpleAsyncTaskExecutor)上的应用程序引导线程并行运行。在localsessionfactorybean上,这可以通过bootstrapexecutor属性获得。在编程的localsessionactorybuilder上,有一个重载的buildsessionFactory方法,它接受引导执行器参数。

从SpringFramework5.1开始,这样的本地Hibernate设置还可以为本地Hibernate访问旁边的标准JPA交互公开JPA EntityManagerFactory。有关详细信息,请参阅JPA的本机休眠设置。

4.3.2. Implementing DAOs Based on the Plain Hibernate API

基于Plain Hibernate API实现DAO

Hibernate有一个称为上下文会话的功能,其中Hibernate本身为每个事务管理一个当前会话。这大致相当于每个事务一个休眠会话的Spring同步。基于纯Hibernate API,相应的DAO实现类似于以下示例:

public class ProductDaoImpl implements ProductDao {

    private SessionFactory sessionFactory;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    public Collection loadProductsByCategory(String category) {
        return this.sessionFactory.getCurrentSession()
                .createQuery("from test.Product product where product.category=?")
                .setParameter(0, category)
                .list();
    }
}

这种风格类似于Hibernate参考文档和示例,除了在实例变量中保存sessionFactory。我们强烈建议在Hibernate的CaveAtemptor示例应用程序中,使用这种基于实例的设置来替代旧的静态HibernateUtil类。(通常,除非绝对必要,否则不要将任何资源保存在静态变量中。)

前面的DAO示例遵循依赖注入模式。它非常适合于一个SpringIOC容器,如果根据Spring的HibernateTemplate进行编码的话。您还可以在普通Java中设置这样的DAO(例如,在单元测试中)。为此,请实例化它并使用所需的工厂引用调用SetSessionFactory(…)。作为一个SpringBean定义,DAO类似于以下内容:

<beans>

    <bean id="myProductDao" class="product.ProductDaoImpl">
        <property name="sessionFactory" ref="mySessionFactory"/>
    bean>

beans>

这种DAO风格的主要优点是它只依赖于Hibernate API。不需要导入任何spring类。从非侵入性的角度来看,这是很有吸引力的,对于休眠non-invasiveness开发人员来说,这可能感觉更自然。

但是,DAO抛出普通的HibernateException(未选中,因此不必声明或捕获),这意味着调用方只能将异常视为一般致命的-,除非它们希望依赖于Hibernate自己的异常层次结构。如果不将调用者与实现策略联系起来,就不可能捕获特定的原因(例如乐观锁定失败)。这种权衡可能是可以接受的应用程序是强烈的休眠基础,不需要任何特殊的异常处理,或两者兼而有之。

幸运的是,Spring的localsessionFactoryBean支持Hibernate的sessionFactory.getcurrentSession()方法用于任何Spring事务策略,返回当前Spring管理的事务会话,即使使用HibernateTransactionManager也是如此。该方法的标准行为仍然是返回与正在进行的JTA事务相关联的当前会话(如果有的话)。无论您是使用Spring的JTatransactionManager、EJB容器管理事务(CMT)还是JTA,这种行为都适用。

总之,您可以基于普通的HibernateAPI实现DAOS,同时仍然能够参与Spring管理的事务。

4.3.3. Declarative Transaction Demarcation

声明性事务划分

我们建议您使用Spring的声明式事务支持,它允许您使用AOP事务拦截器替换Java代码中的显式事务划分API调用。您可以使用Java注释或XML在Spring容器中配置此事务拦截器。这种声明式事务功能使您可以保持业务服务不受重复的事务划分代码的影响,并专注于添加业务逻辑,这是您的应用程序的真正价值。

您可以用@transactional annotations注释服务层,并指示Spring容器查找这些注释,并为这些注释方法提供事务语义。以下示例显示了如何执行此操作:

public class ProductServiceImpl implements ProductService {

    private ProductDao productDao;

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    @Transactional
    public void increasePriceOfAllProductsInCategory(final String category) {
        List productsToChange = this.productDao.loadProductsByCategory(category);
        // ...
    }

    @Transactional(readOnly = true)
    public List<Product> findAllProducts() {
        return this.productDao.findAllProducts();
    }

}

在容器中,您需要设置PlatformTransactionManager实现(作为bean)和一个条目,并在运行时选择进入@transactional processing。以下示例显示了如何执行此操作:


<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="transactionManager"
            class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    bean>

    <tx:annotation-driven/>

    <bean id="myProductService" class="product.SimpleProductService">
        <property name="productDao" ref="myProductDao"/>
    bean>

beans>

4.3.4. Programmatic Transaction Demarcation

程序化事务划分

您可以在应用程序的更高级别划分事务,在跨越任意数量操作的较低级别数据访问服务之上。也不存在对周围业务服务实现的限制。它只需要一个SpringPlatformTransactionManager。同样,后者可以来自任何地方,但最好是通过setTransactionManager(…)方法作为bean引用。另外,productDAO应该由setProductDAO(…)方法设置。下面这对代码片段显示了Spring应用程序上下文中的事务管理器和业务服务定义,以及业务方法实现的示例:

<beans>

    <bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="mySessionFactory"/>
    bean>

    <bean id="myProductService" class="product.ProductServiceImpl">
        <property name="transactionManager" ref="myTxManager"/>
        <property name="productDao" ref="myProductDao"/>
    bean>

beans>
public class ProductServiceImpl implements ProductService {

    private TransactionTemplate transactionTemplate;
    private ProductDao productDao;

    public void setTransactionManager(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public void setProductDao(ProductDao productDao) {
        this.productDao = productDao;
    }

    public void increasePriceOfAllProductsInCategory(final String category) {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            public void doInTransactionWithoutResult(TransactionStatus status) {
                List productsToChange = this.productDao.loadProductsByCategory(category);
                // do the price increase...
            }
        });
    }
}

Spring的TransactionInterceptor允许使用回调代码引发任何已检查的应用程序异常,而TransactionTemplate仅限于回调中未检查的异常。如果出现未选中的应用程序异常或事务仅由应用程序标记为回滚(通过设置TransactionStatus),TransactionTemplate将触发回滚。默认情况下,TransactionInterceptor的行为方式相同,但允许每个方法使用可配置的回滚策略。

4.3.5. Transaction Management Strategies

事务管理策略

TransactionTemplate和TransactionInterceptor都将实际事务处理委托给PlatformTransactionManager实例(可以是HibernateTransactionManager(对于单个Hibernate会话工厂),方法是使用hood下的ThreadLocal会话)或用于Hibernate 的JTatTransactionManager(委托给容器的JTA子系统)。甚至可以使用自定义的PlatformTransactionManager实现。从本机Hibernate事务管理切换到JTA(例如,当面临应用程序某些部署的分布式事务需求时)只是一个配置问题。您可以用Spring的JTA事务实现替换Hibernate事务管理器。事务划分和数据访问代码都可以不做任何更改,因为它们使用通用事务管理API。

对于跨多个休眠会话工厂的分布式事务,可以将JTatransactionManager作为事务策略与多个本地会话factorybean定义相结合。然后,每个DAO将获得一个特定的sessionFactory引用,并将其传递到相应的bean属性中。如果所有底层JDBC数据源都是事务容器,那么业务服务可以在任意数量的DAO和任意数量的会话工厂之间划分事务,而无需特别考虑,只要它使用JTatransactionManager作为策略。

HibernateTransactionManager和JTatTransactionManager都允许使用Hibernate进行适当的JVM级缓存处理,而不需要特定于容器的事务管理器查找或JCA连接器(如果不使用EJB启动事务)。

HibernateTransactionManager可以将Hibernate JDBC连接导出到特定数据源的纯JDBC访问代码。如果您只访问一个数据库,此功能允许在不使用JTA的情况下完全使用混合休眠和JDBC数据访问进行高级事务划分。如果通过localsessionfactorybean类的datasource属性设置了带有数据源的传入sessionFactory,则hibernateTransactionManager会自动将hibernate事务公开为JDBC事务。或者,可以通过HibernateTransactionManager类的DataSource属性显式指定事务应该为其公开的数据源。

4.3.6. Comparing Container-managed and Locally Defined Resources

比较容器管理和本地定义的资源

您可以在容器管理的JNDI会话工厂和本地定义的工厂之间切换,而无需更改一行应用程序代码。无论是在容器中保留资源定义,还是在应用程序中本地保留资源定义,这主要取决于您使用的事务策略。与Spring定义的本地会话工厂相比,手动注册的JNDI会话工厂没有任何好处。通过Hibernate的JCA连接器部署SessionFactory 工厂提供了参与JavaEE服务器管理基础设施的附加值,但没有添加超出该值的实际值。

Spring的事务支持未绑定到容器。当配置了JTA以外的任何策略时,事务支持也可以在独立或测试环境中工作。特别是在单数据库事务的典型情况下,Spring的单资源本地事务支持是JTA的一个轻量级和强大的替代方案。当您使用本地EJB无状态会话bean来驱动事务时,您既依赖于EJB容器,也依赖于JTA,即使您只访问单个数据库,并且只使用无状态会话bean通过容器管理的事务来提供声明性事务。直接使用JTA也需要一个JavaEE环境。就JTA本身和JNDI数据源实例而言,JTA并不只涉及容器依赖性。对于非Spring、JTA驱动的Hibernate事务,必须使用Hibernate JCA连接器或额外的Hibernate事务代码,并将TransactionManagerLookup配置为适当的JVM级缓存。

Spring驱动的事务也可以与本地定义的Hibernate会话工厂一起工作,就像处理本地JDBC数据源一样,前提是它们访问单个数据库。因此,只有当您有分布式事务需求时,才需要使用Spring的JTA事务策略。JCA连接器首先需要特定于容器的部署步骤和(显然)JCA支持。与部署具有本地资源定义和Spring驱动的事务的简单Web应用程序相比,此配置需要更多的工作。此外,如果使用不提供JCA的WebLogicExpress,则通常需要容器的企业版。具有本地资源和跨单个数据库事务的Spring应用程序在任何JavaEE Web容器(没有JTA、JCA或EJB)中工作,例如Tomcat、树脂或甚至纯Jetty。此外,您可以在桌面应用程序或测试套件中轻松地重用这样一个中间层。

考虑到所有的问题,如果您不使用EJB,请遵循本地会话工厂设置和Spring的HibernateTransactionManager或JTatTransactionManager。您可以获得所有的好处,包括适当的事务性JVM级缓存和分布式事务,而不会给容器部署带来不便。通过JCA连接器对Hibernate会话工厂的JNDI注册只会在与EJB结合使用时增加值。

4.3.7. Spurious Application Server Warnings with Hibernate

使用Hibernate 虚拟的应用程序服务器警告

在一些具有非常严格的xadatasource实现的JTA环境中(目前只有一些WebLogic服务器和WebSphere版本),当配置Hibernate时,不考虑该环境的JTA PlatformTransactionManager对象,应用程序服务器日志中可能会显示虚假警告或异常。这些警告或异常表示正在访问的连接不再有效,或者JDBC访问不再有效,可能是因为事务不再活动。例如,WebLogic中的一个实际异常:

java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
further JDBC access is allowed within this transaction.

您可以通过使Hibernate知道JTA PlatformTransactionManager实例来解决此警告,该实例与Spring同步。执行此操作有两个选项:

  • 如果在应用程序上下文中,您已经直接获取jta PlatformTransactionManager对象(可能是从jndi通过jndiobjectFactoryBean或)并将其馈送给Spring的JtaTransactionManager,则最简单的方法是指定对定义此jta PlatformTransactionManager实例的bean的引用。并作为localsessionfactorybean的jtatransactionManager属性的值。然后,Spring使对象可用于Hibernate。
  • 更可能的是,您还没有JTA PlatformTransactionManager实例,因为Spring的JTatransactionManager可以自己找到它。因此,您需要配置Hibernate来直接查找JTA PlatformTransactionManager。您可以通过在Hibernate配置中配置特定于应用程序服务器的TransactionManagerLookup类来实现这一点,如Hibernate手册中所述。

本节的其余部分描述了在Hibernate意识到或没有意识到JTA PlatformTransactionManager的情况下发生的事件序列。

当Hibernate未配置为具有任何JTA PlatformTransactionManager意识时,当JTA事务提交时会发生以下事件:

  • JTA事务提交。
  • Spring的JTatransactionManager与JTA事务同步,因此由JTA事务管理器通过完成后回调进行回调callback 。
  • 在其他活动中,此同步可以通过Hibernate的AfterTransactionCompletion回调(用于清除Hibernate缓存)触发到Hibernate的回调,然后在Hibernate会话上执行显式close()调用,这将导致Hibernate尝试close()JDBC连接。
  • 在某些环境中,此connection.close()调用随后会触发警告或错误,因为应用程序服务器不再认为该连接可用,因为事务已提交。

当Hibernate配置为了解JTA PlatformTransactionManager时,当JTA事务提交时会发生以下事件:

  • JTA事务已准备好提交。
  • Spring的JTatransactionManager与JTA事务同步,因此该事务通过JTA事务管理器在完成之前的回调进行回调。
  • Spring知道Hibernate本身与JTA事务是同步的,其行为与前面的场景不同。假设Hibernate会话需要完全关闭,那么Spring现在将关闭它。
  • JTA事务提交。
  • Hibernate与JTA事务同步,因此该事务通过JTA事务管理器的完成后回调进行回调,并可以正确地清除其缓存。

4.4. JPA

The Spring JPA, available under the org.springframework.orm.jpa package
它以类似于与Hibernate集成的方式提供对Java持久化API的全面支持,同时了解底层实现以提供附加功能。

4.4.1. Three Options for JPA Setup in a Spring Environment

SpringJPA支持提供了三种方法来设置应用程序用于获取实体管理器的JPA EntityManagerFactory。

Using LocalEntityManagerFactoryBean

您只能在简单的部署环境(如独立应用程序和集成测试)中使用此选项。

LocalEntityManagerFactoryBean创建一个EntityManagerFactory,它适用于应用程序仅使用JPA进行数据访问的简单部署环境。工厂bean使用JPA持久性提供程序自动检测机制(根据JPA的JavaSE引导),并且在大多数情况下,要求只指定持久性单元名称。下面的XML示例配置这样的bean:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
        <property name="persistenceUnitName" value="myPersistenceUnit"/>
    bean>
beans>

这种形式的JPA部署是最简单和最有限的。您不能引用现有的JDBC数据源bean定义,并且不支持全局事务。此外,持久类的编织(字节代码转换)是特定于提供程序的,通常需要在启动时指定特定的JVM代理。此选项仅适用于设计了JPA规范的独立应用程序和测试环境。

从JNDI获取EntityManagerFactory

部署到JavaEE服务器时可以使用此选项。检查服务器的文档,了解如何将自定义JPA提供程序部署到服务器中,从而允许使用与服务器默认设置不同的提供程序。

从JNDI(例如,在JavaEE环境中)获得一个EntityManagerFactory ,这是一个改变XML配置的问题,如下面的例子所示:

<beans>
    <jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
beans>

这个动作假定标准的JavaEE引导。JavaEE Server自动检测持久性单元(实际上,在应用程序JAR中的 META-INF/persistence.xml文件)和JavaEE部署描述符(例如Web.xml)中的持久性单元REF条目,并为这些持久性单元定义环境命名上下文位置。

在这种情况下,整个持久单元部署,包括持久类的编织(字节代码转换),都取决于JavaEE服务器。JDBC数据源是通过META-INF/persistence.xml文件中的JNDI位置定义的。EntityManager事务与服务器的JTA子系统集成。Spring只使用获得的EntityManagerFactory,通过依赖注入和管理持久性单元的事务(通常通过JTatransactionManager)将其传递给应用程序对象。

如果在同一个应用程序中使用多个持久性单元,则此类JNDI检索到的持久性单元的bean名称应与应用程序用来引用它们的持久性单元名称相匹配(例如,在@persistenceUnit和@persistenceContext注释中)。

Using LocalContainerEntityManagerFactoryBean

在基于Spring的应用程序环境中,可以将此选项用于完整的JPA功能。这包括Web容器,如Tomcat、独立应用程序和具有复杂持久性需求的集成测试。

如果要专门配置Hibernate设置,直接的替代方法是使用Hibernate 5.2或5.3,并设置本地Hibernate LocalSessionFactoryBean而不是普通的JPA LocalContainerEntityManagerFactoryBean,使其与JPA访问代码以及本地Hibernate访问代码交互。有关详细信息,请参阅JPA交互的本机Hibernate 设置。

LocalContainerEntityManagerFactoryBean提供对EntityManagerFactory配置的完全控制,并且适用于需要细粒度定制的环境。LocalContainerEntityManagerFactoryBean基于persistence.xml文件、提供的DataSourceLookup策略和指定的LoadTimeWeaver创建PersistenceUnitInfo实例。因此,可以在JNDI之外使用自定义数据源并控制编织过程。以下示例显示了LocalContainerEntityManagerFactoryBean的典型bean定义:

<beans>
    <bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="someDataSource"/>
        <property name="loadTimeWeaver">
            <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
        property>
    bean>
beans>

以下示例显示了一个典型的persistence.xml文件:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
        <mapping-file>META-INF/orm.xmlmapping-file>
        <exclude-unlisted-classes/>
    persistence-unit>
persistence>

/快捷方式表示不应该扫描带注释的实体类。显式的“true”值(/true/)也表示没有扫描。/false确实触发扫描。但是,如果希望进行实体类扫描,建议省略exclude-unlisted-classes元素。

使用LocalContainerEntityManagerFactoryBean是最强大的JPA设置选项,允许在应用程序中进行灵活的本地配置。它支持到现有JDBC数据源的链接,支持本地和全局事务,等等。但是,它还对运行时环境施加了要求,例如,如果持久性提供程序需要字节代码转换,则可以使用具有编织功能的类加载器。

此选项可能与JavaEE服务器的内置JPA功能冲突。在一个完整的JavaEE环境中,考虑从JNDI获取您的EntityManagerFactory 工厂。或者,在LocalContainerEntityManagerFactoryBean定义(例如,META-INF/my-persistence.xml)上指定一个自定义PersistenceXmlLocation,并在应用程序JAR文件中仅包含具有该名称的描述符。因为JavaEE服务器只查找默认的META-INF/persistence.xml文件,因此忽略了这样的自定义持久化单元,因此避免了与弹簧驱动的JPA设置的冲突。(例如,这适用于Resin 3.1。)

什么时候需要装载时间编织?
并非所有JPA提供程序都需要一个JVM代理。Hibernate就是一个不这样的例子。如果您的提供程序不需要代理,或者您有其他选择,例如通过自定义编译器或Ant任务在构建时应用增强功能,则不应使用加载时weaver。

loadTimeweaver接口是一个Spring提供的类,它允许JPA ClassTransformer实例以特定的方式插入,这取决于环境是Web容器还是应用程序服务器。通过代理连接ClassTransformer通常是不有效的。代理针对整个虚拟机工作,并检查加载的每个类,这在生产服务器环境中通常是不需要的。

Spring为各种环境提供了许多loadTimeweaver实现,使ClassTransformer实例仅适用于每个类加载器,而不适用于每个VM。

有关LoadTimeweaver实现及其设置的更多信息,请参阅AOP一章中的Spring配置,这些实现可以是通用的,也可以是针对各种平台(如Tomcat、Weblogic、Glassfish、Resin和JBoss)定制的。

如Spring配置中所述,您可以使用context:load-time-weaver xml元素的@enableLoadTimeweaving注释来配置上下文范围的LoadTimeweaver。这样的全局weaver将自动被所有JPA本地容器EntityManagerFactoryBean实例接收。以下示例显示了设置加载时weaver、自动检测平台(weblogic、glassfish、tomcat、resin、jboss或vm agent)以及将weaver自动传播到所有weaver-aware bean的首选方法:

<context:load-time-weaver/>
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    ...
bean>

但是,如果需要,可以通过loadtimeweaver属性手动指定专用的weaver,如下示例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
    property>
bean>

无论LTW是如何配置的,通过使用此技术,依赖于检测的JPA应用程序都可以在目标平台(例如Tomcat)中运行,而不需要代理。当宿主应用程序依赖于不同的JPA实现时,这一点尤其重要,因为JPA转换器仅在类加载器级别应用,因此彼此隔离。

处理多个持久性单元

对于依赖多个持久性单元位置的应用程序(例如,存储在类路径中的各种JAR中),Spring提供了PersistenceUnitManager作为中央存储库,并避免了昂贵的持久性单元发现过程。默认实现允许指定多个位置。这些位置将被解析,然后通过持久性单元名进行检索。(默认情况下,将在类路径中搜索META-INF/persistence.xml文件。)以下示例配置多个位置:

<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
    <property name="persistenceXmlLocations">
        <list>
            <value>org/springframework/orm/jpa/domain/persistence-multi.xmlvalue>
            <value>classpath:/my/package/**/custom-persistence.xmlvalue>
            <value>classpath*:META-INF/persistence.xmlvalue>
        list>
    property>
    <property name="dataSources">
        <map>
            <entry key="localDataSource" value-ref="local-db"/>
            <entry key="remoteDataSource" value-ref="remote-db"/>
        map>
    property>
    
    <property name="defaultDataSource" ref="remoteDataSource"/>
bean>

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="pum"/>
    <property name="persistenceUnitName" value="myCustomUnit"/>
bean>

默认实现允许自定义PersistenceUnitInfo实例(在将其发送到JPA提供程序之前),可以声明方式(通过其属性,影响所有托管单元)也可以编程方式(通过PersistenceUnitPostProcessor,允许选择持久化单元)。如果未指定PersistenceUnitManager,则LocalContainerEntityManagerFactoryBean将在内部创建和使用一个。

后台引导

LocalContainerEntityManagerFactoryBean支持通过bootstrapexecutor属性进行后台引导,如下示例所示:

<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="bootstrapExecutor">
        <bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
    property>
bean>

实际的JPA提供程序引导被移交给指定的执行器,然后并行运行到应用程序引导线程。暴露的EntityManagerFactory代理可以注入到其他应用程序组件中,甚至能够响应EntityManagerFactoryInfo配置检查。但是,一旦其他组件(例如,调用CreateEntityManager)访问了实际的JPA提供程序,这些调用将一直阻塞,直到后台引导完成。特别是,当您使用SpringDataJPA时,请确保为其存储库设置延迟的引导。

4.4.2. Implementing DAOs Based on JPA: EntityManagerFactory and EntityManager

基于JPA实现DAO:EntityManagerFactory和EntityManager

尽管EntityManagerFactory实例是线程安全的,但EntityManager实例不是线程安全的。注入的JPA EntityManager的行为类似于从应用服务器的JNDI环境中获取的EntityManager,正如JPA规范所定义的那样。它将所有调用委托给当前事务实体管理器(如果有)。否则,它将返回到每个操作新创建的EntityManager,实际上使其使用线程安全。

通过使用注入的EntityManagerFactory或EntityManager,可以在不依赖Spring的情况下针对普通JPA编写代码。如果启用了PersistenceAnnotationBeanPostProcessor,Spring可以理解字段和方法级别的@PersistenceUnit和@PersistenceContext注释。以下示例显示了使用@persistenceUnit注释的纯JPA DAO实现:

public class ProductDaoImpl implements ProductDao {

    private EntityManagerFactory emf;

    @PersistenceUnit
    public void setEntityManagerFactory(EntityManagerFactory emf) {
        this.emf = emf;
    }

    public Collection loadProductsByCategory(String category) {
        EntityManager em = this.emf.createEntityManager();
        try {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
        finally {
            if (em != null) {
                em.close();
            }
        }
    }
}

前面的DAO不依赖于Spring,仍然很适合Spring应用程序上下文。此外,DAO利用注释来要求注入默认的EntityManagerFactory,如下面的bean定义示例所示:

<beans>

    
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

beans>

作为显式定义PersistenceAnnotationBeanPostProcessor的替代方法,请考虑在应用程序上下文配置中使用spring-context:annotation-config-xml元素。这样做会自动为基于注释的配置注册所有Spring标准后处理器,包括CommonAnnotationBeanPostProcessor等。

请考虑以下示例:

<beans>

    
    <context:annotation-config/>

    <bean id="myProductDao" class="product.ProductDaoImpl"/>

beans>

这种DAO的主要问题是它总是通过工厂创建一个新的EntityManager。您可以通过请求注入事务性EntityManager(也称为“共享EntityManager”,因为它是实际事务性EntityManager的共享线程安全代理)而不是工厂来避免此问题。以下示例显示了如何执行此操作:

public class ProductDaoImpl implements ProductDao {

    @PersistenceContext
    private EntityManager em;

    public Collection loadProductsByCategory(String category) {
        Query query = em.createQuery("from Product as p where p.category = :category");
        query.setParameter("category", category);
        return query.getResultList();
    }
}

@persistenceContext批注有一个名为type的可选属性,默认为persistenceContextType.Transaction。可以使用此默认值接收共享的EntityManager代理。另一个选项persistenceContextType.extended则完全不同。这会导致一个所谓的扩展EntityManager,它不是线程安全的,因此不能在并发访问的组件(如Spring管理的单例bean)中使用。扩展的EntityManager实例只应用于有状态的组件中,例如,这些组件位于会话中,EntityManager的生命周期与当前事务无关,而是完全取决于应用程序。

方法和字段级注入
可以对类-内的字段或方法应用指示依赖项注入(如@PersistenceUnit和@PersistenceContext)的注释,因此表达式为“方法级注入”和“字段级注入”。字段级注释简洁且易于使用,而方法级注释允许进一步处理注入的依赖项。在这两种情况下,成员可见性(公共、受保护或私有)并不重要。
那么类级注释呢?
在JavaEE平台上,它们用于依赖声明而不是用于资源注入。

注入的EntityManager是Spring管理的(知道正在进行的事务)。尽管新的DAO实现使用EntityManager而不是EntityManagerFactory的方法级注入,但是由于注释的使用,应用程序上下文XML中不需要进行任何更改。

这种DAO风格的主要优点是它只依赖于Java持久化API。不需要导入任何弹簧类。此外,正如JPA注释所理解的那样,Spring容器会自动应用注入。从非侵入性的角度来看,这是很有吸引力的,对于JPA开发人员来说,这更自然。

4.4.3. Spring-driven JPA transactions

Spring驱动的JPA事务
JPA的推荐策略是通过JPA的本地事务支持进行本地事务。Spring的jPatransactionManager针对任何常规的JDBC连接池(no XA requirement)提供了许多本地JDBC事务中已知的功能(例如事务特定的隔离级别和资源级别只读优化)。

SpringJPA还允许配置的JPatransactionManager向访问同一数据源的JDBC访问代码公开JPA事务,前提是注册的JPadialect支持检索基础JDBC连接。Spring为EclipseLink和Hibernate JPA实现提供了方言。有关jpadialect机制的详细信息,请参阅下一节。

作为一种直接的替代方法,Spring的本机HibernateTransactionManager能够与SpringFramework5.1和Hibernate5.2/5.3的JPA访问代码进行交互,以适应几种Hibernate特定情况并提供JDBC交互。这对于结合localsessionfactorybean设置特别有意义。有关详细信息,请参阅JPA交互的本机Hibernate 设置。

4.4.4. Understanding JpaDialect and JpaVendorAdapter

理解JpaDialect和JpaVendorAdapter

作为高级功能,jpatransactionmanager和abstractEntityManagerFactoryBean的子类允许将自定义jpadialect传递到jpadialect bean属性。jpadialect实现可以启用Spring支持的以下高级功能,通常是以特定于供应商的方式实现的:

  • 应用特定的事务语义(例如自定义隔离级别或事务超时)
  • 检索事务性JDBC连接(用于JDBC-based DAOs)
  • PersistenceExceptions到SpringDataAccessExceptions的高级转换

这对于特殊的事务语义和异常的高级翻译特别有价值。默认实现(defaultjpadialect)不提供任何特殊功能,如果需要前面列出的功能,则必须指定适当的方言。

作为一个更广泛的供应商适应设施,主要用于Spring的全功能本地ContainerEntityManagerFactoryBean设置,JPavendorAdapter将JPadialect的功能与其他特定于供应商的默认设置结合在一起。指定HibernatejPavendorAdapter或EclipseLinkJPavendorAdapter是分别为Hibernate或EclipseLink自动配置EntityManagerFactory设置的最方便的方法。请注意,这些提供程序适配器主要设计用于Spring驱动的事务管理(即,用于jPatransactionManager)。

请参阅jpadialect和jpavendoradapter javadoc,了解其操作的更多详细信息,以及如何在Spring的JPA支持中使用它们。

4.4.5. Setting up JPA with JTA Transaction Management

使用JTA事务管理设置JPA

Spring还可以通过JTA实现JavaEE环境下的多资源事务协调,或者与独立事务协调器(如AdomiKOS)进行多资源事务协调。除了选择Spring的JTatransactionManager而不是JPatransactionManager之外,您还需要采取以下几步:

  • 底层JDBC连接池需要支持XA并与事务协调器集成。在JavaEE环境中,这通常是简单的,通过JNDI公开不同类型的数据源。有关详细信息,请参阅应用程序服务器文档。类似地,独立事务协调器通常附带特殊的XA集成数据源实现。再次检查它的文档。
  • 需要为JTA配置JPA EntityManagerFactory设置。这是特定于提供程序的,通常通过在LocalContainerEntityManagerFactoryBean上指定为JPAProperties的特殊属性来实现。在休眠的情况下,这些属性甚至是特定于版本的。有关详细信息,请参阅休眠文档。
  • Spring的hibernatejpavendoradapter强制执行某些面向Spring的默认值,例如连接释放模式on close,它与Hibernate自己在Hibernate 5.0中的默认值匹配,但在5.1/5.2中不再匹配。对于JTA设置,请不要声明HibernatejPavendoradapter以开始,或关闭其PrepareConnection标志。或者,将hibernate 5.2的hibernate.connection.handling_-mode属性设置为delayed_acquisition_和release_after_语句,以恢复hibernate自己的默认值。有关WebLogic的相关说明,请参阅带有Hibernate的虚假应用程序服务器警告。
  • 或者,考虑从应用程序服务器本身获取EntityManagerFactory(即,通过JNDI查找而不是本地声明的LocalContainerEntityManagerFactoryBean)。服务器提供的EntityManagerFactory可能需要服务器配置中的特殊定义(使部署更不可移植),但它是为服务器的JTA环境设置的。

4.4.6. Native Hibernate Setup and Native Hibernate Transactions for JPA Interaction

用于JPA交互的本机Hibernate设置和本机Hibernate事务

从Spring Framework 5.1和Hibernate 5.2/5.3开始,本地的LocalSessionFactoryBean设置与HibernateTransactionManager结合使用,允许与@PersistenceContext和其他JPA访问代码交互。现在Hibernate会话工厂本机实现JPA的EntityManagerFactory接口,而Hibernate会话句柄本机是JPA EntityManager。Spring的JPA支持设施自动检测本机休眠会话。

因此,这种本地Hibernate设置可以在许多情况下替代标准的JPA LocalContainerEntityManagerFactoryBean和JPATransactionManager组合,允许在同一本地事务中与@PersistenceContext EntityManager旁边的sessionFactory.getcurrentSession()(以及HibernateTemplate)交互。在。这样的设置还提供了更强的Hibernate集成和更多的配置灵活性,因为它不受JPA引导契约的约束。

在这种情况下,您不需要HibernatejPavendoradapter配置,因为Spring的本机Hibernate安装程序提供了更多的功能(例如,自定义Hibernate Integrator安装程序、Hibernate 5.3 Bean容器集成以及更强大的只读事务优化)。最后,您还可以通过localsessionfactorybuilder表示本机休眠设置,与@bean风格的配置无缝集成(不涉及factorybean)。

LocalSessionFactoryBean和LocalSessionFactoryBuilder支持后台引导,就像JPA LocalContainerEntityManagerFactoryBean一样。有关介绍,请参阅后台引导。
在localsessionfactorybean上,这可以通过bootstrapexecutor属性获得。在编程的localsessionFactoryBuilder上,重载的buildsessionFactory方法采用引导执行器参数。

5. Marshalling XML by Using Object-XML Mappers

5.1. Introduction

本章介绍Spring的Object-XML Mapping支持。Object-XML Mapping(简称OX映射)是将XML文档转换为对象或从对象转换XML文档的行为。此转换过程也称为XML编组或XML序列化。本章可互换使用这些术语。

在OX映射领域,编组器负责将对象(图形)序列化为XML。以类似的方式,unmarshaller将XML反序列化为对象图。此XML可以采用DOM文档,输入或输出流或SAX处理程序的形式。

使用Spring满足O / X映射需求的一些好处是:

  • 易于配置
  • 一致的接口
  • 一致的异常层次结构

5.1.1. Ease of configuration

Spring的bean工厂可以轻松配置marshallers,而无需构建JAXB上下文,JiBX绑定工厂等。您可以像应用程序上下文中的任何其他bean一样配置marshallers。此外,基于XML命名空间的配置可用于许多marshallers,使配置更简单。

5.1.2. Consistent Interfaces

Spring的O-X映射通过两个全局接口进行操作:marshaller和unmarshaller。这些抽象使您能够相对轻松地切换O-X映射框架,而执行编组的类几乎不需要更改或不需要更改。这种方法还有一个额外的好处,那就是可以用混合匹配方法(例如,一些使用JAXB执行的编组,一些由Castor执行的编组)以非侵入式方式进行XML编组,从而使用每种技术的优势。

5.1.3. Consistent Exception Hierarchy

Spring提供了从底层O-X映射工具到自身异常层次结构的转换,其中xmlmappingexception作为根异常。这些运行时异常包装原始异常,这样就不会丢失任何信息。

5.2. Marshaller and Unmarshaller

marshaller将对象序列化为XML,而unmarshaller将XML流反序列化为对象。本节介绍用于此目的的两个Spring接口。

5.2.1. Understanding Marshaller

Spring抽象了org.springframework.oxm.Marshaller接口后面的所有编组操作 ,其主要方法如下:

public interface Marshaller {

    /**
     * Marshal the object graph with the given root into the provided Result.
     */
    void marshal(Object graph, Result result) throws XmlMappingException, IOException;
}

Marshaller 接口有一个主要方法,它将给定对象封送到给定的javax.xml.transform.result。其结果是一个标记接口,基本上表示一个XML输出抽象。具体实现包含各种XML表示,如下表所示:

Result implementation Wraps XML representation
DOMResult org.w3c.dom.Node
SAXResult org.xml.sax.ContentHandler
StreamResult java.io.File, java.io.OutputStream, or java.io.Writer

尽管该marshal()方法接受普通对象作为其第一个参数,但大多数 Marshaller实现都无法处理任意对象。相反,对象类必须映射到映射文件中,使用注释标记,向编组器注册或具有公共基类。请参阅本章后面的部分,以确定OX技术如何管理它。

5.2.2. Understanding Unmarshaller

与Marshaller类似,org.springframework.oxm.Unmarshaller接口

public interface Unmarshaller {

    /**
     * Unmarshal the given provided Source into an object graph.
     */
    Object unmarshal(Source source) throws XmlMappingException, IOException;
}

此接口还有一个方法,它从给定的 javax.xml.transform.Source(XML输入抽象)读取并返回读取的对象。与此一样Result,Source标记接口有三个具体实现。每个包装一个不同的XML表示,如下表所示:

Source implementation Wraps XML representation
DOMSource org.w3c.dom.Node
SAXSource org.xml.sax.InputSource, and org.xml.sax.XMLReader
StreamSource java.io.File, java.io.InputStream, or java.io.Reader

即使有两个单独的编组接口(Marshaller and Unmarshaller), Spring-WS 中的所有实现都在一个类中实现。这意味着您可以连接一个marshaller类,并在applicationcontext.xml中将其作为marshaller和unmarshaller引用。

5.2.3. Understanding XmlMappingException

Spring将基础OX映射工具中的异常转换为其自己的异常层次结构,并将其XmlMappingException作为根异常。这些运行时异常包装原始异常,因此不会丢失任何信息。

此外,marshalingfailureexception和unmarshalingfailureexception提供了对marshaling和unmarshalling操作的区分,即使底层的O-X映射工具没有这样做。

O-X映射异常层次结构如下图所示:
spring5.1.3使用篇-数据访问_第5张图片

5.3. Using Marshaller and Unmarshaller

您可以在各种情况下使用Spring的OXM。在下面的示例中,我们使用它将Spring管理的应用程序的设置封送为XML文件。在下面的示例中,我们使用一个简单的JavaBean来表示设置:

public class Settings {

    private boolean fooEnabled;

    public boolean isFooEnabled() {
        return fooEnabled;
    }

    public void setFooEnabled(boolean fooEnabled) {
        this.fooEnabled = fooEnabled;
    }
}

应用程序类使用这个bean来存储其设置。除了main方法之外,类还有两个方法:savesettings()将设置bean保存到名为settings.xml的文件中,loadsettings()再次加载这些设置。以下main()方法构造一个Spring应用程序上下文并调用这两个方法:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.oxm.Marshaller;
import org.springframework.oxm.Unmarshaller;

public class Application {

    private static final String FILE_NAME = "settings.xml";
    private Settings settings = new Settings();
    private Marshaller marshaller;
    private Unmarshaller unmarshaller;

    public void setMarshaller(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.unmarshaller = unmarshaller;
    }

    public void saveSettings() throws IOException {
        FileOutputStream os = null;
        try {
            os = new FileOutputStream(FILE_NAME);
            this.marshaller.marshal(settings, new StreamResult(os));
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }

    public void loadSettings() throws IOException {
        FileInputStream is = null;
        try {
            is = new FileInputStream(FILE_NAME);
            this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
        } finally {
            if (is != null) {
                is.close();
            }
        }
    }

    public static void main(String[] args) throws IOException {
        ApplicationContext appContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Application application = (Application) appContext.getBean("application");
        application.saveSettings();
        application.loadSettings();
    }
}

在Application同时需要marshaller和unmarshaller属性进行设置。我们可以使用以下applicationContext.xml方法配置:

<beans>
    <bean id="application" class="Application">
        <property name="marshaller" ref="castorMarshaller" />
        <property name="unmarshaller" ref="castorMarshaller" />
    bean>
    <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>
beans>

此应用程序上下文使用Castor,但我们可以使用本章后面介绍的任何其他marshaller实例。请注意,默认情况下,Castor不需要任何进一步的配置,因此bean定义相当简单。还要注意的是, CastorMarshaller同时实现了Marshaller和Unmarshaller,因此我们可以参考 castorMarshaller bean在两个marshaller和unmarshaller应用程序的性能。

此示例应用程序生成以下settings.xml文件:


<settings foo-enabled="false"/>

5.4. XML Configuration Namespace

XML配置命名空间

您可以使用OXM命名空间中的标记更简洁地配置marshallers。要使这些标记可用,必须首先在XML配置文件的前导中引用相应的模式。以下示例显示了如何执行此操作:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:oxm="http://www.springframework.org/schema/oxm" 
xsi:schemaLocation="http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm.xsd"> 

目前,架构使以下元素可用:

  • jaxb2-marshaller
  • jibx-marshaller
  • castor-marshaller

每个标签都在其各自的编组部分中进行了解释。但是,作为示例,JAXB2 marshaller的配置可能类似于以下内容:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

5.5. JAXB

JAXB绑定编译器将W3C XML Schema转换为一个或多个Java类, jaxb.properties文件以及可能的某些资源文件。JAXB还提供了一种从带注释的Java类生成模式的方法。

Spring支持JAXB2.0API作为XML编组策略,遵循marshaller和unmarshaller中描述的marshaller和unmarshaller接口。相应的集成类位于org.springframework.oxm.jaxb包中。

5.5.1. Using Jaxb2Marshaller

JAXB2marshaller类实现了Spring的Marshaller and Unmarshaller接口。它需要一个上下文路径来操作。可以通过设置ContextPath属性来设置上下文路径。上下文路径是包含模式派生类的冒号分隔的Java包名称的列表。它还提供了一个ClasseStobeBound属性,允许您设置一个类数组,以供封送处理程序支持。模式验证是通过为bean指定一个或多个模式资源来执行的,如下示例所示:

<beans>
    <bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
        <property name="classesToBeBound">
            <list>
                <value>org.springframework.oxm.jaxb.Flightvalue>
                <value>org.springframework.oxm.jaxb.Flightsvalue>
            list>
        property>
        <property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
    bean>

    ...

beans>

XML配置命名空间

jaxb2 marshaller元素配置org.springframework.oxm.jaxb.jaxb2 marshaller,如下示例所示:

<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>

或者,您可以使用class-to-be-bound子元素提供要绑定到编组程序的类列表 :

<oxm:jaxb2-marshaller id="marshaller">
    <oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
    <oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
    ...
oxm:jaxb2-marshaller>

下表描述了可用的属性:

属性 描述 Required
id The ID of the marshaller No
contextPath The JAXB Context path No

5.6. Castor

Castor XML映射是一个开源XML绑定框架。它允许您将Java对象模型中包含的数据转换为XML文档和从XML文档中包含的数据。默认情况下,它不需要任何进一步的配置,但是您可以使用映射文件来对Castor的行为进行更多的控制。

有关Castor的详细信息,请参阅Castor网站。Spring集成类位于org.springframework.oxm.castor包中。

5.6.1. Using CastorMarshaller

与JAXB一样,CastorMarshaller实现了Marshaller和Unmarshaller 接口。它可以按如下方式连接:

<beans>
    <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller" />
    ...
beans>

5.6.2. Mapping

虽然可以依赖Castor的默认编组行为,但可能需要对其进行更多的控制。使用Castor映射文件可以获得更多的控制。有关详细信息,请参见Castor XML映射。

可以使用MappingLocation资源属性设置映射,该属性在下面的示例中用类路径资源表示:


    
        
    

命名空间的XML配置
castor-marshaller标签配置org.springframework.oxm.castor.CastorMarshaller

<oxm:castor-marshaller id="marshaller" mapping-location="classpath:org/springframework/oxm/castor/mapping.xml"/>

可以通过两种方式配置MsHalAlER实例:通过指定映射文件的位置(通过映射位置属性)或通过标识存在对应XML描述符类的Java POJOS(通过目标类或目标包属性)。后一种方法通常与从XML模式生成XML代码一起使用。

下表介绍了可用的属性:

Attribute Description Required
id The ID of the marshaller No
encoding The encoding to use for unmarshalling from XML;编码 No
target-class java的pojos对应每个xml描述符 (as generated through code generation) No
target-package 一个Java包名,标识包含POJOs的包及其对应的Casor XML描述符类(如通过XML模式生成代码生成的) No
mapping-location No

5.7. JiBX

JiBX框架提供了一个类似于Hibernate为ORM提供的解决方案:绑定定义定义了Java对象如何转换为XML对象的规则。在准备绑定和编译类之后,jibx绑定编译器增强了类文件,并添加了代码来处理从XML到XML的类实例转换。

有关JiBX的更多信息,请参阅JiBX网站。Spring集成类位于org.springframework.oxm.jibx包中。

5.7.1. Using JibxMarshaller

JibxMarshaller类实现两个Marshaller和Unmarshaller 接口。要进行操作,需要Marshaller类的名称,您可以使用该targetClass属性进行设置。(可选)您可以通过设置bindingName属性来设置绑定名称 。在以下示例中,我们绑定Flights类:

<beans>
    <bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
        <property name="targetClass">org.springframework.oxm.jibx.Flightsproperty>
    bean>
    ...
beans>

JibxMarshaller配置为单个类。如果要编组多个类,则必须为不同的targetClass 属性值配置多个JibxMarshaller实例。

XML配置命名空间
jibx marshaller标记配置org.springframework.oxm.jibx.jibx marshaller,如下示例所示:

<oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/>

下表介绍了可用的属性:

Attribute Description Required
id The ID of the marshaller No
target-class The target class for this marshaller Yes
bindingName The binding name used by this marshaller No

5.8. XStream

XStream 是一个简单的库,可以将对象序列化为XML,然后再进行序列化。它不需要任何映射并生成干净的XML。

有关XStream的更多信息,请参阅XStream web site.。Spring集成类位于org.springframework.oxm.xstream包中。

5.8.1. Using XStreamMarshaller

XstreamMarshaller不需要任何配置,可以直接在应用程序上下文中配置。要进一步自定义XML,可以设置别名映射,该映射由映射到类的字符串别名组成,如下示例所示:

<beans>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
        <property name="aliases">
            <props>
                <prop key="Flight">org.springframework.oxm.xstream.Flightprop>
            props>
        property>
    bean>
    ...
beans>

默认情况下,XStream允许对任意类进行解组,这可能导致不安全的Java序列化效果。因此,我们不建议使用 XStreamMarshaller从外部源(即Web)解组XML,因为这可能导致安全漏洞。

如果您选择使用XStreamMarshaller从外部源解组XML,请在该supportedClasses属性上设置XStreamMarshaller,如下例所示:

<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
   <property name="supportedClasses" value="org.springframework.oxm.xstream.Flight"/>
   ...
bean>

这样做可确保只有已注册的类才有资格进行解组。
此外,您可以注册 自定义转换器,以确保只能对您支持的类进行解组。除了明确支持应支持的域类的转换器之外,您可能还希望在列表中添加CatchAllConverter作为最后一个转换器。因此,不会调用具有较低优先级和可能的安全漏洞的默认XStream转换器。

请注意,XStream是XML序列化库,而不是数据绑定库。因此,它具有有限的命名空间支持。因此,它非常不适合在Web服务中使用。

6. Appendix

6.1. XML Schemas

附录的这一部分列出了用于数据访问的XML模式,包括以下内容:

6.1.1. The tx Schema

这些tx标签用于配置Spring对事务的全面支持中的所有这些bean。这些标签在标题为“ 事务管理”的章节中介绍 。

我们强烈建议您查看’spring-tx.xsd’Spring发行版附带的文件。此文件包含Spring的事务配置的XML Schema,并涵盖tx命名空间中的所有各种元素,包括属性默认值和类似信息。该文件以内联方式记录,因此,为了遵守DRY(不要重复自己)原则,此处不再重复这些信息。

为了完整性,要使用tx模式中的元素,您需要在Spring XML配置文件的顶部有以下前导码。以下代码段中的文本引用了正确的架构,以便tx命名空间中的标记可供您使用:


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

    

beans>

通常,当您使用tx命名空间中的元素时,您还使用aop命名空间中的元素(因为Spring中的声明性事务支持是通过使用AOP实现的)。前面的XML片段包含引用aop模式所需的相关行,以便aop命名空间中的元素可供您使用。

6.1.2. The jdbc Schema

这些jdbc元素使您可以快速配置嵌入式数据库或初始化现有数据源。这些元素分别记录在 嵌入式数据库支持和 初始化数据源中。

要使用jdbc模式中的元素,您需要在Spring XML配置文件的顶部包含以下前导码。以下代码段中的文本引用了正确的架构,以便jdbc命名空间中的元素可供您使用:


 

    


在这里插入图片描述

你可能感兴趣的:(Spring,翻译)