[参考+理解]hibernate的查询和性能调优

HQL

hql语言,不需要记了,直接用nativeSql
Nativesql>HQL>Ejbql>qbc>qbe

性能优化

1、session.clear
比如,你在同一个session中 一下先取出1000条数据,然后处理,在取1000条数据 处理,其实更前面的session缓存没用了,反而会造成内存问题,所以即使session.clear

2、1+N问题

/** * 描述1+N问题 category 和topic是一对多 * 在取出topic的时候,由于ManyToOne默认的是eager,就会把category取出来 * 解决方案: * 1:把另一方设置lazy * 2:自己写自己写join */
    //把ManyToOne设置为lazy
    @Test
    public void get1() {
        Session session = sf.getCurrentSession();
        session.beginTransaction();
        String sql = "select id, title, createdate, category_id from Topic";
        SQLQuery sqlQuery = session.createSQLQuery(sql);
        sqlQuery.addEntity(Topic.class);
        List<Topic> list = sqlQuery.list();
        session.getTransaction().commit();
    }

//自己写join解决1+N
@Test
    public void get11() {
        Session session = sf.getCurrentSession();
        session.beginTransaction();
        String sql = "select {a.*}, {b.*} from Topic a join Category b" +
                " on a.category_id = b.id";
        SQLQuery sqlQuery = session.createSQLQuery(sql).addEntity("a",Topic.class).addEntity("b",Category.class);
        List<Object[]> list = sqlQuery.list();
        session.getTransaction().commit();
    }

3、list和iterate
3.1、iterate 不支持nativeSql返回的接口查询
3.2、调用HQL后,iterate只会返回id属性,其他属性不返回。
3.3、iterate会使用缓存,而list却不使用缓存

hibernate 缓存

1、一级缓存:就是session级别的缓存,比如get load 在查询前都会先去session的缓存里面查找数据,没有再去数据库里面查找数据。
一级缓存的生命周期和session的生命周期一致,当前sessioin一旦关闭,一级缓存就消失,因此一级缓存也叫

session 级的缓存或事务级缓存,一级缓存只存实体对象的 ,它不会缓存一般的对象属性(查询缓存可以),即当获得

对象后,就将该对象的缓存起来,如果在同一session中如果再去获取这个对象 时,它会先判断缓存中有没有该对象的

ID,如果有就直接从缓存中取出,反之则去数据库中取,取的同时将该对象的缓存起来,有以下方法可以 支持一级缓存


* get()
* load()
* iterate(查询实体对象)
其 中 Query 和Criteria的list() 只会缓存,但不会使用缓存(除非结合查询缓存)。

2、二级缓存:也成sessionFactory级别的缓存
这个缓存借助其他缓存框架来实现,需要手动配置,二级缓存使用需要满足下面特点
(1)数据不会被第三放修改
(2)同一数据系统经常引用
(3)数据大小在可接受范围之内
二级缓存策略的一般过程:

(1) Hibernate进行查询的时候,一次获得所有的符合条件的数据对象。
(2) 把获得的所有数据对象根据ID放入到第二级缓存中。
(3) 当Hibernate根据ID访问数据对象的时候,首先从内部缓存中查找,如果在内部缓存中查不到就配置二级缓存,从二级缓存中查;如果还查不到,再查询数据库,把结果按照ID放入到缓存。
(4)添加数据、删除、更新操作时,同时更新二级缓存。这就是Hibernate做批处理的时候效率不高的原因,原来是要维护二级缓存消耗大量时间的缘故。

二级缓存也是缓存实体对象 ,其实现原理与一级缓存的差不多吧,其方法与一级的相同,只是缓存的生命周期不一样而已:
* get()
* load()
* iterate(查询实体对象)
其中 Query 和Criteria的list() 只会缓存,但不会使用缓存(除非结合查询缓存)。
比如配置ehcache

<property name="cache.use_second_level_cache">true</property>
        <property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>

而且需要在实体上写上注解@cache

@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
//二级的缓存
    @Test
    public void get5_1() {
        Session session = sf.getCurrentSession();
        session.beginTransaction();
        Category c = (Category) session.load(Category.class, 1);
        System.out.println(c.getName());
        session.getTransaction().commit();

        Session session2 = sf.getCurrentSession();
        session2.beginTransaction();
        Category c1 = (Category) session2.get(Category.class, 1);
        System.out.println(c1.getName());
        session2.getTransaction().commit();
    }

3、三级缓存
三级缓存是基于二级缓存的。也成为查询缓存
Hibernate的二级缓存策略是针对ID查询的策略,和对象ID密切相关,那么对于条件查询就怎么适用了。对于这种情况的存在,Hibernate引入了“查询缓存”在一定程度上缓解这个问题。

那么我们先来看看我们为什么使用查询缓存?首先我们来思考一个问题,假如我们对数据表Student进行查询操作,查找age>20的所有学生信息,然后纳入二级缓存;第二次我们的查询条件变了,查找age>15的所有学生信息,显然第一次查询的结果完全满足第二次查询的条件,但并不是满足条件的全部数据。这样的话,我们就要再做一次查询得到全部数据才行。再想想,如果我们执行的是相同的条件语句,那么是不是可以利用之前的结果集呢?

Hibernate就是为了解决这个问题的而引入Query Cache的。 

查询缓存是针对普通属性结果集的缓存,对实体对象的结果集只缓存id(其ID不是对象的真正ID,它与查询的条件相关即where后的条件相关,不同的查询条件,其缓存的id也不一样) ,查询缓存的生命周期,当前关联的表发生修改或是 查询条件改变时,那么查询缓存生命周期结束,它不受一级缓存 和二级缓存 的生命周期的影响。
查询缓存策略的一般过程如下:

(1)Query Cache保存了之前查询的执行过的Select SQL,以及结果集等信息,组成一个Query Key。
(2)当再次遇到查询请求的时候,就会根据Query Key 从Query Cache找,找到就返回。但 是两次查询之间,数据表发生数据变动的话,Hibernate就会自动清除Query Cache中对应的Query Key。
我们从查询缓存的策略中可以看出,Query Cache只是在特定的条件下才会发挥作用,而且要求相当严格:

(1)完全相同的Select SQL重复执行。
(2)重复执行期间,Query Key对应的数据表不能有数据变动(比如添、删、改操作)

其中 Query 和Criteria的list() 就可利用到查询缓存了。 
//三级的缓存
    @Test
    public void get5_3() {
        Session session = sf.getCurrentSession();
        session.beginTransaction();
        session.createQuery("select id,name from Category").setCacheable(true).list();
        session.createQuery("select id,name from Category").setCacheable(true).list();
        session.getTransaction().commit();
    }

三级缓存的另外一个问题:如果不在实体上设置托管cache(@chache),三级缓存的表现情况

    @Test
    public void get5_3() {
        Session session = sf.getCurrentSession();
        session.beginTransaction();
        session.createQuery("select t from Topic t").setCacheable(true).list();
        /* * 检测是否在一级缓存放 * 打印的有结果,而且没有向数据库发送sql语句,说明会向一级缓存放一份 * 因为没有在实体上设置二级缓存,虽然是对实体的查询,但是二级缓存中没有放一份 * 开通了query缓存,那么会根据查询条件生成一个id,但是不保存结果集,而是保存实体的id集合 */
        Topic t = (Topic) session.get(Topic.class, 1);
        System.out.println(t.getTitle());
        session.getTransaction().commit();


        /* * 查询过程 * 1、首先查询一级缓存 没有 * 2、因为没有在Topic上设置@cache,那么查找二级缓存的时候还是没有 * 3、然后在三级缓存中根据查询条件查找到了那个id,但是由于没有保存结果集,所以会根据查询的实体的id集合来查找具体信息,这样就会N+1问题 */
        System.out.println("------------------------");
        Session session2 = sf.getCurrentSession();
        session2.beginTransaction();
        session2.createQuery("select t from Topic t").setCacheable(true).list();
        session2.getTransaction().commit();

    }
@Test
    public void get5_4() {
        Session session = sf.getCurrentSession();
        session.beginTransaction();
        session.createQuery("select t.id,t.title,t.createDate from Topic t").setCacheable(true).list();
        /* * 检测是否在一级缓存放 * 打印的有结果,而且没有向数据库发送sql语句,说明会向一级缓存放一份 * 因为没有在实体上设置二级缓存,但是查询的不是完整实体,而是一些字段属性,二级缓存中没有放一份 * 开通了query缓存,那么会根据查询条件生成一个id,而且查询的是一些字段属性,也会保存结果集 */
        System.out.println("------------------------");
        Topic t = (Topic) session.get(Topic.class, 1);
        System.out.println(t.getTitle());
        session.getTransaction().commit();


        /* * 查询过程 * 1、首先查询一级缓存 没有 * 2、因为没有在Topic上设置@cache,那么查找二级缓存的时候还是没有 * 3、然后在三级缓存中根据查询条件查找到了那个id,因为查询的是字段属性,三级缓存里面有,不会向数据库发送sql * * */
        System.out.println("------------------------");
        Session session2 = sf.getCurrentSession();
        session2.beginTransaction();
        List<Object[]> obj = session2.createQuery("select t.id,t.title,t.createDate,t.category from Topic t").setCacheable(true).list();
        for(int i=0;i<obj.size();i++){
            System.out.println(obj.get(0)[0]+" "+obj.get(0)[1]);
        }
        session2.getTransaction().commit();

    }

总结:如果没有在实体上写上@chache(虽然开了二级缓存,但是没有交给hibernate托管某个实体),然后也开了三级缓存
**如果查询的是一个完整的这个实体对象,那么三级缓存保存只是实体的id集合,等下次在执行同样的查询的时候,会在三级缓存里面查到到有,但是只有id的集合,所以会发出N条语句 根据id来查询具体的信息(就像test5_3)。这样也会产生1+N问题所以利用三级缓存查找这个实体,要让二级缓存托管这个实体(或许如果让hibernate托管这个实体,就是把结果集放到二级缓存中,然后单击缓存调用二级缓存里面的结果?)
如果只是查询几个字段属性,那么三级缓存会保存整个结果集,下次在执行同样的查询的时候,就不会发出sql语句,而是去三级缓存里面查找(就像test5_4)。**

下面我们这种介绍把二级缓存和Hibernate查询缓存结合使用。
当只是用Hibernate查询缓存而关闭二级缓存的时候:
第一:如果查询的是部分属性结果集: 那么当第二次查询的时候就不会发出SQL,直接从Hibernate查询缓存中取数据;
第二:如果查询的是实体结果集eg(from Student) ,首先Hibernate查询缓存存放实体的ID,第二次查询的时候就到Hibernate查询缓存中取出ID 一条一条的到数据库查询,这样,将发出N 条SQL造成了SQL泛滥。

当都开启Hibernate查询缓存和二级缓存的时候:
第一:如果查询的是部分属性结果集: 这个和上面只是用Hibernate查询缓存而关闭 二级缓存的时候一致,因为不涉及实体不会用到二级缓存;
第二:如果查询的是实体结果集eg(from Student),首先Hibernate查询缓存存放实体的ID,第二次查询的时候,就到Hibernate查询缓存中取出ID,到二级缓存区找数据,如果有数据,就不会发出SQL;如果都有,一条SQL都不会发出,直接从二级缓存中取数据。

总结: 查询缓存的key与HQL,查询参数以及分布参数有关,而且一旦查询涉及到的任何一张表的数据发生了变化,缓存就失效了,所以在生产环境中命中率较低。查询缓存保存的是结果集的id列表,而不是结果集本身 ,命中缓存的时候,会根据id一个一个地先从二级缓存查找 ,找不到再查询数据库

总结:(引用网络上前辈的话)
不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。
在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。

参考前辈的链接

另外一个
这里写链接内容

你可能感兴趣的:(Hibernate,缓存,性能优化,三级缓存)