1 什么是缓存
- Cache就是缓存, 它往往是提高系统性能的最重要手段,对数据起到一个蓄水池和缓冲的作用。Cache对于大量依赖数据读取操作的系统而言尤其重要。在大并发量的情况下,如果每次程序都需要向数据库直接做查询操作,它们所带来的性能开销是显而易见的,频繁的网络请求,数据库磁盘的读写操作都会大大降低系统的性能。此时如果能让数据库在本地内存中保留一个镜像,下次访问的时候只需要从内存中直接获取,那么显然可以带来不小的性能提升。
- 引入Cache机制的难点是如何保证内存中数据的有效性,否则脏数据的出现将会给系统带来难以预知的严重后果。
- 虽然一个设计得很好的应用程序不用Cache也可以表现出让人接受的性能,但毫无疑问,一些对读取操作要求比较高的应用程序可以通过Cache获得更高的性能。对于应用程序,Cache通过内存或磁盘保存了数据库中的当前有关数据状态,它是一个存储在本地的数据备份。Cache位于数据库和应用程序之间,从数据库更新数据,并给程序提供数据。
- Hibernate实现了良好的Cache机制,可以借助Hibernate内部的Cache迅速提高系统的数据读取性能。Hibernate中的Cache可分为两层:一级Cache和二级Cache。
一级缓存:
Hibernate默认是开启一级缓存的,一级缓存存放在session上,属于事务级数据缓冲。
二级缓存:
二级缓存是在SessionFactory,所有的Session共享同一个二级Cache。二级Cache的内部如何实现并不重要,重要的是采用哪种正确的缓存策略,以及采用哪个Cache提供器。
二级缓存也分为了两种:
内置缓存:
Hibernate自带的,不可卸载,通常在Hibernate的初始化阶段,Hibernate会把映射元数据和提前定义的SQL语句放置到SessionFactory的缓存中。该内置缓存是仅仅读的。
外置缓存:
通常说的二级缓存也就是外置缓存,也常是第三方插件(有很我供应者),在默认情况下SessionFactory不会启用这个缓存插件,外置缓存中的数据是数据库数据的复制,外置缓存的物理介质能够是内存或者硬盘。
并发訪问策略
适合放入二级缓存中数据
非常少被改动
不是非常重要的数据。同意出现偶尔的并发问题
不适合放入二级缓存中的数据
常常被改动
財务数据,绝对不同意出现并发问题与其它应用数据共享的数据
二级缓存原理,看下面讲:
---已有Department.java,Location.java模型类,和对应的表departments,locations
- 没有关闭会话的情况下,只用get load加载对象,此时只用到一级缓存,查询同一个对象时,不管多少个,只发送一条sql语句。
@Test
public void test() {
//get load方法加载到一级缓存中(同一个对象只发送一条语句),不是二缓存
Location location =session.load(Location.class, 7);
System.out.println(location.getDepartments());
Location location2 = session.get(Location.class, 7);
System.out.println(location2.getDepartments());
Location ...........
}
- 若在加载第一个对象后,关闭了session---即清理了一级缓存,也就是说后面的查询是另外一个缓存。
@Test
public void test() {
//get load方法加载到一级缓存中(同一个对象只发送一条语句),不是二缓存
Location location =session.load(Location.class, 7);
System.out.println(location.getDepartments());
transaction.commit();
session.close();
session =sessionFactory.openSession();
transaction = session.beginTransaction();
Location location2 = session.get(Location.class, 7);
System.out.println(location2.getDepartments());
}
-
配置了类级缓存和集合级缓存,再执行2中的测试代码,此时相当有了共享缓存,只发送了一条sql语句,如同1一样。(不过要导入第三方二缓存供应商的包)
外置(第三方)二级缓存。
- 拷贝jar包和创建配置文件ehcache.xml到src目录下
org.hibernate
hibernate-ehcache
5.2.17.Final
ehcache.xml
- 在hibernate.cfg.xml中开启二级缓存
true
3 配置二级缓存技术提供者
org.hibernate.cache.ehcache.EhCacheRegionFactory
4 配置使用缓存的类,并且为Department模型类使用了专门的缓存区域region="cacheTest",也在d盘中,测试2中准效果。
- 测试:
1---测试二级缓存起到作用否。
不成功不知是什么原因:java.lang.NoClassDefFoundError: net/sf/ehcache/CacheException at java.lang.Class.getDeclaredCon。
原先sts3.9是打包了安装好jboss tools的软件,有700多M.
后来,我解压了一个没有安装jboss tools的STS3.9IDE,再同样配置JBOSS TOOLS,成功。
@Test
public void test() {
//get load方法加载到一级缓存中(同一个对象只发送一条语句),不是二缓存
Department department=session.load(Department.class, 7);
System.out.println(department.getDepartmentName());
transaction.commit();
session.close();
session =sessionFactory.openSession();
transaction = session.beginTransaction();
Department department2=session.load(Department.class, 7);
System.out.println(department2.getDepartmentName());
/*Location location3 = session.get(Location.class, 7);
System.out.println(location3.getDepartments());*/
}
起到作用的情形如下:
这样,即使一级缓存被清除,还是会去二级缓存找。也相当节省了系统肖耗。
2--- 测试缓存到磁盘的过程。
过程:没关闭sessionFactory.close();之前,缓存到磁盘上,关闭了就清理缓存,所以我们要用到调式功能。在
sessionFactory.close();上打一个断点。当运行到此,我们可以看到磁盘上有内容了。
代码:
public void destory() { //After表示在test后执行
transaction.commit();
session.close();
sessionFactory.close(); //打一个断点吧,再执行调试
}
String hql = "from Department d where d.departmentId";
List ls = session.createQuery(hql).setParameter(0, 4).list();
System.out.println(ls);
查询缓存
- 查询缓存要用到的原因。
----上面的测试2,查询同样的数据(可能很多条,不同于测试1那样只是加载一条记当)两次,缓存失效。
这时个要开启查询缓存了。
String hql = "from Department d where d.departmentId";
List ls = session.createQuery(hql).setParameter(0, 4).list();
System.out.println(ls);
List ls1 = session.createQuery(hql).setParameter(0, 4).list();
System.out.println(ls1);
- 开启查询缓存与应用测试
开启分两步:
<1> org.hibernate.query.Query qeuery对象要设置可用缓存能力。
<2>核心配置文件xxx.cfg.xml中。
true
测试:
String hql = "from Department d where d.departmentId";
Query query = session.createQuery(hql).setParameter(0,4);
query.setCacheable(true); //开启查询二级缓存
List ls = query.list();
System.out.println(ls);
List ls1 = query.list();
System.out.println(ls1);
下面两图显示query.setCacheable(true);与注释、、query.setCacheable(true);该代码的执行效果。
时间戳的缓存
该缓存保证了缓存数据与数据库的同步。
中间,做了更新, 后面的query.list() ,就不会再从缓存中取数据了, 会重启发起sql语句, ehcache是怎么知道要从发sql的呢? 这就是时间戳的作用!
在chcache缓存中保存着对数据表的增、改、删的动作发生时的时间戳, 时间戳缓存用这个时间戳来判断, 缓存中存放的结果集是不是过时了? 过时就重发sql, 不过时就使用!
测试:
String hql = "from Department d where d.departmentId";
Query query = session.createQuery(hql).setParameter(0,4);
query.setCacheable(true); //开启查询二级缓存
List ls = query.list();
System.out.println(ls);
Department department = session.get(Department.class, 2);
department.setDepartmentName("计算机技术部"); //修改了两次查询选中的一个记录数据。
List ls1 = query.list();
System.out.println(ls1);
查询性能优化
1. Hibernate 主要从以下几方面来优化查询性能
(1) 使用迫切左外连接或迫切内连接查询策略、查询缓存等方式,减少 select 语句的数目,降低访问数据库的频率。 inner join fetch, left join fetch
(2) 使用延迟查询策略等方式避免加载多余的不需要访问的数据。默认就是 ! 基本上不会把 lazy="false"
(3) 使用 Query 接口的 iterate() 方法减少 select 语句中的字段,从而降低访问数据库的数据量。这个只是稍稍提高性能 , 用的不好 , 反而坏事
Query 接口的 iterate() 方法和 list() 方法都用来执行 SQL 查询语句。在某些情况
下, iterate() 方法能提高查询性能。注意 iterate() 方法是依赖二级缓存 , 没有二级缓存它也无法提高性能
看示例:
2. HQL 优化
HQL 优化是 Hibernate 程序性能优化的一个方面, HQL 的语法与 SQL 非常类似。 HQL 是基于 SQL 的,只是增加了面向对象的封装。如果抛开 HOL 同 Hibernate 本身一些缓存机制的关联, HQL 的优化技巧同 SQL 的优化技巧一样。在编写 HQL 时,需注意以下几个原则。
(1) 避免 or 操作的使用不当。 如果 where 子句中有多个条件,并且其中某个条件没有索引,使用 or ,将导致全表扫描。假定在 HOUSE 表中 TITLE 有索引, PRICE 没有索引,执行以下 HQL 语句:
from House where title= ‘ 出租 - 居室 ’ or price<1500
当 PRICE 比较时,会引起全表扫描。
(2) 避免使用 not 。 如果 where 子句的条件包含 not 关键字,那么执行时该字段的索引失效。这些语句需要分成不同情况区别对待,如查询租金不多于 1800 元的店铺出租转让信息的 HQL 语句:
from House as h where not (h.price>1800)
对于这种不大于(不多于)、不小于(不少于)的条件,建议使用比较运算符来替代not ,如不大于就是小于等于。例如:
from House as h where h.price<=1800
如果知道某一字段所允许的设置值,那么就有其他的解决方法。
例如: 在用户表中增加性别字段,规定性别字段仅能包含 M 和 F ,当要查询非男用户时,为避免使用 not 关键字,将条件设定为查询女用户即可。
(3) 避免 like 的特殊形式。 某些情况下,会在 where 子句条件中使用 like 。如果 like 以一个 “%” 或 “_” 开始即前模糊, 则该字段的索引不起作用 。但是非常遗憾的是,对于这种问题并没有额外的解决方法,只能通过改变索引字段的形式变相地解决。
(4) 避免 having 予句。 在分组的查询语句中,可在两个位置指定条件,一是 where 子旬中,二是在 having 子句中。尽可能地在 where 子句而不是 having 子句中指定条件。Having 是在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where 子句限制记录数目,能减少这方面的开销。
(5) 避免使用 distinct 。 。 指定 distinct 会导致在结果中删除重复的行。这会对处理时间造成一定的影响,因此在不要求或允许冗余时,应避免使用 distinct 。在 java 代码中来排重 : 看演示 !
//。 指定 distinct 会导致在结果中删除重复的行。这会对处理时间造成 一定的影响,因此在不要求或允许冗余时,应避免使用 distinct 。 在 java 代码中来排重 : 看演示
//String hql = "select distinct d.departmentName from Department d"; //查询结果排重,会影响性能。
String hql2 = "select d.departmentName from Department d";
List ls=session.createQuery(hql2).list();
ls=new ArrayList<>(new HashSet<>(ls)); //set集合,元素无序,但元素不能重复,可以排重
System.out.println(ls.size());
System.out.println(ls);
(6) 索引在以下情况下失效,应注意使用。
Ø 只要对字段使用函数,该字段的索引将不起作用,如 substring(aa , 1 , 2)='xx' 。
Ø 只要对字段进行计算,该字段的索引将不起作用,如 price+10 。
Hibernate session的MVC式管理
前面我们都是在测试类里获取到的hibernate访问数据库的会话session对象, 但是很明显这和我们项目中的实际开发, 不可能这样搞的, 回到我们JavaWeb里讲的MVC开发结构, dao层里怎么获取到这个session的对象呢? 我们讨论一下!
前面测试类中是这样的搞的
public class HibernateTest {
SessionFactory sessionFactory=null;
Session session=null;
Transaction transaction=null;
//以上三条语句,生产环境中不能这样干,因为多线程时,会乱,会话要分开来
@Before
public void init() {
//Configuration类对象封装我们的配置文件里的配置信息
Configuration configuration=new Configuration().configure();
//hibernate规定,所有的配置或服务,要生效,必须配置或服务注册到一个服务注册类
ServiceRegistry serviceRegistry=configuration.getStandardServiceRegistryBuilder().build();
//获取会话工厂类对象
sessionFactory=new MetadataSources(serviceRegistry).buildMetadata().buildSessionFactory();
//获取hibernate会话
session =sessionFactory.openSession();
//开启事务处理
transaction=session.beginTransaction();
}
@After
public void destory() { //After表示在test后执行
transaction.commit();
session.close();
sessionFactory.close();
}
............................其它测试代码..............................................................................
}
MVC式我们要这样搞:
- 工具类是单例模式,不用创建对象(懒汉模式-节省内存)
获取Hibernate session: Session session =HibernateSessionUtil.getInstance().getSession();
package cn.ybzy.hibernatemvc.utils;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
/**
* 此工具类的核心作用是获取session对象
* @author Administrator
*
*/
public class HibernateSessionUtil {
//获取hibernate与数据库的连接会话对象,在整个应用的作用域中
//此工具类应该用单例模式。
private static HibernateSessionUtil hibernateSessionUtil =null;
private HibernateSessionUtil() { //私有构造方法,不可外建对象
}
public static HibernateSessionUtil getInstance() {
if(hibernateSessionUtil == null) {
hibernateSessionUtil=new HibernateSessionUtil();
}
return hibernateSessionUtil;
}
public SessionFactory getSessionFactory() {
//Configuration类对象封装我们的配置文件里的配置信息
Configuration configuration=new Configuration().configure();
//hibernate规定,所有的配置或服务,要生效,必须配置或服务注册到一个服务注册类
ServiceRegistry serviceRegistry=configuration.getStandardServiceRegistryBuilder().build();
//获取会话工厂类对象
return new MetadataSources(serviceRegistry).buildMetadata().buildSessionFactory();
}
//要选带hibernate前缀的包
public Session getSession() {
return getSessionFactory().getCurrentSession(); //获取与当前线绑定的session
}
}
- 核心配置文件:xxxx.cfg.xml中开启session线程管理模式
thread线程方式来管理session对象的好处是, 当事务提交的时候, hibernate会自动的关闭session, 我们就更方便了,不用手动去session.close();
thread
- DAO层用法举例:
package cn.ybzy.hibernatemvc.dao;
import org.hibernate.Session;
import cn.ybzy.hibernatedemo.modelhhql.Department;
import cn.ybzy.hibernatemvc.utils.HibernateSessionUtil;
public class DepartmentDao {
public void add(Department department) {
Session session =HibernateSessionUtil.getInstance().getSession();
session.save(department);
}
}
- 测试类 MvcSessionTest.java用法,加一个部门记当。
mport org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.ybzy.hibernatedemo.modelhhql.Department;
import cn.ybzy.hibernatedemo.modelhhql.Location;
import cn.ybzy.hibernatemvc.utils.HibernateSessionUtil;
public class MvcSessionTest {
@Test
public void test() {
Session session =HibernateSessionUtil.getInstance().getSession();
Transaction transaction =session.beginTransaction();
Department department =new Department();
department.setDepartmentName("abc+++++");
Location location =new Location();
location.setLocationId(7);
department.setLocation(location);
session.save(department);
transaction.commit();
}
}