Hibernate之二级缓存

Hibernate二级缓存详解与实战
本文详细介绍了Hibernate的一级和二级缓存机制,重点讨论了二级缓存的作用、应用场景和配置。通过示例展示了如何配置和使用Ehcache作为二级缓存,以及在实际操作中如何优化缓存策略以提高系统性能。

hibernate缓存

Hibernate提供了一级缓存和二级缓存,合理的利用缓存可以有助于提高系统的性能,为了避免不合理的利用缓存导致内存过度消耗降低系统性能,可以通过合理配置缓存的参数来避免这个问题。

缓存的目的是为了通过减少应用程序对物理数据访问的次数来提高程序运行的效率,原理则是把当前或接下来一段时间有可能会用到的数据保存到内存中,在使用时直接从内存中读取,而不是从硬盘上读取,简单说,缓存就是数据库中的数据在内存中的“临时容器”。

一级缓存

Hibernate中的一级缓存由Session管理,二级缓存由SessionFactory来管理。在使用时,二级缓存是可有可无的,但一级缓存是必不可少的。

适用还环静
当使用Session查询数据时,首先会在Session内部查找该对象是否存在,若存在,则直接返回,否则,就到数据库中查询,并将查询到的结果缓存起来以便后期使用。一级缓存的缺点就是当使用Session来表示一次会话时,它的生命周期较短,而且它不是线程安全的,不能被多个线程共享,因此,在实际使用时,对效率的提升不是非常明显。

二级缓存

鉴于以上原因,引入二级缓存的概念。二级缓存用来为Hibernate配置一种全局的缓存,以便实现多个线程与实务共享。在使用了二级缓存机制后,当查询数据时,会首先在内存缓存中查找,如果不存在,接着在二级缓存中查找,最后才去数据库中查找。与一级缓存相比,二级缓存还是独立于Hibernate的软件部件,属于第三方的产品,常见的产品有EhCache、OSCache、JbossCache等。Hibernate3默认使用的产品是EhCache。在使用时,可以根据需求通过配置缓存插件实现二级缓存功能,Hibernate为了集成这些插件,提供了org.hibernate.cache.CacheProvider接口来充当缓存插件与Hibernate之间的适配器。当然,二级缓存除了以内存作为存储介质外,还可以选用硬盘等外部存储设备。

二级缓存出现的场景

数据量较小。(数据量太大会消耗大量内存,造成内存资源紧张,降低系统性能)

对数据的修改较少。(会造成频繁对缓存中的数据进行同步,影响系统的性能)

不会被大量的应用共享的数据。(数据被大量线程或事务共享,多线程访问的同步机制会影响系统性能)

不是很重要的数据。(如果要查询的数据对正确性要求较高,如财务,最好不要使用二级缓存)

为什么需要缓存

1.提高性能
2.加快查询速度
介绍一下数据库的类型:
关系型数据库:数据与数据之间存在关系(联系)的数据库 mysql/Oracle、sqlserver
非关系型数据库:数据与数据之间是不存在关系的,key-value
1、基于文件存储的数据库:ehcache
2、基于内存存储的数据库:redis、memcache
3、基于文档存储的数据库:mongodb
2. 什么样的数据需要缓存
很少被修改或根本不改的数据 数据字典
业务场景比如:耗时较高的统计分析sql、电话账单查询sql等

ehcache

Ehcache 是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大

注1:本章介绍的是2.X版本,3.x的版本和2.x的版本API差异比较大

ehcache的特点

1 够快
Ehcache的发行有一段时长了,经过几年的努力和不计其数的性能测试,Ehcache终被设计于large, high concurrency systems.

2 够简单
开发者提供的接口非常简单明了,从Ehcache的搭建到运用运行仅仅需要的是你宝贵的几分钟。其实很多开发者都不知道自己用在用Ehcache,Ehcache被广泛的运用于其他的开源项目

3 够袖珍
关于这点的特性,官方给了一个很可爱的名字small foot print ,一般Ehcache的发布版本不会到2M,V 2.2.3 才 668KB。

4 够轻量
核心程序仅仅依赖slf4j这一个包,没有之一!

5 好扩展
Ehcache提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多

6 监听器
缓存管理器监听器 (CacheManagerListener)和 缓存监听器(CacheEvenListener),做一些统计或数据一致性广播挺好用的

7 分布式缓存
从Ehcache 1.2开始,支持高性能的分布式缓存,兼具灵活性和扩展性

例子

配置文件:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.xyx</groupId>
	<artifactId>T224_Hibernate</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>T224_Hibernate Maven Webapp</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		
		<junit.version>4.12</junit.version>
		<servlet.version>4.0.0</servlet.version>
		<hibernate.version>5.2.12.Final</hibernate.version>
		<mysql.driver.version>5.1.46</mysql.driver.version>
		
		<ehcache.version>2.10.0</ehcache.version>
		<slf4j-api.version>1.7.7</slf4j-api.version>
		<log4j-api.version>2.9.1</log4j-api.version>
		
	</properties>

	<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>${junit.version}</version>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlet.version}</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>${hibernate.version}</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>${mysql.driver.version}</version>
		</dependency>
		
		
		<dependency>
			<groupId>net.sf.ehcache</groupId>
			<artifactId>ehcache</artifactId>
			<version>${ehcache.version}</version>
		</dependency>

		 <dependency>
	         <groupId>org.hibernate</groupId>
	         <artifactId>hibernate-ehcache</artifactId>
	        <version>5.2.12.Final</version>
	      </dependency>
			
		<!-- slf4j核心包 -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j-api.version}</version>
		</dependency>
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
			<version>${slf4j-api.version}</version>
			<scope>runtime</scope>
		</dependency>
			
		<!--核心log4j2jar包 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-api</artifactId>
			<version>${log4j-api.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>${log4j-api.version}</version>
		</dependency>
				
		<!--用于与slf4j保持桥接 -->
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-slf4j-impl</artifactId>
			<version>${log4j-api.version}</version>
		</dependency>
		
	</dependencies>
	<build>
		<finalName>T224_Hibernate</finalName>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.7.0</version>
				<configuration>
					<source>${maven.compiler.source}</source>
					<target>${maven.compiler.target}</target>
					<encoding>${project.build.sourceEncoding}</encoding>
				</configuration>
			</plugin>
		</plugins>
	</build>
</project>

ehcache.xml

ehcacher的核心配置文件。关于ehcache的配置都在这里面定义。如果没有这个配置文件那么ehcachej就会实用默认的配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
    <!--path:指定在硬盘上存储对象的路径-->
    <!--java.io.tmpdir 是默认的临时文件路径。 可以通过如下方式打印出具体的文件路径 System.out.println(System.getProperty("java.io.tmpdir"));-->
    <diskStore path="D://xxx"/>


    <!--defaultCache:默认的管理策略-->
    <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
    <!--maxElementsInMemory:在内存中缓存的element的最大数目-->
    <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
    <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
    <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
    <!--FIFO:first in first out (先进先出)-->
    <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
    <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>


    <!--name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)-->
    <!-- 取一个名字,指定。默认的有自己的配置 -->
    <cache name="com.hu.one.entity.User" eternal="false" maxElementsInMemory="100"
           overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0"
           timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU"/>
</ehcache>

hibernate.cfg.xml

 <!-- 开启二级缓存 -->
  <property name="hibernate.cache.use_second_level_cache">true</property>
 <!-- 开启查询缓存 -->
 <property name="hibernate.cache.use_query_cache">true</property>
 <!-- EhCache驱动 -->
 <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
	"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
	"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

	<hibernate-configuration>
		<session-factory>
		<!-- 1. 数据库相关 -->
		<property name="connection.username">root</property>
		<property name="connection.password">123</property>
		<property name="connection.url">jdbc:mysql://localhost:3306/t_clazz?useUnicode=true&amp;characterEncoding=UTF-8
		</property>
		<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
		<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

		<!-- 配置本地事务(No CurrentSessionContext configured!-->
		<property name="hibernate.current_session_context_class">thread</property>

		<!-- 2. 调试相关 -->
		<property name="show_sql">true</property><!-- 展示SQL -->
		<property name="format_sql">true</property><!-- 格式化SQL -->

		  <!-- 开启二级缓存 -->
      <property name="hibernate.cache.use_second_level_cache">true</property>
      <!-- 开启查询缓存 -->
      <property name="hibernate.cache.use_query_cache">true</property>
      <!-- EhCache驱动 -->
      <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
		
		<!-- 3. 添加实体映射文件 -->
		<mapping resource="com/hu/one/entity/User.hbm.xml"/>
		
		<!-- 4.讲解主键生成策略 -->
		<mapping resource="com/hu/two/entity/Student.hbm.xml"/>
		<mapping resource="com/hu/two/entity/Worker.hbm.xml"/>
		
		<!-- 一对多 -->
		<mapping resource="com/hu/three/entity/OrderItem.hbm.xml"/>
		<mapping resource="com/hu/three/entity/Order.hbm.xml"/>
		
		<!-- 一对多的自关联 -->
		<mapping resource="com/hu/four/entity/TreeNode.hbm.xml"/>
		
		<!-- 多对多的自关联 -->
		<mapping resource="com/hu/four/entity/Book.hbm.xml"/>
		<mapping resource="com/hu/four/entity/Category.hbm.xml"/>

	</session-factory>
	</hibernate-configuration>

实例

public class EhcacheUtil {

	
    private static CacheManager cacheManager;
    
    static {
    	
        try {
        	//读取配置文件
        	InputStream is = EhcacheUtil.class.getResourceAsStream("/ehcache.xml");
        	//创建管理对象
            cacheManager = CacheManager.create(is);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private EhcacheUtil() {
    }
    
    /**
     * @param cacheName 这是槽名
     * @param key 这是key
     * @param value 这是value
     */
    public static void put(String cacheName, Object key, Object value) {
    	//获取到槽位
        Cache cache = cacheManager.getCache(cacheName);
        //没有添加这个槽位就添加
        if (null == cache) {
            //以默认配置添加一个名叫cacheName的Cache
            cacheManager.addCache(cacheName);
            cache = cacheManager.getCache(cacheName);
        }
        //将数据加入到缓存中
        cache.put(new Element(key, value));
    }

    /**
     * 获取缓存数据
     * @param cacheName
     * @param key
     * @return
     */
    public static Object get(String cacheName, Object key) {
    	//获取槽为
        Cache cache = cacheManager.getCache(cacheName);
        //获取element对象
        Element element = cache.get(key);
        //获取key
        return null == element ? null : element.getValue();
    }
    
    /**
     * 这是删除指定槽为的key
     * @param cacheName 槽位名
     * @param key key
     */
    public static void remove(String cacheName, Object key) {
        Cache cache = cacheManager.getCache(cacheName);
        cache.remove(key);
    }
}


测试,将会在D:xxx 文件夹下加入两个临时文件

/**
 * 演示利用缓存存储数据
 * @author Administrator
 *
 */
public class EhcacheDemo2 {
	public static void main(String[] args) {
		System.out.println(System.getProperty("java.io.tmpdir"));
		EhcacheUtil.put("com.hu.one.entity.User", 11, "zhangsan");
		System.out.println(EhcacheUtil.get("com.hu.one.entity.User", 11));
	}
}

就这玩意
在这里插入图片描述

二级缓存实例

@Test
	public void test1() {
		//session内部缓存
		Session session = SessionFactoryUtils.openSession();
		Transaction transaction = session.beginTransaction();
		User user = session.get(User.class, 7);
		System.out.println(user);
		User user2 = session.get(User.class, 7);
		System.out.println(user2);
		User user3 = session.get(User.class, 7);
		System.out.println(user3);
		transaction.commit();
		session.close();
	}

上面代码会查几次数据库呢?答案是一次,不管你导入还是不导入ehcache都一样。上面就是演示一级缓存,一级缓存是session。session是事务范围的缓存。该缓存直接是Hibernate管理。
再看看下面这个例子:

public class UserDao {
	/**
	*根据id查询user数据
	*/
	public User get(User user) {
		//session内部缓存
		Session session = SessionFactoryUtils.openSession();
		Transaction transaction = session.beginTransaction();
		User u = session.get(User.class, user.getId());	
		transaction.commit();
		session.close();
		return u;
	}
	public static void main(String[] args) {
		UserDao userDao  = new UserDao();
		User u = new User();
		//查询id为7的
		u.setId(7);
		User user = userDao.get(u);
		System.out.println(user);
		User user2 = userDao.get(u);
		System.out.println(user2);
		User user3 = userDao.get(u);
		System.out.println(user3);
	}
}

如果我们不使用二级缓存上面代码会访问3次数据库。也就是说数据并没有被缓存下来。每次读取时都之间去访问了数据。这是以为上面三次查询并不是再一个session中进行的。更准确的说时不在同一个事务中进行。每次查看都是从新构造一个会话对象。这是如果我们适用新的session对象查询,是访问不到之前session缓存的数据。这里特别的注意,一级缓存是session级别的,session之间的缓存数据不能共享。需要适用二级缓存。二级缓存就是所以会话对象共享的缓存。SessionFactory级别的缓存。
ehcache.xml中添加如下配置
PS:在ehcache标签下添加

   <!--name: Cache的名称,必须是唯一的(ehcache会把这个cache放到HashMap里)-->
    <!-- 取一个名字,指定。默认的有自己的配置 -->
    <cache name="com.hu.one.entity.User" eternal="false" maxElementsInMemory="100"
           overflowToDisk="true" diskPersistent="true" timeToIdleSeconds="0"
           timeToLiveSeconds="300" memoryStoreEvictionPolicy="LRU"/>

指定缓存对象。再实体类对应的xml的class标签下添加:

<!-- 开启二级缓存 -->
<cache usage="read-write" region="com.hu.one.entity.User"/> 

UserDao

public User get(User user) {
		Session session = SessionFactoryUtils.openSession();
		Transaction transaction = session.beginTransaction();
		
		User u = session.get(User.class, user.getId());
		
		transaction.commit();
		session.close();
		return u;
	}

会执行三次,调用一次方法,创建一个session对象。

public static void main(String[] args) {
		UserDao userDao  = new UserDao();
		User u = new User();
		u.setId(7);
		User user = userDao.get(u);
		System.out.println(user);
		User user2 = userDao.get(u);
		System.out.println(user2);
		User user3 = userDao.get(u);
		System.out.println(user3);
		
	}

再次运行 UserDao 中的代码就会发现值范围了一次数据库啦。这就是ehcache的级别适用。不过还需要注意一个地方:

	public static void main(String[] args) {
		Session session = SessionFactoryUtils.openSession();
		Transaction transaction = session.beginTransaction();
		Query query = session.createQuery("from User");
		//开启多条记录缓存
		//query.setCacheable(true);
		List list = query.list();
		System.out.println(list);
		List list2 = query.list();
		System.out.println(list2);
		List list3 = query.list();
		System.out.println(list3);		
		transaction.commit();
		session.close();
	}

如果返回结果集有多条数据,那么ehcache默认是不会缓存数据的,以为缓存多条数据非常的耗费内存。如果需要缓存多条需要将注释中的query.setCacheable(true);解开即可

小编刚刚出道,有什么写的不对的地方就指出来,大家一起进步。有喜欢的就点亮一下旁边的小红心吧,给小编的一个支持。。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值