Spring 数据入口(第二部分)

4. 对象关系映射(ORM)数据访问
本节讨论在使用对象关系映射(ORM)时的数据访问。

4.1。用Spring介绍ORM
Spring框架支持与Java Persistence API (JPA)的集成,并支持用于资源管理、数据访问对象(DAO)实现和事务策略的本机Hibernate。例如,对于Hibernate,它提供了一流的支持,提供了几个方便的IoC特性,解决了许多典型的Hibernate集成问题。您可以通过依赖项注入为OR(对象关系)映射工具配置所有受支持的特性。它们可以参与Spring的资源和事务管理,并且遵从Spring的通用事务和DAO异常层次结构。推荐的集成风格是针对普通Hibernate或JPA api编写DAOs代码。

在创建数据访问应用程序时,Spring为您选择的ORM层添加了重要的增强功能。您可以利用尽可能多的集成支持,并且应该将此集成工作与内部构建类似基础设施的成本和风险进行比较。不管采用什么技术,您都可以像使用库一样使用ORM支持,因为所有东西都被设计为一组可重用的javabean。Spring IoC容器中的ORM简化了配置和部署。因此,本节中的大多数示例都展示了Spring容器中的配置。

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

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

注意:要获得更全面的ORM支持,包括对其他数据库技术(如MongoDB)的支持,您可能需要查看Spring Data suite of projects。如果您是一个JPA用户,从https://spring.io开始使用JPA向导访问数据,提供了一个很好的介绍。

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

Spring的ORM一体化的主要目标是明确应用程序分层(与任何数据访问和事务的技术)和松散耦合的应用程序对象,没有更多的业务服务依赖于数据访问或事务策略,不再硬编码资源查找,没有更多的方面单例,没有更多的定制服务注册中心。其目标是使用一种简单且一致的方法来连接应用程序对象,使它们尽可能地可重用且不依赖于容器。所有单独的数据访问特性本身都是可用的,但与Spring的应用程序上下文概念很好地集成在一起,提供了基于xml的配置和不需要对Spring敏感的普通JavaBean实例的交叉引用。在典型的Spring应用程序中,许多重要的对象是javabean:数据访问模板、数据访问对象、事务管理器、使用数据访问对象的业务服务和事务管理器、web视图解析器、使用业务服务的web控制器,等等。

4.2.1。准备资源和事务管理
典型的业务应用程序充斥着重复的资源管理代码。许多项目试图发明自己的解决方案,有时为了方便编程而牺牲了对故障的适当处理。Spring提倡使用简单的解决方案来进行适当的资源处理,即在JDBC中通过模板进行IoC,并为ORM技术应用AOP拦截器。

基础设施提供适当的资源处理,并将特定的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...

}
<beans>

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

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

</beans>

后处理器自动查找所有异常翻译器(PersistenceExceptionTranslator接口的实现),并建议使用@Repository注释标记的所有bean,以便发现的翻译器能够拦截并对抛出的异常应用适当的翻译。

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

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

注意:从Spring Framework 5.0开始,Spring需要Hibernate ORM 4.3或更高版本的JPA支持,甚至需要Hibernate ORM 5.0+来针对本机Hibernate会话API进行编程。请注意,Hibernate团队不再维护5.1之前的任何版本,可能很快会专门关注5.3+。

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

以下摘自XML应用程序上下文定义,展示了如何在其上设置JDBC数据源和Hibernate SessionFactory:

<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.xml</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <value>
                hibernate.dialect=org.hibernate.dialect.HSQLDialect
            </value>
        </property>
    </bean>

</beans>

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

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

您还可以访问位于jndi的SessionFactory,使用Spring的JndiObjectFactoryBean / <jee:jndi-lookup>来检索和公开它。然而,这在EJB上下文之外通常并不常见。

注意:Spring还提供了LocalSessionFactoryBuilder变体,与@Bean风格的配置和编程设置无缝集成(不涉及FactoryBean)。LocalSessionFactoryBean和LocalSessionFactoryBuilder都支持后台引导,在给定的引导执行器(如SimpleAsyncTaskExecutor)上,Hibernate的初始化与应用程序的引导线程并行运行。在LocalSessionFactoryBean上,这可以通过bootstrapExecutor属性获得。在可编程的LocalSessionFactoryBuilder上,有一个重载的buildSessionFactory方法,它接受一个引导执行器参数。

从Spring Framework 5.1开始,这样的本机Hibernate设置还可以在本机Hibernate访问的旁边公开一个JPA EntityManagerFactory,用于标准的JPA交互。有关JPA的详细信息,请参阅本机Hibernate设置。

4.3.2。实现基于普通Hibernate API的dao
Hibernate有一个称为上下文会话的特性,其中Hibernate自己管理每个事务的一个当前会话。这大致相当于Spring对每个事务同步一个Hibernate会话。一个对应的DAO实现类似于下面的例子,基于简单的Hibernate API:

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示例遵循依赖项注入模式。它可以很好地装入Spring IoC容器,就像根据Spring的HibernateTemplate进行编码一样。您还可以在普通Java中设置这样的DAO(例如,在单元测试中)。为此,实例化它并使用所需的工厂引用调用setSessionFactory(..)。作为一个Spring bean的定义,DAO类似于以下内容:

<beans>

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

</beans>

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

但是,DAO抛出的是普通的HibernateException(未选中,因此不必声明或捕获它),这意味着调用者只能将异常视为通常是致命的——除非他们希望依赖于Hibernate自己的异常层次结构。如果不将调用者绑定到实现策略,就不可能捕获特定的原因(例如乐观锁定失败)。对于基于hibernate的应用程序来说,这种折衷可能是可以接受的,不需要任何特殊的异常处理,或者两者兼而有之。

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

总之,您可以基于简单的Hibernate API实现DAOs,同时仍然能够参与spring管理的事务。

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

在继续之前,如果您还没有阅读声明式事务管理,我们强烈建议您阅读它。

您可以使用@Transactional注释注释服务层,并指示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)和<tx:annotation-driven/>条目,并在运行时选择@Transactional处理。下面的例子演示了如何做到这一点:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- SessionFactory, DataSource, etc. omitted -->

    <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。程序性事务界定
您可以在应用程序的较高层(跨越任意数量操作的较低层数据访问服务之上)划分事务。对周围业务服务的实现也没有限制。它只需要一个Spring平台transactionmanager。同样,后者可以来自任何地方,但最好是通过setTransactionManager(..)方法作为bean引用。另外,应该使用setProductDao(..)方法设置productDAO。下面的代码片段展示了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则被限制为回调中未检查的异常。TransactionTemplate在未选中的应用程序异常或事务仅由应用程序(通过设置TransactionStatus)标记为rollback的情况下触发回滚。默认情况下,TransactionInterceptor的行为方式是相同的,但是允许对每个方法配置回滚策略。

4.3.5。事务管理策略
TransactionTemplate和TransactionInterceptor代表实际的事务处理PlatformTransactionManager实例(可以是一个HibernateTransactionManager(一个Hibernate SessionFactory)通过使用ThreadLocal会话下罩)或JtaTransactionManager(委派到容器的JTA子系统),Hibernate应用程序。您甚至可以使用定制的PlatformTransactionManager实现。从本机Hibernate事务管理切换到JTA(例如在面对应用程序的某些部署的分布式事务需求时)只是配置问题。您可以使用Spring的JTA事务实现替换Hibernate事务管理器。事务界定和数据访问代码无需更改即可工作,因为它们使用通用事务管理api。

对于跨多个Hibernate会话工厂的分布式事务,可以将JtaTransactionManager作为事务策略与多个LocalSessionFactoryBean定义组合使用。然后,每个DAO获得一个特定的SessionFactory引用,该引用被传递到对应的bean属性中。如果所有底层JDBC数据源都是事务性容器数据源,那么业务服务可以在任意数量的dao和任意数量的会话工厂之间划分事务,而不需要特别注意,只要它使用JtaTransactionManager作为策略即可。

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

HibernateTransactionManager可以将Hibernate JDBC连接导出为特定数据源的纯JDBC访问代码。这种能力允许在不使用JTA的情况下使用混合Hibernate和JDBC数据访问进行高级事务划分,前提是只访问一个数据库。如果您已经通过LocalSessionFactoryBean类的DataSource属性设置了带数据源的传入SessionFactory,那么HibernateTransactionManager会自动将Hibernate事务公开为JDBC事务。或者,您可以显式地指定应该通过HibernateTransactionManager类的DataSource属性公开事务的数据源。

4.3.6。比较容器管理的资源和本地定义的资源
您可以在容器管理的JNDI SessionFactory和本地定义的JNDI SessionFactory之间进行切换,而无需更改任何应用程序代码。是将资源定义保存在容器中,还是在应用程序中本地保存,这主要取决于您使用的事务策略。与spring定义的本地SessionFactory相比,手动注册的JNDI SessionFactory没有提供任何好处。通过Hibernate的JCA连接器部署SessionFactory提供了参与Java EE服务器的管理基础设施的附加价值,但除此之外不添加实际价值。

Spring的事务支持没有绑定到容器。当使用除JTA之外的任何策略进行配置时,事务支持也可以在独立或测试环境中工作。特别是在典型的单数据库事务的情况下,Spring的单资源本地事务支持是JTA的一个轻量级和强大的替代方案。当您使用本地EJB无状态会话bean来驱动事务时,您同时依赖于EJB容器和JTA,即使您仅访问单个数据库,并且仅使用无状态会话bean来通过容器管理的事务提供声明性事务。以编程方式直接使用JTA还需要Java EE环境。JTA并不仅仅涉及JTA本身和JNDI数据源实例的容器依赖关系。对于非spring、jta驱动的Hibernate事务,您必须使用Hibernate JCA连接器或额外的Hibernate事务代码,并为正确的jvm级缓存配置TransactionManagerLookup。

spring驱动的事务可以在本地定义的Hibernate SessionFactory中工作,就像在本地JDBC数据源中工作一样,前提是它们访问单个数据库。因此,当您有分布式事务需求时,您只需要使用Spring的JTA事务策略。JCA连接器需要特定于容器的部署步骤,并且(显然)首先需要JCA支持。与部署具有本地资源定义和spring驱动的事务的简单web应用程序相比,此配置需要更多的工作。另外,如果使用WebLogic Express(它不提供JCA),则通常需要容器的企业版。具有跨单个数据库的本地资源和事务的Spring应用程序可以在任何Java EE web容器(不包括JTA、JCA或EJB)中工作,比如Tomcat、Resin,甚至是plain Jetty。此外,您可以很容易地在桌面应用程序或测试套件中重用这样的中间层。

总之,如果您不使用ejb,请坚持使用本地SessionFactory设置和Spring的HibernateTransactionManager或JtaTransactionManager。您可以获得所有的好处,包括适当的事务性jvm级缓存和分布式事务,而不需要容器部署带来的不便。通过JCA连接器对Hibernate SessionFactory进行JNDI注册仅在与ejb一起使用时才有价值。

4.3.7。假的应用服务器警告与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实例进行同步。你有两个选择:

  • 直接在您的应用程序上下文,如果你已经获得JTA PlatformTransactionManager对象(大概从JNDI通过JndiObjectFactoryBean或< jee: JNDI查找>)和喂它,例如,春天的JtaTransactionManager,最简单的方法是指定的引用bean定义这个JTA PlatformTransactionManager实例作为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事务管理器的afterCompletion回调被回调的。
  • 在其他活动中,这种同步可以通过Hibernate的afterTransactionCompletion回调(用于清除Hibernate缓存)触发Spring回调到Hibernate,然后在Hibernate会话上显式地调用close(),这会导致Hibernate试图关闭()JDBC连接。
  • 在某些环境中,这个Connection.close()调用然后触发警告或错误,因为应用服务器不再认为连接是可用的,因为事务已经提交了。

当Hibernate配置了对JTA PlatformTransactionManager的感知时,在提交JTA事务时会发生以下事件:

  • JTA事务已经准备提交。
  • Spring的JtaTransactionManager与JTA事务同步,因此事务通过JTA事务管理器的beforeCompletion回调被回调。
  • Spring意识到Hibernate本身与JTA事务是同步的,其行为与前面的场景不同。假设Hibernate会话需要关闭,Spring现在关闭它。
  • 提交JTA事务。
  • Hibernate与JTA事务保持同步,因此事务通过JTA事务管理器的afterCompletion回调被回调,可以正确地清除它的缓存。

4.4。JPA
在org.springframework.orm.jpa包下提供的Spring JPA,以类似于与Hibernate集成的方式提供了对 Java Persistence API 的全面支持,同时还支持底层实现,以便提供额外的功能。

4.4.1。Spring环境中JPA设置的三个选项
Spring JPA支持提供了三种设置JPA EntityManagerFactory的方法,应用程序使用该方法来获取实体管理器。

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

LocalEntityManagerFactoryBean创建了一个适合于简单部署环境的EntityManagerFactory,其中应用程序仅使用JPA进行数据访问。工厂bean使用JPA persistence provider自动检测机制(根据JPA的Java SE引导),并且在大多数情况下,要求您只指定持久性单元名。下面的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
您可以在部署到Java EE服务器时使用此选项。检查服务器的文档,了解如何将自定义JPA提供者部署到服务器中,允许使用不同于服务器默认的提供者。

从JNDI中获得EntityManagerFactory(例如在Java EE环境中)只需更改XML配置,如下面的示例所示:

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

此操作假设使用标准的Java EE引导。Java EE服务器自动检测持久性单元(实际上是应用程序jar中的META-INF/persistence.xml文件)和Java EE部署描述符中的持久性单元-ref条目(例如web.xml),并为这些持久性单元定义环境命名上下文位置。

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

如果您在同一个应用程序中使用多个持久性单元,那么此类jndi检索的持久性单元的bean名称应该与应用程序用来引用它们的持久性单元名称相匹配(例如,在@PersistenceUnit和@PersistenceContext注释中)。

使用LocalContainerEntityManagerFactoryBean
您可以在基于spring的应用程序环境中使用此选项来实现完整的JPA功能。这包括Tomcat等web容器、独立应用程序和具有复杂持久性需求的集成测试。

注意:如果你想专门配置一个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.xml</mapping-file>
        <exclude-unlisted-classes/>
    </persistence-unit>
</persistence>

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

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

此选项可能与Java EE服务器的内置JPA功能相冲突。在完整的Java EE环境中,考虑从JNDI获得EntityManagerFactory。或者,在LocalContainerEntityManagerFactoryBean定义上指定一个定制的persistenceXmlLocation(例如,META-INF/my-persistence.xml),并在应用程序jar文件中只包含一个具有该名称的描述符。因为Java EE服务器只查找默认的META-INF/persistence.xml文件,所以它忽略了这样的自定义持久性单元,从而避免了与spring驱动的JPA设置的冲突。(例如,这适用于树脂3.1。)

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

LoadTimeWeaver接口是一个spring提供的类,它允许以特定的方式插入JPA ClassTransformer实例,具体方式取决于环境是web容器还是应用服务器。通过代理连接类转换器通常是无效的。代理针对整个虚拟机工作,并检查加载的每个类,这在生产服务器环境中通常是不希望的。

Spring为各种环境提供了大量LoadTimeWeaver实现,让ClassTransformer实例只应用于每个类装入器,而不应用于每个VM。
有关LoadTimeWeaver实现及其设置的更多信息,请参阅AOP一章中的Spring配置,这些实现可以是通用的,也可以是针对各种平台(如Tomcat、JBoss和WebSphere)定制的。

如Spring配置中所述,您可以通过使用上下文的@ enableloadtimeweave注释来配置上下文范围的LoadTimeWeaver:load-time-weaver XML元素。这样一个全局编织器会被所有JPA LocalContainerEntityManagerFactoryBean实例自动获取。下面的示例展示了设置加载时编织器的首选方法,它提供了平台的自动检测(例如Tomcat的可编织类加载器或Spring的JVM代理),并将编织器自动传播到所有感知编织的bean:

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

但是,如果需要,您可以通过loadTimeWeaver属性手动指定专用的编织器,如下面的示例所示:

<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.xml</value>
            <value>classpath:/my/package/**/custom-persistence.xml</value>
            <value>classpath*:META-INF/persistence.xml</value>
        </list>
    </property>
    <property name="dataSources">
        <map>
            <entry key="localDataSource" value-ref="local-db"/>
            <entry key="remoteDataSource" value-ref="remote-db"/>
        </map>
    </property>
    <!-- if no datasource is specified, use this one -->
    <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配置检查。但是,一旦实际的JPA提供者被其他组件访问(例如,调用createEntityManager),这些调用就会阻塞,直到后台引导完成。特别是,当您使用Spring Data JPA时,请确保为其存储库设置延迟引导。

4.4.2 实现基于JPA: EntityManagerFactory和EntityManager的DAOs

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

通过使用注入的EntityManagerFactory或EntityManager,可以针对普通JPA编写代码,而不需要任何Spring依赖。如果启用了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) {
        try (EntityManager em = this.emf.createEntityManager()) {
            Query query = em.createQuery("from Product as p where p.category = ?1");
            query.setParameter(1, category);
            return query.getResultList();
        }
    }
}

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

<beans>

    <!-- bean post-processor for JPA annotations -->
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>

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

</beans>

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

<beans>

    <!-- post-processors for all standard config annotations -->
    <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。扩展,是完全不同的事情。这导致了所谓的扩展EntityManager,它不是线程安全的,因此不能在并发访问的组件(如spring管理的单例bean)中使用。扩展的EntityManager实例应该只在有状态组件中使用,例如,驻留在会话中,EntityManager的生命周期不与当前事务绑定,而是完全取决于应用程序。

方法和field-level注入
您可以对类中的字段或方法应用表明依赖项注入的注释(例如@PersistenceUnit和@PersistenceContext)——因此表达式有“方法级注入”和“字段级注入”。字段级注释简洁易用,而方法级注释允许对注入的依赖项进行进一步处理。在这两种情况下,成员可见性(公共的、受保护的或私有的)并不重要。

那么类级别的注释呢?
在Java EE平台上,它们用于依赖项声明,而不是资源注入。

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

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

4.4.3. Spring-driven JPA transactions

我们强烈建议您阅读声明式事务管理(如果您还没有这样做的话),以获得对Spring声明式事务支持的更详细的介绍。

JPA的推荐策略是通过JPA的本地事务支持实现本地事务。Spring的JpaTransactionManager针对任何常规的JDBC连接池(没有XA需求)提供了许多从本地JDBC事务(例如特定于事务的隔离级别和资源级只读优化)获得的功能。

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

注意:作为一个直接的替代方案,Spring的本机HibernateTransactionManager能够在Spring Framework 5.1和Hibernate 5.2/5.3的基础上与JPA访问代码进行交互,从而适应Hibernate的一些细节并提供JDBC交互。这在结合LocalSessionFactoryBean设置时特别有意义。有关JPA交互的详细信息,请参阅本机Hibernate设置。

4.4.4。理解JpaDialect和JpaVendorAdapter
作为一个高级特性,JpaTransactionManager和AbstractEntityManagerFactoryBean的子类允许自定义JpaDialect传递到JpaDialect bean属性。JpaDialect实现可以支持Spring支持的以下高级特性,通常以特定于供应商的方式:

  • 应用特定的事务语义(例如自定义隔离级别或事务超时)
  • 检索事务性JDBC连接(用于公开基于JDBC的dao)
  • 持久化异常到Spring数据访问异常的高级转换

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

警告:作为一个更广泛的提供者适应工具,主要用于Spring的全功能LocalContainerEntityManagerFactoryBean设置,JpaVendorAdapter将JpaDialect的功能与其他特定于提供者的缺省设置结合起来。为Hibernate或EclipseLink分别指定HibernateJpaVendorAdapter或EclipseLinkJpaVendorAdapter是自动配置EntityManagerFactory设置的最方便方法。请注意,这些提供程序适配器主要设计用于spring驱动的事务管理(即用于JpaTransactionManager)。

请参阅JpaDialect和JpaVendorAdapter javadoc,以了解其操作的更多细节,以及如何在Spring的JPA支持中使用它们。

4.4.5。使用JTA事务管理设置JPA
作为JpaTransactionManager的替代方案,Spring还允许通过JTA进行多资源事务协调,无论是在Java EE环境中还是使用独立事务协调器(如Atomikos)。除了选择Spring的JtaTransactionManager而不是JpaTransactionManager,你还需要采取以下几步:

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

4.4.6。JPA交互的本机Hibernate设置和本机Hibernate事务
在Spring框架5.1和Hibernate 5.2/5.3中,本地LocalSessionFactoryBean设置与HibernateTransactionManager相结合,允许与@PersistenceContext和其他JPA访问代码进行交互。Hibernate SessionFactory现在本地实现JPA的EntityManagerFactory接口,而Hibernate会话句柄是JPA EntityManager。Spring的JPA支持功能自动检测本机Hibernate会话。

本机Hibernate这样的设置,因此,作为替代标准JPA LocalContainerEntityManagerFactoryBean和JpaTransactionManager组合在很多情况下,允许交互SessionFactory.getCurrentSession()(还有HibernateTemplate)旁边@PersistenceContext EntityManager在同一个地方事务。这样的设置还提供了更强的Hibernate集成和更多的配置灵活性,因为它不受JPA引导契约的约束。

在这样的场景中,您不需要HibernateJpaVendorAdapter配置,因为Spring的本机Hibernate设置提供了更多的特性(例如,自定义Hibernate Integrator设置、Hibernate 5.3 bean容器集成以及针对只读事务的更强优化)。最后,您还可以通过LocalSessionFactoryBuilder来表示本机Hibernate设置,与@Bean风格的配置无缝集成(不涉及FactoryBean)。

注意:LocalSessionFactoryBean和LocalSessionFactoryBuilder支持后台引导,就像JPA LocalContainerEntityManagerFactoryBean一样。有关介绍,请参阅后台引导。
在LocalSessionFactoryBean上,这可以通过bootstrapExecutor属性获得。在可编程的LocalSessionFactoryBuilder上,一个重载的buildSessionFactory方法接受一个引导执行器参数。

5. 使用对象-XML映射器编组XML

5.1。介绍
本章描述Spring的对象- xml映射支持。对象-XML映射(简称O-X映射)是将XML文档与对象进行转换的过程。这种转换过程也称为XML编组或XML序列化。本章交替使用这些术语。

在O-X映射字段中,封送器负责将对象(图)序列化为XML。以类似的方式,解组程序将XML反序列化为对象图。此XML可以采用DOM文档、输入或输出流或SAX处理程序的形式。
使用Spring满足O/X映射需求的一些好处是:

5.1.1。易于配置
Spring的bean工厂简化了编组器的配置,而不需要构造JAXB上下文、JiBX绑定工厂等等。您可以像配置应用程序上下文中的任何其他bean一样配置封送器。此外,许多封送器都可以使用基于XML名称空间的配置,这使得配置更加简单。

5.1.2中。一致的界面
Spring的O-X映射通过两个全局接口进行操作:编组器和反编组器。通过这些抽象,您可以相对轻松地切换O-X映射框架,而不需要对执行编组的类进行多少更改。这种方法还有一个额外的好处,就是可以使用混合并匹配的方法(例如,一些编组是使用JAXB执行的,另一些是使用XStream执行的)以非侵入性的方式进行XML编组,从而使您能够利用每种技术的优势。

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

5.2. Marshaller and Unmarshaller
如引言中所述,编组程序将对象序列化为XML,而反编组程序将XML流反序列化为对象。本节描述用于此目的的两个Spring接口。

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

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 implementationWraps XML representation

DOMResult

org.w3c.dom.Node

SAXResult

org.xml.sax.ContentHandler

StreamResult

java.io.Filejava.io.OutputStream, or java.io.Writer

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

5.2.2。理解Unmarshaller
与编组器类似,我们有org.springframe .oxm。解组接口,如下清单所示:

public interface Unmarshaller {

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

这个接口还有一个方法,它从给定的javax.xml.transform中读取数据。源(XML输入抽象),并返回读取的对象。因此,Source是一个具有三个具体实现的标记接口。每个封装了不同的XML表示,如下表所示:

Source implementationWraps XML representation

DOMSource

org.w3c.dom.Node

SAXSource

org.xml.sax.InputSource, and org.xml.sax.XMLReader

StreamSource

java.io.Filejava.io.InputStream, or java.io.Reader

尽管有两个独立的编组接口(编组器和反编组器),Spring-WS中的所有实现都在一个类中实现。这意味着您可以连接一个marshaller类,并在您的applicationContext.xml中同时将其作为marshaller和反编组器引用。

5.2.3。理解XmlMappingException
Spring将异常从底层的O-X映射工具转换为它自己的异常层次结构,使用XmlMappingException作为根异常。这些运行时异常包装原始异常,因此不会丢失任何信息。

另外,MarshallingFailureException和UnmarshallingFailureException提供了编组和反编组操作之间的区别,即使底层的O-X映射工具没有这样做。
O-X映射异常层次结构如下图所示:

oxm exceptions

5.3。使用编组和解组器
您可以将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存储其设置。除了一个主方法外,该类还有两个方法:saveset()将设置bean保存到名为settings的文件中。和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 {
        try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
            this.marshaller.marshal(settings, new StreamResult(os));
        }
    }

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

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

应用程序需要设置编组器和反编组器属性。我们可以使用以下applicationContext.xml来实现这一点:

<beans>
    <bean id="application" class="Application">
        <property name="marshaller" ref="xstreamMarshaller" />
        <property name="unmarshaller" ref="xstreamMarshaller" />
    </bean>
    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
</beans>

这个应用程序上下文使用XStream,但是我们可以使用本章后面描述的其他封送器实例。注意,在默认情况下,XStream不需要任何进一步的配置,因此bean定义相当简单。还要注意,XStreamMarshaller同时实现了Marshaller和反编组器,因此我们可以在应用程序的Marshaller和反编组器属性中引用XStreamMarshaller bean。

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

?xml version="1.0" encoding="UTF-8"?>
<settings foo-enabled="false"/>

5.4。XML配置命名空间
通过使用来自OXM名称空间的标记,可以更精确地配置封送器。要使这些标记可用,您必须首先在XML配置文件的前言中引用适当的模式。下面的例子演示了如何做到这一点:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:oxm="http://www.springframework.org/schema/oxm" //1
xsi:schemaLocation="http://www.springframework.org/schema/beans
  https://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/oxm https://www.springframework.org/schema/oxm/spring-oxm.xsd"> //2
  1. 引用oxm模式。
  2. 指定oxm模式位置。

该模式提供了以下元素:

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

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

5.5。JAXB
JAXB绑定编译器将W3C XML模式转换成一个或多个Java类(JAXB)。属性文件,可能还有一些资源文件。JAXB还提供了一种从带注释的Java类生成模式的方法。

Spring支持JAXB 2.0 API作为XML编组策略,遵循编组器和反编组器中描述的接口。相应的集成类驻留在org.springframework.oxm中。jaxb包。

5.5.1。使用Jaxb2Marshaller
Jaxb2Marshaller类同时实现了Spring的Marshaller和Unmarshaller接口。它需要一个上下文路径来操作。您可以通过设置contextPath属性来设置上下文路径。上下文路径是一个冒号分隔的Java包名列表,其中包含模式派生类。它还提供了一个classesToBeBound属性,允许您设置一个由封送器支持的类数组。模式验证是通过向bean指定一个或多个模式资源来执行的,如下面的示例所示:

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

    ...

</beans>

XML配置命名空间
jaxb2-marshaller元素配置org.springframe .oxm.jaxb。Jaxb2Marshaller,如下例所示:

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

或者,您可以使用类绑定子元素提供要绑定到marshaller的类列表:

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

下表描述了可用的属性:

AttributeDescriptionRequired

id

The ID of the marshaller

No

contextPath

The JAXB Context path

No

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

有关JiBX的更多信息,请参见JiBX网站。Spring集成类驻留在org.springframework.oxm.jibx包。

5.6.1。使用JibxMarshaller
JibxMarshaller类同时实现编组器和反编组器接口。要进行操作,需要对类的名称进行封送,可以使用targetClass属性设置该名称。您还可以通过设置bindingName属性来设置绑定名。在下面的例子中,我们绑定了Flights类:

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

JibxMarshaller是为单个类配置的。如果要封送多个类,必须使用不同的targetClass属性值配置多个JibxMarshaller实例。

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

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

下表描述了可用的属性:

AttributeDescriptionRequired

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.7。XStream
XStream是一个简单的库,用于将对象序列化为XML,然后再序列化回来。它不需要任何映射并生成干净的XML。
有关XStream的更多信息,请参见XStream网站。Spring集成类驻留在org.springframework.oxm.xstream包。

5.7.1。使用XStreamMarshaller
XStreamMarshaller不需要任何配置,可以直接在应用程序上下文中配置。为了进一步定制XML,您可以设置一个别名映射,它由映射到类的字符串别名组成,如下面的示例所示:

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

警告:默认情况下,XStream允许对任意类进行解组,这可能导致不安全的Java序列化效果。因此,我们不建议使用XStreamMarshaller从外部源(即Web)解组XML,因为这会导致安全漏洞。
如果您选择使用XStreamMarshaller来从外部源解封XML,请在XStreamMarshaller上设置supportedClasses属性,如下面的示例所示:

<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. 附录
6.1。XML模式
附录的这一部分列出了数据访问的XML模式,包括以下内容:

6.11 The tx Schema
tx标记在Spring对事务的全面支持中配置所有这些bean。这些标记将在“事务管理”一章中讨论。

注意:我们强烈建议您查看“spring-tx”。随Spring发行版一起发布的xsd'文件。该文件包含Spring事务配置的XML模式,并涵盖tx名称空间中的所有元素,包括属性默认值和类似的信息。这个文件是内联文档,因此,为了遵守DRY (Don 't Repeat Yourself)原则,这里不重复信息。

出于完整性的考虑,要使用tx模式中的元素,您需要在Spring XML配置文件的顶部有以下序言。下面代码段中的文本引用了正确的模式,因此tx名称空间中的标记对您是可用的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" //1
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd  //2
        http://www.springframework.org/schema/aop  https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- bean definitions here -->

</beans>
  1. 声明tx名称空间的用法。
  2. 指定位置(使用其他模式位置)。

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

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

要使用jdbc模式中的元素,您需要在Spring XML配置文件的顶部有以下序言。下面代码段中的文本引用了正确的模式,因此jdbc名称空间中的元素对您是可用的:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" //1
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jdbc https://www.springframework.org/schema/jdbc/spring-jdbc.xsd"> //2

    <!-- bean definitions here -->

</beans>
  1. 声明jdbc名称空间的用法。
  2. 指定位置(使用其他模式位置)。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值