Spring ORM数据訪问——Hibernate

Hibernate

我们将首先介绍Spring环境中的Hibernate 5。然后介绍使用Hibernate 5来演示Spring集成O-R映射器的方法。

本节将具体介绍很多问题,并显示DAO实现和事务划分的不同变体。

这些模式中大多数能够直接转换为全部其它支持的ORM工具。

本章中的下面部分将通过简单的样例来介绍其它ORM技术。

从Spring 5.0開始,Spring须要Hibernate ORM 4.3或更高版本号的JPA支持,甚至Hibernate ORM 5.0+能够针对本机Hibernate Session API进行编程。请注意,Hibernate团队可能不会在5.0之前维护不论什么版本号,仅仅专注于5.2以后的版本号。

在Spring容器中配置SessionFactory

开发人员能够将资源如JDBCDataSource或HibernateSessionFactory定义为Spring容器中的bean来防止将应用程序对象绑定到硬编码的资源查找上。应用对象须要訪问资源的时候,都通过相应的Bean实例进行间接查找,详情能够通过下一节的DAO定义来參考。

下面引用的应用的XML元数据定义就展示了怎样配置JDBC的DataSourceHibernateSessionFactory的:

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

这样。从本地的Jaksrta Commons DBCP的BasicDataSource转换到JNDI定位的DataSource仅仅仅仅须要改动配置文件。

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

开发人员也能够通过Spring的JndiObjectFactoryBean或者<jee:jndi-lookup>来获取相应Bean以訪问JNDI定位的SessionFactory

可是,EJB上下文通常不常见。

基于Hibernate API来实现DAO

Hibernate有一个特性称之为上下文会话。在每一个Hibernate本身每一个事务都管理一个当前的Session。这大致相当于Spring每一个事务的一个HibernateSession的同步。例如以下的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();
}
}

除了须要在实例中持有SessionFactory引用以外,上面的代码风格跟Hibernate文档中的样例十分相近。Spring团队强烈建议使用这样的基于实例的实现风格,而非守旧的static HibernateUtil风格(总的来说。除非绝对必要。否则尽量不要使用static变量来持有资源)。

上面DAO的实现全然符合Spring依赖注入的样式:能够非常好的结合Spring IoC容器。就好像Spring的HibernateTemplate代码一样。

当然,DAO层的实现也能够通过纯Java的方式来配置(比方在UT中)。

简单实例化ProductDaoImpl而且调用setSessionFactory(...)就可以。

当然,也能够使用Spring bean来进行注入,參考例如以下XML配置:

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

上面的DAO实现方式的优点在于仅仅依赖于Hibernate API,而无需引入Spring的class。这从非侵入性的角度来看当然是有吸引力的,毫无疑问,Hibernate开发人员将会更加自然。

然而,DAO层会抛出Hibernate自有异常HibernateException(属于非检查异常。无需显式声明和使用try-catch),可是也意味着调用方会将异常看做致命异常——除非调用方将Hibernate异常体系作为应用的异常体系来处理。

而在这样的情况下,除非调用方自己来实现一定的策略,否则捕获一些诸如乐观锁失败之类的特定错误是不可能的。对于强烈基于Hibernate的应用程序和/或不须要对特殊异常处理的应用程序,这样的代价可能是能够接受的。

幸运的是,Spring的LocalSessionFactoryBean支持不论什么Spring事务策略的Hibernate的SessionFactory.getCurrentSession()方法,即使使用HibernateTransactionManager返回当前的Spring管理的事务Session

当然,该方法的标准行为仍然返回与正在进行的JTA事务相关联的当前Session(假设有的话)。不管开发人员是使用Spring的JtaTransactionManager,EJB容器管理事务(CMT)还是JTA,都会适用此行为。

总而言之:开发人员能够基于纯Hibernate API来实现DAO,同一时候也能够參与Spring管理的事务。

声明式事务划分

Spring团队建议开发人员使用Spring声明式的事务支持,能够通过AOP事务拦截器来替代事务API的显式调用。AOP事务拦截器能够在Spring容器中使用XML或者Java的注解来进行配置。这样的事务拦截器能够令开发人员的代码和反复性的事务代码相解耦,而开发人员能够将精力集中在业务逻辑上,而业务逻辑才是应用的核心。

在继续之前,强烈建议开发人员假设没有查阅章节13.5 声明式事务管理的话,能够优先阅读。

开发人员能够在服务层的代码使用注解@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的实现,或者是在XML中配置<tx:annotation-driver/>标签。这样就能够在执行时支持@Transactional的处理了。參考例如以下XML代码:

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

编程式事务划分

开发人员能够在应用程序的更高级别上对事务进行标定,在这样的低级别数据訪问服务之上跨越随意数量的操作。而不正确业务服务的实现进行限制;它仅仅须要定义一个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也是一样的处理逻辑,可是同一时候还同意基于方法配置回滚策略。

事务管理策略

不管是TransactionTemplate或者是TransactionInterceptor都将实际的事务处理代理到PlatformTransactionManager实例上来进行处理的。这个实例的实现能够是一个HibernateTransactionManager(包含一个Hibernate的SessionFactory通过使用ThreadLocalSession)。也能够是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数据訪问进行高级别事务划分。假设开发人员已经通过LocalSessionFactoryBeandataSource属性与DataSource设置了传入的SessionFactoryHibernateTransactionManager将自己主动将Hibernate事务公开为JDBC事务。

或者,开发人员能够通过HibernateTransactionManagerdataSource属性的配置以确定公开事务的类型。

对照容器管理的和本地定义的资源

开发人员能够在不改动一行代码的情况下,在容器管理的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事务代码,并将TransactionManagerLookup配置为正确的JVM级缓存。

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的HibernateTransactionManagerJtaTransactionManager。开发人员能够得到了前面提到的全部优点,包含适当的事务性JVM级缓存和分布式事务支持,而且没有容器部署的不便。仅仅有配合EJB使用的时候,JNDI通过JCA连接器来注冊HibernateSessionFactory才有价值。

Hibernate的虚假应用server警告

In some JTA environments with very strict XADataSource implementations — currently only some WebLogic Server and WebSphere versions — when Hibernate is configured without regard to the JTA PlatformTransactionManager object for that environment, it is possible for spurious warning or exceptions to show up in the application server log. These warnings or exceptions indicate that the connection being accessed is no longer valid, or JDBC access is no longer valid, possibly because the transaction is no longer active. As an example, here is an actual exception from WebLogic:

在某些具有非常严格的XADataSource实现的JTA环境(眼下仅仅有一些WebLogic Server和WebSphere版本号)中。当配置Hibernate时,没有考虑到JTA的 PlatformTransactionManager对象,可能会在应用程序server日志中显示虚假警告或异常。这些警告或异常常常描写叙述正在訪问的连接不再有效,或者JDBC訪问不再有效。这通常可能是由于事务不再有效。比如,这是WebLogic的一个实际异常:

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

开发人员能够通过令Hibernate意识到Spring中同步的JTAPlatformTransactionManager实例的存在,就可以消除掉前面所说的警告信息。开发人员有下面两种选择:

  • 假设在应用程序上下文中,开发人员已经直接获取了JTA PlatformTransactionManager对象(可能是从JNDI到JndiObjectFactoryBean或者<jee:jndi-lookup>标签)。并将其提供给Spring的JtaTransactionManager(当中最简单的方法就是指定一个引用bean将此JTA PlatformTransactionManager实例定义为LocalSessionFactoryBeanjtaTransactionManager属性的值)。

    Spring之后会令PlatformTransactionManager对象对Hibernate可见。

  • 更有可能您还没有JTAPlatformTransactionManager实例。由于Spring的JtaTransactionManager能够自己找到它。

    因此,开发人员须要配置Hibernate直接查找JTA PlatformTransactionManager

    开发人员能够如Hibernate手冊中所述那样通过在Hibernate配置中配置应用程序server特定的TransactionManagerLookup类来执行此操作。

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

当Hibernate未配置不论什么对JTAPlatformTransactionManager的进行查找时,JTA事务提交时会发生下面事件:

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

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

  • JTA事务准备提交
  • Spring的JtaTransactionManager与JTA事务同步,所以JTA事务管理器通过beforeCompletion方法来回调事务。
  • Spring确定Hibernate与JTA事务同步,而且行为与前一种情况不同。假设Hibernate Session须要关闭,Spring将会关闭它。
  • JTA事务提交。
  • Hibernate与JTA事务同步,所以JTA事务管理器通过afterCompletion方法回调事务,能够正确清除其缓存。
上一篇:[JavaEE] Hibernate连接池配置测试


下一篇:JavaEE Hibernate初级概念