Hibernate框架使用归纳

Hibernate基础代码包括:
1.POJO
POJO 在Hibernate 语义中理解为数据库表所对应的Domain Object。这里的POJO
就是所谓的“Plain Ordinary Java Object”,字面上来讲就是无格式普通Java 对象,简单的可以理解为一个不包含逻辑代码的值对象(Value Object 简称VO)。
一个典型的POJO:
package org.hibernate.sample;

public class TUser implements Serializable {
   private String name;
   private TGroup group;
   private Set addresses;

   public User(String name) {
     this.name = name;
   }
   /** default constructor */
   public User() {
   }
   public String getName() {
     return this.name;
   }
   public void setName(String name) {
     this.name = name;
   }

   public TGroup getGroup() {
     return group;
   }

   public void setGroup(TGroup group) {
     this.group = group;
   }

   public Set getAddresses() {
     return addresses;
   }

   public void setAddresses(Set addresses) {
     this.addresses = addresses;
   }

}
注意:在编写代码的时候请,对将POJO的getter/setter方法设定为public,如果设定为private,Hibernate将无法对属性的存取进行优化,只能转而采用传统的反射机制进行操作,这将导致大量的性能开销。

2.Hibernate 映射文件
Hibernate 从本质上来讲是一种“对象-关系型数据映射”(Object Relational Mapping 简称ORM)。前面的POJO在这里体现的就是ORM中Object层的语义,而映射(Mapping)文件则是将对象(Object)与关系型数据(Relational)相关联的纽带,在Hibernate中,映射文件通常以“.hbm.xml”作为后缀。

配置文件名默认为“hibernate.cfg.xml”,Hibernate 初始化期间会自动在CLASSPATH 中寻找这个文件,并读取其中的配置信息,为后期数据库操作做好准备。
配置文件应部署在CLASSPATH 中,对于Web 应用而言,配置文件应放置在在\WEB-INF\classes 目录下。
一个典型的hibernate.cfg.xml配置文件如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-configuration>
   <!—- SessionFactory 配置-->
<session-factory>
   <!—- 数据库URL -->
   <property name="hibernate.connection.url">
     jdbc:oracle:thin:@192.168.4.241:1521:orcl
   </property>
   <!—- 数据库JDBC驱动-->
   <property name="hibernate.connection.driver_class">
     oracle.jdbc.driver.OracleDriver
   </property>
   <!—- 数据库用户名-->
   <property name="hibernate.connection.username">
     User
   </property>
   <!—- 数据库用户密码-->
   <property name="hibernate.connection.password">
     Mypass
   </property>
   <!--dialect ,每个数据库都有其对应的Dialet以匹配其平台特性-->
   <property name="dialect">
     org.hibernate.dialect.OracleDialect
   </property>
   <!—- 是否将运行期生成的SQL输出到日志以供调试-->
   <property name="hibernate.show_sql">
     True
   </property>
   <!—- 是否使用数据库外连接-->
   <property name="hibernate.use_outer_join">
     True
   </property>
   <!—- 事务管理类型,这里我们使用JDBC Transaction -->
   <property name="hibernate.transaction.factory_class">
     net.sf.hibernate.transaction.JDBCTransactionFactory
   </property>
   <!—映射文件配置,注意配置文件名必须包含其相对于根的全路径-->
   <mapping resource="conf/hibernate/TUser.hbm.xml"/>
   <mapping resource="conf/hibernate/TGroup.hbm.xml"/>
  </session-factory>
</hibernate-configuration>

TUser.hbm.xml配置文件如下:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
>
<property
name="name"
type="java.lang.String"
column="name"
not-null="true"
length="50"
>
</class>
</hibernate-mapping>

上面我们已经完成了Hiberante 的基础代码,现在先从一段最简单的代码入手,感受一下Hibernate所提供的强大功能。
下面这段代码是一个JUnit TestCase,演示了TUser 对象的保存和读取。
public class HibernateTest extends TestCase {
Session session = null;
/**
* JUnit中setUp方法在TestCase初始化的时候会自动调用
* 一般用于初始化公用资源
*此例中,用于初始化Hibernate Session
*/
protected void setUp(){
try {
/**
*采用hibernate.properties配置文件的初始化代码:
* Configuration config = new Configuration();
* config.addClass(TUser.class);
*/
//采用hibernate.cfg.xml配置文件
//请注意初始化Configuration时的差异:
// 1.Configuration的初始化方式
// 2.xml文件中已经定义了Mapping文件,因此无需再Hard Coding导入POJO文件的定义
Configuration config = new Configuration().configure();
SessionFactory sessionFactory =
config.buildSessionFactory();
session = sessionFactory.openSession();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
*与setUp方法相对应,JUnit TestCase执行完毕时,会自动调用tearDown方法
*一般用于资源释放,此例中,用于关闭在setUp方法中打开的Hibernate Session
*/
protected void tearDown(){
try {
session.close();
} catch (HibernateException e) {
e.printStackTrace();
}
}
/**
* 对象持久化(Insert)测试方法
* JUnit中,以”test”作为前缀的方法为测试方法,将被JUnit自动添加到测试计划中运行
*/
public void testInsert(){
try {
TUser user = new TUser();
user.setName("Emma");
session.save(user);
session.flush();
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
/**
* 对象读取(Select)测试
* 请保证运行之前数据库中已经存在name=’Erica’的记录
*/
public void testSelect(){
String hql=
" from TUser where name='Erica'";
try {
List userList = session.find(hql);
TUser user =(TUser)userList.get(0);
Assert.assertEquals(user.getName(),"Erica");
} catch (HibernateException e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}
}

可以看到,程序中通过少量代码实现了Java 对象和数据库数据的同步,同时借助Hibernate的有力支持,轻松实现了对象到关系型数据库的映射。
相对传统的JDBC数据访问模式,这样的实现无疑更符合面向对象的思想,同时也大大提高了开发效率。
上面的代码中引入了几个Hibernate基础语义:
1.Configuration
2.SessionFactory
3.Session

Configuration:
正如其名,Configuration 类负责管理Hibernate 的配置信息。Hibernate 运行时需要
获取一些底层实现的基本信息,其中几个关键属性包括:
1.数据库 URL
2.数据库用户
3.数据库用户密码
4.数据库JDBC驱动类
5.数据库 dialect,用于对特定数据库提供支持,其中包含了针对特定数据库特性
的实现,如Hibernate数据类型到特定数据库数据类型的映射等。
使用Hibernate 必须首先提供这些基础信息以完成初始化工作,为后继操作做好准
备。这些属性在hibernate配置文件(hibernate.cfg.xml)中加以设定。
当我们调用:Configuration config = new Configuration().configure();
时,Hibernate会自动在当前的CLASSPATH 中搜寻hibernate.cfg.xml文件并将其读取到内存中作为后继操作的基础配置。Configuration 类一般只有在获取SessionFactory
时需要涉及,当获取SessionFactory 之后,由于配置信息已经由Hibernate 维护并绑定
在返回的SessionFactory之上,因此一般情况下无需再对其进行操作。
我们也可以指定配置文件名,如果不希望使用默认的hibernate.cfg.xml文件作为配
置文件的话:
File file = new File("c:\\sample\\myhibernate.xml");
Configuration config = new Configuration().configure(file);

SessionFactory:
SessionFactory 负责创建Session 实例。我们可以通过Configuation 实例构建
SessionFactory,Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Configuration实例config会根据当前的配置信息,构造SessionFactory实例并返回。SessionFactory 一旦构造完毕,即被赋予特定的配置信息。也就是说,之后config 的任何变更将不会影响到已经创建的SessionFactory 实例(sessionFactory)。如果需要使用基于改动后的config 实例的SessionFactory,需要从config 重新构建一个SessionFactory实例。

Session:
Session是持久层操作的基础,相当于JDBC中的Connection。
Session实例通过SessionFactory实例构建:
Configuration config = new Configuration().configure();
SessionFactory sessionFactory = config.buildSessionFactory();
Session session = sessionFactory.openSession();
之后我们就可以调用Session所提供的save、find、flush等方法完成持久层操作:
Find:
String hql= " from TUser where name='Erica'";
List userList = session.find(hql);
Save:
TUser user = new TUser();
user.setName("Emma");
session.save(user);
session.flush();
最后调用Session.flush方法强制数据库同步,这里即强制Hibernate将user实例立即同步到数据库中。如果在事务中则不需要flush方法,在事务提交的时候,hibernate自动会执行flush方法,另外当Session关闭时,也会自动执行flush方法。

Hibernate数据关联:

一对一关联:
Hibernate中的一对一关联由“one-to-one”节点定义,每个用户对应一个组,这在我们的系统中反映为TUser 到TGroup 的one-to-one 关系。其中TUser 是主控方,TGroup是被动方。
one-to-one关系定义比较简单,只需在主控方加以定义。这里,我们的目标是由TUser 对象获取其对应的TGroup 对象。因此TUser 对象是主控方,为了实现一对一关系,我们在TUser 对象的映射文件TUser.hbm.xml 中加入one-to-one节点,对TGroup对象进行一对一关联:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<one-to-one
name="group"
class="org.hibernate.sample.TGroup"
cascade="none"
outer-join="auto"
constrained="false"
/>
……
</class>
</hibernate-mapping>

一对多关联:
一对多关系在系统实现中也很常见。典型的例子就是父亲与孩子的关系。这个示例中,每个用户(TUser)都关联到多个地址(TAddress),如一个用户可能拥有办公室地址、家庭地址等多个地址属性。这样,在系统中,就反应为一个“一对多”关联。

一对多关系分为单向一对多关系和双向一对多关系。
单向一对多关系只需在“一”方进行配置,双向一对多关系需要在关联双方均加以配置。

单向一对多关系:
对于主控方(TUser),TUser.hbm.xml配置文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<set
name="addresses"
table="t_address"
lazy="false"
<!--inverse=false的一方(主控方)负责维护关联关系。-->
inverse="false" 
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key>
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set>
……
</class>
</hibernate-mapping>

通过单向一对多关系进行关联相对简单,但是存在一个问题。由于是单向关联,为了保持关联关系,我们只能通过主控方对被动方进行级联更新。且如果被关联方的关联字段为“NOT NULL”,当Hibernate创建或者更新关联关系时,还可能出现约束违例。
例如我们想为一个已有的用户“Erica”添加一个地址对象:
......
Transaction tx = session.beginTransaction();
TAddress addr = new TAddress();
addr.setTel("1123");
addr.setZipcode("233123");
addr.setAddress("Hongkong");
user.getAddresses().add(addr);
session.save(user);//通过主控对象级联更新
tx.commit();
......

为了完成这个操作,Hibernate会分两步(两条SQL)来完成新增t_address记录的操作:
1. save(user)时:
insert into t_address (user_id, address, zipcode, tel)
values (null, "Hongkong", "233123", "1123")
2. tx.commit()时
update t_address set user_id=”1”, address="Hongkong",
zipcode="233123", tel="1123" where id=2

第一条SQL用于插入新的地址记录。
第二条SQL用于更新t_address,将user_id设置为其关联的user对象的id值。

问题就出在这里,数据库中,我们的t_address.user_id字段为“NOT NULL”型,当Hibernate执行第一条语句创建t_address记录时,试图将user_id字段的值设为null,于是引发了一个约束违例异常。
因为关联方向是单向,关联关系由TUser对象维持,而被关联的addr对象本身并不知道自己与哪个TUser对象相关联,也就是说,addr对象本身并不知道user_id应该设为什么数值。因此,在保存addr时,只能先在关联字段插入一个空值。之后,再由TUser对象将自身的id值赋予关联字段addr.user_id,这个赋值操作导致addr对象属性发生变动,在事务提交时,hibernate会发现这一改变,并通过update sql将变动后的数据保存到数据库。第一个步骤中,企图向数据库的非空字段插入空值,因此导致了约束违例。

由于Hibernate实现机制中,采用了两条SQL进行一次数据插入操作,相对单条insert,几乎是两倍的性能开销,效率较低,因此,对于性能敏感的系统而言,这样的解决方案所带来的开销可能难以承受。

双向一对多关系的出现则解决了这个问题。它除了避免约束违例和提高性能的好处之外,还带来另外一个优点,由于建立了双向关联,我们可以在关联双方中任意一方访问关联的另一方。

双向一对多关系:
双向一对多关系,实际上是“单向一对多关系”与“多对一关系”的组合。也就是说我们必须在主控方配置单向一对多关系的基础上,在被控方配置多对一关系与其对应。

对于被控方(TUser),TUser.hbm.xml配置文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-mapping>
<class
name="org.hibernate.sample.TUser"
table="t_user"
dynamic-update="true"
dynamic-insert="true"
>
……
<set
name="addresses"
table="t_address"
lazy="false"
inverse="true"
cascade="all"
sort="unsorted"
order-by="zipcode asc"
>
<key
column="user_id"
>
</key>
<one-to-many
class="org.hibernate.sample.TAddress"
/>
</set>
</class>
</hibernate-mapping>

注意:,inverse被设为“true”,这意味着TUser不再作为主控方,而是将关联关系的维护工作交给关联对象org.hibernate.sample.TAddress 来完成。这样TAddress对象在持久化过程中,就可以主动获取其关联的TUser对象的id,并将其作为自己的user_id,之后执行一次insert操作即可完成全部工作。
在 one-to-many 关系中,将many 一方设为主动方(inverse=false)将有助性能的改善。

对于主控方(TAddress),TAddress.hbm.xml配置文件:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration
PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-2.0.
dtd">
<hibernate-mapping>
<class
name="org.hibernate.sample.TAddress"
table="t_address"
dynamic-update="false"
dynamic-insert="false"
>
……
<many-to-one
name="user"
class="org.hibernate.sample.TUser"
cascade="none"
outer-join="auto"
update="true"
insert="true"
access="property"
column="user_id"
not-null="true"
/>
</class>
</hibernate-mapping>

注意:在 TAddress 对象中新增一个TUser field “user”,并为其添加对应的getter/setter 方法。同时删除原有的user_id 属性及其映射配置,否则运行期会报字段重复映射错误。
......
Transaction tx = session.beginTransaction();
TAddress addr = new TAddress();
addr.setTel("1123");
addr.setZipcode("233123");
addr.setAddress("Hongkong");
addr.setUser(user);//设置关联的TUser对象
user.getAddresses().add(addr);
session.save(user);//级联更新
tx.commit();
......

观察Hibernate执行过程中调用的SQL语句:
insert into t_address (user_id, address, zipcode, tel) values
(1, 'Hongkong', '233123', '1123')
正如我们所期望的,保存工作通过单条Insert语句的执行来完成。

多对多关联:
Hibernate关联关系中相对比较特殊的就是多对多关联,多对多关联与一对一关联和一对多关联不同,多对多关联需要另外一张映射表用于保存多对多映射信息。
由于多对多关联的性能不佳(由于引入了中间表,一次读取操作需要反复数次查询),因此在设计中应该避免大量使用。同时,在对多对关系中,应根据情况,采取延迟加载(Lazy Loading 参见后续章节)机制来避免无谓的性能开销。

这里我们以Group和Role之间的映射为例:
TGroup.hbm.xml中关于多对多关联的配置片断:
<hibernate-mapping>
<class
name="org.hibernate.sample.TGroup"
table="t_group"
dynamic-update="false"
dynamic-insert="false"
>
……
<set
name="roles"
table="t_group_role" ①
lazy="false"
inverse="false"
cascade="save-update" ②
>
<key
column="group_id" ③
>
</key>
<many-to-many
class="org.hibernate.sample.TRole"
column="role_id" ④
/>
</set>
</class>
</hibernate-mapping>

注意:
① 这里为t_group 和t_role之间的映射表。
② 一般情况下,cascade应该设置为“save-update”,对于多对多逻辑而言,很少出现删除一方需要级联删除所有关联数据的情况,如删除一个Group,一般不会删除其中包含的Role(这些Role 可能还被其他的Group所引用)。反之删除Role一般也不会删除其所关联的所有Group。
③ 映射表中对于t_group表记录的标识字段。
④ 映射表中对于t_role表记录的标识字段。

TRole.hbm.xml中关于多对多关联的配置片断:
<hibernate-mapping>
<class
name="org.hibernate.sample.TRole"
table="t_role"
dynamic-update="false"
dynamic-insert="false"
>
……
<set
name="groups"
table="t_group_role"
lazy="false"
inverse="true"
cascade="save-update"
sort="unsorted"
>
<key
column="role_id"
>
</key>
<many-to-many
class="org.hibernate.sample.TGroup"
column="group_id"
outer-join="auto"
/>
</set>
</class>
</hibernate-mapping>

多对多关系中,由于关联关系是两张表相互引用,因此在保存关联状态时必须对双方同时保存。
public void testPersist(){
TRole role1 = new TRole();
role1.setName("Role1");
TRole role2 = new TRole();
role2.setName("Role2");
TRole role3 = new TRole();
role3.setName("Role3");
TGroup group1 = new TGroup();
group1.setName("group1");
TGroup group2 = new TGroup();
group2.setName("group2");
TGroup group3 = new TGroup();
group3.setName("group3");
group1.getRoles().add(role1);
group1.getRoles().add(role2);
group2.getRoles().add(role2);
group2.getRoles().add(role3);
group3.getRoles().add(role1);
group3.getRoles().add(role3);
role1.getGroups().add(group1);
role1.getGroups().add(group3);
role2.getGroups().add(group1);
role2.getGroups().add(group2);
role3.getGroups().add(group2);
role3.getGroups().add(group3);
try {
Transaction tx = session.beginTransaction();
//多对多关系必须同时对关联双方进行保存
session.save(role1);
session.save(role2);
session.save(role3);
session.save(group1);
session.save(group2);
session.save(group3);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
Assert.fail(e.getMessage());
}
}

上面的代码创建3个TGroup对象和3个TRole对象,并形成了多对多关系。

猜你喜欢

转载自maosheng.iteye.com/blog/1912035
今日推荐