首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

9.2  Hibernate持久层

采用Hibernate作为持久层技术的最大的好处在于:可以完全以面向对象的方式进行系统分析、系统设计。面向对象的分析和面向对象的设计才最接近于程序员的自然思维。Hibernate的功能十分强大,对于Hibernate的介绍,在这里只仅介绍与本节内容相关的技术,如需了解Hibernate的详细情况,请参考Hibernate的官方文档和相关书籍。

9.2.1  编写PO类

下面介绍系统的类图,在开发过程中可以根据类图,生成关系型数据库表;或者先把数据库的业务表设计好,再通过工具生成对象。有很多设计工具都可以实现以上功能,如PowerDesigner等。但从面向对象分析与设计来讲,推荐使用第一种方法,因为更贴近面向对象的思想。映射配置文件写好以后,我们也可以用Hibernate生成数据库的表。

图9.1显示了本系统PO的类图。

图9.1  系统PO的类图

从类图可以看出,根据要实现的功能,系统的模型Model实现类有四个:Category, News, NewsReview和User,它们均继承父类BaseObject,都是普通的JavaBean,下面是四个基本的 Persisent Object类。

—  News:封装了一条消息。包括标题、内容、发布时间及发布人等。

—  Category:封装了一个消息分类。

—  User:封装了一个用户的信息。

—  NewsReview:封装了一条消息评论。

其中,News有一个Category类型的成员变量及User类型的成员变量;NewsReview有一个News类型的成员变量和一个User类型的成员变量。为了能够使用双向关联(Hibernete的映射功能,稍后解释),Category有一个集合型成员变量,用于存放与这个Category对象有关联的News对象;同样News也有一个集合型成员变量,用于存放与这个News对象有关联的NewsReview对象。

实际上,持久化就是通过成员变量来映射关系数据库里的1-N对多和N-N的关系。

下面是PO父类BaseObject的代码:

//将父类声明为abstract类

public abstract class BaseObject implements Serializable

{

    //PO推荐实现的toString方法

    public abstract String toString();

    //PO推荐实现的equals方法

    public abstract boolean equals(Object o);

    //PO推荐实现的hashCode方法

    public abstract int hashCode();

}

父类BaseObject是一个抽象类,定义了三个抽象方法toString(),equals() 和hashCode(),这三个方法是Hibernate推荐持久化对象时重写的。

关系数据库里的记录可以由主键来唯一标识,但是用什么标准来标记两个对象“相等”呢?两个对象相等与否的判断结果很可能影响到数据的完整性。如果在Hibernate无法自行制定两个对象相等与否的标准时,则需要用户自行定义——即重写equals()方法。

如果希望将持久化对象放进Set里,或者重新接管已脱管(detached)的持久化对象,则必须重写equals() 和hashCode()这两个方法,因为Java语法要求如果两个对象相等,那么它们的hashCode()返回值必须相等,因此该方法也需要重写。另外,toString()方法用于给出该对象的描述信息,因此也推荐重写该方法(Hibernate并没有硬性规定)。此外,BaseObject还实现了Serializable接口,该接口是持久化对象推荐实现的。

让其他类继承这个抽象类只是一个可选的写法,至少直接让其他类重写equals()和hashCode()方法来实现Serializable接口也是可以的。

下面具体来看一下News类。

/**

 * @hibernate.class table="news"

 * @struts.form include-all="false" extends="BaseForm"

 */

public class News extends BaseObject

{

    //标识属性

    private Long id;

    //消息标题

    private String title;

    //消息内容

    private String content;

    //用于关联发布人,对应另一个持久化类

    private User poster;

    //发布日期

    private Date postDate;

    //最后一次回复日期

    private Date lastModifyDate;

    //消息分类,用于关联另一个持久化类

    private Category category;

    //用于关联消息回复

    private Set newsReviews;

    //无参数的构造器

    public News()

    {

    }

    /**

     * 用于获取与此消息相关的全部回复

     * @return 返回消息的全部回复

     */

    public Set getNewsReviews()

    {

        return newsReviews;

    }

    /**

     * 设置消息关联的回复

     * @param newsReview 消息关联的全部回复

     */

    public void setNewsReviews(Set newsReviews)

    {

        this.newsReviews = newsReviews;

    }

    /**

     * 返回消息所属的种类

     * @return 消息所属的种类

     * @hibernate.many-to-one column="id" not-null="true"

     */

    public Category getCategory()

    {

        return category;

    }

    /**

     * 设置消息所在的种类

     * @param 消息所属的种类

     */

    public void setCategory(Category category)

    {

        this.category = category;

    }

    /**

     * 返回消息的最后评论日期

     * @return 消息的最后评论日期

     * @hibernate.property column="last_modify_date" not-null="true"

     */

    public Date getLastModifyDate()

    {

        return lastModifyDate;

    }

    /**

     * 设置消息的最后评论时间

     * @param 消息的最后评论日期

     */

    public void setLastModifyDate(Date lastModifyDate)

    {

        this.lastModifyDate = lastModifyDate;

    }

    /**

     * 返回消息的发布日期

     * @return 消息的发布日期

     * @hibernate.property column="post_date" not-null="true"

     */

    public Date getPostDate()

    {

        return postDate;

    }

    /**

     * 设置消息的发布日期

     * @param  消息的发布日期

     */

    public void setPostDate(Date postDate)

    {

        this.postDate = postDate;

    }

    /**

     * 返回消息的发布者

     * @return 消息的发布者

     * @hibernate.many-to-one column="username" not-null="true"

     */

    public User getPoster()

    {

        return poster;

    }

    /**

     * 设置消息的发布者

     * @param 消息的发布者

     * @hibernate.many-to-one column="username" not-null="true"

     */

    public void setPoster(User poster)

    {

        this.poster = poster;

    }

    /**

     * 返回消息的内容

     * @return 消息的内容

     * @hibernate.property column="content" length="3000" not-null="true"

     */

    public String getContent()

    {

        return content;

    }

    /**

     * 设置消息的内容

     * @param 消息的内容

     */

    public void setContent(String content)

    {

        this.content = content;

    }

    /**

     * 返回消息的id

     * @return 消息的id

     * @hibernate.id column="id" generator-class="increment"

     *               unsaved-value="null"

     */

    public Long getId()

    {

        return id;

    }

    /**

     * 设置消息的id

     * @param 消息的id

     */

    public void setId(Long id)

    {

        this.id = id;

    }

    /**

     * 返回消息的标题

     * @return 消息的标题

     * @hibernate.property column="title" length="50" not-null="true"

     */

    public String getTitle()

    {

        return title;

    }

    /**

     * 设置消息的标题

     * @return 消息的标题

     */

    public void setTitle(String title)

    {

        this.title = title;

    }

    /**

     * 实现抽象父类BaseObject的equals的方法

     *  @see java.lang.Object#equals(Object)

     */

    public boolean equals(Object object)

    {

        if (!(object instanceof News))

        {

            return false;

        }

        News rhs = (News) object;

        return this.poster.equals(rhs.getPoster())

                && this.postDate.equals(rhs.getPostDate());

    }

    /**

     * 实现抽象父类BaseObject的hashCode的方法

     *  @see java.lang.Object#hashCode()

     */

    public int hashCode()

    {

        return this.poster.hashCode() + this.postDate.hashCode();

    }

    /**

     * 实现抽象父类BaseObject的hashCode的方法

     *  @see java.lang.Object#toString()

     */

    public String toString()

    {

    return new ToStringBuilder(this).append("id", this.id).append("title",

            this.title).append("postDate", this.postDate).append("content",

            this.content).append("lastModifyDate", this.lastModifyDate)

                .append("poster", this.poster)

                .append("category", this.category).append("newsReviews",

                        this.newsReviews).toString();

    }

}

Hibernate对PO几乎不作任何要求,一般的POJO(Plain Old Java Object)就可以充当PO。只要有一个默认的不带参数的构造器就可以。推荐实现Serializable接口时,最好重写equals()和hashCode()方法。

所谓的POJO就是只有属性,以及属性对应getter和setter方法的Java对象。

下面将解释各个属性的含义(后面的映射配置文件会有进一步探讨):每个News对象就相当于数据库表里的一条记录,其中id属性映射的是记录的主键;title与content分别是消息的标题和内容;postDate是发布时间;lastModifyDate是最后评论时间;category则是News关联(在数据库里通过外键关联)的Category对象;newsReviews则是News对象关联的所有NewsReview对象。

细心的读者会发现,上面的代码重写equals()和hashCode()方法时,并不是采用id作为判断这两个对象相等的标准。这与Hibernate的机制有关,因为对象的标识属性值由Hibernate负责生成,Hibernate仅对已经持久化的对象分配标识属性值,未被持久化的对象是没有标识属性值的。如果在Set里面的一个新建对象未被持久化,此时持久化该对象并分配主键。由于equals()和hashCode()方法都基于主键,因此,hashCode得出的结果会改变,在Set里面就可能起冲突。

因此建议使用“业务键”来作为equals()和hashCode()方法的基准。“业务键”指的是能在真实世界里区分两个对象的属性。例如News就采用了发布人(poster)跟发布时间(postDate)的组合作为业务键,因为同一用户不可能在同一时间发布多条消息。关于如何选择业务键的更多信息请参照Hibernate官方文档。

注意:除了由用户自己生成equals()和hashCode()方法外,也有一些工具可以自动生成这两个方法。如Eclipse插件commonclipse。有兴趣的读者可以尝试一下。

9.2.2  编写PO的映射配置文件

仅有一个POJO是无法完成数据库的持久化操作,还必须为POJO增加映射文件。当POJO加映射文件后,可以完成O/R Mapping,从而在某个特定对象的管理下完成数据库访问。

因此必须为这个POJO配上一个映射文件,通常将这个映射文件命名为:类名.hbm. xml,并与这个类放置在同一目录下。News.hbm.xml的具体内容如下:

<?xml version="1.0" encoding="gb2312"?>

<!--  Hibernate映射文件的文件头,包含DTD等信息-->

<!DOCTYPE hibernate-mapping PUBLIC

    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"

    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

    <!--  映射News持久化类-->

    <class name="org.yeeku.model.News" table="news">

        <!--  映射标识属性-->

        <id name="id" column="id" unsaved-value="null">

            <!--  定义主键生成器策略-->

            <generator class="increment">

            </generator>

        </id>

        <!--  映射关联类:Category-->

        <many-to-one name="category"

        class="org.yeeku.model.Category" column="category_id" not-null="true">

        </many-to-one>

        <!--  映射最后评论日期属性-->

    <property name="lastModifyDate" column="last_modify_date" not-null="true">

        </property>

        <!--  映射发布日期属性-->

        <property name="postDate" column="post_date" not-null="true">

        </property>

        <!--  映射关联类:User--> 

        <many-to-one name="poster" column="username" not-null="true">

        </many-to-one>

        <!--  映射消息内容属性-->

    <property name="content" column="content" length="3000" not-null="true">

        </property>

        <!--  映射消息标题属性-->

        <property name="title" column="title" length="50" not-null="true">

        </property>

        <!--  映射关联类:NewsReview,映射1-N关联-->

        <set name="newsReviews" lazy="false" inverse="true" cascade="all-

delete-orphan">

            <meta attribute="field-description">

                @hibernate.list lazy="true" inverse="false" cascade="none"

                @hibernate.collection-key column="id"

                @hibernate.collection-one-to-many class="org.yeeku.model.

NewsReview"

            </meta>

            <key>

                <column name="news_id" />

            </key>

            <one-to-many class="org.yeeku.model.NewsReview" />

        </set>

    </class>

</hibernate-mapping>

映射文件根元素hibernate-mapping下面可以有多个class子元素,即在一个映射文件里可以配置多个映射对象,但为了清晰起见,建议为每个类单独写一个映射配置文件。

class的name属性就是要映射的对象类;table是数据库里对应的表名;class下面的子元素就是这个类的属性并与数据库里的字段相对应。

id用于唯一标识该对象,称为标识属性。其中name属性是类里面的属性名;column是数据库的对应字段。另外,id还有generator子元素,用于指定主键生成方式,这里用的是自动增长生成(increment)策略,其他方式请参照Hibernate文档。

这里重点介绍1-N(set是其中一种方式)和N-1(many-to-one,通常就是外键关联)的映射。首先来看1-N对多关系,一条消息对应多条评论。name属性是指类里面存放“N”的一方对象的属性。meta一栏这里只是用作为阅读者提供额外信息。key就是“多”的一方存放“N”的一方外键的字段,one-to-many的class是“N”的一方的映射对象。

注意:lazy属性与inverse属性的设置。lazy是Hibernate的一种延迟加载数据策略,比如说一个News对象与多个NewsReview对象关联,如果使用lazy策略,则只有当NewsReview对象被请求时(例如调用News的getNewsReviews方法)才会从数据库里读取相应记录,从而达到优化性能的目的。特别是系统如果非常复杂,关联很多时,不使用延迟加载将会对性能有比较大的影响。当然使用延迟加载还有很多问题需要注意,这里为了方便起见设为false,有兴趣的读者可以参考Hibernate官方文档。inverse属性标明该元素是否控制关联关系,对于1-N的关系,通常推荐由“N”的一段控制关联关系,因此set元素通常都应包含inverse=“true”属性。

N-1的配置则比较简单,与普通属性的配置一样,依次类推将所有映射文件完成,可以参照src/org/yeeku/model/NewsReview.hbm.xml,Category.hbm.xml和User.hbm.xml。完成后可以通过配置Hibernate的属性(数据库连接驱动,数据库方言,登录用户名跟密码等等)自动生成数据库的表,这里不再赘述。

注意:除了手工编写以外,开发者还有一些工具可以帮助生成映射配置文件。例如Xdoclet就可以用来生成Hibernate和Struts等配置文件,只需在编写源代码的时候在适当的地方加上注释,再用Xdoclet生成即可。

具体例子参看上面的News类源码,类定义上包含如下注释:

@hibernate.class table="news"

标明了该类映射的数据库表是news。所有的getter方法上面的注释里都有类似的注释,用来标明属性映射的字段。例如:

@hibernate.property column="content" length="3000" not-null="true"

等。有兴趣的读者可以对照News.hbm.xml或者直接参考Xdoclet的文档。

9.2.3  连接数据库

除了前面介绍的POJO和映射文件之外,Hibernate还不知道与哪个数据库连接,也不知道连接数据库时需要哪些属性。因此,Hibernate控制数据库连接提供了两种方式:

— 采用hibernate.properties属性文件。

— 采用hibernate.cfg.xml配置文件。

这两种方式只是形式上的区别,实质的内容没有任何改变。都需要指定连接数据库的URL、数据库的驱动、用户名及密码等基本信息。如果需要使用连接池,则还应确定连接池配置信息。

1.直接Hibernate的配置连接

直接Hibernate的配置连接就是使用hibernate.cfg.xml文件,关于使用hibernate. properties的方式与此类似,不再赘述。

下面是使用C3P0连接池的hibernate.cfg.xml配置文件的源代码:

<?xml version="1.0" encoding="GBK"?>

<!--  Hibernate配置文件的文件头,包含DTD等信息-->

<!DOCTYPE hibernate-configuration

    PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"

    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<!--  Hibernte配置文件的根元素-->

<hibernate-configuration>

    <!--  配置SessionFactory-->

    <session-factory>

        <!--  配置连接数据库的URL-->

        <property name="connection.url">jdbc:mysql://localhost/j2ee</

property>

        <!--  配置连接数据库的用户名-->

        <property name="connection.username">root</property>

        <!--  配置连接数据库的密码-->

        <property name="connection.password">32147</property>

        <!--  配置连接数据库的驱动-->

        <property name="connection.driver_class">com.mysql.jdbc.Driver</

property>

        <!--  配置连接数据库的方言-->

        <property name="dialect">org.hibernate.dialect.MySQLDialect</

property>

        <!—JDBC连接池(use the C3P0) -->

        <!--  连接池的最大连接数-->

        <property name="hibernate.c3p0.max_size">500</property>

        <!--  连接池的最小连接数-->

        <property name="hibernate.c3p0.min_size">2</property>

        <!--  连接池的超时时长-->

        <property name="hibernate.c3p0.timeout">5000</property>

        <!--  连接池的缓存statement的数量-->

        <property name="hibernate.c3p0.max_statements">100</property>

        <property name="hibernate.c3p0.idle_test_period">3000</property>

        <!--  连接池每次获取连接的数量-->

        <property name="hibernate.c3p0.acquire_increment">2</property>

        <property name="hibernate.c3p0.validate">true</property>

        <!--  每次执行持久化操作,是否显示对应的SQL语句-->

         <property name="hibernate.show_sql">true</property>

         <!--  是否在启动时,重新创建数据库 -->

        <property name="hbm2ddl.auto">update</property>

        <!--  此处列出所有的映射文件-->

        <mapping resource="Person.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

Hibernate通过上面的配置文件,知道如何控制与数据库的连接,以及采用连接池的方式。

2.采用Spring管理Hibernate

让Spring管理Hibernate时,必须将Hibernate的SessionFactory配置在Spring容器内,此时有两种做法:

— 在Spring容器中配置SessionFactory。

— 让hibernate.cfg.xml文件控制SessionFactory。

这两种做法的效果相似,都是利用Spring的LocalSessionFactoryBean,负责产生Hibernate的SessionFactory,该bean作为其他持久化访问组件的属性注入。

下面是完成在Spring容器配置Hibernate的SessionFactory的形式,该项目采用以下方式:

<beans>

    <!--  配置数据源,使用DBCP数据源-->

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"

        destroy-method="close">

        <!--  MySQL数据库的驱动-->

          <property name="driverClassName" value="com.mysql.jdbc.Driver"/>

        <!--  数据库的URL-->

          <property name="url" value="jdbc:mysql:///newsboard"/>

        <!--  指定数据库的用户名-->

          <property name="username" value="root"/>

        <!--  指定数据库的密码-->

          <property name="password" value="123"/>

        <!--  指定数据库的最大连接数-->

          <property name="maxActive" value="100"/>

        <!--  指定数据库的最大空闲连接数-->

          <property name="maxIdle" value="30"/>

        <!--  指定数据库的最大等待数-->

          <property name="maxWait" value="1000"/>

        <!--  指定数据库的默认自动提交-->

          <property name="defaultAutoCommit" value="true"/>

        <!--  指定数据库的连接超时时是否启动删除-->

          <property name="removeAbandoned" value="true"/>

        <!--  指定数据库的删除数据库连接的超时时长-->

          <property name="removeAbandonedTimeout" value="60"/>

          <property name="logAbandoned" value="true"/>

    </bean>                     

</beans>

本系统使用的数据源是DBCP的数据源,DBCP数据源是来自Apache组织的一个优秀的数据源。

DBCP数据源有四个属性值得注意。

—  driverClassName:数据库驱动,通常由第三方提供。

—  url:数据库URL,不同数据库的写法存在差异。

—  username:数据库的用户名。

—  password:数据库的密码。

配置不同的数据库时,这四个属性通常需要改动。driverClassName由数据库厂商提供,url的写法也由厂商决定,请参照数据库的文档。这里的数据源使用的是MySQL,连接的是newsboard数据库。

另外,Spring提供了对Hibernate的SessionFactory的管理,只需在application Context. xml文件中增加如下配置:

<!--  配置Hibernate的SessionFactory -->

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.

LocalSessionFactoryBean">

        <!--  依赖注入SessionFactory所需的DataSource-->

          <property name="dataSource" ref="dataSource"/>

        <!--  加载所有的映射文件-->

          <property name="mappingResources">

            <!--  下面列出所有的持久化映射文件-->

               <list>

                <value>org/yeeku/model/User.hbm.xml</value>

                <value>org/yeeku/model/News.hbm.xml</value>

                <value>org/yeeku/model/NewsReview.hbm.xml</value>

                <value>org/yeeku/model/Category.hbm.xml</value>

               </list>

          </property>

        <!--  下面指定Hibernate的属性-->

          <property name="hibernateProperties">

               <props>

                <!--  下面指定Hibernate使用的数据库方法-->

                <prop key="hibernate.dialect">org.hibernate.dialect.

MySQLDBDialect</prop>

               </props>

          </property>

    </bean>

其中<property name="dataSource" ref="dataSource"/>就是我们刚才配置的数据源。mappingResources 是我们编写好的映射配置文件。除此之外还需要在hibernateProperties里配置Hibernate访问数据库使用的方法,到此为止,已经基本完成了Spring与Hibernate的整合。

如果需要让hibernate.cfg.xml文件自己负责Hibernate SessionFactory的配置,可以采用如下方式:同样使用LocalSessionFactoryBean生成SessionFactory,但此时无须确定数据库用户名及密码等具体信息,只需在配置文件中确定hibernate.cfg.xml文件的位置。

配置代码如下:

<!--  配置Hibernate的SessionFactory-->

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.

LocalSessionFactoryBean">

    <!--  确定配置文件的位置-->

    <property name="configLocation">

        <value>classpath:hibernate.cfg.xml</value>

    </property>

</bean>

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论