Hibernate有很多值得学习的地方,这里我们主要介绍Hibernate Iterator方法,包括介绍事务控制等方面。
Hibernate Iterator方法
1.完成同样一件事,Hibernate提供了可供选择的一些方式,但具体使用什么方式,可能用性能/代码都会有影响。显示,一次返回十万条记录(List /Set/Bag/Map等)进行处理,很可能导致内存不够的问题,而如果用基于游标(ScrollableResults)或Iterator的结果集,则不存在这样的问题。
2.Session的load/get方法,前者会使用二级缓存,而后者则不使用。
3.Query和list/Iterator,如果去仔细研究一下它们,你可能会发现很多有意思的情况,二者主要区别(如果使用了Spring,在HibernateTemplate中对应find,Hibernate Iterator方法):
◆list只能利用查询缓存(但在交易系统中查询缓存作用不大),无法利用二级缓存中的单个实体,但list查出的对象会写入二级缓存,但它一般只生成较少的执行SQL语句,很多情况就是一条(无关联)。
◆Iterator则可以利用二级缓存,对于一条查询语句,它会先从数据库中找出所有符合条件的记录的ID,再通过ID去缓存找,对于缓存中没有的记录,再构造语句从数据库中查出,因此很容易知道,如果缓存中没有任何符合条件的记录,使用Iterator会产生N+1条SQL语句(N为符合条件的记录数)
◆通过Hibernate Iterator,配合缓存管理API,在海量数据查询中可以很好的解决内存问题,如:
while(it.hasNext()){ YouObject object = (YouObject)it.next(); session.evict(youObject); sessionFactory.evice(YouObject.class, youObject.getId()); }
事务控制
事务方面对性能有影响的主要包括:事务方式的选用,事务隔离级别以及锁的选用
1.事务方式选用:如果不涉及多个事务管理器事务的话,不需要使用JTA,只有JDBC的事务控制就可以。
2.事务隔离级别:参见标准的SQL事务隔离级别
3.锁的选用:悲观锁(一般由具体的事务管理器实现),对于长事务效率低,但安全。乐观锁(一般在应用级别实现),如在Hibernate中可以定义 VERSION字段,显然,如果有多个应用操作数据,且这些应用不是用同一种乐观锁机制,则乐观锁会失效。因此,针对不同的数据应有不同的策略,同前面许多情况一样,很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,你应该对你的应用和业务特征有足够的了解。
Hibernate的优点- -
一、Hibernate是JDBC的轻量级的对象封装,它是一个独立的对象持久层框架,和App Server,和EJB没有什么必然的联系。Hibernate可以用在任何JDBC可以使用的场合,例如Java应用程序的数据库访问代码,DAO接口的实现类,甚至可以是BMP里面的访问数据库的代码。从这个意义上来说,Hibernate和EJB不是一个范畴的东西,也不存在非此即彼的关系。
二、Hibernate是一个和JDBC密切关联的框架,所以Hibernate的兼容性和JDBC驱动,和数据库都有一定的关系,但是和使用它的Java程序,和App Server没有任何关系,也不存在兼容性问题。
三、Hibernate不能用来直接和Entity Bean做对比,只有放在整个J2EE项目的框架中才能比较。并且即使是放在软件整体框架中来看,Hibernate也是做为JDBC的替代者出现的,而不是Entity Bean的替代者出现的,让我再列一次我已经列n次的框架结构:
传统的架构:
1) Session Bean <-> Entity Bean <-> DB
为了解决性能障碍的替代架构:
2) Session Bean <-> DAO <-> JDBC <-> DB
使用Hibernate来提高上面架构的开发效率的架构:
3) Session Bean <-> DAO <-> Hibernate <-> DB
就上面3个架构来分析:
1、内存消耗:采用JDBC的架构2无疑是最省内存的,Hibernate的架构3次之,EB的架构1最差。
2、运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。EB的架构效率会差的很远。
3、开发效率:在有JBuilder的支持下以及简单的项目,EB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EB架构很可能会失败。
4、分布式,安全检查,集群,负载均衡的支持
由于有SB做为Facade,3个架构没有区别。
四、EJB和Hibernate学习难度在哪里?
EJB的难度在哪里?不在复杂的XML配置文件上,而在于EJB运用稍微不慎,就有严重的性能障碍。所以难在你需要学习很多EJB设计模式来避开性能问题,需要学习App Server和EJB的配置来优化EJB的运行效率。做EJB的开发工作,程序员的大部分精力都被放到了EJB的性能问题上了,反而没有更多的精力关注本身就主要投入精力去考虑的对象持久层的设计上来。
Hibernate难在哪里?不在Hibernate本身的复杂,实际上Hibernate非常的简单,难在Hibernate太灵活了。
当你用EJB来实现持久层的时候,你会发现EJB实在是太笨拙了,笨拙到你根本没有什么可以选择的余地,所以你根本就不用花费精力去设计方案,去平衡方案的好坏,去费脑筋考虑选择哪个方案,因为只有唯一的方案摆在你面前,你只能这么做,没得选择。
Hibernate相反,它太灵活了,相同的问题,你至少可以设计出十几种方案来解决,所以特别的犯难,究竟用这个,还是用那个呢?这些方案之间到底有什么区别呢?他们的运行原理有什么不同?运行效率哪个比较好?光是主键生成,就有七八种方案供你选择,你为难不为难?集合属性可以用Set,可以用List,还可以用Bag,到底哪个效率高,你为难不为难?查询可以用iterator,可以用list,哪个好,有什么区别?你为难不为难?复合主键你可以直接在hbm里面配置,也可以自定义CustomerType,哪种比较好些?你为难不为难?对于一个表,你可以选择单一映射一个对象,也可以映射成父子对象,还可以映射成两个1:1的对象,在什么情况下用哪种方案比较好,你为难不为难?
这个列表可以一直开列下去,直到你不想再看下去为止。当你面前摆着无数的眼花缭乱的方案的时候,你会觉得幸福呢?还是悲哀呢?如果你是一个负责的程序员,那么你一定会仔细研究每种方案的区别,每种方案的效率,每种方案的适用场合,你会觉得你已经陷入进去拔不出来了。如果是用EJB,你第一秒种就已经做出了决定,根本没得选择,比如说集合属性,你只能用Collection,如果是Hibernate,你会在Bag,List和Set之间来回犹豫不决,甚至搞不清楚的话,程序都没有办法写。
Hibernate的缓存机制介绍时间:2008-02-23 08:07来源:互联网
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。 缓存的介质一般是
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
Hibernate的Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,
applicationContext-hibernate.xml介绍
作者:日章 提交日期:2006-4-10 3:01:00
<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
每一种的数据都有一种的org.hibernate.dialect.,可以到hibernate3.jar>org>hibernate>dialect里查找你要对应的数据库属性
</props>
</property>
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory"/>
</property>
</bean>
<bean id="nationTarget" class="com.employee.demo.dao.nation.NationDaoImp">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="nationDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="target">
<ref local="nationTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
用到了代理,代理的好处就是Hibernate的包自己帮你管理事务。不要自己管,
<bean id="nationTarget" class="com.employee.demo.dao.nation.NationDaoImp">
现在要用一个Hibernate类来代理NationDaoImp类
<bean id="nationDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target">
<ref local="nationTarget"/>
“target”是要把nationDao代理nationTarget,就可以了,到要用到NationDaoImp时。直接用nationDao就可以了,注意:
nationDao现在已经是NationDaoImp类的一个对象了,因为<bean>它自己会帮你创建对应类的对象
</beans>查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
Hibernate Shard简介介绍
HibernateShard 多数据库水平分区解决方案。 1. 简介 Hibernate 的一个扩展,用于处理多数据库水平分区架构。 由google工程师 2007年 捐献给 Hibernate社区。 http://www.hibernate.org/414.html 目前版本: 3.0.0 beta2, 未发GA版。 条件:Hibernate Core 3.2, JDK 5.0 2. 水平分区原理 一个库表如 Order 存在于多个数据库实例上。按特定的分区逻辑,将该库表的数据存储在这些实例中,一条记录的主键 PK,在所有实例中不得重复。 水平分区在大型网站,大型企业应用中经常采用。 像www.sina.com.cn ,www.163.com www.bt285.cn www.guihua.org 目的出于海量数据分散存储,分散操作,分散查询以便提高数据处理量和整体数据处理性能。 使用: google工程师的设计还是非常好的,完全兼容 Hibernate本身的主要接口。 Java代码 org.hibernate.Session org.hibernate.SessionFactory org.hibernate.Criteria org.hibernate.Query org.hibernate.Session org.hibernate.SessionFactory org.hibernate.Criteria org.hibernate.Query 因此程序员开发变化不大,甚至不需要关心后台使用了分区数据库。程序迁移问题不大。而且配置上比较简明。 3. 三种策略: 1) ShardAccessStrategy, 查询操作时,到那个分区执行。 默认提供两个实现: 顺序策略:SequentialShardAccessStrategy, 每个query按顺序在所有分区上执行。 平行策略:ParallelShardAccessStrategy, 每个query以多线程方式并发平行的在所有分区上执行。 此策略下,需要使用线程池机制满足特定的性能需要,java.util.concurrent.ThreadPoolExecutor。 2) ShardSelectionStrategy, 新增对象时,存储到哪个分区。 框架默认提供了一个轮询选择策略 RoundRobinShardSelectionStrategy, 但一般不这样使用。 通常采用“attribute-based sharding”机制,基于属性分区。一般是用户根据表自己实现一个基于属性分区的策略类ShardSelectionStrategy ,例如,以下WeatherReport基于continent属性选择分区: Java代码 public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy { public ShardId selectShardIdForNewObject(Object obj) { if(obj instanceof WeatherReport) { return ((WeatherReport)obj).getContinent().getShardId(); } throw new IllegalArgumentException(); } public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy { public ShardId selectShardIdForNewObject(Object obj) { if(obj instanceof WeatherReport) { return ((WeatherReport)obj).getContinent().getShardId(); } throw new IllegalArgumentException(); } } 3) ShardResolutionStrategy, 该策略用于查找单个对象时,判断它在哪个或哪几个分区上。 默认使用 AllShardsShardResolutionStrategy ,可以自定义例如: Java代码 public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy { public WeatherReportShardResolutionStrategy(List>ShardId< shardIds) { super(shardIds); } public List>ShardId< selectShardIdsFromShardResolutionStrategyData( ShardResolutionStrategyData srsd) { if(srsd.getEntityName().equals(WeatherReport.class.getName())) { return Continent.getContinentByReportId(srsd.getId()).getShardId(); } return super.selectShardIdsFromShardResolutionStrategyData(srsd); } } public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy { public WeatherReportShardResolutionStrategy(List>ShardId< shardIds) { super(shardIds); } public List>ShardId< selectShardIdsFromShardResolutionStrategyData( ShardResolutionStrategyData srsd) { if(srsd.getEntityName().equals(WeatherReport.class.getName())) { return Continent.getContinentByReportId(srsd.getId()).getShardId(); } return super.selectShardIdsFromShardResolutionStrategyData(srsd); } } 4. 水平分区下的查询 对于简单查询 HibernateShard 可以满足。 水平分区下多库查询是一个挑战。主要存在于以下三种操作: 1) distinct 因为需要遍历所有shard分区,并进行合并判断重复记录。 2) order by 类似 1) 3) aggregation count,sim,avg等聚合操作先分散到分区执行,再进行汇总。 是不是有点类似于 MapReduce ? 呵呵。 目前 HibernateShard 不支持 1), 2), 对 3) 部分支持 HibernateShard 目前通过 Criteria 接口的实现对 聚合提供了较好的支持, 因为 Criteria 以API接口指定了 Projection 操作,逻辑相对简单。 而HQL,原生 SQL 还不支持此类操作。 5. 再分区和虚拟分区 当数据库规模增大,需要调整分区逻辑和数据存储时, 需要再分区。 两种方式: 1)数据库数据迁移其他分区; 2) 改变记录和分区映射关系。这两种方式都比较麻烦。尤其“改变记录和分区映射关系”,需要调整 ShardResolutionStrategy。 HibernateShard 提供了一种虚拟分区层。当需要调整分区策略时,只需要调整虚拟分区和物理分区映射关系即可。以下是使用虚拟分区时的配置创建过程: Java代码 Map>Integer, Integer< virtualShardMap = new HashMap>Integer, Integer<(); virtualShardMap.put(0, 0); virtualShardMap.put(1, 0); virtualShardMap.put(2, 1); virtualShardMap.put(3, 1); ShardedConfiguration shardedConfig = new ShardedConfiguration( prototypeConfiguration, configurations, strategyFactory, virtualShardMap); return shardedConfig.buildShardedSessionFactory(); Map>Integer, Integer< virtualShardMap = new HashMap>Integer, Integer<(); virtualShardMap.put(0, 0); virtualShardMap.put(1, 0); virtualShardMap.put(2, 1); virtualShardMap.put(3, 1); ShardedConfiguration shardedConfig = new ShardedConfiguration( prototypeConfiguration, configurations, strategyFactory, virtualShardMap); return shardedConfig.buildShardedSessionFactory(); 6. 局限: 1)HibernateShard 不支持垂直分区, 垂直+水平混合分区。 2) 水平分区下 查询功能受到一定限制,有些功能不支持。实践中,需要在应用层面对水平分区算法进行更多的考虑。 3) 不支持跨分区的 关系 操作。例如:删除A分区上的 s 表,B分区上的关联子表 t的记录无法进行参照完整性约束检查。 (其实这个相对 跨分区查询的挑战应该说小的多,也许google工程师下个版本会支持,呵呵) 4) 解析策略接口似乎和对象ID全局唯一性有些自相矛盾, AllShardsShardResolutionStrategy 的接口返回的是给定对象ID所在的 shard ID集合,按理应该是明确的一个 shard ID. 参考资料:HibernateShard 参考指南。
在 Hibernate 中实现复杂的数据映射
文档选项
打印本页
将此页作为电子邮件发送
级别: 初级
陈亚强 (cyqcims@mail.tsinghua.edu.cn), 高级软件工程师
2003 年 10 月 13 日
在前一篇文章《使用Hibernate来操作持久对象》中,介绍了Hibernate的基本概念,然后用实例演示了怎么在Web应用中使用Hibernate来封装持久数据对象。然而在现实的项目中,我们往往需要操作多个数据表,并且多个表之间往往存在复杂的关系,在本文,将介绍怎么在Hibernate中描述多个表的映射关系,并且演示怎么操作关系复杂的持久对象。
阅读本文前您需要以下的知识和工具:
Tomcat 5.09,可以从 www.apache.org下载;
Hibernate2.0 相关运行环境,可以从 http://hibernate.bluemars.net/下载;
至少安装了一个数据库服务器并且有相关的JDBC驱动程序。
本文的参考资料见 参考资料
本文的全部代码在这里 下载
案例介绍
在第一篇文章中,我们对一个表进行了简单的封装。在这篇文章中,我们讨论更加复杂的情况。
在这个例子中,将考虑到表之间的一对一、一对多、多对多的情况。如图1所示。
图1 实体之间的映射关系
在上面的数据模型图中,Student是所有表的核心,它和Classes表是一对多的关系,和Course表是多对多的关系(通过Student_Course_Link表来链接),和Address表是一对一的关系。
通过分析,我们可以把上面的数据模型转换成如下的Java持久对象,如图2所示。
图2 持久对象之间的关系
可以看出,数据模型图和Java持久对象的类图有非常大的相似性,但是不完全相同。比如Classes表和Student表是一对多的关系;在类图中,两者仍然是一对多的关系,但是在Classes类中添加了一个student属性,属性的类型是java.util.Set,它表示Classes对象中包含的所有Student对象。
回页首
创建Hibernate持久对象
已经对数据模型经过了分析,现在就可以创建持久对象了。持久对象之间的关系由图2所示的类图指定。
我们首先来看Student类,它是这个关系映射的核心,代码如例程1所示。
例程1 Student持久对象(Student.java)
package com.hellking.study.hibernate;
import java.util.Set;
/**
*在hibernate中代表了Students表的类。
*/
public class Student
{
/**属性,和students表中的字段对应**/
private String id;
private String name;
/**和其它类之间的映射关系**/
private Set courses;
private Classes classes;
private Address address;
/**属性的访问方法,必须是公共的方法**/
public void setId(String string) {
id = string;
}
public String getId() {
return id;
}
public void setName(String name)
{
this.name=name;
}
public String getName()
{
return this.name;
}
/**操作和其它对象之间的关系**/
public void setCourses(Set co)
{
this.courses=co;
}
public Set getCourses()
{
return this.courses;
}
public void setAddress(Address ad)
{
this.address=address;
}
public Address getAddress()
{
return this.address;
}
public void setClasses(Classes c)
{
this.classes=c;
}
public Classes getClasses()
{
return this.classes;
}
}
在Student类中,由于Students表和Classes的表是多对一的关系,故它包含了一个类型为Classes的classes属性,它的实际意义是一个学生可以有一个班级;Students表和Address的表是一对一的关系,同样也包含了一个类型为Address的address属性,它的实际意义是一个学生有一个地址;Students表和Course是多对多的关系,故它包含了一个类型为java.util.Set的course属性,它的实际意义是一个学生可以学习多门课程,同样,某个课程可以由多个学生来选修。
Classes对象和Student对象是一对多的关系。Classes对象包含一个类型为java.util.Set的students属性,它的代码如例程2所示。
例程2 Classes持久对象(Classes.java)
package com.hellking.study.hibernate;
import java.util.Set;
/**
*在hibernate中代表了Classes表的类。
*/
public class Classes
{
/**属性,和classes表的字段一致**/
private String id;
private String name;
/**和其它类之间的映射关系**/
private Set students;
/**属性的访问方法,必须是公共的方法**/
public void setId(String string) {
id = string;
}
public String getId() {
return id;
}
public void setName(String name)
{
this.name=name;
}
public String getName()
{
return this.name;
}
/**操作和其它对象之间的关系**/
public void setStudents(Set stud)
{
this.students=stud;
}
public Set getStudents()
{
return this.students;
}
}
Course持久对象在前一篇文章已经介绍,在这里就不再列举。Address持久对象比较简单,除了表字段定义的属性外,没有引入其它的属性,请参考本文的代码。
回页首
描述对象之间的关系
现在我们已经编写好了持久对象,下面的任务就是描述它们之间的关系。首先我们看Student持久对象的描述,如例程3所示。
例程3 Student持久对象的描述(Student.hbm.xml)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class
name="com.hellking.study.hibernate.Student"
table="Students"
dynamic-update="false"
>
<!-- 描述ID字段-->
<id
name="id"
column="StudentId"
type="string"
unsaved-value="any"
>
<generator class="assigned"/>
</id>
<!-- 属性-->
<property
name="name"
type="string"
update="true"
insert="true"
column="Name"
/>
<!-- 描述Student和Course多对多的关系-->
<set
name="courses"
table="Student_Course_Link"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="StudentId"
/>
<many-to-many
class="com.hellking.study.hibernate.Course"
column="CourseId"
outer-join="auto"
/>
</set>
<!-- 描述Student和Classes之间多对一的关系-->
<many-to-one
name="classes"
class="com.hellking.study.hibernate.Classes"
cascade="none"
outer-join="auto"
update="true"
insert="true"
column="ClassesId"
/>
<!-- 描述Student和Address之间一对一的关系-->
<one-to-one
name="address"
class="com.hellking.study.hibernate.Address"
cascade="none"
outer-join="auto"
constrained="false"
/>
</class>
</hibernate-mapping>
在Student.hbm.xml描述符中,共描述了三种关系。第一种是Student和Address之间一对一的关系,它是最简单的关系,使用:
<one-to-one name="" class="">
来描述,这里的name表示的是Student对象中名称为address的属性;class表示的是address属性的类型:com.hellking.study.hibernate.Address。
接下来看Student和Classes之间多对一的关系,使用:
<many-to-one name="classes" class="com.hellking.study.hibernate.Classes" column="ClassesId" … />
来描述。同样,name表示的是Student对象中名称为classes的属性;class表示的是classes属性的类型,column表示Student表引用Classes表使用的外部键名称。对应的,在Classes类中也引用了Student类,它使用了以下的描述符来描述这个关系:
<set
name="students"
table="Students"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="ClassesId"
/>
<one-to-many
class="com.hellking.study.hibernate.Student"
/>
</set>
在描述Student和Course之间多对多关系时,使用了以下的方法:
<set
name="courses"
table="Student_Course_Link"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="StudentId"
/>
<many-to-many
class="com.hellking.study.hibernate.Course"
column="CourseId"
outer-join="auto"
/>
</set>
在映射多对多关系时,需要另外使用一个链接表,这个表的名字由table属性指定,链接表包含了两个字段:CourseId和StudentId。以下的描述:
<key column="StudentId">
指定了Student对象在Student_Course_Link链接表中的外部键。对应的,在Course持久对象使用了以下的描述符来描述这个关系:
<set
name="students"
table="Student_Course_Link"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="CourseId"
/>
<many-to-many
class="com.hellking.study.hibernate.Student"
column="StudentId"
outer-join="auto"
/>
</set>
由于其它持久对象的描述基本一样,在这里就不一一列举了,请参考本文的源代码。最后别忘了在hibernate.cfg.xml里增加这几个对象的描述。
<!-- Mapping files -->
<mapping resource="Address.hbm.xml"/>
<mapping resource="Student.hbm.xml"/>
<mapping resource="Classes.hbm.xml"/>
<mapping resource="Course.hbm.xml"/
回页首
使用映射关系
下面我们开发一个简单的实例来测试这个映射。持久对象使用最频繁的操作是增加数据、查询数据、删除数据、更新数据。对于更新数据的操作的情况,多个表的操作和单个表没有两样,在这里不举例了。
添加数据到数据库
我们在这里测试前三种操作,首先来看添加数据到数据库的情况,如例程4所示。
例程4 测试持久对象之间的映射关系之添加数据(MapTestBean.java部分代码)
/**
*在数据库中添加数据
*/
public void addData(String studentId,String classesId,String coursesId)
throws HibernateException {
try
{
/**
*以下的代码添加了一个Student,同时为Student指定了
*Address、Courses和Classses。
*/
beginTransaction();
//创建一个Student对象 。
Student student = new Student();
student.setName("hellking2");
student.setId(studentId);
//创建一个Address对象。
Address addr=new Address();
addr.setCity("beijing");
addr.setState("bj");
addr.setStreet("tsinghua");
addr.setZip("100083");
addr.setId(student.getId());
//设置Student和address的关系。
student.setAddress(addr);
Set set=new HashSet();
set.add(student);
//创建一个course对象。
Course course=new Course ();
course.setId(coursesId);
course.setName("computer_jsp");
//设置course和student对象之间的关系。
course.setStudents(set);
//创建一个classes对象。
Classes cl=new Classes();
cl.setId(classesId);
cl.setName("engine power");
//设置某个classes对象包含的students对象。
cl.setStudents(set);
//由于是双向的关系,student对象也需要设置一次。
student.setClasses(cl);
//保存创建的对象到session中。
session.save(cl);
session.save(course);
session.save(student);
session.save(addr);
//提交事务,使更改生效。
endTransaction(true);
}
catch(HibernateException e)
{
System.out.println("在添加数据时出错!");
e.printStackTrace();
throw e;
}
}
在例程4中,添加数据到数据库之前,首先设置持久对象的各个属性,如:
student.setName("hellking2");
这种设置属性的方式和普通的类没有什么区别,设置完所有的属性后,就设置持久对象之间的关系,如:
student.setAddress(addr);
如果存在对象之间的多重关系,那么可能需要把对象保存在Set集合中,然后再进行设置,如:
Set set=new HashSet();
set.add(student);
course.setStudents(set);
当设置完所有的属性和对象关系之后,就可以调用:
session.save(persistentObject);
方法把持久对象保存到Hibernate会话中。最后,调用endTransaction来提交事务,并且关闭Hibernate会话。
数据查询
在复杂的实体对象映射中,往往查询也比较复杂。作为演示,我们在这里也提供了几个查询方法,如例程5所示。
例程5 测试持久对象之间的映射关系之查询数据(MapTestBean.java部分代码)
/**
*获得某个给定studentid的Student的地址信息
*/
public Address getAddress(String id) throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
Address addr=(Address)session.load(Address.class,st.getId());
endTransaction(false);
return addr;
}
/**
*获得某个给定studentid的Student的所有课程
*/
public Set getCourses(String id)throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
endTransaction(false);
return st.getCourses();
}
/**
*测试获得某个给定studentid的Student所属的Classes
*/
public Classes getClasses(String id)throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
System.out.println(st.getClasses().getId());
endTransaction(false);
return st.getClasses();
}
这里提供了三种查询方法,分别是:
查询给定id的Student的Address信息;
查询给定id的Student的所有Courses信息;
查询给定id的Student所属的Classes信息。
在查询时,首先使用beginTransaction()方法创建一个Hibernate会话对象,并且开始一个新Hibernate事务;然后通过session.load()方法获得给定ID的Student对象,如:
Student st=(Student)session.load(Student.class,id);
最后调用student.getXXX()方法返回指定的对象。
删除数据
在表的关系比较复杂时,要删除数据,往往存在级联删除的情况,由于级联删除的情况比较复杂,在这里就不举例了。假设我们要删除和某个给定id的student对象的所有相关的记录,就可以使用例程6所示的方法。
例程6 测试持久对象之间的映射关系之删除数据(MapTestBean.java部分代码)
/**
*删除和某个学生相关的所有信息
*(这里只是测试,我们暂且不说这种操作的意义何在)。
*/
public void delteStudent(String id)throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
Address addr=(Address)session.load(Address.class,st.getId());
//删除address信息。
session.delete(addr);
//删除classes信息。
session.delete(st.getClasses());
/**
*逐个删除course。
*/
for(Iterator it=st.getCourses().iterator();it.hasNext();)
{
Course c=(Course)it.next();
session.delete(c);
}
//最后,删除student对象。
session.delete(st);
endTransaction(true);
}
同样,在执行删除前,首先使用beginTransaction()方法创建一个新Hibernate会话和一个新Hibernate事务,然后把要删除的对象Load进来,接下来调用session.delete()方法来删除指定对象。
如果要删除的是集合中的对象,那么可以通过一个迭代来逐个删除,如例程6中删除courses的方法。
回页首
测试
在这里提供了在JSP中调用MapTestBean进行测试的程序,具体代码见maptest.jsp文件。在进行测试前,请确保连接数据库的配置完好,并且每个持久对象的配置都没有错误。如果配置出现困难,请参考本文的源代码。
回页首
行动起来
经过两篇文章由浅入深的学习,希望能够起到抛砖引玉的作用,相信读者对Hibernate的认识已经有一个整体的把握。Hibernate由于它的易用性和良好的移植性等特点,逐渐在企业级应用开发中广泛使用。Hibernate官方网站提供了非常好的使用手册,您可以参考它。如果您并非精通JDBC并且不想学习它,不妨考虑使用Hibernate;如果您在使用实体Bean之类的持久框架遇到困难,也许Hibernate可以助你一臂之力!
Hibernate Iterator方法
1.完成同样一件事,Hibernate提供了可供选择的一些方式,但具体使用什么方式,可能用性能/代码都会有影响。显示,一次返回十万条记录(List /Set/Bag/Map等)进行处理,很可能导致内存不够的问题,而如果用基于游标(ScrollableResults)或Iterator的结果集,则不存在这样的问题。
2.Session的load/get方法,前者会使用二级缓存,而后者则不使用。
3.Query和list/Iterator,如果去仔细研究一下它们,你可能会发现很多有意思的情况,二者主要区别(如果使用了Spring,在HibernateTemplate中对应find,Hibernate Iterator方法):
◆list只能利用查询缓存(但在交易系统中查询缓存作用不大),无法利用二级缓存中的单个实体,但list查出的对象会写入二级缓存,但它一般只生成较少的执行SQL语句,很多情况就是一条(无关联)。
◆Iterator则可以利用二级缓存,对于一条查询语句,它会先从数据库中找出所有符合条件的记录的ID,再通过ID去缓存找,对于缓存中没有的记录,再构造语句从数据库中查出,因此很容易知道,如果缓存中没有任何符合条件的记录,使用Iterator会产生N+1条SQL语句(N为符合条件的记录数)
◆通过Hibernate Iterator,配合缓存管理API,在海量数据查询中可以很好的解决内存问题,如:
while(it.hasNext()){ YouObject object = (YouObject)it.next(); session.evict(youObject); sessionFactory.evice(YouObject.class, youObject.getId()); }
事务控制
事务方面对性能有影响的主要包括:事务方式的选用,事务隔离级别以及锁的选用
1.事务方式选用:如果不涉及多个事务管理器事务的话,不需要使用JTA,只有JDBC的事务控制就可以。
2.事务隔离级别:参见标准的SQL事务隔离级别
3.锁的选用:悲观锁(一般由具体的事务管理器实现),对于长事务效率低,但安全。乐观锁(一般在应用级别实现),如在Hibernate中可以定义 VERSION字段,显然,如果有多个应用操作数据,且这些应用不是用同一种乐观锁机制,则乐观锁会失效。因此,针对不同的数据应有不同的策略,同前面许多情况一样,很多时候我们是在效率与安全/准确性上找一个平衡点,无论如何,优化都不是一个纯技术的问题,你应该对你的应用和业务特征有足够的了解。
Hibernate的优点- -
一、Hibernate是JDBC的轻量级的对象封装,它是一个独立的对象持久层框架,和App Server,和EJB没有什么必然的联系。Hibernate可以用在任何JDBC可以使用的场合,例如Java应用程序的数据库访问代码,DAO接口的实现类,甚至可以是BMP里面的访问数据库的代码。从这个意义上来说,Hibernate和EJB不是一个范畴的东西,也不存在非此即彼的关系。
二、Hibernate是一个和JDBC密切关联的框架,所以Hibernate的兼容性和JDBC驱动,和数据库都有一定的关系,但是和使用它的Java程序,和App Server没有任何关系,也不存在兼容性问题。
三、Hibernate不能用来直接和Entity Bean做对比,只有放在整个J2EE项目的框架中才能比较。并且即使是放在软件整体框架中来看,Hibernate也是做为JDBC的替代者出现的,而不是Entity Bean的替代者出现的,让我再列一次我已经列n次的框架结构:
传统的架构:
1) Session Bean <-> Entity Bean <-> DB
为了解决性能障碍的替代架构:
2) Session Bean <-> DAO <-> JDBC <-> DB
使用Hibernate来提高上面架构的开发效率的架构:
3) Session Bean <-> DAO <-> Hibernate <-> DB
就上面3个架构来分析:
1、内存消耗:采用JDBC的架构2无疑是最省内存的,Hibernate的架构3次之,EB的架构1最差。
2、运行效率:如果JDBC的代码写的非常优化,那么JDBC架构运行效率最高,但是实际项目中,这一点几乎做不到,这需要程序员非常精通JDBC,运用Batch语句,调整PreapredStatement的Batch Size和Fetch Size等参数,以及在必要的情况下采用结果集cache等等。而一般情况下程序员是做不到这一点的。因此Hibernate架构表现出最快的运行效率。EB的架构效率会差的很远。
3、开发效率:在有JBuilder的支持下以及简单的项目,EB架构开发效率最高,JDBC次之,Hibernate最差。但是在大的项目,特别是持久层关系映射很复杂的情况下,Hibernate效率高的惊人,JDBC次之,而EB架构很可能会失败。
4、分布式,安全检查,集群,负载均衡的支持
由于有SB做为Facade,3个架构没有区别。
四、EJB和Hibernate学习难度在哪里?
EJB的难度在哪里?不在复杂的XML配置文件上,而在于EJB运用稍微不慎,就有严重的性能障碍。所以难在你需要学习很多EJB设计模式来避开性能问题,需要学习App Server和EJB的配置来优化EJB的运行效率。做EJB的开发工作,程序员的大部分精力都被放到了EJB的性能问题上了,反而没有更多的精力关注本身就主要投入精力去考虑的对象持久层的设计上来。
Hibernate难在哪里?不在Hibernate本身的复杂,实际上Hibernate非常的简单,难在Hibernate太灵活了。
当你用EJB来实现持久层的时候,你会发现EJB实在是太笨拙了,笨拙到你根本没有什么可以选择的余地,所以你根本就不用花费精力去设计方案,去平衡方案的好坏,去费脑筋考虑选择哪个方案,因为只有唯一的方案摆在你面前,你只能这么做,没得选择。
Hibernate相反,它太灵活了,相同的问题,你至少可以设计出十几种方案来解决,所以特别的犯难,究竟用这个,还是用那个呢?这些方案之间到底有什么区别呢?他们的运行原理有什么不同?运行效率哪个比较好?光是主键生成,就有七八种方案供你选择,你为难不为难?集合属性可以用Set,可以用List,还可以用Bag,到底哪个效率高,你为难不为难?查询可以用iterator,可以用list,哪个好,有什么区别?你为难不为难?复合主键你可以直接在hbm里面配置,也可以自定义CustomerType,哪种比较好些?你为难不为难?对于一个表,你可以选择单一映射一个对象,也可以映射成父子对象,还可以映射成两个1:1的对象,在什么情况下用哪种方案比较好,你为难不为难?
这个列表可以一直开列下去,直到你不想再看下去为止。当你面前摆着无数的眼花缭乱的方案的时候,你会觉得幸福呢?还是悲哀呢?如果你是一个负责的程序员,那么你一定会仔细研究每种方案的区别,每种方案的效率,每种方案的适用场合,你会觉得你已经陷入进去拔不出来了。如果是用EJB,你第一秒种就已经做出了决定,根本没得选择,比如说集合属性,你只能用Collection,如果是Hibernate,你会在Bag,List和Set之间来回犹豫不决,甚至搞不清楚的话,程序都没有办法写。
Hibernate的缓存机制介绍时间:2008-02-23 08:07来源:互联网
缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能。缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。 缓存的介质一般是
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
Hibernate的Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,
applicationContext-hibernate.xml介绍
作者:日章 提交日期:2006-4-10 3:01:00
<?xml version="1.0"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
每一种的数据都有一种的org.hibernate.dialect.,可以到hibernate3.jar>org>hibernate>dialect里查找你要对应的数据库属性
</props>
</property>
<property name="dataSource">
<ref bean="dataSource"/>
</property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory"/>
</property>
</bean>
<bean id="nationTarget" class="com.employee.demo.dao.nation.NationDaoImp">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<bean id="nationDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="target">
<ref local="nationTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
用到了代理,代理的好处就是Hibernate的包自己帮你管理事务。不要自己管,
<bean id="nationTarget" class="com.employee.demo.dao.nation.NationDaoImp">
现在要用一个Hibernate类来代理NationDaoImp类
<bean id="nationDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="target">
<ref local="nationTarget"/>
“target”是要把nationDao代理nationTarget,就可以了,到要用到NationDaoImp时。直接用nationDao就可以了,注意:
nationDao现在已经是NationDaoImp类的一个对象了,因为<bean>它自己会帮你创建对应类的对象
</beans>查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
Hibernate Shard简介介绍
HibernateShard 多数据库水平分区解决方案。 1. 简介 Hibernate 的一个扩展,用于处理多数据库水平分区架构。 由google工程师 2007年 捐献给 Hibernate社区。 http://www.hibernate.org/414.html 目前版本: 3.0.0 beta2, 未发GA版。 条件:Hibernate Core 3.2, JDK 5.0 2. 水平分区原理 一个库表如 Order 存在于多个数据库实例上。按特定的分区逻辑,将该库表的数据存储在这些实例中,一条记录的主键 PK,在所有实例中不得重复。 水平分区在大型网站,大型企业应用中经常采用。 像www.sina.com.cn ,www.163.com www.bt285.cn www.guihua.org 目的出于海量数据分散存储,分散操作,分散查询以便提高数据处理量和整体数据处理性能。 使用: google工程师的设计还是非常好的,完全兼容 Hibernate本身的主要接口。 Java代码 org.hibernate.Session org.hibernate.SessionFactory org.hibernate.Criteria org.hibernate.Query org.hibernate.Session org.hibernate.SessionFactory org.hibernate.Criteria org.hibernate.Query 因此程序员开发变化不大,甚至不需要关心后台使用了分区数据库。程序迁移问题不大。而且配置上比较简明。 3. 三种策略: 1) ShardAccessStrategy, 查询操作时,到那个分区执行。 默认提供两个实现: 顺序策略:SequentialShardAccessStrategy, 每个query按顺序在所有分区上执行。 平行策略:ParallelShardAccessStrategy, 每个query以多线程方式并发平行的在所有分区上执行。 此策略下,需要使用线程池机制满足特定的性能需要,java.util.concurrent.ThreadPoolExecutor。 2) ShardSelectionStrategy, 新增对象时,存储到哪个分区。 框架默认提供了一个轮询选择策略 RoundRobinShardSelectionStrategy, 但一般不这样使用。 通常采用“attribute-based sharding”机制,基于属性分区。一般是用户根据表自己实现一个基于属性分区的策略类ShardSelectionStrategy ,例如,以下WeatherReport基于continent属性选择分区: Java代码 public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy { public ShardId selectShardIdForNewObject(Object obj) { if(obj instanceof WeatherReport) { return ((WeatherReport)obj).getContinent().getShardId(); } throw new IllegalArgumentException(); } public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy { public ShardId selectShardIdForNewObject(Object obj) { if(obj instanceof WeatherReport) { return ((WeatherReport)obj).getContinent().getShardId(); } throw new IllegalArgumentException(); } } 3) ShardResolutionStrategy, 该策略用于查找单个对象时,判断它在哪个或哪几个分区上。 默认使用 AllShardsShardResolutionStrategy ,可以自定义例如: Java代码 public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy { public WeatherReportShardResolutionStrategy(List>ShardId< shardIds) { super(shardIds); } public List>ShardId< selectShardIdsFromShardResolutionStrategyData( ShardResolutionStrategyData srsd) { if(srsd.getEntityName().equals(WeatherReport.class.getName())) { return Continent.getContinentByReportId(srsd.getId()).getShardId(); } return super.selectShardIdsFromShardResolutionStrategyData(srsd); } } public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy { public WeatherReportShardResolutionStrategy(List>ShardId< shardIds) { super(shardIds); } public List>ShardId< selectShardIdsFromShardResolutionStrategyData( ShardResolutionStrategyData srsd) { if(srsd.getEntityName().equals(WeatherReport.class.getName())) { return Continent.getContinentByReportId(srsd.getId()).getShardId(); } return super.selectShardIdsFromShardResolutionStrategyData(srsd); } } 4. 水平分区下的查询 对于简单查询 HibernateShard 可以满足。 水平分区下多库查询是一个挑战。主要存在于以下三种操作: 1) distinct 因为需要遍历所有shard分区,并进行合并判断重复记录。 2) order by 类似 1) 3) aggregation count,sim,avg等聚合操作先分散到分区执行,再进行汇总。 是不是有点类似于 MapReduce ? 呵呵。 目前 HibernateShard 不支持 1), 2), 对 3) 部分支持 HibernateShard 目前通过 Criteria 接口的实现对 聚合提供了较好的支持, 因为 Criteria 以API接口指定了 Projection 操作,逻辑相对简单。 而HQL,原生 SQL 还不支持此类操作。 5. 再分区和虚拟分区 当数据库规模增大,需要调整分区逻辑和数据存储时, 需要再分区。 两种方式: 1)数据库数据迁移其他分区; 2) 改变记录和分区映射关系。这两种方式都比较麻烦。尤其“改变记录和分区映射关系”,需要调整 ShardResolutionStrategy。 HibernateShard 提供了一种虚拟分区层。当需要调整分区策略时,只需要调整虚拟分区和物理分区映射关系即可。以下是使用虚拟分区时的配置创建过程: Java代码 Map>Integer, Integer< virtualShardMap = new HashMap>Integer, Integer<(); virtualShardMap.put(0, 0); virtualShardMap.put(1, 0); virtualShardMap.put(2, 1); virtualShardMap.put(3, 1); ShardedConfiguration shardedConfig = new ShardedConfiguration( prototypeConfiguration, configurations, strategyFactory, virtualShardMap); return shardedConfig.buildShardedSessionFactory(); Map>Integer, Integer< virtualShardMap = new HashMap>Integer, Integer<(); virtualShardMap.put(0, 0); virtualShardMap.put(1, 0); virtualShardMap.put(2, 1); virtualShardMap.put(3, 1); ShardedConfiguration shardedConfig = new ShardedConfiguration( prototypeConfiguration, configurations, strategyFactory, virtualShardMap); return shardedConfig.buildShardedSessionFactory(); 6. 局限: 1)HibernateShard 不支持垂直分区, 垂直+水平混合分区。 2) 水平分区下 查询功能受到一定限制,有些功能不支持。实践中,需要在应用层面对水平分区算法进行更多的考虑。 3) 不支持跨分区的 关系 操作。例如:删除A分区上的 s 表,B分区上的关联子表 t的记录无法进行参照完整性约束检查。 (其实这个相对 跨分区查询的挑战应该说小的多,也许google工程师下个版本会支持,呵呵) 4) 解析策略接口似乎和对象ID全局唯一性有些自相矛盾, AllShardsShardResolutionStrategy 的接口返回的是给定对象ID所在的 shard ID集合,按理应该是明确的一个 shard ID. 参考资料:HibernateShard 参考指南。
在 Hibernate 中实现复杂的数据映射
文档选项
打印本页
将此页作为电子邮件发送
级别: 初级
陈亚强 (cyqcims@mail.tsinghua.edu.cn), 高级软件工程师
2003 年 10 月 13 日
在前一篇文章《使用Hibernate来操作持久对象》中,介绍了Hibernate的基本概念,然后用实例演示了怎么在Web应用中使用Hibernate来封装持久数据对象。然而在现实的项目中,我们往往需要操作多个数据表,并且多个表之间往往存在复杂的关系,在本文,将介绍怎么在Hibernate中描述多个表的映射关系,并且演示怎么操作关系复杂的持久对象。
阅读本文前您需要以下的知识和工具:
Tomcat 5.09,可以从 www.apache.org下载;
Hibernate2.0 相关运行环境,可以从 http://hibernate.bluemars.net/下载;
至少安装了一个数据库服务器并且有相关的JDBC驱动程序。
本文的参考资料见 参考资料
本文的全部代码在这里 下载
案例介绍
在第一篇文章中,我们对一个表进行了简单的封装。在这篇文章中,我们讨论更加复杂的情况。
在这个例子中,将考虑到表之间的一对一、一对多、多对多的情况。如图1所示。
图1 实体之间的映射关系
在上面的数据模型图中,Student是所有表的核心,它和Classes表是一对多的关系,和Course表是多对多的关系(通过Student_Course_Link表来链接),和Address表是一对一的关系。
通过分析,我们可以把上面的数据模型转换成如下的Java持久对象,如图2所示。
图2 持久对象之间的关系
可以看出,数据模型图和Java持久对象的类图有非常大的相似性,但是不完全相同。比如Classes表和Student表是一对多的关系;在类图中,两者仍然是一对多的关系,但是在Classes类中添加了一个student属性,属性的类型是java.util.Set,它表示Classes对象中包含的所有Student对象。
回页首
创建Hibernate持久对象
已经对数据模型经过了分析,现在就可以创建持久对象了。持久对象之间的关系由图2所示的类图指定。
我们首先来看Student类,它是这个关系映射的核心,代码如例程1所示。
例程1 Student持久对象(Student.java)
package com.hellking.study.hibernate;
import java.util.Set;
/**
*在hibernate中代表了Students表的类。
*/
public class Student
{
/**属性,和students表中的字段对应**/
private String id;
private String name;
/**和其它类之间的映射关系**/
private Set courses;
private Classes classes;
private Address address;
/**属性的访问方法,必须是公共的方法**/
public void setId(String string) {
id = string;
}
public String getId() {
return id;
}
public void setName(String name)
{
this.name=name;
}
public String getName()
{
return this.name;
}
/**操作和其它对象之间的关系**/
public void setCourses(Set co)
{
this.courses=co;
}
public Set getCourses()
{
return this.courses;
}
public void setAddress(Address ad)
{
this.address=address;
}
public Address getAddress()
{
return this.address;
}
public void setClasses(Classes c)
{
this.classes=c;
}
public Classes getClasses()
{
return this.classes;
}
}
在Student类中,由于Students表和Classes的表是多对一的关系,故它包含了一个类型为Classes的classes属性,它的实际意义是一个学生可以有一个班级;Students表和Address的表是一对一的关系,同样也包含了一个类型为Address的address属性,它的实际意义是一个学生有一个地址;Students表和Course是多对多的关系,故它包含了一个类型为java.util.Set的course属性,它的实际意义是一个学生可以学习多门课程,同样,某个课程可以由多个学生来选修。
Classes对象和Student对象是一对多的关系。Classes对象包含一个类型为java.util.Set的students属性,它的代码如例程2所示。
例程2 Classes持久对象(Classes.java)
package com.hellking.study.hibernate;
import java.util.Set;
/**
*在hibernate中代表了Classes表的类。
*/
public class Classes
{
/**属性,和classes表的字段一致**/
private String id;
private String name;
/**和其它类之间的映射关系**/
private Set students;
/**属性的访问方法,必须是公共的方法**/
public void setId(String string) {
id = string;
}
public String getId() {
return id;
}
public void setName(String name)
{
this.name=name;
}
public String getName()
{
return this.name;
}
/**操作和其它对象之间的关系**/
public void setStudents(Set stud)
{
this.students=stud;
}
public Set getStudents()
{
return this.students;
}
}
Course持久对象在前一篇文章已经介绍,在这里就不再列举。Address持久对象比较简单,除了表字段定义的属性外,没有引入其它的属性,请参考本文的代码。
回页首
描述对象之间的关系
现在我们已经编写好了持久对象,下面的任务就是描述它们之间的关系。首先我们看Student持久对象的描述,如例程3所示。
例程3 Student持久对象的描述(Student.hbm.xml)
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<class
name="com.hellking.study.hibernate.Student"
table="Students"
dynamic-update="false"
>
<!-- 描述ID字段-->
<id
name="id"
column="StudentId"
type="string"
unsaved-value="any"
>
<generator class="assigned"/>
</id>
<!-- 属性-->
<property
name="name"
type="string"
update="true"
insert="true"
column="Name"
/>
<!-- 描述Student和Course多对多的关系-->
<set
name="courses"
table="Student_Course_Link"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="StudentId"
/>
<many-to-many
class="com.hellking.study.hibernate.Course"
column="CourseId"
outer-join="auto"
/>
</set>
<!-- 描述Student和Classes之间多对一的关系-->
<many-to-one
name="classes"
class="com.hellking.study.hibernate.Classes"
cascade="none"
outer-join="auto"
update="true"
insert="true"
column="ClassesId"
/>
<!-- 描述Student和Address之间一对一的关系-->
<one-to-one
name="address"
class="com.hellking.study.hibernate.Address"
cascade="none"
outer-join="auto"
constrained="false"
/>
</class>
</hibernate-mapping>
在Student.hbm.xml描述符中,共描述了三种关系。第一种是Student和Address之间一对一的关系,它是最简单的关系,使用:
<one-to-one name="" class="">
来描述,这里的name表示的是Student对象中名称为address的属性;class表示的是address属性的类型:com.hellking.study.hibernate.Address。
接下来看Student和Classes之间多对一的关系,使用:
<many-to-one name="classes" class="com.hellking.study.hibernate.Classes" column="ClassesId" … />
来描述。同样,name表示的是Student对象中名称为classes的属性;class表示的是classes属性的类型,column表示Student表引用Classes表使用的外部键名称。对应的,在Classes类中也引用了Student类,它使用了以下的描述符来描述这个关系:
<set
name="students"
table="Students"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="ClassesId"
/>
<one-to-many
class="com.hellking.study.hibernate.Student"
/>
</set>
在描述Student和Course之间多对多关系时,使用了以下的方法:
<set
name="courses"
table="Student_Course_Link"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="StudentId"
/>
<many-to-many
class="com.hellking.study.hibernate.Course"
column="CourseId"
outer-join="auto"
/>
</set>
在映射多对多关系时,需要另外使用一个链接表,这个表的名字由table属性指定,链接表包含了两个字段:CourseId和StudentId。以下的描述:
<key column="StudentId">
指定了Student对象在Student_Course_Link链接表中的外部键。对应的,在Course持久对象使用了以下的描述符来描述这个关系:
<set
name="students"
table="Student_Course_Link"
lazy="false"
inverse="false"
cascade="all"
sort="unsorted"
>
<key
column="CourseId"
/>
<many-to-many
class="com.hellking.study.hibernate.Student"
column="StudentId"
outer-join="auto"
/>
</set>
由于其它持久对象的描述基本一样,在这里就不一一列举了,请参考本文的源代码。最后别忘了在hibernate.cfg.xml里增加这几个对象的描述。
<!-- Mapping files -->
<mapping resource="Address.hbm.xml"/>
<mapping resource="Student.hbm.xml"/>
<mapping resource="Classes.hbm.xml"/>
<mapping resource="Course.hbm.xml"/
回页首
使用映射关系
下面我们开发一个简单的实例来测试这个映射。持久对象使用最频繁的操作是增加数据、查询数据、删除数据、更新数据。对于更新数据的操作的情况,多个表的操作和单个表没有两样,在这里不举例了。
添加数据到数据库
我们在这里测试前三种操作,首先来看添加数据到数据库的情况,如例程4所示。
例程4 测试持久对象之间的映射关系之添加数据(MapTestBean.java部分代码)
/**
*在数据库中添加数据
*/
public void addData(String studentId,String classesId,String coursesId)
throws HibernateException {
try
{
/**
*以下的代码添加了一个Student,同时为Student指定了
*Address、Courses和Classses。
*/
beginTransaction();
//创建一个Student对象 。
Student student = new Student();
student.setName("hellking2");
student.setId(studentId);
//创建一个Address对象。
Address addr=new Address();
addr.setCity("beijing");
addr.setState("bj");
addr.setStreet("tsinghua");
addr.setZip("100083");
addr.setId(student.getId());
//设置Student和address的关系。
student.setAddress(addr);
Set set=new HashSet();
set.add(student);
//创建一个course对象。
Course course=new Course ();
course.setId(coursesId);
course.setName("computer_jsp");
//设置course和student对象之间的关系。
course.setStudents(set);
//创建一个classes对象。
Classes cl=new Classes();
cl.setId(classesId);
cl.setName("engine power");
//设置某个classes对象包含的students对象。
cl.setStudents(set);
//由于是双向的关系,student对象也需要设置一次。
student.setClasses(cl);
//保存创建的对象到session中。
session.save(cl);
session.save(course);
session.save(student);
session.save(addr);
//提交事务,使更改生效。
endTransaction(true);
}
catch(HibernateException e)
{
System.out.println("在添加数据时出错!");
e.printStackTrace();
throw e;
}
}
在例程4中,添加数据到数据库之前,首先设置持久对象的各个属性,如:
student.setName("hellking2");
这种设置属性的方式和普通的类没有什么区别,设置完所有的属性后,就设置持久对象之间的关系,如:
student.setAddress(addr);
如果存在对象之间的多重关系,那么可能需要把对象保存在Set集合中,然后再进行设置,如:
Set set=new HashSet();
set.add(student);
course.setStudents(set);
当设置完所有的属性和对象关系之后,就可以调用:
session.save(persistentObject);
方法把持久对象保存到Hibernate会话中。最后,调用endTransaction来提交事务,并且关闭Hibernate会话。
数据查询
在复杂的实体对象映射中,往往查询也比较复杂。作为演示,我们在这里也提供了几个查询方法,如例程5所示。
例程5 测试持久对象之间的映射关系之查询数据(MapTestBean.java部分代码)
/**
*获得某个给定studentid的Student的地址信息
*/
public Address getAddress(String id) throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
Address addr=(Address)session.load(Address.class,st.getId());
endTransaction(false);
return addr;
}
/**
*获得某个给定studentid的Student的所有课程
*/
public Set getCourses(String id)throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
endTransaction(false);
return st.getCourses();
}
/**
*测试获得某个给定studentid的Student所属的Classes
*/
public Classes getClasses(String id)throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
System.out.println(st.getClasses().getId());
endTransaction(false);
return st.getClasses();
}
这里提供了三种查询方法,分别是:
查询给定id的Student的Address信息;
查询给定id的Student的所有Courses信息;
查询给定id的Student所属的Classes信息。
在查询时,首先使用beginTransaction()方法创建一个Hibernate会话对象,并且开始一个新Hibernate事务;然后通过session.load()方法获得给定ID的Student对象,如:
Student st=(Student)session.load(Student.class,id);
最后调用student.getXXX()方法返回指定的对象。
删除数据
在表的关系比较复杂时,要删除数据,往往存在级联删除的情况,由于级联删除的情况比较复杂,在这里就不举例了。假设我们要删除和某个给定id的student对象的所有相关的记录,就可以使用例程6所示的方法。
例程6 测试持久对象之间的映射关系之删除数据(MapTestBean.java部分代码)
/**
*删除和某个学生相关的所有信息
*(这里只是测试,我们暂且不说这种操作的意义何在)。
*/
public void delteStudent(String id)throws HibernateException
{
beginTransaction();
Student st=(Student)session.load(Student.class,id);
Address addr=(Address)session.load(Address.class,st.getId());
//删除address信息。
session.delete(addr);
//删除classes信息。
session.delete(st.getClasses());
/**
*逐个删除course。
*/
for(Iterator it=st.getCourses().iterator();it.hasNext();)
{
Course c=(Course)it.next();
session.delete(c);
}
//最后,删除student对象。
session.delete(st);
endTransaction(true);
}
同样,在执行删除前,首先使用beginTransaction()方法创建一个新Hibernate会话和一个新Hibernate事务,然后把要删除的对象Load进来,接下来调用session.delete()方法来删除指定对象。
如果要删除的是集合中的对象,那么可以通过一个迭代来逐个删除,如例程6中删除courses的方法。
回页首
测试
在这里提供了在JSP中调用MapTestBean进行测试的程序,具体代码见maptest.jsp文件。在进行测试前,请确保连接数据库的配置完好,并且每个持久对象的配置都没有错误。如果配置出现困难,请参考本文的源代码。
回页首
行动起来
经过两篇文章由浅入深的学习,希望能够起到抛砖引玉的作用,相信读者对Hibernate的认识已经有一个整体的把握。Hibernate由于它的易用性和良好的移植性等特点,逐渐在企业级应用开发中广泛使用。Hibernate官方网站提供了非常好的使用手册,您可以参考它。如果您并非精通JDBC并且不想学习它,不妨考虑使用Hibernate;如果您在使用实体Bean之类的持久框架遇到困难,也许Hibernate可以助你一臂之力!
相关推荐
歌词的第一部分,“You are my sunshine, my only sunshine”,直接表达了歌手对所爱之人无比的依赖和珍视。这里的“sunshine”不仅象征着温暖和光明,还代表了歌手生活中的希望和快乐源泉。他们通过这个比喻,强调...
This textbook contains no new scientific results, and my only contribution was to compile existing knowledge and explain it with my examples and intuition. I have made a great effort to cover ...
- “And fare thee weel, my only Luve!”:向爱人告别,表示祝福。 - “And fare thee weel a while!”:再次表示短暂的告别。 - “And I will come again, my Luve”:承诺将会归来。 - “Tho’ it were ten ...
- “And fare thee weel, my only Luve! / And fare thee weel a while!” - 在这段中,诗人表达了与爱人分别时的不舍,但承诺会回来相见。 - “And I will come again, my Luve, / Tho’ it were ten thousand ...
作为形容词,意为唯一的,如my only brother;作为副词,常置于动词前,表示仅仅,只,如I only watch them on TV。固定搭配only if表示只有,not only...but (also)...表示不仅...而且...。 本单元的学习将帮助...
模板中的文字“Reading is my only entertainment. I don't waste my time in hotels”是一个关于阅读的名言,强调阅读是个人娱乐的主要方式,优于消磨时间的其他活动。这句话可以用作PPT的引言或者主题陈述,突显...
- **连接代词和连接副词**:who, whom, whose, what, which, whoever, whatever, whichever, where, when, how, why(例如"What she did is not yet known."和"Wherever you are is my home —— my only home.")。...
”和“You are my sunshine, my only sunshine...”这样的句子表达了对美好天气的描述和对某人的情感表达。在学习英语的同时,学生能够学习到如何用英语描述环境以及表达感情和喜好,这对于提高学生对英语文化的理解...
首先,它可以指财产或所有权,不可数,如"This small house is my only property."。其次,它也可以表示特定地点的地产,如"property in the center"。此外,property还可以指事物的性质或特性,例如"steel's ...
此外,翻译中的词汇选择也是难点,如"I My only capital is diligence",其中“diligence”不仅是勤奋的意思,还暗示了成功的途径。 对于中国学生来说,阅读和翻译的困难可能源于两种截然不同的文字系统。中文采用...
- **完成句子**:要求学生补充完整句子,如歌词中的“你是我的唯一”(You Are My Only One),检验学生对固定搭配和语境的理解。 3. **试题特点** - **全面性**:试题覆盖了词汇、听力理解、情景对话等多个方面...
12. He was my only Chinese companion during my stay in Australia. ——companion 伙伴。 13. Pip is about eighteen years old when this happens, and the fortune sets him free from financial worries. ——...
5. **动名词**:动名词如"growing", "reading", "seeing"等作为主语时,通常表示一个持续性的行为或状态,例如"Growing flowers is my only hobby.","growing flowers"是主语,表明一个爱好。 6. **主语从句**:...
例如:"Wherever you are is my home ---- my only home."(你所在的任何地方就是我的家——我唯一的家。) 3. **主语从句的特殊形式** - **形式主语"It"**:在某些复杂的句子中,为了避免头重脚轻,可以用"It...
7. **情感稳定性**:“You have been and still are my only true Valentine.”这类语句强调了情感的稳定性和专一性,表达了对伴侣不变的爱。 8. **爱的力量**:“I didn’t think that I could ever trust ...
4. "It’s my only h__________." 填入的是“hobby”,表示“这是我唯一的爱好”。 5. "she always gets full m__________ in exams." 填入的是“marks”,表示“她总是在考试中得到满分”。 二、根据汉语提示写出...
My only wish is that for the next edition, having included basic vectors (not vector calculus) in this edition, there is a chapter on matrices (at least) or full, practical linear algebra as far as ...
Onlyoffice 中文字体包,感谢网上同学提供。 下载后,参考此同学的文章进行更新:https://my.oschina.net/u/4321424/blog/3714201 如还存在多余的字体,参看:...
牙医 用于去缩进的 ES6 模板字符串助手(改编自 )。 var deindent = require ( 'deindent' ) // as a string function deindent ( ` this is the ${ "end" } my only ... my only ...deindent ( String .... my only
和弦 歌曲文件格式的 ruby 解析器。 它可以像这样转换 chordpro 格式的歌曲........You are my sunshine my only sunshine [G7]You make me [C]happy when skies are [G]gray [G7]You'll never [C]know dear how mu