投稿 评论 顶部

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 配置文件,如下面代码所示。

复制代码

                        com.mysql.jdbc.Driver                            jdbc:mysql://localhost:3306/sampledb                root        1234                    org.hibernate.dialect.MySQLDialect                true        true        thread

复制代码

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

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

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

2)更具 Spring 风格的配置

Spring 对 ORM 技术的一个重要支持就是提供统一的数据源管理机制,也许更多的开发者更愿意使用 Spring 配置数据源,即在 Spring 容器中定义数据源、指定映射文件、设置 Hibernate 控制属性等信息,完成集成组装的工作,完全抛开 hibernate.cfg.xml配置文件,如下面代码所示。

复制代码

                                                                classpath*:/com/smart/orm/domain/Forum.hbm.xml                classpath*:/com/smart/orm/domain/Topic.hbm.xml                                                                                            org.hibernate.dialect.MySQLDialect                                                    true                                            ...

复制代码

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

首先,①处指定的数据源是 Spring 容器中的数据源,不管是直接在 Spring 容器中配置,还是通过 

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

复制代码

...

复制代码

除了 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[]。既可以通过内嵌多个 

其他 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;@Repositorypublic 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 findForumByName(String forumName) {//④使用HQL查询        return (List) 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):保存或更新一个实体。还有一个和该方法功能类似的

(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 dolnHibernate(org.hibernate.Session session) throws HibernateException,SQLException

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

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

(1)

(2)List executeFind(HibernateCallback

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

复制代码

public long getForumNum2() {    Long forumNum = getHibernateTemplate().execute(            new HibernateCallback() {                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 生效,代码如下:

复制代码

                                                                                        org.hibernate.dialect.MySQLDialect                                                    true

复制代码

在 Spring 配置文件中,首先需要配置一个 HibernateTemplateBean,它基于 SessionFactory 工作,如②处所示。在①处使用

 

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 的映射文件,如下面代码所示。

复制代码

                                                                            

复制代码

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

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

复制代码

    ...

复制代码

这样,仅需简单地使用 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 中。

复制代码

    ...

复制代码

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 接口,如下面代码所示。

复制代码

@Repositorypublic 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。

复制代码

    ②                    com.smart.orm.domain.Forum                        ...

复制代码

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

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

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

复制代码

                ...

复制代码

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


 

7.事务处理

<p