(一)构建Hibernate框架环境
任何一项新技术的出现都有它的必然性,Hibernate也不例外,所以在掌握Hibernate的具体应用之前我们一定先要了解Hibernate是什么?使用Hibernate会给我们的程序开发带来哪些好处?使用Hibernate的好处简单来讲体现在以下几个方面:
1. Hibernate说白了就是对持久层进行的封装,它简化了对数据库的操作,使得我们可以以对象方式直接访问数据库中的数据。实现了完全的OO思想。
2. 另外由于Hibernate对JDBC的彻底封装,使得我们可以不用在程序中写传统的sql语句便能实现对数据库的各种操作,极大的提高了生产力。
3. Hibernate支持MySQL,SQL Server等多种数据库。Hibernate提供了多种数据库的接口,只要更改配置信息可以实现对不同数据库的操作,换句话说也就是你可以轻视的实现数据库更换。
说了这么多Hibernate的好处,那Hibernate到底该怎样用呢?别急下面我们就来看看Hibernate环境是如何搭建起来的。
1. 建立Java项目(也可以是java web项目)。
2. 导入相关jar包。导入jar包的方式可以有两种。第一种是直接添加外部jar包,也就是我们的“Add
External JARs”。另外一种是先建立自己的jar包库,然后向新建的库中加入jar包,如果使用这种方式最后一定不要忘记将建好的库引入到项目中。两种方式均可,但一般更推荐第二种方式。
加入的jar包主要有以下这些:
2.1在Hibernate_Home/lib/路径下的所有jar包。
2.2加入Hibernate核心jar包:hibernate3.jar。
2.3加入所使用的数据库的JDBC驱动包。使用MySQL数据库则加入MySQL数据库驱动mysql-connector-java-3.1.13-bin.jar。
3.配置Hibernate核心文件hibernate_cfg.xml。以使用MySQL数据库为例,其配置文件描述如下:
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory > <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_session</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.show_sql">true</property> <mapping resource="com/bjpowernode/hibernate/User.hbm.xml"/> </session-factory> </hibernate-configuration>
|
在配置文件中通过更改数据库驱动,数据库连接,以及方言等配置便可以轻松的更改数据库。
通过以上步骤搭建好Hibernate环境后在程序调试的时候是没有日志记录的,这就导致虽然应用了Hibernate框架但是由于日志文件的缺失在编写程序的时候给我们的调试带来了很大的麻烦,为了解决这个问题我们需要将log4j.properties文件拷贝到XPath能搜所到的路径下,一般来讲可以直接拷贝到src目录下。这样就完美实现了Hibernate环境的搭建。
(二)剪不断理还乱之:一对多关联映射
一对多关联映射和多对一关联映射的原理是一致的,都是在多的一端加入一个外键,指向一的一端。但是他们又有所区别,有了多指向一的关系,在加载多的时候可以将一加载上来,而有了一对多的关系,在加载一的时候可以将多加载上来。那有了一对多的关联映射,是怎样实现加载一的时候同时加载多呢?要实现这样的功能当然离不开我们配置文件,下面我们就来通过一个小Demo看看如何通过配置文件实现这样的功能。
我们以学生Student和Classes为例,一个班级对应多个学生,每个学生属于一个班级,从班级的角度来看这是一个典型的一对多关系,既然一个班级对应着多个学生,那班级中自然要含有对多个学生的引用,在代码中通常是以集合的形式体现的,来看Student和Classes的具体代码:
package com.bjpowernode.hibernate; public class Student { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class Classes { private int id; private String name; private Set students; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set getStudents() { return students; } public void setStudents(Set students) { this.students = students; } }
|
看对应的Student.hbm.xml文件和Classes.hbm.xml文件:
<!-- Classes.hbm.xml文件 --> <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.bjpowernode.hibernate.Classes" table="t_classes"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="students"> <key column="classesid"/> <one-to-many class="com.bjpowernode.hibernate.Student"/> </set> </class> </hibernate-mapping> |
<!-- Student.hbm.xml文件 --> <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.bjpowernode.hibernate.Student" table="t_student"> <id name="id"> <generator class="native"/> </id> <property name="name"/> </class> </hibernate-mapping> |
通过Classes.hbm.xml文件我们可以清楚地看到,Classes类以集合的形式持有对Student的引用,而Student类并不知道Classes的存在。这种一的一端持有多的一端的引用,多的一段不知道一的一端的存在的一对多关联映射形式我们称之为单项关联。但是我们仔细分析一下这种方式单项关联映射存在两个问题:
1. 因为多的一端Student不知道Classes的存在(也就是Student没有维护与Classes的关系)所以在保存Student的时候关系字段classid是为null的,如果将该关系字段设置为空,则将无法保存数据。
2. 另外因为Student不维护关系,而Classes维护关系,classes就会发出多余的update语句,保证classes和Student有关系这样加载Classes的时候才能把该Classes对应得学生加载出来。
解决上面问题的关键就在于将维护classid字段的任务交给Student端来维护,让Student端知道Classes端的存在。这也就是我们最经常用的双向关联。采用双向关联后我们需要在配置文件上做的改动只有两个地方:
1. 在Student.hbm.xml文件中加入<many-to-one name="classes"column="classesid"/>标签,注意一定要让column属性值与Classes.hbm.xml文件中的<key
column="classesid" />标签中的column属性值相同,否则会出现数据混乱。
2. 在Classes.hbm.xml文件中的<set name="students"
inverse="true">标签中加入inverse="true"属性,inverse
属性可以用在一对多和多队多双向关联上,inverse属性默认为false,为false表示本端可以维护关系
,如果inverse为true,表示本端不能维护关系,会交给另一端维护关系,本端失效。一对多关联映射我们通常在多的一端维护关系,让一的一端失效,所以设置inverse为true。
通过双向关联实现控制方向上的反转的主要目的是解决一对多单项关联的缺陷而不是需求驱动的。
(三)剪不断理还乱之:多对多关联映射
hibernate多对多关联映射同样可以分为单向的关联映射和双向的关联映射,与一对多关联映射相比,双方之间的关系将不再由其中多的一方维护而是变成了由第三张表来维护。第三张表的出现不仅减少了两张表中的数据冗余,而且大大提高了数据存储的灵活性。同样我们将从用户和角色的角度出发从单向和双向两个角度来分析多对多关联映射。
我们假定所给需求为知道用户id可以取得该用户所对应的的角色名称(反过来也一样,同样可以知道角色id可以取得该角色下的所有用户名称),则User类中持有对Role的引用,但是Role不知道User的存在,他们的代码如下:
package com.bjpowernode.hibernate; import java.util.Set; public class User { private int id; private String name; private Set roles; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set getRoles() { return roles; } public void setRoles(Set roles) { this.roles = roles; } } public class Role { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
|
所对应的的User.hbm.xml文件和Role.hbm.xml文件的描述如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.bjpowernode.hibernate.Role" table="t_role"> <id name="id"> <generator class="native"/> </id> <property name="name"/> </class> <class name="com.bjpowernode.hibernate.User" table="t_user"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="roles" table="t_user_role"> <key column="userid"/>
<many-to-many class="com.bjpowernode.hibernate.Role" column="role_id"/> </set> </class> </hibernate-mapping> |
从User.hbm.xml文件描述中我们可以看出第三张表t_user_role中的字段由User表和Role表中主键组成,需要尤其注意的是如果将其改为双向关联的话,第一表的名称不能改变,第二表中字段的名称不能改变,因为这两个类(或者说两个表)同时维护着第三张表。与多对一关联关系相比多对多关联关系中hbm.xml文件并没有太大变化,只不过是引入了第三张表,并将<one-to-many>标签改为<many-to-many>标签而已。
(四)剪不断理还乱之:复合主键 && 组合映射
一:复合主键
复合主键即两个或多个字段联合起来作为主键,它的通常做法是将主键相关字段抽取出来放到一个单独的类中,但是这样的类是有要求的:
1. 必须实现序列化接口
2. 必须覆盖equals和hashCode方法
以会计核算期中核算年和核算月做主键为例,将这两个主键相关字段放到FiscalYearPeriodPK类中,代码如下:
package com.bjpowernode.hibernate; import java.io.Serializable; public class FiscalYearPeriodPK implements Serializable { //核算年 private int fiscalYear; //核算月 private int fiscalPeriod; public int getFiscalYear() { return fiscalYear; } public void setFiscalYear(int fiscalYear) { this.fiscalYear = fiscalYear; } public int getFiscalPeriod() { return fiscalPeriod; } public void setFiscalPeriod(int fiscalPeriod) { this.fiscalPeriod = fiscalPeriod; } //要实现序列化接口需要覆盖hashCode和equals方法,这样才能比较 @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + fiscalPeriod; result = prime * result + fiscalYear; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FiscalYearPeriodPK other = (FiscalYearPeriodPK) obj; if (fiscalPeriod != other.fiscalPeriod) return false; if (fiscalYear != other.fiscalYear) return false; return true; } }
|
持有该类引用的FiscalYearPeriod类代码如下:
package com.bjpowernode.hibernate; import java.util.Date; public class FiscalYearPeriod { private FiscalYearPeriodPK fiscalYearPeriodPK; //开始日期 private Date beginDate; //结束日期 private Date endDate; //状态 private String periodSts; public Date getBeginDate() { return beginDate; } public void setBeginDate(Date beginDate) { this.beginDate = beginDate; } public Date getEndDate() { return endDate; } public void setEndDate(Date endDate) { this.endDate = endDate; } public String getPeriodSts() { return periodSts; } public void setPeriodSts(String periodSts) { this.periodSts = periodSts; } public FiscalYearPeriodPK getFiscalYearPeriodPK() { return fiscalYearPeriodPK; } public void setFiscalYearPeriodPK(FiscalYearPeriodPK fiscalYearPeriodPK) { this.fiscalYearPeriodPK = fiscalYearPeriodPK; } } |
来看最重要的部分,映射文件是如何进行配置的,在复合主键映射的配置中需要引入一个新的标签<composite-id>,这个标签的作用就是告诉Hibernate,生成的表使用的是复合主键,其中的<key>标签便是具体的联合主键字段,下面是映射文件内容:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.bjpowernode.hibernate.FiscalYearPeriod" table="t_fiscal_year_period"> <composite-id name="fiscalYearPeriodPK"> <key-property name="fiscalYear"/> <key-property name="fiscalPeriod"/> </composite-id> <property name="beginDate" type="date"/> <property name="endDate" type="date"/> <property name="periodSts"/> </class> </hibernate-mapping> |
二:组合映射
采用组合映射的条件是由两个或多个类,他们持有多个相同的具有相同特点的成员变量(变量类型和名称一样),这时可以将这些相同的成员变量抽取出来放到另外一个类中,该类只是实体的一部分(注意这个类并不是实体类),与实体类相比它没有对象id,我们将其称之为值类。比如有User和Employee两个类,均持有
,id,name,email,address,zipCode,contactTel属性,通过观察可知,后四个字段都属于通讯方面的信息,所以可以将这四个字段抽取出来放到类Contact中,然后让User和Employee类都持有Contact的引用即可,只要在映射文件中做好相应的配置,同样可以达到原来的映射效果。下面来看User.hbm.xml(Employee.hbm.xml与User.hbm.xml内容类似)映射文件是如何配置的:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <!-- User.hbm.xml文件 --> <class name="com.bjpowernode.hibernate.User" table="t_user"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <component name="userContact"> <property name="email"/> <property name="address"/> <property name="zipCode"/> <property name="contactTel"/> </component> </class> <!-- Employee.hbm.xml文件 --> <class name="com.bjpowernode.hibernate.Employee" table="t_employee"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <component name="employeeContact"> <property name="email"/> <property name="address"/> <property name="zipCode"/> <property name="contactTel"/> </component> </class> </hibernate-mapping> |
通过上述配置同样可以达到每个类持有六个成员变量的效果,但是使用组合映射可以实现对象模型的细粒度划分,复用率高,含义明确,另外如果想要扩展属性的只要在抽取的类中进行扩展就可以,更加灵活。
|