【Java EE 学习 48】【Hibernate学习第五天】【抓取策略】【二级缓存】【HQL】
一、抓取策略。
1.hibernate中提供了三种抓取策略。
(1)连接抓取(Join Fetch):这种抓取方式是默认的抓取方式。使用这种抓取方式hibernate会在select中内连接的方式获取对象的关联对象或者关联集合。
(2)查询抓取(select Fetch):这种抓取方式会另外发送一条select语句抓取当前对象的关联实体或者集合。除非指定lazy=false,否则只有在真正访问关联关系的时候才会执行第二条select语句。
(3)子查询抓取(subselect Fetch):另外发送一条select语句抓取在前面查询到的所有实体对象的关联集合。除非指定lazy=false,否则只有在真正访问关联关系的时候才会执行第二条select语句。
2.案例,获取选修了课程号为1或者2的所有学生信息。
结果表明,如果使用select或者join,则效率相同,发送的sql语句完全相同;如果使用subselect的话,效率要高很多,只需要两条sql语句。
1 package com.kdyzm.fetchtest; 2 3 import java.util.List; 4 import java.util.Set; 5 6 import org.hibernate.Session; 7 import org.hibernate.SessionFactory; 8 import org.hibernate.cfg.Configuration; 9 import org.junit.Test; 10 11 import com.kdyzm.hibernate.domain.Course; 12 import com.kdyzm.hibernate.domain.Student; 13 14 public class FetchTest { 15 private static SessionFactory sessionFactory; 16 static{ 17 Configuration configuration=new Configuration(); 18 configuration.configure(); 19 sessionFactory=configuration.buildSessionFactory(); 20 } 21 /* 22 * n+1条查询是显著的特点。 23 */ 24 @Test 25 public void baseTest(){ 26 Session session=sessionFactory.openSession(); 27 List<Student>students=session.createQuery("from Student").list(); 28 for(Student student:students){ 29 Set<Course>courses=student.getCourses(); 30 for(Course course:courses){ 31 System.out.println(course); 32 } 33 } 34 session.close(); 35 } 36 37 /* 38 * 查询班级cid为1,3的所有学生 39 * 40 * 如果需要用到子查询一般就是用subselect(fetch属性值) 41 * 使用subselect只需要两条SQL语句。 42 * 43 */ 44 @Test 45 public void test2(){ 46 Session session=sessionFactory.openSession(); 47 List<Course>courses=session.createQuery("from Course where cid in(1,3)").list(); 48 for(Course course:courses){ 49 Set<Student>students=course.getStudents(); 50 for(Student student:students){ 51 System.out.println(student); 52 } 53 } 54 session.close(); 55 } 56 57 /* 58 * 总结:以上的需求中,使用select和join方法效率相同,使用子查询subselect效率最高。 59 */ 60 61 62 }
二、二级缓存
1.二级缓存hibernate没有提供解决方案,必须借助第三方插件实现。
2.二级缓存常见的缓存策略提供商:
Cache |
Provider class |
Type |
Cluster Safe |
Query Cache Supported |
Hashtable (not intended for production use) |
org.hibernate.cache.HashtableCacheProvider |
memory |
|
yes |
EHCache |
org.hibernate.cache.EhCacheProvider |
memory,disk |
|
yes |
OSCache |
org.hibernate.cache.OSCacheProvider |
memory,disk |
|
yes |
SwarmCache |
org.hibernate.cache.SwarmCacheProvider |
clustered (ip multicast) |
yes (clustered invalidation) |
|
JBoss Cache 1.x |
org.hibernate.cache.TreeCacheProvider |
clustered (ip multicast), transactional |
yes (replication) |
yes (clock sync req.) |
JBoss Cache 2 |
org.hibernate.cache.jbc.JBossCacheRegionFactory |
clustered (ip multicast), transactional |
yes (replication or invalidation) |
yes (clock sync req.) |
3.二级缓存存放的是共享缓存,在二级缓存中存放的是共享数据
4.什么时候使用二级缓存
(1)修改不是非常频繁
(2)可以公开的数据,如省市等
(3)很多模块都要用到
5.二级缓存保存在SessionFactory对象中,其生命周期和SessionFactory相同。
6.以Ehcache为例说明二级缓存的配置和使用过程
(1)首先去官网下载jar包:http://www.ehcache.org/,并将jar包导入项目中
(2)在hibernate.cfg.xml文件中配置启用二级缓存:
<property name="hibernate.cache.use_second_level_cache"> true </property>
(3)在hibernate.cfg.xml配置文件中指定缓存策略提供商
<property name="cache.provider_class"> org.hibernate.cache.EhCacheProvider </property>
(4)在映射文件中指定使用二级缓存的类或集合或者在配置文件中配置使用二级缓存的类或集合。
* 推荐在配置文件下声明使用二级缓存的类或者集合。
在配置文件中声明:
<class-cache usage="read-only" class="com.kdyzm.hibernate.domain.Student"/> <collection-cache usage="read-only" collection="com.kdyzm.hibernate.domain.Student.courses"/>
(5)在classpath根目录下新建一个文件名为ehcache.xml的文件:
<?xml version="1.0" encoding="UTF-8"?> <ehcache> <diskStore path="secondCache"/> <defaultCache name="" maxElementsInMemory="1" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" diskPersistent="false" /> </ehcache>
关于这段代码的意思稍后作解释。
(6)配置完成,对代码进行测试
1 public void testOne(){ 2 Session session=sessionFactory.openSession(); 3 Student student1=(Student) session.get(Student.class, 1L); 4 Student student2=(Student) session.get(Student.class, 2L); 5 Student student3=(Student)session.get(Student.class, 3L); 6 Student student4=(Student)session.get(Student.class, 4L); 7 8 /** 9 * 这里加上延迟的作用是什么 10 */ 11 try { 12 Thread.sleep(1000); 13 } catch (InterruptedException e) { 14 e.printStackTrace(); 15 } 16 session.close(); 17 }
(7)测试结果:控制台打印结果和配置二级缓冲之前完全相同,但是在项目根目录下,新建了一个文件夹,文件夹名称为secondCache,里面有两个文件:
两个文件分别是类缓存和类中的集合缓存,打开文件之后发现差不多有三个元素:
7.详解配置过程中的相关问题
(1)为什么是类和集合分别在二级缓存中存储?
二级缓存只能缓存类中的普通属性,默认不缓存类中的集合属性,想要缓存类中的集合属性,需要在配置文件中特别说明,这样就会在二级缓存中对类中的集合属性单独存储了。所以当二级缓存中允许存在的对象数量达到上限的时候,类的实例会保存到磁盘上,同时类中的集合属性值作为对象也会保存到磁盘上,所以在测试中缓存文件夹中会出现两个文件。
(2)ehcache.xml配置文件配置参数详解
* ehcache标签:根标签。
* diskStore 标签:指定缓冲文件保存的位置;如果指定具体路径的话将会使用该路径保存缓冲文件,该路径一定是个文件夹的路径,否则会报错,如果路径不存在,则会创建相应的文件夹路径直到符合条件;如果没有指定盘符,则默认就是当前工程的根目录,以上例子中使用的就是这种方法。
* defaultCache 标签:指定二级缓存使用的各项参数。
name属性:缓存的名称,它的取值为类的全限定名或者类的集合的名字
maxElementsInMemory:该对象允许在内存中存在的最大数量,如果超出该限制,就使用策略解决超出限制的对象(如保存到硬盘)。
external:设置对象是否为永久的,true表示为永久对象,这时候将会忽略timeToIdleSeconds和timeToLiveSeconds两个属性的设置;默认值是false。
timeToIdleSeconds:设置对象空闲的最长时间,以秒为单位,超过这个时间,对象过期。当对象过期的时候,EHCache将会讲该对象在缓存中清除。如果该值为0,则表示该对象能够无限制的处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果该值为0,则表示该对象能够无限制的存在于二级缓存中,该属性值必须大于或者等于timeToIdleSeconds属性值。
overflowToDisk:设置当缓存中的对象数量达到上限之后,是否把溢出的对象写到基于硬盘的缓存中。
diskPersistent:当jvm结束的时候是否持久化对象,默认值是false。
diskExpiryThreadIntervalSeconds:指定专门用于清除过期对象的监听线程的轮询时间。
(3)使用Thread.sleep方法增加延迟的作用是什么?
为了防止溢出的对象写入文件之前程序结束,需要给写入程序足够的时间将溢出的对象保存到磁盘中,Thread.sleep方法就是发挥着这种作用。
8.二级缓存的各项操作
(1)测试二级缓存:当session关闭之后,Hibernate的一级缓存也就消失了,这个时候再查询相同的数据,肯定会发出SQL语句;但是如果当前对象已经保存到了二级缓存中,session关闭,但是二级缓存并没有消失,所以这个时候再查询相同的数据,也不会发出SQL语句。
注意:这里调用的是close方法,而不是clear方法,clear方法会将一级缓存和二级缓存全部清空!
public void testGet(){ Session session =sessionFactory.openSession(); Student student=(Student)session.get(Student.class, 1L); System.out.println(student); session.close();//session关闭之后一级缓存消失,但是二级缓存仍然存在! session=sessionFactory.openSession(); Student student2=(Student)session.get(Student.class, 1L); System.out.println(student2); session.close(); }
结果是,只有第一次的get方法发出了SQL语句,第二次调用的get方法并没有发出SQL语句。
Hibernate: select student0_.sid as sid2_1_, student0_.sname as sname2_1_, courses1_.sid as sid2_3_, course2_.cid as cid3_, course2_.cid as cid0_0_, course2_.cname as cname0_0_ from test.stu student0_ left outer join course_stu courses1_ on student0_.sid=courses1_.sid left outer join test.course course2_ on courses1_.cid=course2_.cid where student0_.sid=? Student [sid=1, sname=张三] Student [sid=1, sname=张三]
(2)把数据同步到二级缓存:测试对对象的操作,会将对对象的操作同步到二级缓存中。
进行该项测试的时候,首先应当修改配置文件,将对Student对象的操作修改为read-write,否则会直接报异常。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <!DOCTYPE hibernate-configuration PUBLIC 3 "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 4 "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> 5 <hibernate-configuration> 6 <session-factory> 7 <property name="connection.driver_class"> 8 com.mysql.jdbc.Driver 9 </property> 10 <property name="connection.username">root</property> 11 <property name="connection.password">5a6f38</property> 12 <property name="connection.url"> 13 jdbc:mysql://localhost:3306/test 14 </property> 15 <property name="show_sql">true</property> 16 <property name="hbm2ddl.auto">update</property> 17 <property name="dialect"> 18 org.hibernate.dialect.MySQLDialect 19 </property> 20 <property name="javax.persistence.validation.mode">none</property> 21 <!-- 开启二级缓存 --> 22 <property name="hibernate.cache.use_second_level_cache"> 23 true 24 </property> 25 <!-- 最后是映射文件的注册 --> 26 <!-- 使用的二级缓存策略提供商 --> 27 <property name="cache.provider_class"> 28 org.hibernate.cache.EhCacheProvider 29 </property> 30 <mapping resource="com/kdyzm/hibernate/config/Course.hbm.xml" /> 31 <mapping resource="com/kdyzm/hibernate/config/Student.hbm.xml" /> 32 <!-- 声明使用二级缓存的类和集合 --> 33 <class-cache usage="read-write" class="com.kdyzm.hibernate.domain.Student"/> 34 <collection-cache usage="read-only" collection="com.kdyzm.hibernate.domain.Student.courses"/> 35 </session-factory> 36 </hibernate-configuration>
测试代码:
public void test2(){ Session session=sessionFactory.openSession(); Transaction transaction=session.beginTransaction(); Student student=(Student)session.get(Student.class, 1L); System.out.println(student); student.setSname("新学生!"); transaction.commit();//执行commit操作的时候会将数据同时更新到二级缓存。 session.close();//关闭session,一级缓存消失了,但是二级缓存仍然存在! //如果调用clear方法的话,则会将一级缓存和二级缓存都清空掉! session=sessionFactory.openSession(); Student student2=(Student)session.get(Student.class, 1L); System.out.println(student2); session.close(); }
结果:
预计结果是应当只有两次查询,一次是获取,一次是更新。
更新完成之后的获取不应当再发出SQL语句,结果表明预测是正确的。
Hibernate: select student0_.sid as sid2_1_, student0_.sname as sname2_1_, courses1_.sid as sid2_3_, course2_.cid as cid3_, course2_.cid as cid0_0_, course2_.cname as cname0_0_ from test.stu student0_ left outer join course_stu courses1_ on student0_.sid=courses1_.sid left outer join test.course course2_ on courses1_.cid=course2_.cid where student0_.sid=? Student [sid=1, sname=张三] Hibernate: update test.stu set sname=? where sid=? Student [sid=1, sname=新学生!]
结论:调用commit方法的时候,同时将二级缓存中的数据更新掉了,但是这个时候必须对配置文件进行修改,使得能对二级缓存中的对象进行修改。
三、HQL语言
1.HQL是Hibernate Query Language的缩写,它是面向对象的查询语言,和SQL语言有些相似,在Hibernate提供的各种检索方式中,HQL是使用最为广泛的一种检索方式。
2.HQL的功能
(1)在查询语句中设定各种查询条件
(2)支持投影查询,即仅仅检索出部分对象的属性
(3)支持分页查询
(4)支持连接查询
(5)支持分组查询,允许使用having和group by关键字。
(6)提供内置聚集函数,如sum(),min(),和max()等。
(7)能够调用用户自定义的函数或者标准的SQL函数。
(8)支持子查询
(9)支持动态绑定函数
3.HQL的练习操作
(1)最简单的对单表的查询操作
public void testOne(){ Session session=sessionFactory.openSession(); Query query=session.createQuery("from Student where sid='1'"); List<Student>students=query.list(); for(Student student:students){ System.out.println(student); } session.close(); }
(2)详情查看3.6.5的文档第16章。
练习的源代码:https://github.com/kdyzm/day44_hibernate03