Hibernate Cache 一篇透

/ JAVA WEB / 没有评论 / 2610浏览

        在我们刚接触Hibernate时我们几乎都忽略缓存的存在;因为DEMO的数据都是寥寥无几的几条,加了缓存查询速度的提升也是微乎其微。在实际项目中我们遇到了一些特殊情况;数据库和WEB应用不在一台机器,Oracle数据库多用户多实例还有些周期性的数据用Shell脚本或者ETL工具定时处理。这种情况下数据库的性能基本上被压榨差不多了;尤其是在批量处理数据时高频率的IO读写情况下,我们查询数据库的延迟会很大;这个时候如果用上Cache,那么对系统性能的提升将是非常显著的。所以几乎所有的高并发、高负载的系统都离不开缓存。

         随着我们使用Hibernate的深入会陆续接触到缓存,诸如一级缓存、二级缓存,后来又接触到查询缓存、@Cache注释或XML文件配置、再后来又接触到Spring-Cache;在没有经过系统梳理前,这些离散的知识点会非常混乱;处理不好就会发生查询到脏数据的情况。所以我希望通过这篇博客来让JAVA开发人员对Cache这个概念能门清,能精准的掌控整个缓存的生命周期,让它为系统添彩还不是添麻烦。

         下面引入今天的主题Hibernate缓存,它包含:一级缓存、二级缓存、查询缓存。

         Hibernate一级缓存

               一级缓存(又称Session缓存)和session的生命周期紧密相连;他是由Hibernate默认开启且无法关闭的缓存机制。session开启时缓存生命周期开启;session关闭(session.close())时生命周期结束。根据hibernate的日志我们可以看到hibernate会把对象按照包名.类名#ID的形式缓存例如:info.huzd.bean.User#1。我们通过下面的例子来验证:

         本篇博客例子中需要使用到的JAVA BEAN:

//实体类,注意这里我们没有做任何缓存的配置

@Entity

public class User {

@Id

@GeneratedValue

private long id;

@Column(unique=true)

private String username;

private int age;

            ... get set

}


<!-- Hibernate 配置文件;我们这里不做任何缓存的配置。 -->

<hibernate-configuration>

    <session-factory>

        <!-- mysql数据库驱动 -->

        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

        <!-- mysql数据库名称 -->

        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>

        <!-- 数据库的登陆用户名 -->

        <property name="hibernate.connection.username">root</property>

        <!-- 数据库的登陆密码 -->

        <property name="hibernate.connection.password">123456</property>

        <property name="format_sql">true</property>

        <property name="show_sql">true</property>

        <property name="hbm2ddl.auto">update</property>

        <property name="connection.autocommit">true</property>

        <!-- 方言:为每一种数据库提供适配器,方便转换 -->

        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

        <mapping class="info.huzd.hibernate.bean.User"/>

    </session-factory>

</hibernate-configuration>


        @Test

public void testGet(){

SessionFactory sessionFactory = SessionFactoryUtil.getSessionFactory();

System.out.println("--");

Session session = sessionFactory.openSession();

User user = (User) session.get(User.class,2L);

System.out.println("--username:"+user.getUsername());

System.out.println("========================");

User user2 = (User) session.get(User.class,2L);

System.out.println("--username:"+user2.getUsername());

session.close();

                //如果这里的代码稍加改造;在查询出第一个User时就关闭session,再次开启查询第二次User你会发现日志结果和之前截然不同,会发出两个SQL语句;

}


运行上面的代码日志如下:

--

Hibernate: 

    select

        user0_.id as id0_0_,

        user0_.age as age0_0_,

        user0_.username as username0_0_ 

    from

        User user0_ 

    where

        user0_.id=?

--username:admin

========================

--username:admin

  我们可以清楚的看到只发出了一条查询语句;第二次查询User时因为还在同一个session中;所以直接从一级缓存中取info.huzd.bean.User#2缓存对象。但是仅仅局限在同一个人session中使用缓存这是远远不够的;于是我们就需要用二级缓存。

     Hibernate 二级缓存

     二级缓存(SessionFactory级别缓存)它和sessionFactory的生命周期紧密相关,用户可以决定二级缓存的开启和关闭。Hibernate只提供了二级缓存的接口;具体事项需要第三方的缓存框架来实现,Hibernate也提供了默认的基于HashMap的缓存方案;开启二级缓存我们需要做如下配置JAVA BEAN&Hibernate配置文件:

@Entity

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

public class User {

@Id

@GeneratedValue

private long id;

@Column(unique=true)

private String username;

private int age;

        ... get set ...

}

 

在JAVA实体类中添加@Cache注释或者在XML中添加 <cache usage="read-write" />标签,标签的位置必须放在<Id>的前面;如果这样做感觉比较分散,可以在Hibernate配置文件中做统一配置,Hibernate.cfg.xml配置文件(或者Spring集成时配置sessionFactory Bean)如下:

<hibernate-configuration>

    <session-factory>

        <!-- mysql数据库驱动 -->

        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>

        <!-- mysql数据库名称 -->

        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate</property>

        <!-- 数据库的登陆用户名 -->

        <property name="hibernate.connection.username">root</property>

        <!-- 数据库的登陆密码 -->

        <property name="hibernate.connection.password">123456</property>


<property name="hibernate.cache.use_second_level_cache">true</property><!-- 开启二级缓存 -->

        <!-- hibernate4以前的配置方法,4以后属性名称为 hibernate.cache.region.factory_class -->

        <property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property> 

        <property name="format_sql">true</property>

        <property name="show_sql">true</property>

        <property name="hbm2ddl.auto">update</property>

        <property name="connection.autocommit">true</property>

        <!-- 方言:为每一种数据库提供适配器,方便转换 -->

        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property

        <mapping class="info.huzd.hibernate.bean.User"/>

        <!-- <class-cache usage="read-only" class="info.huzd.hibernate.bean.User"/> 这里和在JAVA bean中使用@Cache效果一样;注释相对分散,这里可以集中配置。 -->

    </session-factory>


     

我们通过如下的类做测试:

@Test

public void testGetWithTwoSession(){

SessionFactory sessionFactory = SessionFactoryUtil.getSessionFactory();

System.out.println("--");

Session session = sessionFactory.openSession();

session.getTransaction().begin();

User user = (User) session.get(User.class,2L);

System.out.println("--username:"+user.getUsername());

session.getTransaction().commit();

session.close();

System.out.println("========================");

session = sessionFactory.openSession();

User user2 = (User) session.get(User.class,2L);

System.out.println("--username:"+user2.getUsername());

session.close();

}

打印日志如下:

--

Hibernate:     select

        user0_.id as id0_0_,

        user0_.age as age0_0_,

        user0_.username as username0_0_ 

    from

        User user0_ 

    where

        user0_.id=?

--username:admin

========================

--username:admin

我们可以发现,虽然不在同一个session中;由于开启了二级缓存;hibernate同样只进行了一次数据库查询。

        事情到这里本应该就结束了;但是Hibernate的二级缓存是只针对于对象的查询才会缓存;这其中包括get()、load()、list() ,但是hql二级缓存也是忽略不管的;如果你只查询了对象的部分属性例如:

                //二级缓存不会缓存username
                String username1 = (String)session.createQuery("select u.username from User u where u.id = 2").uniqueResult();

System.out.println("-- username1 :"+username1);


                //二级缓存会缓存User对象
User user1 = (User)session.createQuery("select u from User u where u.id = 2"). uniqueResult();

System.out.println("-- username1 :"+ user1.getUsername());

这样的查询因为只查询了User对象中的一个属性;那么二级缓存是不管不问的。于是Hibernate就提供了查询缓存。

    Hibernate查询缓存

    查询缓存(Query Cache)也是sessionFactory级别的缓存,开启查询缓存我们需要做如下配置,在hibernate.cfg.xml中添加如下配置:

<property name="hibernate.cache.use_query_cache">true</property>

于此同时还需要在程序中显示的开启:

query.setCacheable(true); //我基于Hibernate3中是需要在代码中显示开启的;否则只配置hibernate.cache.use_query_cache 是不生效的。

我们通过如下的例子来测试效果:

@SuppressWarnings("unchecked")

@Test

public void testQueryCache(){

SessionFactory sessionFactory = SessionFactoryUtil.getSessionFactory();

Session session = sessionFactory.openSession();

Query query = session.createQuery("select s.username from User s");

query.setCacheable(true);

List<String> usernames = query.list();

for(String temp:usernames){

System.out.println("~~"+temp+"~~");

}

System.out.println("================================"); 

Query query_2 = session.createQuery("select s.username from User s");

query_2.setCacheable(true);

List<String> usernames_2 = query_2.list();

for(String temp:usernames_2){

System.out.println("~~"+temp+"~~");

}

session.close();

}

在测试过程中我发现一个有趣的问题Hibernate3.6下;如果关闭二级缓存那么查询缓存也是生效的;查询缓存可以理解和二级缓存无直接关系;但是查询缓存也依赖hibernate.cache.provider_class这个属性。换句话说不配置或者关闭二级缓存;只配置provider_class和开启查询缓存;查询缓存依然是生效的。

        

       到此Hibernate所有和缓存相关的东西基本上都说完了。大家可以通过例子逐一的去体会;开篇我提到了Spring Cache其实他和Hibernate缓存是相对独立的;我在使用它们两时是同时使用;有些地方是交替互补。Spring Cache可以让我们更灵活的配置和实现缓存,我们可以自己决定缓存以什么样的方式存储;这样可以针对我们的业务逻辑定制。其实我们在使用Spring Cache时是可以忽略Hibernate缓存的,这句话不太严格但是如果你知道他们各自的作用和原理时就可以选择性的忽略。

       文末提供了一个网址我认为针对Hibernate这块讲的也非常好;而且例子比较全;博主也是花了2个小时写的;大家可以参考;按照文中的demo来体会Hibernate Cache的神奇。




参考资料:

https://www.cnblogs.com/xiaoluo501395377/p/3377604.html