《Spring 5 官方文档》16.ORM和数据访问(二)

    xiaoxiao2024-06-06  35

    16.3.4编程式事务划分

    开发者可以在应用程序的更高级别上对事务进行标定,而不用考虑低级别的数据访问执行了多少操作。这样不会对业务服务的实现进行限制;只需要定义一个Spring的PlatformTransactionManager即可。当然,PlatformTransactionManager可以从多处获取,但最好是通过setTransactionManager(..)方法以Bean来注入,正如ProductDAO应该由setProductDao(..)方法配置一样。下面的代码显示Spring应用程序上下文中的事务管理器和业务服务的定义,以及业务方法实现的示例:

    <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允许任何检查的应用异常到callback代码中去,而TransactionTemplate还会非受检异常触发进行回调。TransactionTemplate则会因为非受检异常或者是由应用标记事务回滚(通过TransactionStatus)。TransactionInterceptor也是一样的处理逻辑,但是同时还允许基于方法配置回滚策略。

    16.3.5事务管理策略

    无论是TransactionTemplate或者是TransactionInterceptor都将实际的事务处理代理到PlatformTransactionManager实例上来进行处理的,这个实例的实现可以是一个HibernateTransactionManager(包含一个Hibernate的SessionFactory通过使用ThreadLocal的Session),也可以是JatTransactionManager(代理到容器的JTA子系统)。开发者甚至可以使用一个自定义的PlatformTransactionManager的实现。现在,如果应用有需求需要需要部署分布式事务的话,只是一个配置变化,就可以从本地Hibernate事务管理切换到JTA。简单地用Spring的JTA事务实现来替换Hibernate事务管理器即可。因为引用的PlatformTransactionManager的是通用事务管理API,事务管理器之间的切换是无需修改代码的。

    对于那些跨越了多个Hibernate会话工厂的分布式事务,只需要将JtaTransactionManager和多个LocalSessionFactoryBean定义相结合即可。每个DAO之后会获取一个特定的SessionFactory引用。如果所有底层JDBC数据源都是事务性容器,那么只要使用JtaTransactionManager作为策略实现,业务服务就可以划分任意数量的DAO和任意数量的会话工厂的事务。

    无论是HibernateTransactionManager还是JtaTransactionManager都允许使用JVM级别的缓存来处理Hibernate,无需基于容器的事务管理器查找,或者JCA连接器(如果开发者没有使用EJB来实例化事务的话)。

    HibernateTransactionManager可以为指定的数据源的Hibernate JDBC的Connection转成为纯JDBC的访问代码。如果开发者仅访问一个数据库,则开发者完全可以不使用JTA,通过Hibernate和JDBC数据访问进行高级别事务划分。如果开发者已经通过LocalSessionFactoryBean的dataSource属性与DataSource设置了传入的SessionFactory,HibernateTransactionManager会自动将Hibernate事务公开为JDBC事务。或者,开发者可以通过HibernateTransactionManager的dataSource属性的配置以确定公开事务的类型。

    16.3.6对比由容器管理的和本地定义的资源

    开发者可以在不修改一行代码的情况下,在容器管理的JNDISessionFactory和本地定义的SessionFactory之间进行切换。是否将资源定义保留在容器中,还是仅仅留在应用中,都取决于开发者使用的事务策略。相对于Spring定义的本地SessionFactory来说,手动注册的JNDISessionFactory没有什么优势。通过Hibernate的JCA连接器来发布一个SessionFactory只会令代码更符合J2EE服务标准,但是并不会带来任何实际的价值。

    Spring对事务支持不限于容器。使用除JTA之外的任何策略配置,事务都可以在独立或测试环境中工作。特别是在单数据库事务的典型情况下,Spring的单一资源本地事务支持是一种轻量级和强大的替代JTA的解决方案。当开发者使用本地EJB无状态会话Bean来驱动事务时,即使只访问单个数据库,并且只使用无状态会话Bean来通过容器管理的事务来提供声明式事务,开发者的代码依然是依赖于EJB容器和JTA的。同时,以编程方式直接使用JTA也需要一个J2EE环境的。 JTA不涉及JTA本身和JNDI DataSource实例方面的容器依赖关系。对于非Spring,JTA驱动的Hibernate事务,开发者必须使用Hibernate JCA连接器或开发额外的Hibernate事务代码,并为JVM级缓存正确配置TransactionManagerLookup。

    Spring驱动的事务可以与本地定义的HibernateSessionFactory一样工作,就像本地JDBC DataSource访问单个数据库一样。但是,当开发者有分布式事务的要求的情况下,只能选择使用Spring JTA事务策略。JCA连接器是需要特定容器遵循一致的部署步骤的,而且显然JCA支持是需要放在第一位的。JCA的配置需要比部署本地资源定义和Spring驱动事务的简单web应用程序需要更多额外的的工作。同时,开发者还需要使用容器的企业版,比如,如果开发者使用的是WebLogic Express的非企业版,就是不支持JCA的。具有跨越单个数据库的本地资源和事务的Spring应用程序适用于任何基于J2EE的Web容器(不包括JTA,JCA或EJB),如Tomcat,Resin或甚至是Jetty。此外,开发者可以轻松地在桌面应用程序或测试套件中重用中间层代码。

    综合前面的叙述,如果不使用EJB,请尽量使用本地的SessionFactory设置和Spring的HibernateTransactionManager或JtaTransactionManager。开发者能够得到了前面提到的所有好处,包括适当的事务性JVM级缓存和分布式事务支持,而且没有容器部署的不便。只有必须配合EJB使用的时候,JNDI通过JCA连接器来注册HibernateSessionFactory才有价值。

    16.3.7Hibernate的虚假应用服务器警告

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

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

    开发者可以通过配置令Hibernate意识到Spring中同步的JTAPlatformTransactionManager实例的存在,这样即可消除掉前面所说的虚假警告信息。开发者有以下两种选择:

    如果在应用程序上下文中,开发者已经直接获取了JTA PlatformTransactionManager对象(可能是从JNDI到JndiObjectFactoryBean或者<jee:jndi-lookup>标签),并将其提供给Spring的JtaTransactionManager(其中最简单的方法就是指定一个引用bean将此JTA PlatformTransactionManager实例定义为LocalSessionFactoryBean的jtaTransactionManager属性的值)。 Spring之后会令PlatformTransactionManager对象对Hibernate可见。更有可能开发者无法获取JTAPlatformTransactionManager实例,因为Spring的JtaTransactionManager是可以自己找到该实例的。因此,开发者需要配置Hibernate令其直接查找JTA PlatformTransactionManager。开发者可以如Hibernate手册中所述那样通过在Hibernate配置中配置应用程序服务器特定的TransactionManagerLookup类来执行此操作。

    本节的其余部分描述了在PlatformTransactionManager对Hibernate可见和PlatformTransactionManager对Hibernate不可见的情况下发生的事件序列:

    当Hibernate未配置任何对JTAPlatformTransactionManager的进行查找时,JTA事务提交时会发生以下事件:

    JTA事务提交Spring的JtaTransactionManager与JTA事务同步,所以它被JTA事务管理器通过afterCompletion回调调用。在其他活动中,此同步令Spring通过Hibernate的afterTransactionCompletion触发回调(用于清除Hibernate缓存),然后在Hibernate Session上调用close(),从而令Hibernate尝试close()JDBC连接。在某些环境中,因为事务已经提交,应用程序服务器会认为Connection不可用,导致Connection.close()调用会触发警告或错误。

    当Hibernate配置了对JTAPlatformTransactionManager进行查找时,JTA事务提交时会发生以下事件:

    JTA事务准备提交Spring的JtaTransactionManager与JTA事务同步,所以JTA事务管理器通过beforeCompletion方法来回调事务。Spring确定Hibernate与JTA事务同步,并且行为与前一种情况不同。假设Hibernate Session需要关闭,Spring将会关闭它。JTA事务提交。Hibernate与JTA事务同步,所以JTA事务管理器通过afterCompletion方法回调事务,可以正确清除其缓存。

     

    16.4 JPA

    Spring JPA在org.springframework.orm.jpa包中已经可用,Spring JPA用了Hibernate集成相似的方法来提供更易于理解的JPA支持,与此同时,了解了JPA底层实现,可以理解更多的Spring JPA特性。

    16.4.1 Spring中JPA配置的三个选项

    Spring JPA支持提供了三种配置JPAEntityManagerFactory的方法,之后通过EntityManagerFactory来获取对应的实体管理器。

    LocalEntityManagerFactoryBean

    通常只有在简单的部署环境中使用此选项,例如在独立应用程序或者进行集成测试时,才会使用这种方式。

    LocalEntityManagerFactoryBean创建一个适用于应用程序且仅使用JPA进行数据访问的简单部署环境的EntityManagerFactory。工厂bean会使用JPAPersistenceProvider自动检测机制,并且在大多数情况下,仅要求开发者指定持久化单元的名称:

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

    这种形式的JPA部署是最简单的,同时限制也很多。开发者不能引用现有的JDBCDataSource bean定义,并且不支持全局事务。而且,持久化类的织入(weaving)(字节码转换)是特定于提供者的,通常需要在启动时指定特定的JVM代理。该选项仅适用于符合JPA Spec的独立应用程序或测试环境。

    从JNDI中获取EntityManagerFactory

    在部署到J2EE服务器时可以使用此选项。检查服务器的文档来了解如何将自定义JPA提供程序部署到服务器中,从而对服务器进行比默认更多的个性化定制。

    从JNDI获取EntityManagerFactory(例如在Java EE环境中),只需要在XML配置中加入配置信息即可:

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

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

    在这种情况下,整个持久化单元部署(包括持久化类的织入(weaving)(字节码转换))都取决于J2EE服务器。JDBC DataSource通过META-INF/persistence.xml文件中的JNDI位置进行定义; 而EntityManager事务与服务器JTA子系统集成。 Spring仅使用获取的EntityManagerFactory,通过依赖注入将其传递给应用程序对象,通常通过JtaTransactionManager来管理持久性单元的事务。

    如果在同一应用程序中使用多个持久性单元,则这种JNDI检索的持久性单元的bean名称应与应用程序用于引用它们的持久性单元名称相匹配,例如@PersistenceUnit和@PersistenceContext注释。

    LocalContainerEntityManagerFactoryBean

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

    LocalContainerEntityManagerFactoryBean可以完全控制EntityManagerFactory的配置,同时适用于需要细粒度定制的环境。 LocalContainerEntityManagerFactoryBean会基于persistence.xml文件,dataSourceLookup策略和指定的loadTimeWeaver来创建一个PersistenceUnitInfo实例。因此,可以在JNDI之外使用自定义数据源并控制织入(weaving)过程。以下示例显示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设置选项,允许在应用程序中进行灵活的本地配置。它支持连接到现有的JDBCDataSource,支持本地和全局事务等。但是,它对运行时环境施加了需求,其中之一就是如果持久性提供程序需要字节码转换,就需要有织入(weaving)能力的类加载器。

    此选项可能与J2EE服务器的内置JPA功能冲突。在完整的J2EE环境中,请考虑从JNDI获取EntityManagerFactory。或者,在开发者的LocalContainerEntityManagerFactoryBean定义中指定一个自定义persistenceXmlLocation,例如META-INF/my-persistence.xml,并且只在应用程序jar文件中包含有该名称的描述符。因为J2EE服务器仅查找默认的META-INF/persistence.xml文件,所以它会忽略这种自定义持久性单元,从而避免了与Spring驱动的JPA设置之间发生冲突。 (例如,这适用于Resin 3.1)

    何时需要加载时间织入?

    并非所有JPA提供商都需要JVM代理。Hibernate就是一个不需要JVM代理的例子。如果开发者的提供商不需要代理或开发者有其他替代方案,例如通过定制编译器或Ant任务在构建时应用增强功能,则不用使用加载时间编织器。

    LoadTimeWeaver是一个Spring提供的接口,它允许以特定方式插入JPAClassTransformer实例,这取决于环境是Web容器还是应用程序服务器。 通过代理挂载ClassTransformers通常性能较差。代理会对整个虚拟机进行操作,并检查加载的每个类,这是生产服务器环境中最不需要的额外负载。

    Spring为各种环境提供了一些LoadTimeWeaver实现,允许ClassTransformer实例仅适用于每个类加载器,而不是每个VM。

    有关LoadTimeWeaver的实现及其设置的通用或定制的各种平台(如Tomcat,WebLogic,GlassFish,Resin和JBoss)的更多了解,请参阅AOP章节中的Spring配置一节。

    如前面部分所述,开发者可以使用@EnableLoadTimeWeaving注解或者load-time-weaverXML元素来配置上下文范围的LoadTimeWeaver。所有JPALocalContainerEntityManagerFactoryBeans都会自动拾取这样的全局织入器。这是设置加载时间织入器的首选方式,为平台(WebLogic,GlassFish,Tomcat,Resin,JBoss或VM代理)提供自动检测功能,并将织入组件自动传播到所有可以感知织入者的Bean:

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

    开发者也可以通过LocalContainerEntityManagerFactoryBean的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转换器仅应用于类加载器级,彼此隔离。

    处理多个持久化单元

    例如,对于依赖存储在类路径中的各种JARS中的多个持久性单元位置的应用程序,Spring将PersistenceUnitManager作为中央仓库来避免可能昂贵的持久性单元发现过程。默认实现允许指定多个位置,这些位置将通过持久性单元名称进行解析并稍后检索。(默认情况下,搜索classpath下的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>

    在默认实现传递给JPA provider之前,是允许通过属性(影响全部持久化单元)或者通过PersistenceUnitPostProcessor以编程(对选择的持久化单元进行)进行对PersistenceUnitInfo进行自定义的。如果没有指定PersistenceUnitManager,则由LocalContainerEntityManagerFactoryBean在内部创建和使用。

    转载自 并发编程网 - ifeve.com

    相关资源:Spring中文帮助文档
    最新回复(0)