Spring 中使用 Hibernate

Hibernate 在 ORM 领域具有广泛的影响,拥有广大的使用群体。它提供了 ORM 最完整、最丰富的实现,在 Spring 4.0 中目前全面支持 Hibernate 5.0,不再支持 Hibernate 3.6 之前的版本。因为 iBatis 的升级版 MyBatis 自身已经提供了对 Spring 整合的支持,所以 Spring 不再为 MyBatis 提供直接支持,大家直接使用 MyBatis 自带的整合功能即可。

1.配置 SessionFactory

使用 Hibernate 框架的首要工作是编写Hibernate的配置文件,其次是如何使用这些配置文件实例化 SessionFactory,创建 Hibernate 的基础设施。

Spring 为创建 SessionFactory 提供了一个好用的 FactoryBean 工厂类:org.springframework.orm.hibernateX.LocalSessionFactoryBean,通过配置一些必要的属性,就可以获取一个SessionFactoryBean。

LocalSessionFactoryBean 配置灵活度很高,支持开发者的不同习惯,让开发者拥有充分的选择权——这是Spring一贯的风格。

1)零过渡障碍的配置方式

让我们回忆一下使用 HibernateAPI 创建一个 SessionFactory 的过程:首先编写好对象关系的映射文件 xxx.hbm.xml;然后通过 Hibernate 的配置文件 hibernate.cfg.xml 将所有的 xxx.hbm.xml 映射文件组装起来;最后通过以下两行经典的代码得到 SessionFactory 的实例:

Configuration cfg = new Configuration().configure("hibernate.cfg.xml"):
SessionFactory sessionFactory = cfg.buildSessionFactory();

hibernate.cfg.xml 配置文件拥有创建 Hibernate 基础设施所需的配置信息,来看一个最简单的 Hibernate 配置文件,如下面代码所示。

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <property name="connection.url">
            jdbc:mysql://localhost:3306/sampledb
        </property>
        <property name="connection.username">root</property>
        <property name="connection.password">1234</property>
        <property name="dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="show_sql">true</property>
        <property name="format_sql">true</property>
        <property name="current_session_context_class">thread</property>
        <mapping resource="com/smart/domain/Forum.hbm.xml" />
    </session-factory>
</hibernate-configuration>

这个配置文件定义了3个方面的信息:数据源、映射文件及 Hibernate 控制属性。既然在 Hibernate 中可以使用一个配置文件创建一个 SessionFactory 实例,在 Spring 中也可以顺理成章地通过指定一个Hibernate配置文件,利用 LocalSessionFactoryBean 来达到相同的目的。

<!-- 直接使用hibernate配置 -->
<bean id="sessionFactory"
  class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
  p:configLocation="classpath:hibernate.cfg.xml"/>①

如①处所示,通过 configLocation 属性指定了一个 Hibernate 配置文件。如果有多个 Hibernate 配置文件,则可以通过 configLocations 属性指定,多个文件之间用逗号分隔。

LocalSessionFactoryBean 将利用 Hibernate 配置文件创建一个 SessionFactory 代理对象,以便和 Spring 的事务管理机制配合工作:当数据访问代码使用 SessionFactory 时,可以获取线程绑定的 Session,不管工作在本地或全局的事务,都能正确参与到当前的 Spring 事务管理中去。

2)更具 Spring 风格的配置

Spring 对 ORM 技术的一个重要支持就是提供统一的数据源管理机制,也许更多的开发者更愿意使用 Spring 配置数据源,即在 Spring 容器中定义数据源、指定映射文件、设置 Hibernate 控制属性等信息,完成集成组装的工作,完全抛开 hibernate.cfg.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:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p" 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-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="${jdbc.driverClassName}"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
          p:dataSource-ref="dataSource"><!--①数据源-->
        
        <!--②指定Hibernate实体类映射文件-->
        <property name="mappingLocations">
            <list>
                <value>classpath*:/com/smart/orm/domain/Forum.hbm.xml</value>
                <value>classpath*:/com/smart/orm/domain/Topic.hbm.xml</value>
            </list>
        </property>
        
        <!--③指定Hibernate配置属性-->
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.MySQLDialect
                </prop>
                <prop key="hibernate.show_sql">
                    true
                </prop>
            </props>
        </property>
    </bean>
    ...
</beans>

数据源、映射文件及 Hibernate 控制属性这三方面的信息在 LocalSessionFactoryBean 中得到了完美集成,完全替代了 hibernate.cfg.xml 的作用,但这种配置对于 Spring 开发者而言更加亲切。

首先,①处指定的数据源是 Spring 容器中的数据源,不管是直接在 Spring 容器中配置,还是通过 <jee:jndi-lookup> 从 EJB 容器中获取,对引用者而言是完全透明的。

其次,凭借 Spring 资源处理的强大功能,指定 Hibernate 映射文件变得相当灵活。在②处采用了逐个指定映射文件的方法,其实这个方法是最笨拙的。由于 mappingLocations 属性的类型是 Resource[],因此它还支持以下简洁的配置方式:

<bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
          p:dataSource-ref="dataSource"
          p:mappingDirectoryLocations="classpath:/com/smart/orm/domain/**/*.hbm.xml">
...
</bean>

除了 mappingLocations 属性外,也可以选择通过其他资源属性指定 Hibernate 映射文件。

1)mappingJarLocations:如果映射文件位于JAR文件中,则可以通过该属性指定,如 WEB-INF/Iib/example.hbm.jar

2)p:mappingDirectoryLocations:可以指定多个放置 Hibernate 映射文件的目录,Spring 将加载这些目录下的所有 Hibernate 映射文件,如 classpath:.com/smart/orm/domain。

3 )mappingResources:通过相对于类路径的方式指定映射文件,属性类型为 String[]。既可以通过内嵌多个  <value> 的 <list> 元素指定映射文件,也可以通过逗号分隔的方式指定,如"topic.hbm.xml,post.hbm.xml”

其他 Hibernate 属性通过键值对的方式提供,如③处所示,键的名称请参考 Hibernate 的说明文档。

由于这种配置方式将所有的配置信息都统一到 Spring 中,给管理、维护和调整工作带来了方便,因而被广泛接受。

 

2.使用 HibernateTemplate

基于模板类使用 Hibernate 是最简单的方式,它可以在不牺牲 Hibernate 强大功能的前提下,以一种更简洁的方式使用 Hibernate,极大地降低了 Hibernate 的使用难度。按照 Spring 的风格,它提供了使用模板的支持类 HibernateDaoSupport,并通过 getHibernateTemplate() 方法向子类开放模板类实例的调用。

为了能够使用注解配置的功能,我们自已编写一个 BaseDao,如下:

public class BaseDao { 
    @Autowired
    private HibernateTemplate hibernateTemplate;
    
    ...
}

然后通过扩展这个 BaseDao 基类创建一个使用 HibernateTemplate 的 Dao,如下面代码所示。

package com.smart.dao.hibernate;

import java.sql.SQLException;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.orm.hibernate4.HibernateCallback;
import org.springframework.stereotype.Repository;

import com.smart.domain.Forum;

@Repository
public class ForumHibernateDao extends BaseDao {

    public void addForum(Forum forum) {
        getHibernateTemplate().save(forum);//①保存实体对象
    }

    public void updateForum(Forum forum) {
        getHibernateTemplate().update(forum);//②更改实体对象
    }

    public Forum getForum(int forumId) {
        return getHibernateTemplate().get(Forum.class, forumId);//③获取实体对象
    }

    public List<Forum> findForumByName(String forumName) {//④使用HQL查询
        return (List<Forum>) getHibernateTemplate().find(
                "from Forum f where f.forumName like ?", forumName + "%");
    }
    public long getForumNum() {//⑤使用Iterate返回结果
        Object obj = getHibernateTemplate().iterate(
                "select count(f.forumId) from Forum f").next();
        return (Long) obj;
    }
    
}

HibernateTemplate 代理了 HibernateSession 的大多数持久化操作,并以一种更简洁的方式提供调用。 HibernateTemplate 所提供的大部分方法对于 Hibernate 开发者来说都是熟悉亲切的,因为模板类的方法大都可以在 Session 接口中找到镜像。

1)常用的 API 方法

下面来了解一下 HibernateTemplate 开放的一些有代表性的 API 方法,其他更多的方法请参见 Spring 的 Javadoc 文档。

(1)Serializable save(Object entity):保存实体对象,并返回主键值。还有一个和该方法功能类似的 void persist(Object entity)方法,后者是 JSR-220 规定的方法。

(2)void update(Object  entity):更新实体对象。

(3)void saveOrUpdate(Object  entity):保存或更新一个实体。还有一个和该方法功能类似的 <T> T merge(T entity) 方法,后者是 JSR-220 规定的方法。

(4)void delete(Object entity):删除一个实体。

(5)List find(String queryString):根据 HQL 查询实体。该方法拥有几个带参的重载版本,如find(String queryString,Object value)、List find(String queryString,Object values)。在内部,模板类会自动创建 Query 并执行查询。

(6)List findByNamedQuery(String queryName):执行命名查询。其也拥有多个可绑定参数的重载版本。

(7)List findByCriteria(DetachedCriteria criteria):Criteria版本的查询。该方法有一个可限定范围的重载版本:List findByCnteria(org.hibernate.criterion.DetachedCrlteria crlteria,int firstResult,int maxResults)。

2)使用回调接口

一般情况下,使用模板类的简单代理方法就可以满足要求了,如果希望使用更多 Hibernate 底层的功能,则可以使用回调接口。Spring 定义了一个回调接口org.springframework.orm.hibernate5.HibernateCallback<T>,该接口拥有唯一的方法,如下:

T dolnHibernate(org.hibernate.Session session) throws HibernateException,SQLException

该接口配合 HibernateTemplate 进行工作,它无须关心 HibernateSession 的打开/关闭等操作,仅需定义数据访问逻辑即可。可以通过该接口返回结果,结果可以是一个实体对象或一个实体对象的 List。回调接口中抛出的异常将传播到模板类中并被转换成 Spring DAO 异常体系的对应类。

HibernateTemplate 定义了两个使用 HibernateCallback 回调接口的方法。

(1)<T> T execute(HibernateCallback<T> action):一般使用该方法执行数据更新、新增等操作。

(2)List executeFind(HibernateCallback<?> action):一般使用该方法执行数据查询操作,返回的结果是一个List。

使用回调接口对上面的 getForumNum() 方法提供另一个版本的实现,如下:

public long getForumNum2() {
    Long forumNum = getHibernateTemplate().execute(
            new HibernateCallback<Long>() {
                public Long doInHibernate(Session session)
                        throws HibernateException{
                    Object obj = session.createQuery("select count(f.forumId) from Forum f")
                            .list()
                            .iterator()
                            .next();
                    return (Long) obj;
                }
            });
    return forumNum;
}

代码中粗体部分以匿名类的方式提供了 HibernateCallback 回调的实现,直接使用 HibernateSession 接口完成查询的工作并返回结果。

3)在 Spring 中配置 DAO
在编写好基于 HibernateTemplate的DAO 类后,接下来要做的就是在 Spring 中进行具体配置,使该 DAO 生效,代码如下:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:p="http://www.springframework.org/schema/p" 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-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">

    <!-- ①扫描类包以启动注解驱动的Bean -->
    <context:component-scan base-package="com.smart.dao.hibernate"/>
    <context:component-scan base-package="com.smart.service.hibernate"/>

    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close"
          p:driverClassName="${jdbc.driverClassName}"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"/>

    
    <bean id="lobHandler"
          class="org.springframework.jdbc.support.lob.DefaultLobHandler"
          lazy-init="true"/>

    <bean id="sessionFactory"
          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
          p:dataSource-ref="dataSource"
          p:mappingDirectoryLocations="classpath:/com/smart/domain">
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">
                    org.hibernate.dialect.MySQLDialect
                </prop>
                <prop key="hibernate.show_sql">
                    true
                </prop>
            </props>
        </property>
    </bean>
    
    <!-- ②配置hibernateTemplate Bean-->
    <bean id="hibernateTemplate"
          class="org.springframework.orm.hibernate4.HibernateTemplate"
          p:sessionFactory-ref="sessionFactory"/>
    
    <!-- ③配置hibernate的事务管理器 -->
    <bean id="transactionManager"
          class="org.springframework.orm.hibernate4.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

在 Spring 配置文件中,首先需要配置一个 HibernateTemplateBean,它基于 SessionFactory 工作,如②处所示。在①处使用 <context:component-scan> 扫描特定的类包以启用注解驱动的 Bean,这样 ForumHibernateDao 类的 @Repository 及 @Autowired 注解就可以起作用,将 ForumHibernateDao 装配为 Spring 容器中的 Bean。

 

3.处理 LOB 类型的数据

对 LOB 类型的数据的处理始终是各种 ORM 框架比较头疼的事,需要认真对待。Hibernate 为处理特殊数据类型字段定义了一个接口:org.hibernate.usertype.UserType。Spring 在 org.springframework.orm.hibernate5.support 包中为 BLOB 和 CLOB 类型提供了几个 UserType 的实现类。因此,可以在 Hibernate 的映射文件中直接使用这两个实现类轻松处理 LOB 类型的数据。

1)BlobByteArrayType:将 BLOB 数据映射为 byte[] 类型的属性。

2)BlobStringType:将 BLOB 数据映射为 String 类型的属性。

3)BlobSerializableType:将 BLOB 数据映射为 Serializable 类型的属性。

4)ClobStringType:将 CLOB 数据映射为 String 类型的属性。

前面使用的 Post 领域对象有两个分别对应 CLOB 和 BLOB 字段类型的属性,下面使用 Spring 的 UserType 为 Post 配置 Hibernate 的映射文件,如下面代码所示。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping auto-import="true" default-lazy="false">
    <class name="com.smart.domain.Post" table="t_post">
        <id name="postId" column="post_id">
            <generator class="assigned" />
        </id>
        <property name="userId" column="user_id"/>
        <property name="postText" column="post_text" 
        type="org.springframework.orm.hibernate4.support.ClobStringType"/>
        <property name="postAttach" column="post_attach" 
        type="org.springframework.orm.hibernate4.support.BlobByteArrayType"/>
        <property name="postTime" column="post_time" type="date" />
        <many-to-one name="topic" column="topic_id"
                     class="com.smart.domain.Topic" />
    </class>
</hibernate-mapping>

postText 为 String 类型的属性,对应数据库的 CLOB 类型:而 postAttach 为 byte[] 类型的属性,对应数据库的 BLOB 类型。分别使用 Spring 所提供的相应 UserType 实现类进行配置,如①和②处所示。

在配置好映射文件后,还需要在 Spring 配置文件中定义 LOB 数据处理器,让 SessionFactory 拥有处理 LOB 数据的能力。

<bean id="lobHandler"
    class="org.springframework.jdbc.support.lob.DefaultLobHandler"
    lazy-init="true"/>

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
    p:dataSource-ref="dataSource"
    p:mappingDirectoryLocations="classpath:/com/smart/domain"
    p:lobHandler-ref="lobHandler"><!--①设置LOB数据处理器-->
    ...
</bean>

这样,仅需简单地使用 HibernateTemplate#save(Object entity) 等方法,就可以正确地保存 LOB 数据。如果是 Oracle 9i数据库,还需要配置一个本地 JDBC 抽取器,并使用特定的 LobHandler 实现类。

在使用 LobHandler 操作 LOB 数据时,需要在事务环境下才能工作,所以必须事先配置事务管理器,否则会抛出异常。

注意:在使用 Spring JDBC 时,除了可以按 byte[]、String 类型处理 LOB 数据外,还可以使用流的方式操作 LOB数据,当数据超过一定的大小时(比如100MB),流操作是唯一可行的方式。可惜,Spring 并未提供以流方式操作 LOB 数据的 UserType(Spring 开发组成员认为在实现上存在难度)。

 

4.添加 Hibernate 事件监听器

Hibernate 设计了一个功能完备的事件模型,允许为事件装配一个或多个事件监听器。通过 Configuration#setListener(String type,Object listener) 等方法向 Hibernate 框架注册事件监听器。Hibernate 在org.hibernate.event 包中定义了事件及对应的事件监听器接口,并在 org.hibernate.event.def 包中提供了事件监听器接口的默认实现。

LocalSessionFactoryBean 允许用户通过 eventListeners 属性向 Hibernate 注册事件监听器。Spring 本身就提供了一个 Hibernate 的事件监听器 IdTransferringMergeEventListener。

下面通过简单的配置将其注册到 Hibernate 中。

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    ...
    <property name="eventListeners"><!---->
        <map>
            <entry key="merge"><!-- ②-1事件监听器类型 -->
                <bean class="org.springframework.orm.hibernate5.support.IdTransferringMergeEventListener"/><!-- ②-2事件监听器实现类 -->
            </entry>
        </map>
    </property>
</bean>    

eventListeners 属性是 Map 类型的,它以键值对的方式接收事件监听器的注册信息,其中键为事件类型,而值为事件监听器实现类。事件类型必须是 Hibernate 预定义的类型,包括 auto-flush、merge、create、delete、dirty-check、evict、flush、flush-entity、load、load-collection、lock、refresh、replicate 和 save-update 等。注册监听器时需要指定事件类型,在这一点上,Hibernate 做得实在不够友好。它完全可以利用类反射机制,根据监听器实现类自动判断对应的事件类型,就像在 web.xml 中注册 servlet 容器事件监听器一样,但 Hibernate 却要求必须显式指定事件的名称。

IdTransferringMergeEventListener 是 Spring 提供的一个 Hibernate 事件监听器,它必须和 HibernateTemplate#merge(Object entity) 方法配合使用。因为 Hibernate 的 merge() 方法在对一个新对象进行操作时,并不会将 ID 值传递给原对象,Spring 通过该事件监听器完成这项工作。

 

5.使用原生的 Hibernate API
Hibernate 3.0 引入了一个新的特性:通过 SessionFactory#getCurrentSession() 方法能够获取和当前线程绑定的Session。这一特性使得 Hibernate 自身具备了获取和事务线程绑定的 Session 对象的功能,这与在 Spring 的 HibernateTemplate 中使用和事务绑定的 Session 是相同。

因此,可以在 Spring 中使用原生的 Hibernate API 编写 DAO,它同样可以正确地和 Spring 的事务管理器一起工作。下面使用原生的 Hibernate API 实现 ForumDao 接口,如下面代码所示。

@Repository
public class ForumHibernateDao {

    private SessionFactory sessionFactory;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
    
    //①直接注入Hibernate原生的sessionFactory对象
    public void addForum(Forum forum) {
        sessionFactory.getCurrentSession().save(forum);
    }
    public void updateForum(Forum forum) {
        sessionFactory.getCurrentSession().update(forum);
    }
    
}

DAO 注入了一个 sessionFactory 对象,如①处所示。这样,DAO 中的所有数据访问方法都可以通过SessionFactory#getCurrentSession() 方法获取和当前线程绑定的 Session,以便 Spring 的事务管理器能够正确地工作。

和使用 HibernateTemplate 不同的是,使用原生 Hibernate API 所抛出的异常是 Hibernate 异常(也是运行期异常)。这意味着 DAO 的调用者只能以普通的错误来处理这些异常,而无法在声明式事务中使用通用的 Spring DAO异常体系进行回滚设置。一般情况下,这种差异是可以接受的,并不会带来什么问题。

 

6.使用注解配置

和 Spring 类似,Hibernate 不但可以使用 XML 提供 ORM 的配置信息,也可以直接在领域对象类中通过注解定义 ORM 映射信息。Hibernate 不但自已定义了一套注解,还支持 JSR-220 的 JPA 注解。

下面使用注解对 Forum 进行 ORM 的配置,如下面代码所示。

@Entity
@Table(name="T_FORUM")
public class Forum implements Serializable{
    @Id
    @Column(name = "FORUM_ID")
    private int forumId;
    
    @Column(name = "FORUM_NAME")
    private String forumName;
    
    @Column(name = "FORUM_DESC")
    private String forumDesc;

}

Hibemate 通过 AnnotationConfiguration 的 addAnnotatedClass() 或 addPackage() 方法加载使用 JPA 注解的实体类,获取映射的元数据信息,并在此基础上创建 SessionFactory 实例。

需要特别注意的是,使用 addPackage 并不是加载类包下所有标注了 ORM 注解的实体类,而是加载类包下 package-info.java 文件中定义的 Annotation,而该类包下的所有持久化类仍然需要通过 addAnnotatedClass() 方法加载。

Spring 专门提供了一个配套的 AnnotationSessionFactoryBean,用于创建基于 JPA 注解的 SessionFactory。

<!--①通过AnnotationSessionFactoryBean定义sessionFactory-->
<bean id="sessionFactory"
    class="org.springframework.orm.hibernate5.annotation.AnnotationSessionFactoryBean"
    p:dataSource—ref="dataSource">
    <property name="annotatedClasses"><list>
            <value>com.smart.orm.domain.Forum</value>
        </list>
    </property>
    <Property name="hibernateProperties">
        ...
    </property>
</bean>

AnnotationSessionFactoryBean 扩展了 LocalSessionFactoryBean 类,增强的功能是:可以根据实体类的注解获取 ORM 的配置信息。也可以混合使用 XML 配置和注解配置对象关系映射,Hibernate 内部自动将这些元数据信息进行整合,并不会产生冲突。

annotatedCIasses 属性指定使用 JPA 注解的实体类名,如②处所示。如果实体类比较多,不要想当然地以为通过annotatedPackages 属性指定实体类所在包名就可以了,annotatedPackages 在内部通过调用 Hibernate 的 AnnotationConfiguration 的 addPackage() 方法加载包中 package-info.java 文件定义的 Annotation,而非包中标注注解的实体类。

Spring 为了通过扫描方式加载带注解的实体类,提供了一个易用的 packagesToScan 属性,可以指定一系列包名,Spring 将扫描并加载这些包路径(包括子包)的所有带注解实体类。

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate5.annotation.AnnotationSessionFactoryBean"
    p:dataSource—ref="dataSource">
    <property name="packagesToScan" value="com.smart.orm.domain"/>
    <Property name="hibernateProperties">
        ...
    </property>
</bean>

packagesToScan 属性可接收多个类包路径,用逗号分隔即可,例如:

<property name="packagesToScan" value="packagel,package2"/>

 

7.事务处理

Spring 的通用事务管理模型对 Hibernate 是完全适用的,包括编程式事务、基于 TransactionProxyFactoryBean、基于 aop/tx 及基于 @Transaction 注解的事务管理。在这里,仅给出基于 @Transaction 注解的事务管理。

首先,为 BbtForumSerive 添加 @Transactional 注解。

@Transactional
@Service
public class BbtForumSerive{
    @Autowired
    private ForumHibernateDao forumDao;

    @Autowired
    private TopicHibernateDao topicDao;

    @Autowired
    private PostHibernateDao postDao;
    ...
}

其次,在 Spring 配置文件中配置 Hibernate 事务管理器,并启用注解驱动事务。

<bean id="transactionManager"
      class="org.springframework.orm.hibernate4.HibernateTransactionManager"
      p:sessionFactory-ref="sessionFactory"/><!--①为事务管理器指定sessionFactory-->

<!--②默认查找名为transactionManager的事务管理器,因此可以不显示指定-->
<tx:annotation-driven transaction-manager="transactionManager"/>

Hibernate 的事务管理器需要注入一个 sessionFactory 实例,将其命名为 transactionManager 后,在 <tx:annotation-driven/> 中就无须通过 transaction-manager 默认显式指定了。不管 BbtForumImpl 所用的 DAO 是基于 HibernateTemplate 还是基于 Hibernate 原生的API,BbtForumImpl 中的所有方法都具有事务性。


8.延迟加载问题

Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 HibernateSession 范围之内。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 HibernateSession 已经关闭,因而将导致延迟加载数据的访问异常。

Spring 为此专门提供了一个 OpenSessionInViewFilter 过滤器,它的主要功能是使每个请求过程绑定一个 HibernateSession,即使最初的事务已经完成,也可以在 Web 层进行延迟加载操作。

OpenSessionInViewFilter 过滤器将 HibernateSession 绑定到请求线程中,它将自动被 Spring 的事务管理器探测到。所以 OpenSessionInViewFilter 适用于 Service 层使用 HibernateTransactionManager 或 JtaTransactionManager 进行事务管理的环境,也可用于非只读事务的数据操作中。

要启用这个过滤器,必须在 web.xml 中进行如下配置:

...
<filter—name>hibernateFilter</filter—name>
    <filter—class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</fiiter—class>
</filter>
<filter—mapping>
    <filter—name>hibernateFilter</filter—name>
    <url—pattern>*.html</url—pattern>
</filter—mapping>
...

在上面的配置中,假设使用 .html 后缀作为 Web 框架的 URL 匹配模式。如果用户使用 struts 等 Web 框架,则可以将其改为对应的 “.do” 模型。

 

posted @ 2019-08-04 23:14  认真对待世界的小白  阅读(4126)  评论(0编辑  收藏  举报