HIbernate最佳实践
1、使用Configuration装载映射文件时,不要使用绝对路径装载。最好的方式是通过getResourceAsStream()装载映射文件,这样Hibernate会从classpath中寻找已配置的映射文件。
2、SessionFactory的创建非常消耗资源,整个应用一般只要一个SessionFactory就够了,只有多个数据库的时候才会使用多个SessionFactory。
3、在整个应用中,Session和事务应该能够统一管理。(Spring为Hibernate提供了非常好的支持)
4、将所有的集合属性配置设置为懒加载(lazy=”true”)。在hibernate2.x版本中,lazy默认值是“false”,但hibernate3.x已经将lazy的默认改为“true”了。
5、在定义关联关系时,集合首选Set,如果集合中的实体存在重复,则选择List(在定义配置文件时,可以将List定义为bag),数组的性能最差。
6、在一对多的双向关联中,一般在一这端将集合的inverse属性设置为true,让集合的对方维护关联关系。例如:Group-User,由User来维护Group和User的关联关系,下面详述。
8、在非分布式架构中,不需要使用DTO来向上层传输数据。直接使用POJO的Entity就可以了。
9、如果要精通Hibernate,熟练掌握关系数据库理论和SQL是前提条件。
10、HQL代码 > fetch(配置) > lazy (配置)
fetch和lazy关键字
Lazy是在获取对象时是否进行延迟加载,而fetch是在延迟加载的情况下对对象的抓取策略,其值有select,join,subselect和batch fetching,即只有当Lazy=true时必须有fetch关键字否则就会报异常。lazy的值可以设置为true ,false,proxy。两者的配合使用问题:
1、当lazy="true" fetch = "select" 时,即使用延迟策略,开始只查询出一端实体,多端的不会查询,只有当用到的时候才会发出sql语句去查询;
2、当lazy="false" fetch = "select" 时,即没有用延迟策略,同时查询出一端和多端,同时产生1+n条sql.
3、当lazy="true"/lazy="false" fetch = "join"的时候,这个时候延迟已经没有什么用了,因为采用的是外连接查询,同时把一端和多端都查询出来了,延迟没有起作用。
避免N+1次查询的方法除了以上方法,还可以使用二级缓存,下面缓存一节有详述
Inner Join 的主要精神就是 exclusive , 叫它做排他性吧! 就是讲 Join 规则不相符的资料就会被排除掉
Outer Join: Select <要查询的字段> From <Left 资料表> <Left | Right> [Outer] Join <Right 资料表> On <Join 规则> , 语法中的 Outer 是可以省略的, 例如你可以用 Left Join 或是 Right Join, 在本质上, Outer Join 是 inclusive, 叫它做包容性吧! 不同于 Inner Join 的排他性, 因此在 Left Outer Join 的查询结果会包含所有 Left 资料表的资料, 颠倒过来讲, Right Outer Join 的查询就会包含所有 Right 资料表的资料
注意HQL在使用join时不能使用on关键字,可以使用with,而使用Join时,在hbm.xml中设置好对象的关联主键就可以了,否则使用了On就会报下面的错误:
from Apostpdas ap left outer join Apostpdaddas op on ap.id=op.apostpdId right outer join Clintcbasclb on ap.cnum=clb.cnum 老是报Causedby:org.hibernate.hql.ast.QuerySyntaxError:unexpectedtoken:onnearline1,column127[selectcount(*)
通过HQL的with关键字,你可以提供额外的join条件。
from Cat
as
cat left join cat.kittens
as
kitten with kitten.bodyWeight > 10.0
延迟抓取要注意的问题。在一个打开的Hibernate session上下文之外调用延迟集合会导致一次意外。比如:
s = sessions.openSession();
Transaction tx = s.beginTransaction();
User u = (User) s.createQuery("from User u where u.name=:userName").setString("userName", userName).uniqueResult();
Map permissions = u.getPermissions();
tx.commit();
s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
在Session关闭后,permessions集合将是未实例化的、不再可用,因此无法正常载入其状态。
Hibernate对脱管对象不支持延迟实例化. 这里的修改方法是:将permissions读取数据的代码 移到tx.commit()之前。
Fetch抓取策略可以O/R映射文件中声明,也在特定的HQL或条件查询(Criteria Query)中重载声明, 在映射文档中定义的抓取策略对HQL无效,而会对以下列表条目产生影响:(1)通过get()或load()方法取得数据。 (2)只有在关联之间进行导航时,才会隐式的取得数据。如下例为在Criteria Query中使用抓取策略的例子和在HQL中使用join fetch的例子:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add(Restrictions.idEq(userId) )
.uniqueResult();
注意Criteria使用setFetchMode, 而HQL使用left join fetch
Parent parent = (Parent)hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
Query q = session.createQuery(
"from Parent as parent "+
" left outer join fetch parent.childs " +
" where parent.id = :id"
);
q.setParameter("id",new Long(15));
return (Parent)q.uniqueResult();
}
});
Assert.assertTrue(parent.getChilds().size() > 0);
HQL和Criteria Query中的查询策略默认为Select方式,在HQL中指定join Fetch某个关联对象将使用join的方式(如上例),一般而言,不使用映射文件定制抓取策略。更多的是,保持其默认值(Select方式),然后在特定的事务中,使用HQL或Criteria Query中的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。
如上所说,OR配置文件中的配置策略会对Session的get和load方法产生影响,下面为使用get和load的例子:
Session session=sessionFactory.openSession();
tx = session.beginTransaction();
Customer customer=(Customer)session.get(Customer.class,new Long(1));
tx.commit();
session.close();
tx = session.beginTransaction();
Customer c1=(Customer)session.load(Customer.class,new Long(1));
Customer c2=(Customer)session.load(Customer.class,new Long(1));
System.out.println(c1==c2);
tx.commit();
session.close();
关于Get和load还有一点区别:
hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。
除了以上介绍的join和select方式的抓取策略,还有另外两种抓取策略:
单端代理的批量抓取:<many-to-one name="classes" column="classesid" fetch="select"/>
集合代理的批量抓取:<set name="students" inverse="true" cascade="all" fetch="select">
使用场景:
Query query = session.createQuery("from Classes where id in(1,2,3)");
List<Classes> list = query.list();
for(Classes c : list){
for(Iterator i = c.getStudents().iterator();i.hasNext();){
Student s = (Student) i.next();
System.out.println("姓名:"+s.getName()+"班级:"+c.getClassName());
}
}
子查询抓取(fetch="subselect") - 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。 假若一个延迟集合或单值代理需要抓取,Hibernate会使用一个subselect重新运行原来的查询,一次性读入所有的实例。这和批量抓取的实现方法是一样的,不会有破碎的加载。
批量抓取(Batch fetching) - 对查询抓取的优化方案, 通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。
批量抓取是延迟查询抓取的优化方案,你可以在两种批量抓取方案之间进行选择:在类级别和集合级别。类级别的批量加载实体<class name="Classes" table="t_classes" batch-size="3"> 类上的批量抓取 from Student s where id in(1,11,21)
集合级别的批量加载实体 <set name="students" inverse="true" cascade="all" batch-size="5"> 集合上的批量抓取 from Classes c where id in(1,2,3)
类/实体级别的批量抓取很容易理解。假设你在运行时将需要面对下面的问题:你在一个Session中载入了25个Cat实例,每个Cat实例都拥有一个引用成员owner,其指向Person,而Person类是代理,同时lazy="true"。 如果你必须遍历整个cats集合,对每个元素调用getOwner()方法,Hibernate将会默认的执行25次SELECT查询, 得到其owner的代理对象。这时,你可以通过在映射文件的Person属性,显式声明batch-size,改变其行为:
<class name="Person" batch-size="10">...</class>
随之,Hibernate将只需要执行三次查询,分别为10、10、 5。
你也可以在集合级别定义批量抓取。例如,如果每个Person都拥有一个延迟载入的Cats集合, 现在,Sesssion中载入了10个person对象,遍历person集合将会引起10次SELECT查询, 每次查询都会调用getCats()方法。如果你在Person的映射定义部分,允许对cats批量抓取, 那么,Hibernate将可以预先抓取整个集合。请看例子:
<class name="Person">
<set name="cats" batch-size="3">
...
</set>
</class>
如果整个的batch-size是3,那么Hibernate将会分四次执行SELECT查询, 按照3、3、3、1的大小分别载入数据。
这里的每次载入的数据量还具体依赖于当前Session中未实例化集合的个数。
cascade 和 inverse
cascade 和 inverse 主要是用来级联插入和修改的 cascade主要是简化了在代码中的级联更新和删除。只有集合标记(set/map/list/array/bag)才有inverse属性,只有“关系标记”才有cascade属性:many-to-one,one-to-one ,any。
其实Inverse的意思是反转,inverse的默认值是false,即不反转,即控制权属于false的一方,所以使用默认值,双方都要进行控制,但是对于一对多的情况下,如果一这一端进行控制,对造成性能上的限制,所以将一这一方的inverse设置为true,则由多方来进行控制。如下面的一对多的配置中,一方是events,多方是participants,在events的配置中将关系的inverse设置为true,则participants将来维护这个一对多的关系。
<class name="events.Event" table="events">
<id name="id" column="event_id">
<generator class="native"/>
</id>
<property name="date" column="events_date" type="timestamp"/>
<property name="title" column="events_title"/>
<set name="participants" table="person_event" inverse="true">
<key column="event_id"/>
<many-to-many column="person_id" class="events.Person"/>
</set>
</class>
所谓的关系,一般是指外键关联的关系,而维护关系,是指通过与cacade协作,共同维护好关联关系,保持数据的一致性,
cacade的配置项:
- none - do not do any cascades, let the users handles them by themselves.
- save-update - when the object is saved/updated, check the associations and save/update any object that require it (including save/update the associations in many-to-many scenario).
- delete - when the object is deleted, delete all the objects in the association.
- delete-orphan - when the object is deleted, delete all the objects in the association. In addition to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
- all - when an object is save/update/delete, check the associations and save/update/delete all the objects found.
- all-delete-orphan - when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
cascade:在对主控方操作时,级联发生,而这个主控方是inverse=false的一方。 inverse: 在flush时(commit会自动执行flush),对session中的所有set,hibernate判断每个set是否有变化,对有变化的set执行相应的sql,执行之前,会有个判断:if( inverse == true ) return; 可以看出cascade在先,inverse在后。
关于cascade和inverse的详细介绍, 还是看stackoverflow上的答案吧:cascade和inverse的区别
1. inverse: This is used to decide which side is the relationship owner to manage the relationship (insert or update of the foreign key column).
2. cascade: In cascade, after one operation (save, update and delete) is done, it decide whether it need to call other operations (save, update and delete) on another entities which has relationship with each other.
Conclusion: In short, the “inverse” is decide which side will update the foreign key, while “cascade” is decide what’s the follow by operation should execute. Both are look quite similar in relationship, but it’s totally two different things. Hibernate developers are worth to spend time to research on it, because misunderstand the concept or misuse it will bring serious performance or data integrity issue in your application.
测试:一对多关系的两张表:boy、girl(一个男孩可以多个女朋友)
boy表结构
Field Type
------ -----------
name varchar(50) pk
age varchar(50)
girl表结构
Field Type
------ -----------
name varchar(50) pk
bf varchar(50) fk
【保存时:Inverse与cascade】
创建三个girl对象和一个boy对象,让这是三个girl都是boy的女朋友,如下代码:
Boy boy = new Boy("tom","23", null);
Set girls = new HashSet();
Girl g[] = new Girl[]{new Girl("Alice1", boy), new Girl("Alice2", boy), new Girl("Alice3", boy) };
girls.add(g[0]);
girls.add(g[1]);
girls.add(g[2]);
boy.setGirls(girls);
session.save(boy);
在Boy.hbm.xml中设置,
1.Inverse = true,不指定cascade 既为none
cascade的默认值为none, 当对boy进行保存操作时, girl什么都不做. 所以只保存了boy对象, 没有保存girl对象
2.Inverse = true,cascade=all
boy与girl对象,包括外键都成功保存。只不过girl表中的对应的id是null,发生SQL语句次数:SELECT 3, INSERT 4,分析:boy控制反转,关联关系有girls们维护,但有cascade进行了级联操作,girls们插入到数据中去了
3.Inverse = false,不指定cascade,既为none
报错。因为boy为主控方,负责维护关系,在维护关系是发现并不存在girl记录, 所以不能建立关系。
4.Inverse = false,cascade=all (由一方维护关系,那么会产生update)
boy与girl对象,包扩外键都成功保存。发生SQL语句次数:SELECT 3, INSERT 4, UPDATE 3。分析:除了4条INSERT语句之外,其他的6条语句中,3条SELECT语句用来判断girl对象是否在数据表中已经存在,3条UPDATE语句是为了维护外键关系
高效率的做法:在Boy.hbm.xml中设置Inverse=true,在Girl.hbm.xml中设置cascade=all,然后保存三个girl对象,发生SQL语句次数:SELECT 1, INSERT 4
【删除时:Inverse与cascade】希望通过删除boy,也将3个girl对象删除。程序中先查出boy对象,然后进行删除
-----------------------------------------
Boy boy = (Boy) s.get(Boy.class, "tom");
s.delete(boy);
-----------------------------------------
同样在Boy.hbm.xml中进行设置
1.Inverse = true cascade = none
可以猜到结果是出错。原因:外键约束错误,他没有维护关系,所以引起外键冲突
2.Inverse = false cascade = none
boy删除,girl表中外键变为null,没有删除记录,发生SQL语句次数:UPDATE 1, DELETE 1。由此可见,作为主控方如果不指定cascade,将不进行级联操作,对于外键来说,只是将外键约只为null, 而设置为cascade之后,将进行级联操作,对于删除来说,是先将外键约束改为null, 即先解除外键约束,在进行删除,如3.
3.Inverse = false, cascade = all
全部删除;在删除有外键的从表时,先把从表外键置为null,然后删除主表记录,最后根据从表主键删除所有相关从表记录,发生SQL语句次数:UPDATE 1, DELETE 4
4.Inverse = true, cascade = all
全部删除,发生SQL语句次数:DELETE 4,关于这一点应该是从Girls端进行维护关系,
SQL,HQL,Criteria Query
概述:数据查询与检索是Hibernate中的一个亮点。相对其他ORM实现而言,Hibernate提供了灵活多样的查询机制。
标准化对象查询(Criteria Query):以对象的方式进行查询,将查询语句封装为对象操作。优点:可读性好,符合Java 程序员的编码习惯。缺点:不够成熟,不支持投影(projection)或统计函数(aggregation)
Hibernate语言查询(Hibernate Query Language,HQL):它是完全面向对象的查询语句,查询功能非常强大,具备继承、多态和关联等特性 。Hibernate官方推荐使用HQL进行查询。
Native SQL Queries(原生SQL查询):直接使用数据库提供的SQL方言进行查询。
悲观锁和乐观锁
Hibernate的锁机制,悲观锁和乐观锁。悲观锁一般是借助数据库管理实现的,如Stirng hql=“from user where user.name='sa'”. Query query=session.createQuery(hql); query.setLockMode("user",LockMode.UPDATE); List list=query.list();
它指的是对数据被外界修改持保守态度。假定任何时刻存取数据时,都可能有另一个客户也正在存取同一笔数据,为了保持数据被操作的一致性,于是对数据采取了数据库层次的锁定状态,依靠数据库提供的锁机制来实现。
基于jdbc实现的数据库加锁如下:
select * from account where name="test" for update
在更新的过程中,数据库处于加锁状态,任何其他的针对本条数据的操作都将被延迟。本次事务提交后解锁。
而hibernate悲观锁的具体实现如下:
String sql="查询语句";
Query query=session.createQuery(sql);
query.setLockMode("对象别名",LockModel.UPGRADE);
session.load(Person.class, 1,LockMode.UPGRADE);
hibernate的加锁模式:
LockMode.NONE:无锁机制。
LockMode.WRITE:Hibernate在Insert和Update记录的时候会自动获取。
LockMode.READ:Hibernate在读取记录的时候会自动获取。
这三种加锁模式是供hibernate内部使用的,与数据库加锁无关:
LockMode.UPGRADE:利用数据库的for update字句加锁。
只有在查询开始之前(也就是hiernate生成sql语句之前)加锁,才会真正通过数据库的锁机制加锁处理。
乐观锁
乐观锁是借助一些诸如数据版本的机制实现,比如在数据表上增加一个字段version,如首先在class的属性上加上optimistic-lock=version,除version之外还有none,dirty和all等,然后配置文件中加一个version节点,如<version name="version" column="version" type="java.lang.String" 显然在数据表上增加的vesion字段最终要体现到OR文件上。
当前事务如果正在试图提交一个过期数据,将会抛出StaleObjectStateException异常,通过捕捉这个异常,我
们就可以在乐观锁校验失败时进行相应处理。Stale陈腐的,顾这个异常的意思是陈腐的对象状态异常
关联关系
唯一主键关联(一对一),双方的class中都有一个one-to-one 指向双方,且一方的主键有另一方主键生成,比如<id name='cid' type=java.lang.integer><column name='C_ID'><generator class='assigned'></id>,另一方为<id name='cid' type=java.lang.integer><column name='C_ID'><generator class='foreign'>上方的类路径<param name='property></param><genetor></id>,
唯一外键关联,如B的主键是A的外键,则B中使用one-to-one,A中使用many-to-one进行两者的关联,因为多个外键可以对应一个主键,顾外键的要使用many-to-one
多对一关联(双向),多方中使用many-to-one,一方使用<set><key/></one-to-many></set>,如果是单向的,则或者多方使用many-to-one,一方不用,或者一方使用<set><key/></one-to-many></set>,多方不用,由此可见,一对多关联要根据是否是双向有不同的实现,如果是双向的显然一个为one-to-many,一个为many-to-one,单向的则为任何一方维护即可,下面的这个例子是Photo一对多Pictures,这个地方的set属于Photo的,顾这个地方设置了inverse=true,意思不维护这个关系,由Picture来维护,而cascade=all顾要进行级联操作
<set name="pictures" inverse="true" cascade="all">
<key> <column name="photosid" not-null="true" /> </key>
<one-to-many class="girl.domain.Picture" />
</set>
many-to-one 和one-to-many关联的例子
<class name=" com.test.hibernate.User" table="TBL_USER">
<id name="id" column="userId"><generator class="native"/></id>
<many-to-one name=“group” column=“groupId” outer-join="false"/>
</class>
<class name=" com.test.hibernate .Group" table="TBL_GROUP">
<id name="id" column="groupId"><generator class="native"/></id>
</class>
<class name="com.test.hibernate.User" table="TBL_USER">
<id name="id" column="userId"><generator class="native"/></id>
<set name="addresses" lazy="true" cascade="all">
<key column="addressId"/>
<one-to-many class="com.test.hibernate.Address"/>
</set>
</class>
<class name="com.test.hibernate.Address" table="TBL_ADDRESS">
<id name="id" column="addressId"> <generator class="native"/></id>
</class>
多对多是使用中间表来实现的,两个多方均用<set></many-to-many></set>实现 一个多对多关联的例子
完整的配置文件Student.hbm.xml如下所示。
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="hibernate.ch06.Student" table="student" catalog="joblog">
<id name="id" type="integer">
<column name="id" />
<generator class="identity"></generator>
</id>
<property name="sno" type="integer"> <!--映射学号-->
<column name="Sno" not-null="true" />
</property>
<property name="sname" type="string"> <!--映射姓名-->
<column name="Sname" length="45" />
</property>
<property name="sdept" type="string"> <!--映射系部-->
<column name="Sdept" length="10" />
</property>
<property name="sage" type="integer"> <!--映射年龄-->
<column name="Sage" />
</property>
<property name="ssex" type="string"> <!--映射性别-->
<column name="Ssex" length="2" />
</property>
<property name="saddress" type="string"> <!--映射住址-->
<column name="Saddress" length="45" />
</property>
<set name="course" table="sc" lazy="false" cascade="save-update"> <!--映射课程表-->
<key column="sno" />
<many-to-many class="hibernate.ch06.Course" column="cno" /> <!--多对多-->
</set>
</class>
</hibernate-mapping>
缓存
Hibernate缓存分类:
一级缓存:Session缓存(又称作事务缓存):Hibernate内置的,不能卸除。缓存范围:缓存只能被当前Session对象访问。缓存的生命周期依赖于Session的生命周期,当Session被关闭后,缓存也就结束生命周期。持久化对象经过save()方法会放到session缓存中,get和load如果是从数据库中拿出的对象也会放在一级缓存,其他调用HQL和JDBC等从数据库中查询出来的数据也会放到session缓存中。
二级缓存:SessionFactory缓存(又称作应用缓存):使用第三方插件,可插拔。缓存范围:缓存被应用范围内的所有session共享, 不同的Session可以共享。这些session有可能是并发访问缓存,因此必须对缓存进行更新。缓存的生命周期依赖于应用的生命周期,应用结束时,缓存也就结束了生命周期,二级缓存存在于应用程序范围。
二级缓存实现原理:
Hibernate如何将数据库中的数据放入到二级缓存中?注意,你可以把缓存看做是一个Map对象,它的Key用于存储对象OID,Value用于存储POJO。首先,当我们使用Hibernate从数据库中查询出数据,获取检索的数据后,Hibernate将检索出来的对象的OID放入缓存中key 中,然后将具体的POJO放入value中,等待下一次再次向数据查询数据时,Hibernate根据你提供的OID先检索一级缓存,若有且配置了二级缓存,则检索二级缓存,如果还没有则才向数据库发送SQL语句,然后将查询出来的对象放入缓存中。所以 Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query缓存。
针对OID的缓存:
1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。
2) 把获得的所有数据对象根据ID放入到第二级缓存中。
3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
4) 删除、更新、增加数据的时候,同时更新缓存。
针对条件查询的Query缓存:
Hibernate的Query缓存策略的过程如下:
1) Hibernate首先根据这些信息组成一个Query Key,Query Key包括条件查询的请求一般信息:SQL, SQL需要的参数,记录范围(起始位置rowStart,最大记录个数maxRows),等。
2) Hibernate根据这个Query Key到Query缓存中查找对应的结果列表。如果存在,那么返回这个结果列表;如果不存在,查询数据库,获取结果列表,把整个结果列表根据Query Key放入到Query缓存中。
3) Query Key中的SQL涉及到一些表名,如果这些表的任何数据发生修改、删除、增加等操作,这些相关的Query Key都要从缓存中清空。
数据从缓存中清除:
1. evit()将指定的持久化对象从缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
2. clear()将缓存中的所有持久化对象清除,释放其占用的内存资源。
其他缓存操作:
1. contains()判断指定的对象是否存在于缓存中。
2. flush()刷新缓存区的内容,使之与数据库数据保持同步。
只有经正确的配置后二级缓存才会发挥作用。同时在进行条件查询时必须使用相应的方法才能从缓存中获取数据。比如Query.iterate()方法、load、get方法等。必须注意的是session.find方法永远是从数据库中获取数据,不会从二级缓存中获取数据,即便其中有其所需要的数据也是如此。
查询时使用缓存的实现过程为:首先查询一级缓存中是否具有需要的数据,如果没有,查询二级缓存,如果二级缓存中也没有,此时再执行查询数据库的工作。要注意的是:此3种方式的查询速度是依次降低的。即先一级后二级再数据库,这是从范围来考虑的。
Load默认使用二级缓存,就是当查一个对象的时候,它先会去二级缓存里面去找,如果找到了就不去数据库中查了。
Iterator默认的也会使用二级缓存,有的话就不去数据库里面查了,不发送select语句了。
List默认的往二级缓存中加数据,假如有一个Query,把数据拿出来之后会放到二级缓存,但是执行查询的时候不会到二级缓存中查,会在数据库中查。原因每个Query中查询条件不一样。
为了提高使用hibernate的性能,除了常规的一些需要注意的方法比如:
使用延迟加载、迫切外连接、查询过滤等以外,还需要配置hibernate的二级缓存。对系统整体性能的改善往往具有立竿见影的效果!
只要使用hibernate的API,hibernate就会自行维护二级缓存中的数据,以保证缓存中的数据和数据库中的真实数据的一致性!无论何时,当调用save()、update()或 saveOrUpdate()方法传递一个对象时,或使用load()、 get()、list()、iterate() 或scroll()方法获得一个对象时, 该对象都将被加入到Session的内部缓存中。 当随后flush()方法被调用时,对象的状态会和数据库取得同步。
但还有几种情况下不会绕开Hibernate导致缓存出现不一致性问题,不过使用缓存清除的方法可以手动保证数据的时效性,
其中二级缓存提供的清除方法为:(1)
按对象class清空缓存 (2)
按对象class和对象的主键id清空缓存 (3)
清空对象的集合中的缓存数据等。
所以:
1、建议不要使用sql直接执行数据持久化对象的数据的更新,但是可以执行 批量删除。(系统中需要批量更新的地方也较少)
2、如果必须使用sql执行数据的更新,必须清空此对象的缓存数据。调用SessionFactory.evict(class) SessionFactory.evict(class,id) 等方法。
3、在批量删除数据量不大的时候可以直接采用hibernate的批量删除,这样就不存在绕开hibernate执行sql产生的缓存数据一致性的问题。
4、不推荐采用hibernate的批量删除方法来删除大批量的记录数据,即删除大量数据的时候使用SQl进行删除
原因是hibernate的批量删除会执行1条查询语句外加 满足条件的n条删除语句。而不是一次执行一条条件删除语句!!
当待删除的数据很多时会有很大的性能瓶颈!!!如果批量删除数据量较大,比如超过50条,可以采用JDBC直接删除。这样作的好处是只执行一条sql删除语句,性能会有很大的改善。同时,缓存数据同步的问题,可以采用 hibernate清除二级缓存中的相关数据的方法。
调用 SessionFactory.evict(class) ;SessionFactory.evict(class,id)等方法。
所以说,对于一般的应用系统开发而言(不涉及到集群,分布式数据同步问题等),因为只在中间关联表执行批量删除时调用了sql执行,同时中间关联表一般是执行条件查询不太可能执行按id查询。所以,此时可以直接执行sql删除,甚至不需要调用缓存的清除方法。这样做不会导致以后配置了二级缓存引起数据有效性的问题。
很多hibernate的使用者在调用其相应方法时都迷信的相信“hibernate会自行为我们处理性能的问题”,或者“hibernate会自动为我们的所有操作调用缓存”,实际的情况是hibernate虽然为我们提供了很好的缓存机制和扩展缓存框架的支持,但是必须经过正确的调用其才有可能发挥作用!!所以造成很多使用hibernate的系统的性能问题,实际上并不是hibernate不行或者不好,而是因为使用者没有正确的了解其使用方法造成的。相反,如果配置得当hibernate的性能表现会让你有相当“惊喜的”发现。下面我讲解具体的配置方法.
Hibernate提供了二级缓存的接口: net.sf.hibernate.cache.Provider, 同时提供了一个默认的 实 net.sf.hibernate.cache.HashtableCacheProvider,
二级缓存中常用的缓存策略:<cache usage="read-write"/> read-only,read-write,transactional等
1 只读缓存 read only
不须要锁与事务,因为缓存自数据从数据库加载后就不会改变。
如果数据是只读的,例如引用数据,那么总是使用“read-only”策略,因为它是最简单、最高效的策略,也是集群安全的策略。是性能第一的策略 。
2 读写缓存 read write
对缓存的更新发生在数据库事务完成后。缓存需要支持锁。
在一个事务中更新数据库,在这个事务成功完成后更新缓存,并释放锁。
锁只是一种特定的缓存值失效表述方式,在它获得新数据库值前阻止其他事务读写缓存。那些事务会转而直接读取数据库。
缓存必须支持锁,事务支持则不是必须的。如果缓存是一个集群,“更新缓存”的调用会将新值推送给所有副本,这通常被称为“推(push)”更新策略。
如果你的数据是又读又写的,那么使用“read-write”策略。这通常是性能第三的策略,因为它要求有缓存锁,缓存集群中使用重量级的“推”更新策略。
二级缓存方法提供商:
提供了HashTable缓存,EHCache,OSCache,SwarmCache,jBoss Cathe2,这些缓存机制,其中EHCache,OSCache是不能用于集群环境(Cluster Safe)的,而SwarmCache,jBoss Cathe2是可以的。HashTable缓存主要是用来测试的,只能把对象放在内存中,EHCache,OSCache可以把对象放在内存(memory)中,也可以把对象放在硬盘(disk)上
使用EhCache来进行配置二级缓存实例:
在本Blog中的一偏Spring和Hibernate融合的XML配置中也有关于缓存的配置实例:http://blog.csdn.net/luoshenfu001/article/details/5816425
(1)在hibernate.cfg.xml中,打开二级缓存:
<!--使用二级缓存 - ->
<property name="cache.use_second_level_cache">true</property>
<!--设置缓存的类型,设置缓存的提供商-->
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
(2)配置 ehcache.xml
<ehcache>
<!-- 缓存到硬盘的路径 -->
<diskStore path="d:/ehcache"/>
<defaultCache
maxElementsInMemory="200"<!-- 最多缓存多少个对象 -->
eternal="false"<!-- 内存中的对象是否永远不变 -->
timeToIdleSeconds="50"<!--发呆了多长时间,没有人访问它,这么长时间清除 -->
timeToLiveSeconds="60"<!--活了多长时间,活了1200秒后就可以拿走,一般Live要比Idle设置的时间长 -->
overflowToDisk="true"<!--内存中溢出就放到硬盘上 -->
/>
<!--指定缓存的对象,缓存哪一个实体类,下面出现的的属性覆盖上面出现的,没出现的继承上面-->
<cache name="com.suxiaolei.hibernate.pojos.Order"
maxElementsInMemory="200"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
(3)使用二级缓存需要在实体类中加入注解:需要ehcache-1.2.jar和commons_loging1.1.1.jar包
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
(4)也可以在需要被缓存的对象中hbm文件中的<class>标签下添加一个<cache>子标签:
<hibernate-mapping> <class name="com.suxiaolei.hibernate.pojos.Order" table="orders"> <cache usage="read-only"/> <id name="id" type="string"> <column name="id"></column> <generator class="uuid"></generator> </id>
<property name="orderNumber" column="orderNumber" type="string"></property> <property name="cost" column="cost" type="integer"></property> <many-to-one name="customer" class="com.suxiaolei.hibernate.pojos.Customer" column="customer_id" cascade="save-update"> </many-to-one> </class> </hibernate-mapping></code></pre><br /><p><span><span style="font-size:13px;line-height:1.5;">存在一对多的关系,想要在在获取一方的时候将关联的多方缓存起来,需要在集合属性下添加</span><span style="font-size:13px;line-height:1.5;font-family:'Times New Roman';"><cache></span></span><span style="font-size:13px;line-height:1.5;font-family:'宋体';"><span>子标签</span>,这里需要将关联的对象的</span><span style="font-size:13px;line-height:1.5;font-family:'Times New Roman';">hbm</span><span style="font-size:13px;line-height:1.5;font-family:'宋体';">文件中必须在存在</span><span style="font-size:13px;line-height:1.5;font-family:'Times New Roman';"><class></span><span style="font-size:13px;line-height:1.5;font-family:'宋体';">标签下也添加</span><span style="font-size:13px;line-height:1.5;font-family:'Times New Roman';"><cache></span><span style="font-size:13px;line-height:1.5;font-family:'宋体';">标签,不然</span><span style="font-size:13px;line-height:1.5;font-family:'Times New Roman';">Hibernate</span><span style="font-size:13px;line-height:1.5;font-family:'宋体';">只会缓存</span><span style="font-size:13px;line-height:1.5;font-family:'Times New Roman';">OID</span><span style="font-size:13px;line-height:1.5;font-family:'宋体';">。</span></p>
<hibernate-mapping>
<class name="com.suxiaolei.hibernate.pojos.Customer" table="customer">
<!-- 主键设置 -->
<id name="id" type="string">
<column name="id"></column>
<generator class="uuid"></generator>
</id>
<!-- 属性设置 -->
<property name="username" column="username" type="string"></property>
<property name="balance" column="balance" type="integer"></property>
<set name="orders" inverse="true" cascade="all" lazy="false" fetch="join">
<cache usage="read-only"/>
<key column="customer_id" ></key>
<one-to-many class="com.suxiaolei.hibernate.pojos.Order"/>
</set>
</class>
</hibernate-mapping>