Hibernate中的 3-2-1

one-to-one有三种方法来做

one-to-many有两种方法

many-to-many有一种方法。

单向的many-to-one 与单向的one-to-one的写法是一样的。因为单向的one-to-one是在一个表里设置了外健。有了外健所以one-to-one就要改成many-to-one不过,加了一个属性。Unique=”true这是第一种特殊的情况。
one-to-many
说一下单向的one-to-many,单向也就是只在一边设置关系,另一边不知道。双向就是两个实体类都有另一表的属性。
比如一个人有多个地址。单向的one-to-many就是只在one这个进行设置。

<set >

<key column="personId" />

<one-to-many />

</set> 单向的one-to-many设置方法
这个addresses是在person类里面加的一个set属性的名子。这个集合其实在person表里根本就没有的。只是为了做关系。Key就是指明这个集合的外健。意思好像是本类在用key找另一个表的外健。那个外健也就是本表的主健。下面指明是one-to-many的关系。指明对应的类。因为这个程序就是加载多个Address类来实现one-to-many
首先在person里面要多加一个属性就是一个集合,然后对这个集合进行配制。
昨天写了在hibernate中的值映射。用楼与单元做的例子。一个楼有多个单元,所以就把这个单元做到另一张表中。在hibernate中映射时,就只建一个楼的实体bean,里面有一个属性是list或set的属性单元。然后在映射关系中把楼加上就做成了值的映射。现在来写一个关系的映射。
Many-to-one
这个关系中最简单的一种:
有两张表。students与teachers,students为many.所以外健在students中。
建两个实体,一个student实体,因为在student里面有外健,但是不是在实体里面直接加一个外健id就可以的。要变成在Student这个实体中加入一个Teacher对象作为属性。配制文件是这样写的

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping >

<class name="vo.Student" table="students">

<id column="id">

<generator />

</id>

<property type="string" />

<property type="int" />

用这一个代表关系,意思是外健在这个表中,对应在表中的字段是tea_id这个很简单

<many-to-one column="tea_id" />


</class>

<class table="teachers">

<id >

<generator />

</id>

<property />

<property />

</class>

</hibernate-mapping>
One-to-one
l 一对一关系有三种实现方式:
l 一是通过外键方式实现,即当前对象持有关联对象的外键;
l 二是通过主键方式实现,即当前对象的主键即是主键也是外键,也就是说当前对象的主键和与之相关联对象的主键是相同的。
l 三是通过关系表实现,即关联双方都以外键的形式存储在关系表中,通过关系表反映一对一的关系,这种方式很少使用

这是关系表中最为复杂的一个关系
这个是双向的外健方式实现的,
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="vo">

<class table="students">
<id column="id">
<generator />
</id>
<property />
<property />
<many-to-one column="tea_id" lazy="no-proxy" unique="true"/>
</class>
<class table="teachers">
<id >
<generator />
</id>
<property />
<property />
在没有处健的表中也要定义Student student这个属性。来产生一对一的关系。
<one-to-one property-ref="teacher" />

</class>
</hibernate-mapping>
这是一个单外主健方式来实现的。因为我们要先存teacher.所有在teacher里面设置一个属性student,在存teacher的时候同时也给student的id进行附值。

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping >

<class name="vo.Student" table="students">

<id column="id">

<generator />

</id>

<property type="string" />

<property type="int" />

</class>

<class table="teachers">

<id >

这个主健同时也做外健。

<generator >

<param >student</param>

</generator>

</id>

<property />

<property />

关系为一对一,它会影响student.所对应的表。

<one-to-one c/>

</class>

</hibernate-mapping>


下面这个配制是一对一用关系表的方式实现。可以看来。如果关系真的是一对一的话用上面的主健是最正常的,可是用多对一是最简单的。下面这一些是最麻烦的。

首先是建一个关系表。关系表里面没有主健,放的是两个表的主健。

然后在配制文件中在两个表的配制中同时映射到关系表上,

<join table=”关系表 optional=”true”>这个optional的理解为update就是如果不加这个的话会执行插入操作完了以后在执行update,反正这东西我理解的也不清楚。还有一个属性是inverse这个如果设为true的话就是

两个特例,one-to-one用关系表来的做的。持有外健的一方要变为many-to-one.

第二个是one-to-many的时候。要改成many-to-many

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC

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

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

<hibernate-mapping package="vo">


<class table="students">

<id column="id">

<generator />

</id>

<property />

<property />

<join table="stu_tea" optional="true">

<key column="stu_id" />

<many-to-one column="tea_id" />

</join>

</class>

<class table="teachers">

<id >

<generator />

</id>

<property />

<property />

<join table="stu_tea" optional="true" inverse="true">

<key column="tea_id" />

<many-to-one column="stu_id" />

</join>

</class>

</hibernate-mapping>

补上
hibernate作为目前杰出的O-R mapping 工具,对对象间关系的良好映射支持也是其一大核心。
对象间关系:在面向对象设计和实体模型关系中,一般有四种关系:一对一(one-to-one)、一对多(one-to-many)、多对一(many-to-one)、多对多(many-to-many)。
Hibernate的对象映射中,对这四种关系有着比较全面的支持。
在描述时,我们假定有两个对象:A和B;所指的hibernate中的映射一般是指 hibernate的.hbm.xml映射文件中的映射描述;对象模型只是用伪语言进行描述。Collection 类型可以是任何类型,除非针对具体的语言进行解释;关系分为单向和双向。以下四种关系都可以作为单向和双向两种映射配置。
考虑问题时,请在脑海中想象:A作为关联的左端,B作为关联的右端,这样使得关联关系有立体感。

1、一对一(one-to-one)
关系举例:即A->B之间是一一对应的关系。比如一个国家有一个总统,一个总统只能是一个国家的总统。
class A{B b;}
class B{A a;}
A和B的关系是一一对应的关系,但是A不需要
Hibernate中映射:
A的映射:
<one-to-one />
B的映射:
<many-to-one column="a_ID" />
实现说明:
两个表:数据库表两个,A对应的表和B对应的表;a表中不包含 B的任何信息,但是B中要包含A的id,即a_id。
如果实现时,只在A中做了<one-to-one /> 映射描述,而B中没有做映射描述,我们称之为单向关系。
Person person = new Person();
person.setName("newps");
Account account = new Account();
//other goes here......
person.setAccount(account);

//session.save(account);
session.save(person);
但是如果只在任意一个映射中做了 <many-to-one /> 的映射,而没有做其他的映射,并且 另外一个对象没有序列华时,hibernate则会给出异常信息。

2、一对多(one-to-many)
关系举例:A->之间是一对多的关系,比如一个老师可以教授多名学生。
对象模型:
class A{ Collection bs;}
class B{A a;}
Hibernate中映射:
A的映射:
<bag cascade="all">
<key column="a_id"/> <!-- 指 b对应的表中存放a的id的字段,关系的左端-->
<one-to-many ></one-to-many><关系的右端>
</bag>
B的映射:
<many-to-one column="a_id"/> <!-- 指 b对应的表中存放a的id的字段-->

实现说明:
数据库表两个,A对应的表和B对应的表;
如果 Collection 是List 这里用bag,如果是Set 则用set。两者的区别主要是 set是无序的。
而且在用in 等查询的时候,只能用于list。

3、多对一(many-to-one)
关系举例:比如多个学生可以由一个老师教授。
多对一和一对多 是互逆关系。对于2 中的B来说,B和A之间的关系就是多对一。

4、多对多(many-to-many)
关系举例:比如一所大学可以有多名教授,而教授也可以在多所大学任教。
对象模型:
class A{ List bs;}
class B{ List as;}
Hibernate中的映射:
<bag table="a" cascade="save-update" inverse="true">
<key column="a_id"/>
<many-to-many column="b_id"/>
</bag>
<bag table="a" inverse="false" >
<key column="b_id"/>
<many-to-many column="a_id" />
</bag>
实现说明:
数据库表三个,A对应的表和B对应的表;以及A和B的关联表ab,ab中的字段是(a_id,b_id);
需要注意的是inverse的值必须是true和false两个能形成直线,也就是说,a中是true,b只能是false。
在关系的映射中,可以加入cascade 的属性决定是否级联操作。

其实 Hibernate对关系的映射支持非常灵活。同时,在面向对象的建模中,由于这些映射关系的支持,使得我们可以大胆的建立对象间的映射,而不用怎么担心对象的存取。真正取体会面向对象设计的方便和乐趣。
cascade
一对多关联关系的使用
一对多关系很觉,例如班级与学生的关系就是典型的一对多的关系。在实际编写程序时,一对多关系有两种实现方式:单向关联和双向关联。单向的一对多关系只需要在一方进行映射配置,而双向的一对多需要在关联的双方进行映射配置。下面以Group(班级)和Student(学生)为例讲解如何配置一对多的关系。
单向关联:
单向的一对多关系只需要在一方进行映射配置,所以我们只配置Group的映射文件: <hibernate-mapping>
<class table="t_group" lazy="true">
<id type="java.lang.Integer">
<column />
<generator />
</id>
<!-- insert属性表示被映射的字段是否出现在SQL的INSERT语句中 -->
<property type="java.lang.String" update="true" insert="true">
<column length="20" />
</property>

<!-- set元素描述的字段对应的类型为java.util.Set类型。
inverse用于表示双向关联中的被动一端。inverse的值
为false的一方负责维护关联关系。
sort排序关系,其可选值为:unsorted(不排序)。
natural(自然排序)。
comparatorClass(由某个实现了java.util.comparator接口的类型指定排序算法。)
<key>子元素的column属性指定关联表(t_student表)的外键。
-->
<set
table="t_student"
lazy="true"
inverse="false"
cascade="all"
sort="unsorted">
<key column="ID"/>
<one-to-many />
</set>
</class>
</hibernate-mapping>双向关联:
如果要设置一对多双向关联关系,那么还需要在“多”方的映射文件中使用<many-to-one>标记。例如,在Group与Student一对多的双向关联中,除了Group的映射文件外还需要在Student的映射文件中加入如下代码: <many-to-one

cascade="none"
outer-join="auto"
update="true"
insert="true"
column="ID" />
inert和update设定是否对column属性指定的关联字段进行insert和update操作。
此外将Group.hbm.xml中<set>元素的inverse设置为true.

多对多关联关系的使用
Student(学生)和Course(课程)的关系就是多对多关系。在映射多对多关系时需要另外使用一个连接表(如Student_Course)。Student_Course表包含二个字段:courseID和studentID。此处它们的映射文件中使用<many-to-many>标记,在Student的映射文件中加入以下描述信息:

<set
table="student_course"
lazy="false"
inverse="false"
cascade="save-update">
<key column="studentID" />
<many-to-many column="CourseID"/>
</set>相应的Course的映射文件中加入以下: <set
table="student_course"
lazy="false"
inverse="true"
cascade="save-update">
<key column="CourseID" />
<many-to-many column="StudentID"/>
</set>添加关联关系:
首先编写一个程序来看看一个名为Bill的学生选择了什么课程: //获取代表Bill的Student对象
Student stu = (Student)session.createQuery("from Student s where s.name='Bill'").uniqueResult();
List list = new ArrayList(stu.getCourses());
for(int i = 0 ; i < list.size(); i++)
{
Course course = (Course)list.get(i);//取得Course对象
System.out.println(course.getName());//打印出Bill所选课程的清单
}现在Bill还想chemistry课程,这对于程序员来说只是为Bill添加一个到chemistry的关联,也就是说在student_course表中新增加一条记录,而T_student和T_Course表都不用变更。 //获取代表Bill的Student对象
Student stu = (Student)session.createQuery("from Student s where s.name='Bill'").uniqueResult();
Course course = (Course)session.createQuery("from Course c where c.name='chemistry'").uniqueResult();
//设置stu与course的关联关系
stu.getCourses().add(course);
course.getStudents().add(stu);删除关联关系:
删除关联关系比较简单,直接调用对象集合的remove()方法删除不要的对象就可。例如:要从学生Bill的选课清单中删除politics和chemistry两门课,程序代码如下: //获取代表Bill的Student对象
Student stu = (Student)session.createQuery("from Student s where s.name='Bill'").uniqueResult();
Course course1 = (Course)session.createQuery("from Course c where c.name='politics'").uniqueResult();
Course course2 = (Course)session.createQuery("from Course c where c.name='chemistry'").uniqueResult();
stu.getCourse().remove(course1);//删除politics课程
stu.getCourse().remove(course2);//删除chemistry课程运行以上程序将从student_course表中删除这两条记录,但T_student和T_course表没有任何变化
Hibernate关系映射的说明 老外精彩的解释
Hibernate关系映射的说明
开源框架对数据层的映射包括实体的映射和关系的映射,其中关系的映射是最复杂的。如果你掌握不好关系映射,你干脆就不要用,否则会严重地影响性能。   

Hibernate中的实体关系分为四种,基本上与我们在数据库建模中了解到的实体关系是一样的,即一对一关系、一对多关系、多对一关系、多对多关系。我们下面分别给出说明:   

一、一对一关系、多对一关系   

一对一、多对一关系在代码上的体现是在JavaBean中包含一个实体类属性。比如,夫妻关系是一对一的关系,那么丈夫的属性中就应该有一个属性是妻子,妻子的属性中也应该有一个属性是丈夫。同样,多对一的关系中,在代码上的体现也是在Java中包含一个实体类属性。比如,孩子与妈妈的关系就是多对一的关系,每一个孩子都应该有一个属性是妈妈。我们发现,无论是一对一,还是多对一,它们在代码是都是一样的,就是属性中包含一个实体类属性。而事实上,一对一关系是多对一关系的一种特例而已。所以,在映射时,由外键实现的一对一关系或多对一关系时,无论是哪一种,外键所在一方关系属性都是通过many-to-one映射的,而不是用one-to-one。   

二、一对多关系、多对多关系

这两种关系也有些共性,就是它们在代码上的体现都是在JavaBean中包含一个集合属性。比如在上面说的妈妈与孩子的关系,妈妈应该包含一个集体属性,在这个集合中包含了她所有的小孩。这种一对多关系使用one-to-many来映射,在数据库中的体现是在一的一方是不含有外键的。而多对多的关系虽然也是在属性中包含一个集合属性,但在映射时使用的却是many-to-many。这主要是因为多对多关系需要一个关系表,必须告诉Hibernate这个关系表是谁。   

以上只是简单的概括了一下Hibernate中一些关系映射的特点,下面来说说Hibernate关系映射中的难点问题。   

如果不是真正使用Hibernate开发过项目,恐怕很难理解为什么说如果掌握不好关系最好不要使用关系。这里面有这样几个问题:

1、关系的级联操作问题。  

我们知道,在数据库中,建立外键的同时还要建立约束关系。这就存在一个问题,即当外键所指向表的行被删除时,当前行应该如何操作。比如,丈夫表含有一个外键指向妻子,如果妻子被删除了,那么丈夫所在行的外键的值应该如何操作?如果保持原有值不变,但他所指向的妻子却已经不在了,这样的记录就没有意义了,所以必须要采取一些行为。在数据库中一般会根据用户的设置采取三种行为,一是给出提示,告诉你因为有外键指向这个行,所以不能删,如果非要删除,必须先将指向它的外键却除;二是在删除当前行后,自动将指向它的外键置为空;三是删除当前行后,将所有指向它的行也同时删除。  

一般数据库默认情况下都采取第一种行为,这样如果不做任何设置,当我们对一个实体进行删除时,就会报错,告诉你有外键指向不能删除。  

这种问题如何解决呢?你可以事先做好数据库设计,让数据库帮你采取一种更合适的行为。两种可选的确定行为,即置空或级联删除。究竟采用哪一种更合适,要视你的应用而定,限于篇幅,我这里就不做过多的讲解了。再者,可以使用Hibernate的cascade属性,它的delete代表删除时置空外键,而delete-orphan则代表删除时同时删除指向它的行。

2、关系的方向性问题 

“一个巴掌拍不响”,一个关系也一定有两个实体。这就存在了另外一个问题,当一个实体发生变化时,关系是不是也一定要跟着变化?这种问题很难说清,因为它跟具体的应用关联。有时,我们只要更新实体,而不想更新关系;而有时我们又想更新关系而不更新实体;还有些情况下,我们是实体和关系同时都要更新。在默认情况下,Hibernate对于实体和关系是同时更新的,即使你根本没有更改过关系。这对于性能的影响比较大。我们可以给关系设置一个inverse属性,告诉它在任何变化下,都不要更新关系。当然还有其它的办法,读者可以参考其文档。总之,这是一个非常复杂的问题,要视你的应用而定。

3、N+1查询问题  

关于什么是N+1查询,我不想解释,读者可以看一下我前面的文章或者到网上去查询。总的来说N+1查询的问题就是性能太低,在有些情况下甚至会导致系统崩溃。但有些时候它又是有益的。因为N+1查询实际上是延迟加载了,它节省了空间。Hibernate有一个fetch属性,用于说明抓取数据的策略,如果选择了join则不会使用N+1查询,但加载上来了所有的数据并不一定都是你想要的,也可能会浪费存储空间。

4、延迟加载  

延迟加载就是并不是在读取的时候就把数据加载进来,而是等到使用时再加载。那么Hibernate是怎么知识用户在什么时候使用数据了呢?又是如何加载数据呢?其实很简单,它使用了代理机制。返回给用户的并不是实体本身,而是实体对象的代理。代理对象在用户调用getter方法时就会去数据库加载数据。但加载数据就需要数据库连接。而当我们把会话关闭时,数据库连接就同时关闭了。这种情况就叫做未初始化的关系。 

延迟加载的好处就是节省了存储空间。因为我们并不是在所有情况下都需要关系数据。比如,妈妈和孩子。如果你只想修改妈妈的数据,而Hibernate将她10几个孩子也同时给你加载进来了,这显然是无意义的。所以你可以使用Hibernate.initialize()方法主动地去决定是否初始化关系。当然也可以在配置文件中通过lazy属性,但这样一来就固定了,要么延迟,要么不延迟。   

Hibernate的关系还有很多问题,这里限于篇幅先讲这么多。还是开头的那句话,如果你没有掌握好Hibernate中关系的映射,那你干脆就不要用了,否则严重地影响性能。
如何学习Hibernate
Hibernate入门容易,掌握精通我也不敢自夸。我第一遍看Hibernate文档的时候也觉得很吃力,但不是因为Hibernate难掌握而感到吃力,是因为Hibernate文档处处都是持久层设计的经验和最佳实践。Hibernate文档准确的来说,绝大部分内容都在讲对象的持久层设计,而不是简单的Hibernate使用,使用问题查Java doc就够了。所以学习Hibernate,主要是在学习持久层的设计模式,如果你把Hibernate文档都看完了,还整天只会提那些 Hibernate的配置问题,Hibernate的类调用问题,我觉得这样的人还没有真正的入门,算是白学了。
我对Hibernate 的那些配置也不是特别纯熟,每次写hbm,都要对照文档一点点的检查;类调用参数也不太记得,写代码也要Java doc随时备查。但是我在学习Hibernate的时候即集中所有精力来理解Hibernate的运行原理,集中精力来掌握持久层设计应该把握的原则和技巧,这些才对我是最重用的东西。毫不夸张的说,学习完Hibernate,我对JDBC的编程也提高了一大截,更不要说对于J2EE架构的持久层的框架设计,基本上是了然于胸了,即使将来换了API,不用Hibernate的,改用JDO,Castor什么的,这些经验一样照用。
学习Hibernate主要不是在学习Hibernat怎么配置,用工具怎么生成hbm文件,如果你把重点放在这里,基本上等于白学了Hibernate。Hibernate的精华在于无与伦比的灵巧的对象持久层设计,这些持久层设计经验不会因为你不用Hibernate而丧失掉,我自己学习Hibernate,已经明显感觉到对持久层设计能力已经长了很多经验值了,这些经验甚至不光可以用在Java上,用在.net上也是一样。所以Hibernate配置的学习,我只是简单看看,用的时候知道到那里去查就行了,一堆复杂的生成工具我根本就看都不去看,这样算下来,掌握Hibernate的配置,可以用Hibernate来替代JDBC写程序,不过花上3天时间就足够了。我想3天时间对你来说不算很奢侈的学习代价吧。
为什么我这么强调学习Hibernate的对象持久层设计理念呢?那就看你的理想是想一辈子做一个程序员呢?还是想向更高的方向发展呢?从纯做技术的角度来说,职业发展的最高点是“系统架构师”,Bill Gates不是还叫做微软的首席系统架构师吗?System Architect职位需要的是你的学习和领悟能力,如果你不能把学习Hibernate得到的设计经验运用到其它地方,那么你是失败的,也没有资格做 System Architect。
不管JDO也好,Hibernate也好,TopLink也好,CocoBase也好,还是 Castor,还是什么Torque,OJB,软件的使用和配置方法可以各异,但本质上都是ORM,都是对JDBC的对象持久层封装,所以万变不离其宗,如果你完整的学习和掌握Hibernate花了1个月的时间,那么你再学习OJB的时间不应该超过1个星期,因为你已经把对象持久层设计都了然于胸了,你需要的只是熟悉一下OJB的API和配置罢了,至于怎么运用OJB进行持久层的开发你早就已经熟悉了。
所以当你掌握了两种以上的ORM,你应该能够不拘于使用的ORM软件的限制,设计出适合于你的项目的持久层来,这才是System Architect的水准。用金庸小说来打个比方来说吧,张无忌学太极剑,只记剑意,不记剑招,这才是真正的高手,而低手就只会去学习剑招,而不去领会剑招背后蕴含的剑意,所以一辈子都是低手,永远不能真正学会太极剑。所以周颠看到张三丰第二次演示太极剑,招式完全不同就以为是另一套东西,其实本质上都一样。学习Hibernate也不要舍本逐末的去学各种五花八门的工具,重点掌握它的对象持久层设计理念。
Hibernate查询方法与缓存的关系
在开发中,通常是通过两种方式来执行对数据库的查询操作的。一种方式是通过ID来获得单独的Java对象,另一种方式是通过HQL语句来执行对数据库的查询操作。下面就分别结合这两种查询方式来说明一下缓存的作用。
通过ID来获得Java对象可以直接使用Session对象的load()或者get()方法,这两种方式的区别就在于对缓存的使用上。
● load()方法
在使用了二级缓存的情况下,使用load()方法会在二级缓存中查找指定的对象是否存在。
在执行load()方法时,Hibernate首先从当前Session的一级缓存中获取ID对应的值,在获取不到的情况下,将根据该对象是否配置了二级缓存来做相应的处理。
如配置了二级缓存,则从二级缓存中获取ID对应的值,如仍然获取不到则还需要根据是否配置了延迟加载来决定如何执行,如未配置延迟加载则从数据库中直接获 取。在从数据库获取到数据的情况下,Hibernate会相应地填充一级缓存和二级缓存,如配置了延迟加载则直接返回一个代理类,只有在触发代理类的调用 时才进行数据库的查询操作。
在Session一直打开的情况下,并在该对象具有单向关联维护的时候,需要使用类似Session.clear(),Session.evict()的方法来强制刷新一级缓存。
● get()方法
get()方法与load()方法的区别就在于不会查找二级缓存。在当前Session的一级缓存中获取不到指定的对象时,会直接执行查询语句从数据库中获得所需要的数据。
在Hibernate中,可以通过HQL来执行对数据库的查询操作。具体的查询是由Query对象的list()和iterator()方法来执行的。这两个方法在执行查询时的处理方法存在着一定的差别,在开发中应该依据具体的情况来选择合适的方法。
● list()方法
在执行Query的list()方法时,Hibernate的做法是首先检查是否配置了查询缓存,如配置了则从查询缓存中寻找是否已经对该查询进行了缓 存,如获取不到则从数据库中进行获取。从数据库中获取到后,Hibernate将会相应地填充一级、二级和查询缓存。如获取到的为直接的结果集,则直接返 回,如获取到的为一些ID的值,则再根据ID获取相应的值(Session.load()),最后形成结果集返回。可以看到,在这样的情况下,list ()方法也是有可能造成N次查询的。
查询缓存在数据发生任何变化的情况下都会被自动清空。
● iterator()方法
Query的iterator()方法处理查询的方式与list()方法是不同的,它首先会使用查询语句得到ID值的列表,然后再使用Session的load()方法得到所需要的对象的值。
在获取数据的时候,应该依据这4种获取数据方式的特点来选择合适的方法。在开发中可以通过设置show_sql选项来输出Hibernate所执行的SQL语句,以此来了解Hibernate是如何操作数据库的。
Hibernate缓存
1. 关于hibernate缓存的问题:
1.1.1. 基本的缓存原理
Hibernate缓存分为二级,
第一级存放于session中称为一级缓存,默认带有且不能卸载。
第二级是由sessionFactory控制的进程级缓存。是全局共享的缓存,凡是会调用二级缓存的查询方法 都会从中受益。只有经正确的配置后二级缓存才会发挥作用。同时在进行条件查询时必须使用相应的方法才能从缓存中获取数据。比如Query.iterate()方法、load、get方法等。必须注意的是session.find方法永远是从数据库中获取数据,不会从二级缓存中获取数据,即便其中有其所需要的数据也是如此。
查询时使用缓存的实现过程为:首先查询一级缓存中是否具有需要的数据,如果没有,查询二级缓存,如果二级缓存中也没有,此时再执行查询数据库的工作。要注意的是:此3种方式的查询速度是依次降低的。
1.2. 存在的问题
1.2.1. 一级缓存的问题以及使用二级缓存的原因
因为Session的生命期往往很短,存在于Session内部的第一级最快缓存的生命期当然也很短,所以第一级缓存的命中率是很低的。其对系统性能的改善也是很有限的。当然,这个Session内部缓存的主要作用是保持Session内部数据状态同步。并非是hibernate为了大幅提高系统性能所提供的。
为了提高使用hibernate的性能,除了常规的一些需要注意的方法比如:
使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibernate的二级缓存。其对系统整体性能的改善往往具有立竿见影的效果!
(经过自己以前作项目的经验,一般会有3~4倍的性能提高)
1.2.2. N+1次查询的问题
执行条件查询时,iterate()方法具有著名的 “n+1”次查询的问题,也就是说在第一次查询时iterate方法会执行满足条件的查询结果数再加一次(n+1)的查询。但是此问题只存在于第一次查询时,在后面执行相同查询时性能会得到极大的改善。此方法适合于查询数据量较大的业务数据。
但是注意:当数据量特别大时(比如流水线数据等)需要针对此持久化对象配置其具体的缓存策略,比如设置其存在于缓存中的最大记录数、缓存存在的时间等参数,以避免系统将大量的数据同时装载入内存中引起内存资源的迅速耗尽,反而降低系统的性能!!!
1.3. 使用hibernate二级缓存的其他注意事项:
1.3.1. 关于数据的有效性
另外,hibernate会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!无论何时,当你调用save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。 当随后flush()方法被调用时,对象的状态会和数据库取得同步。
也就是说删除、更新、增加数据的时候,同时更新缓存。当然这也包括二级缓存!
只要是调用hibernate API执行数据库相关的工作。hibernate都会为你自动保证 缓存数据的有效性!!
但是,如果你使用了JDBC绕过hibernate直接执行对数据库的操作。此时,Hibernate不会/也不可能自行感知到数据库被进行的变化改动,也就不能再保证缓存中数据的有效性!!

这也是所有的ORM产品共同具有的问题。幸运的是,Hibernate为我们暴露了Cache的清除方法,这给我们提供了一个手动保证数据有效性的机会!!

一级缓存,二级缓存都有相应的清除方法。
其中二级缓存提供的清除方法为:
按对象class清空缓存
按对象class和对象的主键id清空缓存
清空对象的集合中的缓存数据等。
1.3.2. 适合使用的情况
并非所有的情况都适合于使用二级缓存,需要根据具体情况来决定。同时可以针对某一个持久化对象配置其具体的缓存策略。
适合于使用二级缓存的情况:
1、数据不会被第三方修改;
一般情况下,会被hibernate以外修改的数据最好不要配置二级缓存,以免引起不一致的数据。但是如果此数据因为性能的原因需要被缓存,同时又有可能被第3方比如SQL修改,也可以为其配置二级缓存。只是此时需要在sql执行修改后手动调用cache的清除方法。以保证数据的一致性
2、数据大小在可接收范围之内;
如果数据表数据量特别巨大,此时不适合于二级缓存。原因是缓存的数据量过大可能会引起内存资源紧张,反而降低性能。
如果数据表数据量特别巨大,但是经常使用的往往只是较新的那部分数据。此时,也可为其配置二级缓存。但是必须单独配置其持久化类的缓存策略,比如最大缓存数、缓存过期时间等,将这些参数降低至一个合理的范围(太高会引起内存资源紧张,太低了缓存的意义不大)。
3、数据更新频率低;
对于数据更新频率过高的数据,频繁同步缓存中数据的代价可能和 查询缓存中的数据从中获得的好处相当,坏处益处相抵消。此时缓存的意义也不大。
4、非关键数据(不是财务数据等)
财务数据等是非常重要的数据,绝对不允许出现或使用无效的数据,所以此时为了安全起见最好不要使用二级缓存。
因为此时 “正确性”的重要性远远大于 “高性能”的重要性。

2. 目前系统中使用hibernate缓存的建议
1.4. 目前情况
一般系统中有三种情况会绕开hibernate执行数据库操作:
1、多个应用系统同时访问一个数据库
此种情况使用hibernate二级缓存会不可避免的造成数据不一致的问题,
此时要进行详细的设计。比如在设计上避免对同一数据表的同时的写入操作,
使用数据库各种级别的锁定机制等。
2、动态表相关
所谓“动态表”是指在系统运行时根据用户的操作系统自动建立的数据表。
比如“自定义表单”等属于用户自定义扩展开发性质的功能模块,因为此时数据表是运行时建立的,所以不能进行hibernate的映射。因此对它的操作只能是绕开hibernate的直接数据库JDBC操作。
如果此时动态表中的数据没有设计缓存,就不存在数据不一致的问题。
如果此时自行设计了缓存机制,则调用自己的缓存同步方法即可。
3、使用sql对hibernate持久化对象表进行批量删除时
此时执行批量删除后,缓存中会存在已被删除的数据。
分析:
当执行了第3条(sql批量删除)后,后续的查询只可能是以下三种方式:
a. session.find()方法:
根据前面的总结,find方法不会查询二级缓存的数据,而是直接查询数据库。
所以不存在数据有效性的问题。
b. 调用iterate方法执行条件查询时:
根据iterate查询方法的执行方式,其每次都会到数据库中查询满足条件的id值,然后再根据此id 到缓存中获取数据,当缓存中没有此id的数据才会执行数据库查询;
如果此记录已被sql直接删除,则iterate在执行id查询时不会将此id查询出来。所以,即便缓存中有此条记录也不会被客户获得,也就不存在不一致的情况。(此情况经过测试验证)
c. 用get或load方法按id执行查询:
客观上此时会查询得到已过期的数据。但是又因为系统中执行sql批量删除一般是
针对中间关联数据表,对于中间关联表的查询一般都是采用条件查询 ,按id来查询某一条关联关系的几率很低,所以此问题也不存在!
如果某个值对象确实需要按id查询一条关联关系,同时又因为数据量大使用 了sql执行批量删除。当满足此两个条件时,为了保证按id 的查询得到正确的结果,可以使用手动清楚二级缓存中此对象的数据的方法!!
(此种情况出现的可能性较小)
1.5. 建议
1、建议不要使用sql直接执行数据持久化对象的数据的更新,但是可以执行 批量删除。(系统中需要批量更新的地方也较少)
2、如果必须使用sql执行数据的更新,必须清空此对象的缓存数据。调用
SessionFactory.evict(class)
SessionFactory.evict(class,id)等方法。
3、在批量删除数据量不大的时候可以直接采用hibernate的批量删除,这样就不存在绕开hibernate执行sql产生的缓存数据一致性的问题。
4、不推荐采用hibernate的批量删除方法来删除大批量的记录数据。
原因是hibernate的批量删除会执行1条查询语句外加 满足条件的n条删除语句。而不是一次执行一条条件删除语句!!
当待删除的数据很多时会有很大的性能瓶颈!!!如果批量删除数据量较大,比如超过50条,可以采用JDBC直接删除。这样作的好处是只执行一条sql删除语句,性能会有很大的改善。同时,缓存数据同步的问题,可以采用 hibernate清除二级缓存中的相关数据的方法。
调用 SessionFactory.evict(class) ;SessionFactory.evict(class,id)等方法。
所以说,对于一般的应用系统开发而言(不涉及到集群,分布式数据同步问题等),因为只在中间关联表执行批量删除时调用了sql执行,同时中间关联表一般是执行条件查询不太可能执行按id查询。所以,此时可以直接执行sql删除,甚至不需要调用缓存的清除方法。这样做不会导致以后配置了二级缓存引起数据有效性的问题。


退一步说,即使以后真的调用了按id查询中间表对象的方法,也可以通过调用清除缓存的方法来解决。
4、具体的配置方法
根据我了解的很多hibernate的使用者在调用其相应方法时都迷信的相信“hibernate会自行为我们处理性能的问题”,或者“hibernate 会自动为我们的所有操作调用缓存”,实际的情况是hibernate虽然为我们提供了很好的缓存机制和扩展缓存框架的支持,但是必须经过正确的调用其才有可能发挥作用!!所以造成很多使用hibernate的系统的性能问题,实际上并不是hibernate不行或者不好,而是因为使用者没有正确的了解其使用方法造成的。相反,如果配置得当hibernate的性能表现会让你有相当“惊喜的”发现。下面我讲解具体的配置方法.
ibernate提供了二级缓存的接口:
net.sf.hibernate.cache.Provider,
同时提供了一个默认的 实现net.sf.hibernate.cache.HashtableCacheProvider,
也可以配置 其他的实现 比如ehcache,jbosscache等。
具体的配置位置位于hibernate.cfg.xml文件中
<property >true</property>
<property >net.sf.hibernate.cache.HashtableCacheProvider</property>
很多的hibernate使用者在 配置到 这一步 就以为 完事了,
注意:其实光这样配,根本就没有使用hibernate的二级缓存。同时因为他们在使用hibernate时大多时候是马上关闭session,所以,一级缓存也没有起到任何作用。结果就是没有使用任何缓存,所有的hibernate操作都是直接操作的数据库!!性能可以想见。
正确的办法是除了以上的配置外还应该配置每一个vo对象的具体缓存策略,在影射文件中配置。例如:
<hibernate-mapping>
<class table="dcm_datatype">
<cache usage="read-write"/>
<id column="TYPEID" type="java.lang.Long">
<generator />
</id>
<property column="NAME" type="java.lang.String"/>
<property column="DBTYPE" type="java.lang.String"/>
</class>
</hibernate-mapping>
关键就是这个<cache usage="read-write"/>,其有几个选择
read-only,read-write,transactional,等
然后在执行查询时 注意了 ,如果是条件查询,或者返回所有结果的查询,此时session.find()方法 不会获取缓存中的数据。只有调用query.iterate()方法时才会调缓存的数据。
同时 get 和 load方法 是都会查询缓存中的数据 .
对于不同的缓存框架具体的配置方法会有不同,但是大体是以上的配置
(另外,对于支持事务型,以及支持集群的环境的配置我会争取在后续的文章中中 发表出来)

3.总结
总之是根据不同的业务情况和项目情况对hibernate进行有效的配置和正确的使用,扬长避短。不存在适合于任何情况的一个“万能”的方案。
Hibernate数据缓存
Hibernate缓存是一种提高系统性能的比较好的工具,如果使用合理,则能极大地提高系统性能,但如果使用不合理也会使用系统性能下降。

Hibernate缓存分类:
Hibernate缓存我们通常分两类,一类称为一级缓存也叫内部缓存,另一类称为二级缓存。Hibernate的这两级缓存都位于持久化层,存放的都是数据库数据的拷贝,那么它们之间的区别是什么呢?为了理解二者的区别,需要深入理解持久化层的缓存的一个特性:缓存的范围。
缓存的范围决定了缓存的生命周期以及可以被谁访问。缓存的范围分为三类。
(1) 事务范围:缓存只能被当前事务访问。缓存的生命周期依靠于事务的生命周期,当事务结束时,缓存也就结束生命周期。在此范围下,缓存的介质是内存。事务可以是数据库事务或者应用事务,每个事务都有独自的缓存,缓存内的数据通常采用相互关联的的对象形式, 一级缓存就属于事务范围。
(2) 应用范围:缓存被应用范围内的所有事务共享。这些事务有可能是并发访问缓存,因此必须对缓存采取必要的事务隔离机制。缓存的生命周期依靠于应用的生命周期,应用结束时,缓存也就结束了生命周期,二级缓存存在于应用范围。
(3) 集群范围:在集群环境中,缓存被一个机器或者多个机器的进程共享。缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致性,缓存中的数据通常采用对象的松散数据形式,二级缓存也存在与应用范围。
注重:对大多数应用来说,应该慎重地考虑是否需要使用集群范围的缓存,因为访问它的速度不一定会比直接访问数据库数据的速度快多少,再加上集群范围还有数据同步的问题,所以应当慎用。
持久化层可以提供多种范围的缓存。假如在事务范围的缓存中没有查到相应的数据,还可以到应用范围或集群范围的缓存内查询,假如还是没有查到,那么只有到数据库中查询了。

Session缓存(一级缓存):
当调用Session的保存、更新、查询操作时,在Session缓存中不存在相应对象,则把这些对象加入Session缓存。同一个Session操作,第一次通过ID调用load()或get()查询持久对象,先从Session缓存中查询发现该对象不命中,随即发送一条SQL语句生成一个持久对象并把该对象放入Session缓存。第二次再通过相同ID调用load()或get()查询时将直接从Session缓存将该对象返回,避免多余的数据库连接和查询的开销。
Session的load()和get()方法使用区别:
1、当数据库不存在对应ID数据时,调用load()方法将会抛出ObjectNotFoundException异常,get()方法将返回null。
2、当对象.hbm.xml配置文件<class>元素的lazy属性设置为true时(延迟加载),调用load()方法时则返回持久对象的代理类实例,此时的代理类实例是由运行时代理动态生成的类,该代理类实例包括原目标对象的所有属性和方法,该代理类实例的属性除了ID不为null外,所在属性为null值,查看日志并没有Hibernate SQL输出,说明没有执行查询操作,当代理类实例通过getXXX()方法获取属性值时,Hiberante才真正执行数据库查询操作。当对象.hbm.xml配置文件<class>元素的lazy属性设置为false时,调用load()方法则是立即执行数据库并直接返回实体类,并不返回代理类。而调用get()方法时不管lazy为何值,都直接返回实体类。
3、load()和get()都会先从Session缓存中查找,如果没有找到对应的对象,则查询Hibernate二级缓存,再找不到该对象,则发送一条SQL语句查询。
Session的evict()方法将持久对象从Session缓存中清除,clear()方法将清空整个缓存。

二级缓存(SesionFactory):
二级缓存由SessionFactory创建的所有Session对象共享使用,我们什么情况下要使用二级缓存?假如满足以下条件,则可以将其纳入二级缓存:
(1)数据不会被第三放修改
(2)同一数据系统经常引用
(3)数据大小在可接受范围之内
(4)非要害数据,或不会被并发的数据
Hibernate本身并不提供二级缓存的产品化实现,而是为众多支持Hibernate的第三方缓存组件提供整和接口。
现在主流的EHCache,它更具备良好的调度性能。
配置:在hibernate中启动二级类缓存,需要在hibernate.cfg.xml配置以下参数:
<hibernate-configuration>
<session-factory>
……
<property > org.hibernate.cache.EhCacheProvider
<./property>
</session-factory>
</hibernate-configuration>
另外还需要对ehcache.xml进行配置,这是一个单独的xml文件,示例如下:
ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="10000"
timeToLiveSeconds="10000"
overflowToDisk="true"
/>
<cache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="10000"
timeToLiveSeconds="10000"
overflowToDisk="true"
/>
</ehcache>
<diskStore>表示当内存缓存中对象数量超过类设置内存缓存数量时,将缓存对象写到硬盘,path=”java.io.tmpdir”表示把数据写到这个目录下。Java.io.tmpdir目录在运行时会根据相对路径生成。
<defaultCache>表示设定缓存的默认数据过期策略。
<cache>表示设定用具体的命名缓存的数据过期策略。
name表示具体的缓存命名。
maxElementsInMemory表示cache中最大允许保存的对象数据量。
eternal表示cache中数据是否为常量。
timeToIdleSeconds表示缓存数据钝化时间
timeToLiveSeconds表示缓存数据的生命时间。
overflowToDisk表示内存不足时,是否启用磁盘缓存。

Hibernate提供了四种缓存同步策略:
(1)read-only策略:只读,对于数据库表的数据不会改变的数据,可以使用只读型缓存。例如城市表的数据不会发生变化,则可配置类缓存为:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class table="tbl_city" lazy="false" mutable="false">
<cache usage="read-only" />
<id type="java.lang.Integer">
<column />
<generator ></generator>
</id>
<property type="java.lang.String">
<column />
</property>
<property type="java.lang.String">
<column />
</property>
<property type="java.lang.Integer">
<column />
</property>
</class>
</hibernate-mapping>
(2)nonstrict-read-write
不严格可读写缓存。假如应用程序对并发访问下的数据同步要求不是很严格的话,而且数据更新操作频率较低。采用本项,可获得良好的性能。
(3) read-write
对于经常被读但很少修改的数据,可以采用这种隔离类型,因为它可以防止脏读这类的并发问题.
(4)transactional(事物型)
在Hibernate中,事务型缓存必须运行在JTA事务环境中。

查询缓存:我们前面提到查询缓存(Query Cache)依靠二级缓存,这到底是怎么回事呢?我看看二级缓存策略的一般过程:
(1) Hibernate进行条件查询的时候,总是发出一条select * from XXX where …(XXX为 表名,类似的语句下文统称Select SQL)这样的SQL语句查询数据库,一次获得所有的符合条件的数据对象。
(2) 把获得的所有数据对象根据ID放入到第二级缓存中。
(3) 当Hibernate根据ID访问数据对象的时候,首先从内部缓存中查找,假如在内部缓存中查不到就配置二级缓存,从二级缓存中查;假如还查不到,再查询数据库,把结果按照ID放入到缓存。
(4)添加数据、删除、更新操作时,同时更新二级缓存。这就是Hibernate做批处理的时候效率不高的原因,原来是要维护二级缓存消耗大量时间的缘故。
我们看到这个过程后,可以明显的发现什么?那就是Hibernate的二级缓存策略是针对ID查询的策略,和对象ID密切相关,那么对于条件查询就怎么适用了。对于这种情况的存在,Hibernate引入了“查询缓存”在一定程度上缓解这个问题。
那么我们先来看看我们为什么使用查询缓存?首先我们来思考一个问题,假如我们对数据表Student进行查询操作,查找age>20的所有学生信息,然后纳入二级缓存;第二次我们的查询条件变了,查找age>15的所有学生信息,显然第一次查询的结果完全满足第二次查询的条件,但并不是满足条件的全部数据。这样的话,我们就要再做一次查询得到全部数据才行。再想想,假如我们执行的是相同的条件语句,那么是不是可以利用之前的结果集呢?

Hibernate就是为了解决这个问题的而引入Query Cache的。

查询缓存策略的一般过程如下:
(1)Query Cache保存了之前查询的执行过的Select SQL,以及结果集等信息,组成一个Query Key。(2)当再次碰到查询请求的时候,就会根据Query Key 从Query Cache找,找到就返回。但 是两次查询之间,数据表发生数据变动的话,Hibernate就会自动清除Query Cache中对应的Query Key。
我们从查询缓存的策略中可以看出,Query Cache只是在特定的条件下才会发挥作用,而且要求相当严格:
(1)完全相同的Select SQL重复执行。
(2)重复执行期间,Query Key对应的数据表不能有数据变动(比如添、删、改操作)
为了启用Query Cache,我们需要在hibernate.cfg.xml中进行配置,参考配置如下(只列出核心配置项):
<hibernate-configuration>
<session-factory> …………
<property ………… </session-factory>
</hibernate-configuration>
应用程序中必须在查询执行之前,将Query.Cacheable设置为true,而且每次都应该这样。比如:
Query query=session.createQuery(hql).setInteger(0.15); query.setCacheable(true); ………
在Hibernate中,缓存将在以下情况中发挥作用:
1.通过id[主键]加载数据的时候
2.延迟加载

一级缓存:
又称内部缓存,保存了与当前session相关联的数据对象,伴随Session实例的创建而创建,消亡而消亡。因此又称此缓存为Session level cache。
一级缓存正常情况下又Hibernate自动维护,如果需要手动干预,可以通过以下方法完成。
1.Session.evict
将某个特定对象从内部缓存中清除。
2.Sessin.clear
清空内部缓存

二级缓存:
又称为SessionFactory Level Cache.
对什么样的数据使用二级缓存?对所有数据都进行缓存是最简单的办法,也是最常用的办法。但是某些情况下,反而会影响性能,比如电话费查询系统,如果实行缓存,内存会被几乎不可能再被重用的数据充斥,导致性能下降。
如果数据满足以下条件,可以将其纳入缓存管理:
1.数据不会被第三方应用修改。
2.data size在可以接受的范围之内
3.数据更新频率较低
4.同一数据可能会被系统频繁引用
5.非关键数据
Hibernate本身并没提供二级缓存的产品化实现(只提供了一个基于HashTable的简单缓存以供调试),可以使用第三方缓存来实现。默认采用EHCache作为二级缓存实现。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值