Hibernate二级缓存与查询性能优化,session的MVC式管理

1 什么是缓存

  1. Cache就是缓存, 它往往是提高系统性能的最重要手段,对数据起到一个蓄水池和缓冲的作用。Cache对于大量依赖数据读取操作的系统而言尤其重要。在大并发量的情况下,如果每次程序都需要向数据库直接做查询操作,它们所带来的性能开销是显而易见的,频繁的网络请求,数据库磁盘的读写操作都会大大降低系统的性能。此时如果能让数据库在本地内存中保留一个镜像,下次访问的时候只需要从内存中直接获取,那么显然可以带来不小的性能提升。
  2. 引入Cache机制的难点是如何保证内存中数据的有效性,否则脏数据的出现将会给系统带来难以预知的严重后果。
  3. 虽然一个设计得很好的应用程序不用Cache也可以表现出让人接受的性能,但毫无疑问,一些对读取操作要求比较高的应用程序可以通过Cache获得更高的性能。对于应用程序,Cache通过内存或磁盘保存了数据库中的当前有关数据状态,它是一个存储在本地的数据备份。Cache位于数据库和应用程序之间,从数据库更新数据,并给程序提供数据。
  4. Hibernate实现了良好的Cache机制,可以借助Hibernate内部的Cache迅速提高系统的数据读取性能。Hibernate中的Cache可分为两层:一级Cache和二级Cache。

一级缓存:

Hibernate默认是开启一级缓存的,一级缓存存放在session上,属于事务级数据缓冲。

二级缓存:

二级缓存是在SessionFactory,所有的Session共享同一个二级Cache。二级Cache的内部如何实现并不重要,重要的是采用哪种正确的缓存策略,以及采用哪个Cache提供器。
二级缓存也分为了两种:
内置缓存:
Hibernate自带的,不可卸载,通常在Hibernate的初始化阶段,Hibernate会把映射元数据和提前定义的SQL语句放置到SessionFactory的缓存中。该内置缓存是仅仅读的。
外置缓存:
通常说的二级缓存也就是外置缓存,也常是第三方插件(有很我供应者),在默认情况下SessionFactory不会启用这个缓存插件,外置缓存中的数据是数据库数据的复制,外置缓存的物理介质能够是内存或者硬盘。

image.png

并发訪问策略
image.png

适合放入二级缓存中数据
非常少被改动
不是非常重要的数据。同意出现偶尔的并发问题
不适合放入二级缓存中的数据
常常被改动
財务数据,绝对不同意出现并发问题与其它应用数据共享的数据

二级缓存原理,看下面讲:

---已有Department.java,Location.java模型类,和对应的表departments,locations

  1. 没有关闭会话的情况下,只用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 ...........
    }
image.png
  1. 若在加载第一个对象后,关闭了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());
        
    }
image.png
  1. 配置了类级缓存和集合级缓存,再执行2中的测试代码,此时相当有了共享缓存,只发送了一条sql语句,如同1一样。(不过要导入第三方二缓存供应商的包)


    image.png

外置(第三方)二级缓存。

  1. 拷贝jar包和创建配置文件ehcache.xml到src目录下

  org.hibernate
  hibernate-ehcache
  5.2.17.Final

ehcache.xml







   

  1. 在hibernate.cfg.xml中开启二级缓存
true

3 配置二级缓存技术提供者

org.hibernate.cache.ehcache.EhCacheRegionFactory

4 配置使用缓存的类,并且为Department模型类使用了专门的缓存区域region="cacheTest",也在d盘中,测试2中准效果。

 

  
  1. 测试:
    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());*/
        
    }

起到作用的情形如下:

image.png

这样,即使一级缓存被清除,还是会去二级缓存找。也相当节省了系统肖耗。
2--- 测试缓存到磁盘的过程。
过程:没关闭sessionFactory.close();之前,缓存到磁盘上,关闭了就清理缓存,所以我们要用到调式功能。在
sessionFactory.close();上打一个断点。当运行到此,我们可以看到磁盘上有内容了。
代码:

public void destory() { //After表示在test后执行
        transaction.commit();
        session.close();
        sessionFactory.close();  //打一个断点吧,再执行调试
    }
        String hql = "from Department d where d.departmentId ls = session.createQuery(hql).setParameter(0, 4).list();
        System.out.println(ls);

查询缓存

  1. 查询缓存要用到的原因。
    ----上面的测试2,查询同样的数据(可能很多条,不同于测试1那样只是加载一条记当)两次,缓存失效。
    这时个要开启查询缓存了。
        String hql = "from Department d where d.departmentId 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. 开启查询缓存与应用测试
    开启分两步:
    <1> org.hibernate.query.Query qeuery对象要设置可用缓存能力。
    <2>核心配置文件xxx.cfg.xml中。
 
  true

测试:

    String hql = "from Department d where d.departmentId 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);该代码的执行效果。


image.png
image.png

时间戳的缓存

该缓存保证了缓存数据与数据库的同步。
中间,做了更新, 后面的query.list() ,就不会再从缓存中取数据了, 会重启发起sql语句, ehcache是怎么知道要从发sql的呢? 这就是时间戳的作用!
在chcache缓存中保存着对数据表的增、改、删的动作发生时的时间戳, 时间戳缓存用这个时间戳来判断, 缓存中存放的结果集是不是过时了? 过时就重发sql, 不过时就使用!


image.png

测试:

String hql = "from Department d where d.departmentId 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);
        
image.png

查询性能优化

1. Hibernate 主要从以下几方面来优化查询性能

(1) 使用迫切左外连接或迫切内连接查询策略、查询缓存等方式,减少 select 语句的数目,降低访问数据库的频率。 inner join fetch, left join fetch
(2) 使用延迟查询策略等方式避免加载多余的不需要访问的数据。默认就是 ! 基本上不会把 lazy="false"
(3) 使用 Query 接口的 iterate() 方法减少 select 语句中的字段,从而降低访问数据库的数据量。这个只是稍稍提高性能 , 用的不好 , 反而坏事
Query 接口的 iterate() 方法和 list() 方法都用来执行 SQL 查询语句。在某些情况
下, iterate() 方法能提高查询性能。注意 iterate() 方法是依赖二级缓存 , 没有二级缓存它也无法提高性能
看示例:


image.png

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式我们要这样搞:

  1. 工具类是单例模式,不用创建对象(懒汉模式-节省内存)
    获取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
    }
}
  1. 核心配置文件:xxxx.cfg.xml中开启session线程管理模式
    thread线程方式来管理session对象的好处是, 当事务提交的时候, hibernate会自动的关闭session, 我们就更方便了,不用手动去session.close();

  thread
  1. 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);
   }
}
  1. 测试类 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();
    }

}

你可能感兴趣的:(Hibernate二级缓存与查询性能优化,session的MVC式管理)