一, Hibernate 介绍:
Hibernate 只是一个将持久化类与数据库表相映射的工具,每个持久化类实例均对应于数据库表中的一个数据行而已。用户只需直接使用面向对象的方法操作此持久化类实例,即可完成对数据库表数据的插入、删除、修改、读取等操作。
当然实际的 Hibernate 框架非常复杂,用分层的概念划分的话,它相当于在 业务逻辑处理层 和 数据库底层JDBC驱动之间的一层,即通常说的持久化层,而用户通过 XML 配置文件将具体的持久化类与数据库表映射起来。Hibernate 的实际过程还需依赖 SQL 语言和 JDBC 编程接口,但是 Hibernate 将原本分散的 JDBC 和 SQL 配合产生的接口变成了对象化的接口,定义了自己的基于面向对象设计的 HQL(Hibernate Query Language)查询语言,通过它生成实际的 SQL 语句传递到数据库执行的。
1) Configuration
读取并解析hibernate.cfg.xml配置文件(或hibernate.properties),负责配置并启动hibernate,创建SessionFactory。
Configuration 类负责管理 Hibernate 运行时需要获取一些底层实现的基本配置信息,如:数据库 URL、数据库用户、数据库用户密码、数据库 JDBC 驱动类、数据库适配器(dialect,用于对特定数据库支持)等。
Hibernate 的配置文件为 hibernate.cfg.xml 或者 hibernate.properties,缺省在 CLASSPATH 路径下,
可调用如下进行初始化:
Configuration config = new Configuration().configure();
2) SessionFactory
负责初始化hibernate,创建session对象。SessionFactory 负责创建 Session 实例,通过 Configuation 实例创建它:
SessionFactory sessionFactory = config.buildSessionFactory();
如果需要访问多个数据库,要分别为其创建对应的 SessionFactory 实例。
一个程序里面有一个SessionFactory就够用了,但是Session是线程不安的,所以一个线程需要一个Session
SessionFactory 实例中保存了当前数据库配置的所有映射关系,同时也负责维护当前的二级数据缓存和 Statement Pool。一般是将查询结果放在二级缓存里.是session 缓存的副本;
3) Session
负责被持久化对象CRUD操作。Session 是 Hibernate 持久化操作的基础,提供了如save、update、delete等这些持久化操作。
Session 实例是由SessionFactory 创建的,并且是非线程安全的,如下:
Session session = sessionFactory.openSession();
创建了实例,就可以使用它完成持久层操作,如下:
// 新增名为“tina”的用户记录
TUser user = new TUser();
user.setName("tina");
session.save(user);
Session 具有一个缓存, 位于缓存中的对象出于持久化状态,它和数据库中的相关记录对应,
Session能够在某些时间点,按照缓存中持久化对象的属性变化来同步更新数据库,这一过程被称为清理缓存.
理解Session:
首先理解,当应用程序通过new语句创建一个java对象时, jvm会为这个对象分配一块内存空间,只要这个对象被引用变量引用, 它就一直存在于内存中. 如果这个对象不被任何引用变量引用,它就结束生命周期, 此时jvm的垃圾回收器会在适当的时候回收它占用的内存.
java的集合(list,set,map)的一个重要特征是: 集合中存放的是java对象的引用.
当向集合中添加一个对象时, 其实是把这个对象的引用添加到集合中;
如果希望一个java对象一直出于生命周期中, 就必须保证至少有一个变量引用它,或者在一个java集合中存放了这个对象的引用.
在session接口的实现类sessionImpl中定义了一系列的java集合, 这些java集合构成了session的缓存. 例如:
// Map集合中的键对象代表持久化对象的OID,值对象代表持久化对象
Private final Map entitiesByKey;
…
EntitiesByKey.put(key,object);// 向session的缓存中加入一个持久化对象
…
EntitiesByKey.remove(key);//从session的缓存中删除一个持久化对象
EntitiesByKey.clear();
当session的save()方法持久化一个User对象时, user对象被加入到session的缓存中, 以后即使应应用程序中的引用变量不再引用user对象, 只要session的缓存还没有被清空, user
对象仍然处于生命周期中. 当session的load()方法试图从数据库中加载一个user对象时, session先判断缓存中是否已经存在这个user对象,如果存在, 就不需要再到数据库中检索.
Session 的缓存两大作用:
(1) 减少访问数据库的频率.应用程序从内存中读取持久化对象的速度显然比到数据库中查询数据的速度快多了, 因此session的缓存可以提高数据访问的性能;
(2) 保证缓存中的对象与数据库中的相关记录保持同步.位于缓存中的对象被称为持久化对象.当缓存中持久化对象的状态发生了变化, session并不会立即执行相关的SQL语句, 这使得session能够把几条相关的SQL语句合并为一条SQL语句,以便减少访问数据库的次数,从而提高应用程序的性能.
例如以下程序代码对user的name属性修改了两次:
T=session.beginTransaction();
User user=(User)session.load(User.class,new Long(1));
User.setName(“jack”);
User.setName(“tom”);
T.commit();
当session清空缓存时, 只需执行一条update语句:
Update t_user set name=”tom” where id=1;
注: 当session加载了user后, 会为user对象的值类型的属性复制一份快照.当session清理缓存时,通过比较user对象的当前属性与它的快照,session能够判断user对象的那些属性发生了变化.
在默认情况下, session会在下面的时间点清理缓存
1) 当应用程序调用commit()方法的时候,commit方法先清理缓存, 然后再向数据库提交事务;
2) 当应用程序显示调用session的flush()方法的时候.
Session进行清理缓存的例外情况是, 如果对象使用native生成器来生成oid,那么当调用session的save方法保存对象时, 会立即执行向数据库中插入该实体的insert语句.
注意: session 的commit方法和flush方法的区别, flush方法进行清理缓存的操作,执行一系列SQL语句,但不会提交事务; commit方法会先调用flush方法,然后提交事务.
提交事务意味着对数据库所做的更新被永久保存下来.
默认 flush 的时机:
1)提交commit时会隐含的进行flush;
2)直接调用flush;
3)执行某些查询之前hibernate会自动进行flush;
hibernate 在用oid 去查询时,不一定去数据库里查,先从session中查;
可以通过session的setFlushMode方法设置清理缓存的时间点; FlushMode.AUTO,FlushMode.COMMIT , FlushMode.NEVER, 用来约束session的查询方法, commit方法,flush方法是否清理缓存; 第一个是默认值, 保证在整个事务中,数据保持一致.
Cmmit模式. 可以避免在执行session的查询方法时先清理缓存, 提高应用程序的性能.
工作原理
hibernate工作原理:
1.通过Configuration config = new Configuration().configure();//读取并解析hibernate.cfg.xml配置文件
2.由hibernate.cfg.xml中的<mapping resource="com/xx/User.hbm.xml"/>读取并解析映射信息
3.通过SessionFactory sf = config.buildSessionFactory();//创建SessionFactory
4.Session session = sf.openSession();//打开Sesssion
5.Transaction tx = session.beginTransaction();//创建并启动事务Transation
6.persistent operate操作数据,持久化操作
7.tx.commit();//提交事务
8.关闭Session
9.关闭SesstionFactory
二,映射
1、实体类到数据库表的映射
表级别的映射,要放到hibernate.cfg.xml文件中,语法比较固定,在类中包的路径层次使用点号表示,在这里使用"/"表示路径符,指引实体类映射文件的路径,如:
<mapping resource="com/bjpowernode/hibernate/Classes.hbm.xml"/>
2、Hibernate主键映射
关于Hibernate的映射要说明的一点就是关于ID的访问权限,property以及field的区别。以前使用的时候根本没有注意过这个问题,这里简单的强调一下。
表的主键在内存中对应一个OID对象描述标识符,需要在xml的配置文件中要指定对象标识符的生成方式。
assinged是自然主键的方式,这种策略需要用户指定ID才可以,在这个知识点里先忽略。
其他的方式比如sequence通过序列生成主键。identity,increment等是自动增长。这种方式生成的主键一般是由hibernate完成的,所以我们在编写实体对象的时候,id的get和set方法权限应该注意:
class XXX{
private long id;
public long getId();
private void setId();
}
这里应该设置get的访问权限是public的,set的访问权限是private的。由于hibernate在访问实体模型时,是不考虑权限的,因此这样就避免了用户指定主键。
另外一个知识点就是,如果不通过property指定一个列,而使用field。那么hibernate就会直接访问属性,而不会通过get set访问属性。
关于对象映射标识符OID
这一块相对来说也是hibernate的重点,什么是OID?如何指定OID?OID与主键是什么关系?
什么是OID?
OID 全拼是object identifier,也就是对象标识符。因为数据库表中一般都通过主键来识别一个表中的不同行,而JVM中一般使用地址来识别不同的对象。在 Session缓存中,当然也需要一个标识符来表示不同的缓存对象。因此,OID也就排上了用场。
由于涉及到缓存的概念,就先说一下缓存!
上节说过,SessionFactory是重量级的缓存,里面包含了数据库的连接,预定义的SQL等等。而Session的缓存是轻量级的,里面包含一些增删改查的对象。
如果同一个JVM中的对象,加入到不同的session中,也是不同的缓存对象。而不同的对象加入到同一个Session中,也需要保证OID不同。因为Session不管你存的是什么,都需要通过对象标识符来检索对象。
如何指定OID?
通常分为两种:
1 自然主键(assigned)
也就是带有业务含义的,比如学生的学号,工作的编号,通常包含了年份,部门或者班级,专业等等业务上的意义,因此需要手动的合成或者拼接指定。这种情况下就需要使用assinged方式,这种方式如果不指定主键就提交缓存进行更新,会报错!
2 代理主键
也就是没有业务含义的,通常是通过编码自动生成的。
increment:不依赖于底层数据库,适合单个数据库场合不适合集群,必须为long int short类型。插入式,先选择最大的id值,再加1
identity:依赖底层数据库系统。支持自动增长字段: OID 为long,int,short
sequence:MYSQL不支持序列。依赖底层,必须支持序列。Oracle db2 sap db postgresql
hilo:计算公式hi*(max_lo+1)+lo 不依赖底层数据库系统,Long,int,short,只能在一个数据库中保持唯一
native:跨平台,自动选择使用哪个策略。
由于上面的identity,sequence都需要依赖于底层数据库,不同的数据库可能不支持这种方式。那么一般推荐使用native,自动进行选择。
OID与主键是什么关系?
一般来说,OID就是一个对象持久化之前是null,持久化的时候hibernate或者我们手动指定一个id,这个ID被插入到数据库当做主键,在session中当做索引。也因为这个原因,需要保证OID与主键的一致性,比如类型啊,长度之类的。
3、一对一
共享主键方式实现一对一
package com.reiyen.hibernate.domain;
public class Person {
private int id;
private String name;
private IdCard idCard;
}
package com.reiyen.hibernate.domain;
public class IdCard {
private int id;
private Date authorizeDate;
private Person person;
//setter和getter方法
}
Person.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 package="com.reiyen.hibernate.domain">
<class name="Person" >
<id name="id" >
<generator class="native" />
</id>
<property name="name" />
<one-to-one name="idCard" />
</class>
</hibernate-mapping>
IdCard.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 package="com.reiyen.hibernate.domain">
<class name="IdCard" table="id_card">
<id name="id">
<!-- id_card的主键来源person,也就是共享idCard的主键 -->
<generator class="foreign">
<param name="property">person</param>
</generator>
</id>
<property name="authorizeDate" column="authorize_date" />
<!-- one-to-one标签的含义,指示hibernate怎么加载它的关联对象,默认根据主键加载,constrained="true",表明当前主键上存在一个约束,id_card的主键作为外键参照了person -->
<one-to-one name="person" constrained="true"></one-to-one>
</class>
</hibernate-mapping>
唯一外键方式实现一对一
IdCard.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 package="com.itcast.hibernate.domain">
<class name="IdCard" table="id_card">
<id name="id">
<generator class="native" />
</id>
<property name="authorizeDate" column="authorize_date" />
<!-- 指定多的一端的unique=true,这样就限制了多的一端的多重性为一
通过这种手段映射一对一唯一外键关联 -->
<many-to-one name="person" column="person_id" unique="true" />
</class>
</hibernate-mapping>
Person.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 package="com.itcast.hibernate.domain">
<class name="Person" >
<id name="id" >
<generator class="native" />
</id>
<property name="name" />
<!-- 没有下面的one-to-one标签也行,但那样就变成了单向关联(IdCard ----》 Person) ,也就是当知道IdCard后,能找到它属于的对应的人,但知道某人后,却无法找到相对应的IdCard-->
<one-to-one name="idCard" property-ref="person"/>
</class>
</hibernate-mapping>
4, 一对多的关系:
(1) user的xml文件:(一的一方,有一个account的 set集合)
<set name="accounts" cascade="all" inverse="true">
<key column="fid"></key>
<one-to-many class="Account">
</set>
1、集合里存的是PO,集合代表一对多;并且是关联属性; <one-to-many>
2、class="Account" ;说明集合中的PO类型;因为泛型只是编译时有效,虚拟机看不到,所以还要说明类型;
3、key 标签说明的是,关系的另一方(Account)表中与本表关联的外键字段的名字;
关系的维护权:
一对多的关系中, 在一的一方写上inverse="true"; 将关系的维护权放在多的一方;
inverse="false"; // 默认的, User维护关系,存user时, 会先存account;
这时存account时,account有外键字段,先插入三条account语句,fid=null;
再插入user,并且要更新account的外键字段;
inverse="true"; // User放弃维护关系,会先存user,user的oid先算出来, 然后再存account,fid就有值了;
1)、防止外键为空;把关系的维护交给多的一方;
2)、防止n+1次更新;
如果为false ; user.add(account); s.saveOrUpdate(user);
更新user时会把集合的元素更新一遍;再更新user;
如果为true; 存account时会先存user ,再把自己存上就行了;
(2) account的xml文件:多的一方,有一个user属性,维护外键;
<property name="actNo" unique="true" not-null="true">//自动建表时加上约束
<many-to-one name="user" column="fid" cascade="save-update">
cascade="save-update":帐户保存和更新时更新用户,但删帐户时不能删用户;
5, 多对一的关系:
6.组件映射(User - Name)
关联的属性是个复杂类型的持久化类,但不是实体,即:数据库中没有表与该属性对应,但该类的属性要永久保存的。
<component name="name" class="com.test.hibernate.domain.Name">
<property name="initial" />
<property name="first" />
<property name="last" />
</component>
当组件的属性不能和表中的字段简单对应的时候可以选择实现:
org.hibernate.usertype.UserType or
org.hibernate.usertype.CompositeUserType
7.集合映射
1、set映射
关联对象的属性除了外键之外,只有1、2个属性,那么就可以使用set映射使用了set标签的element元素,不用创建关联对象就可以实现单向一对多的关联关系
public class Room implements Serializable{
private int id;
private String roomNumber;
private Set<String> equipments = new HashSet<String>();
private Set<Image> images = new HashSet<Image>();
}
<set name="equipments" table="equipment_set">
<key column="roomid" foreign-key="fk_equip_room_set"/>
<element column="description" type="string" length="128" not-null="true"/>
</set>
<set name="images" table="image_set">
<key column="roomid" foreign-key="fk_img_room_set"/>
<composite-element class="Image">
<property name="path" column="path" type="string" length="50" not-null="true"/>
<property name="width" column="width" type="integer" />
<property name="height" column="height" type="integer" />
</composite-element>
</set>
2、map映射
非常有用的一种集合映射
public class Room implements Serializable{
private int id;
private String roomNumber;
private Map<String, String> equipments = new HashMap<String, String>();
private Map<String, Image> images = new HashMap<String, Image>();
}
<map name="equipments" table="equipment_map">
<key column="roomid" foreign-key="fk_equip_room_map"/>
<map-key column="name" type="string" length="15" />
<element column="description" type="string" length="128" not-null="true"/>
</map>
<map name="images" table="image_map">
<key column="roomid" foreign-key="fk_img_room_map"/>
<map-key column="name" type="string" length="15" />
<composite-element class="Image">
<property name="path" column="path" type="string" length="50" not-null="true"/>
<property name="width" column="width" type="integer" />
<property name="height" column="height" type="integer" />
</composite-element>
</map>
3、list映射
public class Quiz {
private Long oid;
private String quizNo;
private List answers = new ArrayList();
}
<list name="answers" table="yinglinhai_answers">
<key column="fid"></key>
<list-index column="indexs"></list-index>
<element column="answer" type="string"></element>
</list>
4、bag映射
public class Lab {
private Long oid;
private int labNo;
private List records = new ArrayList();
}
<idbag name="records" table="yinglinhai_records">
<collection-id type="long" column="cid">
<generator class="increment"></generator>
</collection-id>
<key column="fid"/>
<element column="record" type="string"></element>
</idbag>
三, Hibernate应用中 java对象的状态 :
transient:
暂态,瞬态; 该对象和任何session无关,在数据库中没有它的记录;刚new出来的对象;或已经删除的对象;
persistent:
持久对象的持久态;该对象处于某session的管理中;在数据库中有记录;从数据库中取出的对象;通过save()方法同步到数据库中的对象
detached:
游离态、脱管态;当前和任何session无关,但在数据库中有记录; session关闭后,就成了游离态了;update()同步到数据库中;持久化类与持久化对象是不同的概念, 持久化类的实例可以处于临时状态, 持久化状态和游离状态, 其中处于持久化状态的实例被称为持久化对象.对象的状态有两种含义, 一种含义是指由对象的属性表示的数据, 一种含义是指临时状态, 持久化状态或游离状态之一. 应该根据上下文来辨别状态的具体含义.
操作方法
get();
如果找到对象就返回,否则返回空;
load();
如果找到对象就返回,否则抛异常throw unrecoverableException;
close();
关闭session,对象变成游离的;
clear();
将session的缓冲区清空;对象变成游离的对象;内存中还有,但跟session已经无关了
evict(o1);
将一个对象清出去,变成游离状态
update(),
saveOrUpdate();
使游离对象变成持久状态;
saveOrUpdate()方法同时包含了save与update方法的功能, 如果传入的参数是临时对象,就调用save()方法, 如果传入的参数是游离对象,就调用update()方法, 如果传入的参数是持久化对象, 就直接返回. 那么, 它是如何判断一个对象处于临时状态还是游离状态呢?
如果满足以下情况之一, Hibernate 就把它当成临时对象:
1)Java对象的OID是null;
2)Java对象具有version属性并且取值为version
3)在映射文件中为id元素设置了unsaved-value属性,并且oid的取值与unsaved-value属性值匹配;这种情况指的的是id属性是long类型, 它的默认值是0, 此时需要显式设置id元素的unsaved-value=”0”;
4) 在映射文件为version属性设置了unsaved-value属性, 并且version属性取值与unsaved-value属性值匹配;
delete()方法, 用于从数据库中删除与java对象对应的记录.如果传入的参数是持久化对象, session就计划执行一个delete语句, 如果传入的参数是游离对象, 先使游离对象被session关联,使他变为持久化对象,然后计划执行一个delete语句, 值得注意的是, session只有在清理缓存的时候才会执行delete语句. 此外只有在调用session的close方法时, 才会从session的缓存中删除该对象.
级联操作:
1、cascade
cascade属性是设置级联操作的. 也就是在操作一端的数据如果影响到多端数据时会进行级联操作, cascade="none",cascade="save-update",cascade="delete",cascade="all" cascade="persist" cascade="delete-orphan",cascade
属性的值常用的设置为以上五项:
none就是不使用级联操作,默认级联是none。
save-update也就是只有对象保存操作(持久化操作)或者是持久化对象的更新操作,才会级联操作关联对象(子对象)。persist就只是将级联对象也持久化到数据库。
delete对持久化对象的删除操作时会进行级联操作关联对象(子对象)。
all 对持久化对象的所有操作都会级联操作关联对象(子对象)。 包含save-update, 和delete 行为;
all-delete-orphan,从集合中删除时,同步将数据库中的记录删掉; 包含save-update, 和delete-orphan行为;
delete 之后将表中表示关联的外键id置成null,不会将这条纪录也删除掉;
delete-orphan 就不会留有空纪录,而是级联的把相关纪录删除掉。
四、get、load的具体区别:
如果你使用load方法,hibernate认为该id对应的对象(数据库记录)在数据库中是一定存在的,所以它可以放心的使用,它可以放心的使用代理来延迟加载该对象。在用到对象中的其他属性数据时才查询数据库,但是万一数据库中不存在该记录,那没办法,只能抛异常ObjectNotFoundException,所说的load方法抛异常是指在使用该对象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时。由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理。所以如果你知道该id在数据库中一定有对应记录存在就可以使用load方法来实现延迟加载。
对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查数据库,数据库中没有就返回null。
虽然好多书中都这么说:“get()永远只返回实体类”,但实际上这是不正确的,get方法如果在session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是返回的还是代理对象,只不过已经加载了实体数据。
前面已经讲了,get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;反而load方法创建时首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库。
总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,就抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
五、缓存
缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事件会同步缓存和物理数据源的数据。
一级缓存
Hibernate一级缓存又称为“Session的缓存”。Session内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。一级缓存中,持久化类的每个实例都具有唯一的OID。
一级缓存的管理:
evit(Object obj) 将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
clear() 将一级缓存中的所有持久化对象清除,释放其占用的内存资源。
contains(Object obj) 判断指定的对象是否存在于一级缓存中。
flush() 刷新一级缓存区的内容,使之与数据库数据保持同步。
一级缓存和二级缓存对比
一级缓存 |
二级缓存 |
|
存放数据的形式 |
相互关联的持久化对象 |
对象的散装数据 |
缓存的范围 |
事务范围,每个事务都拥有单独的一级缓存 |
进程范围或集群范围,缓存被同一个进程或集群范围内所有事务共享 |
并发访问策略 |
由于每个事务都拥有单独的一级缓存不会出现并发问题,因此无须提供并发访问策略 |
由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别 |
数据过期策略 |
处于一级缓存中的对象永远不会过期,除非应用程序显示清空或者清空特定对象 |
必须提供数据过期策略,如基于内存的缓存中对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间 |
物理介质 |
内存 |
内存和硬盘,对象的散装数据首先存放到基于内存的缓存中,当内存中对象的数目达到数据过期策略的maxElementsInMemory值,就会把其余的对象写入基于硬盘的缓存中 |
缓存软件实现 |
在Hibernate的Session的实现中包含 |
由第三方提供,Hibernate仅提供了缓存适配器,用于把特定的缓存插件集成到Hibernate中 |
启用缓存的方式 |
只要通过Session接口来执行保存,更新,删除,加载,查询,Hibernate就会启用一级缓存,对于批量操作,如不希望启用一级缓存,直接通过JDBCAPI来执行 |
用户可以再单个类或类的单个集合的粒度上配置第二级缓存,如果类的实例被经常读,但很少被修改,就可以考虑使用二级缓存,只有为某个类或集合配置了二级缓存,Hibernate在运行时才会把它的实例加入到二级缓存中 |
用户管理缓存的方式 |
一级缓存的物理介质为内存,由于内存的容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目,Session的evit()方法可以显示的清空缓存中特定对象,但不推荐 |
二级缓存的物理介质可以使内存和硬盘,因此第二级缓存可以存放大容量的数据,数据 过期策略的maxElementsInMemory属性可以控制内存中的对象数目,管理二级缓存主要包括两个方面:选择需要使用第二级缓存的持久化类,设 置合适的并发访问策略;选择缓存适配器,设置合适的数据过期策略。SessionFactory的evit()方法也可以显示的清空缓存中特定对象,但不 推荐 |
二级缓存配置
在hibernate.cfg.xml中添加如下代码:
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider
</property>
<property name="expiration">120</property>
expiration代表缓存过期时间,单位为秒。
设置完后,还需要在对象的映射文件中配置二级缓存的策略,比如我在User.hbm.xml中如下配置,注意红色字体部分:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="NhibernateSample1.User,NhibernateSample1" table="Users" lazy="false">
<cache
usage="transactional|read-write|nonstrict-read-write|read-only"
region="RegionName"
include="all|non-lazy"
/>
<id name="Id" column="Id" unsaved-value="0">
<generator class="native" />
</id>
<property name="Name" column="Name" type="string" length="64" not-null="true" unique="true"></property>
<property name="Pwd" column="Pwd" type="string" length="64" not-null="true"></property>
<many-to-one name="Role" class="NhibernateSample1.Role,NhibernateSample1" column="RoleID"></many-to-one>
</class>
</hibernate-mapping>
属性 |
含义和作用 |
必须 |
默认值 |
usage |
指定缓存策略,可选的策略包括:transactional,read-write,nonstrict-read-write或read-only |
Y |
|
region |
指定二级缓存区域名 |
N |
|
include |
指定是否缓存延迟加载的对象。all,表示缓存所有对象;non-lazy,表示不缓存延迟加载的对象 |
N |
all |
当然在利用缓存的时候,缓存不会知道另外一个进程存储的实体发生变化,应该自己建立一些策略来及时地更新缓存快照。而且当ISessionFactory销毁的时候,二级缓存也会随之销毁。
二级缓存介绍
与Session相对的是,SessionFactory也提供了相应的缓存机制。SessionFactory缓存可以依据功能和目的的不同而划分为内置缓存和外置缓存。
SessionFactory的内置缓存中存放了映射元数据和预定义SQL语句,映射元数据是映射文件中数据的副本,而预定义SQL语句是在 Hibernate初始化阶段根据映射元数据推导出来的。SessionFactory的内置缓存是只读的,应用程序不能修改缓存中的映射元数据和预定义 SQL语句,因此SessionFactory不需要进行内置缓存与映射文件的同步。
SessionFactory的外置缓存是一个可配置的插件。在默认情况下,SessionFactory不会启用这个插件。外置缓存的数据是数据库数据的副本,外置缓存的介质可以是内存或者硬盘。SessionFactory的外置缓存也被称为Hibernate的二级缓存。
Hibernate的二级缓存的实现原理与一级缓存是一样的,也是通过以ID为key的Map来实现对对象的缓存。
由于Hibernate的二级缓存是作用在SessionFactory范围内的,因而它比一级缓存的范围更广,可以被所有的Session对象所共享。
二级缓存工作内容
Hibernate的二级缓存同一级缓存一样,也是针对对象ID来进行缓存。所以说,二级缓存的作用范围是针对根据ID获得对象的查询。
二级缓存的工作可以概括为以下几个部分:
● 在执行各种条件查询时,如果所获得的结果集为实体对象的集合,那么就会把所有的数据对象根据ID放入到二级缓存中。
● 当Hibernate根据ID访问数据对象的时候,首先会从Session一级缓存中查找,如果查不到并且配置了二级缓存,那么会从二级缓存中查找,如果还查不到,就会查询数据库,把结果按照ID放入到缓存中。
● 删除、更新、增加数据的时候,同时更新缓存。
二级缓存使用范围
Hibernate的二级缓存作为一个可插入的组件在使用的时候也是可以进行配置的,但并不是所有的对象都适合放在二级缓存中。
在通常情况下会将具有以下特征的数据放入到二级缓存中:
● 很少被修改的数据。
● 不是很重要的数据,允许出现偶尔并发的数据。
● 不会被并发访问的数据。
● 参考数据。
而对于具有以下特征的数据则不适合放在二级缓存中:
● 经常被修改的数据。
● 财务数据,绝对不允许出现并发。
● 与其他应用共享的数据。
在这里特别要注意的是对放入缓存中的数据不能有第三方的应用对数据进行更改(其中也包括在自己程序中使用其他方式进行数据的修改,例如,JDBC),因为那样Hibernate将不会知道数据已经被修改,也就无法保证缓存中的数据与数据库中数据的一致性。
二级缓存插件
组件 |
Provider类 |
类型 |
集群 |
查询缓存 |
Hashtable |
org.hibernate.cache.HashtableCacheProvider |
内存 |
不支持 |
支持 |
EHCache |
org.hibernate.cache.EhCacheProvider |
内存,硬盘 |
最新支持 |
支持 |
OSCache |
org.hibernate.cache.OSCacheProvider |
内存,硬盘 |
不支持 |
支持 |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
集群 |
支持 |
不支持 |
JBoss TreeCache |
org.hibernate.cache.TreeCacheProvider |
集群 |
支持 |
支持 |
关于Session缓存——清理缓存
缓存的概念,一般学过基础理论的都应该理解,就是为了缓冲数据,减少与真实数据库的频繁交互。与计算机的缓存类似,经常访问硬盘效率太低,IO 太慢,就把内存当做缓存,CPU每次与内存直接交互,内存中找不到的数据再去读硬盘。内存又觉得慢了,就弄个Cahce当做缓存,经常访问的数据再放到这 里,更加快了速度。
Session缓存也是如此,与Web中的Session也类似。在网页中,也有Session这样一种概念,比如我们登陆淘宝,会记录我们的 用户信息,当浏览器关闭或者退出时,Session关闭。这期间就完全通过Session来识别用户的身份,无需每次登陆进行校验。Hibernate中 也是如此,我们从SessionFactory中开启这个Session,持久化一个对象,然后提交事务,增删改查,最后关闭Session,就像一个对 话一样。
那么Session缓存具体有什么作用呢?
比如我们通过Session.get(xxx.class,new Long(1));来获取Session中OID为1的对象,它会首先到缓存中查找,如果找到了就直接用。如果找不到就去读取数据库,然后存储到缓存中!第二次,就可以直接从缓存中获取数据了!
这样就减少了访问数据库的频率!
另外,我们频繁的修改一个对象,如果这个对象放在缓存中,而且还是用了事务,那么只有事务在commit的时候,才会执行真正的SQL语句!
这样就对对象与数据库的表进行了动态的映射!
Session缓存又是什么时候提交清理的呢?
1 当使用事务时,transaction.commit()会触发缓存的清理。
2 直接调用Session.flush()也会触发缓存的清理。
3 如果使用的是native,那么在持久化的时候也会清理缓存,也就是session.save()时。
4 执行查询时。
这里就不得不提一下commit与Session的flush的区别了:
当使用flush时,并没有提交事务,只是清理缓存而已。
而commit的时候,是先调用flush再提交事务。
Session缓存中的状态变更
这又是Hibernate的一大块重点!
临时对象:OID为null、不再Session中、数据库中无记录、
持久化对象:OID不为Null、在Session中,有相关记录,清理时根据属性变化更新数据库。Session的save load get update saveOrUpdate lock,Query的list()。
删除对象:OID不为null、从Session中删除、有相关记录、已经计划从数据库中删除、清理缓存就会执行删除、不能再使用。rollback Session的delete 级联删除时
游离对象:OID 不为null,不再Session缓存中,Session close清空缓存、evict清除一个持久化对象、clear清除缓存中的所有持久化对象
关于Session中的方法使用
save()
Session调用save时,一般都是创建或者获取到了一个瞬时态的对象,这时对象的OID有可能是空的,session需要指定生成一个OID。再计划生成一条insert语句,这条语句只是简单的缓存起来,当事务提交时才执行。而持久化的对象,OID是不能随便更改的,这也是为什么前面的setId推荐设置成private的访问权限。
load()和get()
他们都是加载一个对象,或者从缓存中查找。区别在于,如果使用load,如果数据库中不存在该对象对应的数据,会抛出异常。而get会得到null。
update()
这个方法是把一个游离态的对象持久化,比如一个对象如果session清理了,那么session中就找不到这个对象了,但是数据库中仍然存 在。我们通过这个对象的引用,可以通过update在Session中创建它的实例。这样,会生成一条update语句。如果此后在修改无论多少次,都只 会生成一条update语句。总结起来就是,update方法,会生成一条对应的update语句来同步缓存与数据库中的对象。
如果数据库中对应的表设置了触发器,那么就蛋疼了、!因为无论你是否修改了数据,都会生成一条update语句,这样就会导致触发了大量无效的触发器。不要担心,可以通过设置select-before-update属性,一看名字就能猜到,是在update前,进行一次select,如果数据一致,就必须要update了,如果数据不一致,才update。
saveOrUpdate()
这个方法就给力了,它会自动判断传入的参数是什么类型的,然后采取什么措施!完全的自动化,最喜欢这样的了!跟native一个套路。
merge()
对象的复制,它首先获取到OID,然后去session中查找是否存在这样的对象,如果存在直接修改或者使用;如果不存在,就复制这个对象的属性。
delete()
如果删除的对象时一个游离态的对象,那么需要先进行持久化,在删除。
replicate()
这个方法可以跨Sessionfactory拷贝对象。
Hibernate事务控制
Hibernate控制的事务(ACID,atomicity consistency isolation durability)事务保证原子操作的不可分,也就是操作的同时成功或同时失败。
Transaction tran=session.beginTranaction();
//....事务操作
tran.commit();
tran.rollback();
以上是事务对象的方法,来实现对事务的支持。
hibernate的事务隔离级别
hibernate的事务隔离级别和JDBC中大致相同。
设置时要在hibernate.cfg.xml配置
<property name="hibernate.connection.isolation">4</property>
1,读未提交的数据(Read uncommitted isolation)
2,读已提交的数据(Read committed isolation)
4,可重复读级别(Repeatable read isolation)
8,可串行化级别(Serializable isolation)
hibernate的锁(悲观锁,乐观锁)
1. 悲观锁: 是由数据库本身所实现的,会对数据库中的数据进行锁定,也就是锁行, 在同一时间内只能有一个读写数据操作。
LockMode.UPGRADE,修改锁,在get()方法中加上这个设置作为第三个参数。
LockMode.NONE 无锁机制
LockMode.READ 读取锁(JDBC中的for update)
LockMode.WRITE 写入锁,不能在程序中直接使用
例如:tran = s.beginTransaction();
user = (User)s.get(User.class , userid , LockMode.UPGRADE);
user.setName("new name");
tran.commit();
还可以使用Session.lock() Query.setLockMode() Criteria.setLockMode()方法来设置锁
2. 乐观锁: 也就是通过对记录加上某些信息来解决并发访问的问题。解决冲突的手段: 加上版本versionNo
if(read_versionNo=versionNo) write versionNo++ else do it again
<version name="version"/>必须紧跟在<id>之后
persist就只是将级联对象也持久化到数据库。
Query/Criteria:
Query
1.Query接口封装了Hibernate强大的对象查询能力,同时也支持数据库的更新操作
2.提供了动态查询的参数绑定功能
3.提供list(),iterator(),scroll()等对象导航方法
4.提供uiqueResult()方法获取单独的对象
5.提供executeUpdate()方法来执行DML语句
6.提供了可移植的分页查询方法
1.非集成Spring
Hibernate的检索方式,主要有以下五种。
1.导航对象图检索方式。(根据已经加载的对象,导航到其他对象。)
2.OID检索方式。(按照对象的OID来检索对象。)
3.HQL检索方式。(使用面向对象的HQL查询语言。)
4.QBC检索方式。(使用QBC(Qurey By Criteria) API来检索对象。)
5.本地SQL检索方式。(使用本地数据库的SQL查询语句。)
1、导航对象图检索方式
利用类与类之间的关系来检索对象。譬如我们要查找一份订单,就可以由订单对象自动导航找到订单所属的客户对象。当然,前提是必须在对象-关系映射文件上配置了它们的多对一的关系。
Order order = (Order )session.get(Order.class,1);
Customer customer = order.getCustomer();
2、OID检索方式
主要指用Session的get()和load()方法加载某条记录对应的对象。
Customer customer = (Customer )session.get(Customer.class,1);
Customer customer = (Customer )session.load(Customer.class,1);
3、HQL检索方式
HQL(Hibernate Query Language)是面向对象的查询语言,它和SQL查询语言有些相似。在Hibernate提供的各种检索方式中,HQL是使用最广的一种检索方式。它具有以下功能:
在查询语句中设定各种查询条件。
支持投影查询,即仅检索出对象的部分属性。
支持分页查询。
支持分组查询,允许使用group by和having关键字。
提供内置聚集函数,如sum()、min()和max()。
能够调用用户定义的SQL函数。
支持子查询,即嵌套查询。
支持动态绑定参数。
Session类的Qurey接口支持HQL检索方式,它提供了以上列出的各种查询功能。
注:Qurey接口支持方法链编程风格,它的set方法都返回自身实例,而不是返回void类型。方法链编程风格能使程序代码更加简洁。
示例代码:
[java] view plain copy
- 1. Query query = session.createQuery("from Customer as c where " +"c.name=:customerName and c.age=:customerAge");
2. // 动态绑定参数
- 3. query.setString("customerName", "Test");
- 4. query.setInteger("customerAge", 21);
5. // 执行检索
- 6. List result = query.list();
7. // 方法链编程风格
- 8. List result1 = session.createQuery( "from Customer as c where c.name=:customerName and c.age=:customerAge").setString( "customerName", "Test").setInteger("customerAge", 21) .list();
4、QBC(Qurey By Criteria)检索方式
采用HQL检索方式时,在应用程序中需要定义基于字符串形式的HQL查询语句。QBC API提供了检索对象的另一种方式,它主要由Criteria接口、Criterion接口和Expression类组成,它支持在运行时动态生成查询语句。
示例代码:
[java] view plain copy
1. Criteria criteria = session.createCriteria(Customer.class);
2. Criterion criterion1 = Expression.like("namr", "T%");
3. Criterion criterion2 = Expression.eq("age", new Integer(21));
4. criteria = criteria.add(criterion1);
5. criteria = criteria.add(criterion2);
6. // 执行检索 List result = criteria.list();
7. // 方法链编程风格 List result1 = session.createCriteria(Customer.class).add(Expression.like("namr""T%")).add(Expression.
8. eq("age", new Integer(21))).list();
- 9. Hibernate还提供了QBE(Qurey By Example)检索方式,它是QBC的子功能。QBE允许先创建一个随想模板,然后检索出和这个样板相同的对象。
- 示例代码:
- Customer exampleCustomer=new Customer();
- exampleCustomer.setAge(21);
- List result1 = session.createCriteria(Customer.class).add( Example.create(exampleCustomer)).list();
QBE的功能不是特别强大,仅在某些场合下有用。一个典型的使用场合就是在查询窗口中让用户输入一系列的查询条件,然后返回匹配的对象。QBE方式目前只能支持对象属性字段的等于查询和字符串的模糊匹配,不能支持区间,或者,大于等操作。在这些情况下,还是采用HQL检索方式或QBC检索方式。
5、本地SQL检索方式
采用HQL或QBC检索方式时,Hibernate生成标准的SQL查询语句,使用于所有的数据库平台,因此这两种检索方式都是跨平台的。有的应用程序可能需要根据底层数据库的SQL方言,来生成一些特殊的查询语句。在这种情况下,可以利用Hibernate提供的SQL检索方式。
示例代码:
1. Query query = session.createSQLQuery("select {c.*} from CUSTOMER as c where c.NAME like :customerName and c.AGE=:customerAge");
2. // 动态绑定参数
3. query.setString("customerName", "Test");
4. query.setInteger("customerAge", 21);
5. // 执行检索
6. List result = query.list();
2.集成Spring
1. HibernateTemplate
HibernateTemplate提供持久层访问模板,使用HibernateTemplate无须实现特定接口,它只需要提供一个SessionFactory的引用就可以执行持久化操作。SessionFactory对象既可通过构造函数传入,也可以通过设值传入。
HibernateTemplate的常用方法简介
void delete(Object entity)
deleteAll(Collection entities)
find(String queryString)
findByNameQuery(String queryName)
get(Class entityClass,Serializable id)
save(Object entity)
saveOrUpdate(Object entity)
update(Object entity)
setMaxResults(int maxResults) 设置分页的大小
2. hibernateTemplate的回调机制
1. /**
- * 使用hql 语句进行操作
- * @param hql
- * @param offset
- * @param length
- * @return List
- */
- public List getListForPage(final String hql, final int offset,
- final int length) {
- List list = getHibernateTemplate().executeFind(new HibernateCallback() {
- public Object doInHibernate(Session session)
- throws HibernateException, SQLException {
- Query query = session.createQuery(hql);
- query.setFirstResult(offset);
- query.setMaxResults(length);
- List list = query.list();
- return list;
- }
- });
- return list;
- }