目标
1、集成ehcache
2、集成redis
Mybatis集成ehcache
为什么需要缓存
拉高程序的性能
什么样的数据需要缓存
很少被修改或根本不改的数据
业务场景比如:耗时较高的统计分析sql、电话账单查询sql等
ehcache是什么
Ehcache 是现在最流行的纯Java开源缓存框架,配置简单、结构清晰、功能强大
注1:本章介绍的是2.X版本,3.x的版本和2.x的版本API差异比较大
ehcache的特点
4.1 够快
Ehcache的发行有一段时长了,经过几年的努力和不计其数的性能测试,Ehcache终被设计于large, high concurrency systems.
4.2 够简单
开发者提供的接口非常简单明了,从Ehcache的搭建到运用运行仅仅需要的是你宝贵的几分钟。其实很多开发者都不知道自己用在用Ehcache,Ehcache被广泛的运用于其他的开源项目
4.3 够袖珍
关于这点的特性,官方给了一个很可爱的名字small foot print ,一般Ehcache的发布版本不会到2M,V 2.2.3 才 668KB。
4.4 够轻量
核心程序仅仅依赖slf4j这一个包,没有之一!
4.5 好扩展
Ehcache提供了对大数据的内存和硬盘的存储,最近版本允许多实例、保存对象高灵活性、提供LRU、LFU、FIFO淘汰算法,基础属性支持热配置、支持的插件多
4.6 监听器
缓存管理器监听器 (CacheManagerListener)和 缓存监听器(CacheEvenListener),做一些统计或数据一致性广播挺好用的
4.7 分布式缓存
从Ehcache 1.2开始,支持高性能的分布式缓存,兼具灵活性和扩展性
ehcache的使用
3.1 导入相关依赖
3.2 核心接口
CacheManager:缓存管理器
Cache:缓存对象,缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口
Element:单条缓存数据的组成单位
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-ehcacheartifactId>
<version>1.1.0version>
dependency>
<dependency>
<groupId>net.sf.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>2.10.0version>
dependency>
4.2 修改日志配置,因为ehcache使用了Slf4j作为日志输出日志我们使用slf4j,并用log4j来实现。SLF4J不同于其他日志类库,与其它有很大的不同。SLF4J(Simple logging Facade for Java)不是一个真正的日志实现,而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志类库。
<log4j2.version>2.9.1log4j2.version>
<log4j2.disruptor.version>3.2.0log4j2.disruptor.version>
<slf4j.version>1.7.13slf4j.version>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>${slf4j.version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>${log4j2.version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>${log4j2.version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j-implartifactId>
<version>${log4j2.version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-webartifactId>
<version>${log4j2.version}version>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.lmaxgroupId>
<artifactId>disruptorartifactId>
<version>${log4j2.disruptor.version}version>
dependency>
4.3 在Resource中添加一个ehcache.xml的配置文件
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir"/>
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
ehcache>
4.4 在applicationContext.xml中加入chache配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"/>
<property name="shared" value="true">property>
bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="cacheManagerFactory"/>
bean>
beans>
4.5 mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存
4.5.1 开启mybatis的二级缓存
applicationContext-mybatis.xml中添加 :
<property name="configurationProperties">
<props>
<prop key="cacheEnabled">trueprop>
<prop key="lazyLoadingEnabled">falseprop>
<prop key="aggressiveLazyLoading">trueprop>
props>
property>
4.5.2 在XxxMapper.xml中配置cache
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
<property name="timeToIdleSeconds" value="3600"/>
<property name="timeToLiveSeconds" value="3600"/>
<property name="maxEntriesLocalHeap" value="1000"/>
<property name="maxEntriesLocalDisk" value="10000000"/>
<property name="memoryStoreEvictionPolicy" value="LRU"/>
cache>
4.5.3 可以通过select标签的useCache属性打开或关闭二级缓存
注意有三:
1、mybatis默认使用的二级缓存框架就是ehcache(org.mybatis.caches.ehcache.EhcacheCache),无缝结合
2、Mybatis缓存开关一旦开启,可缓存单条记录,也可缓存多条,hibernate不能缓存多条。
3、Mapper接口上的所有方法上另外提供关闭缓存的属性
Mybatis集成redis
1.2 JedisPoolConfig
Redis连接池
1.3 ShardedJedis
基于一致性哈希算法实现的分布式Redis集群客户端
实现 mybatis 的二级缓存,一般来说有如下两种方式:
1) 采用 mybatis 内置的 cache 机制。
2) 采用三方 cache 框架, 比如ehcache, oscache 等等.
<redis.version>2.9.0redis.version>
<redis.spring.version>1.7.1.RELEASEredis.spring.version>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>${redis.version}version>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>${redis.spring.version}version>
dependency>
版本冲突问题:
1. ClassNotFoundException : redis/client/util/geoUtils 说这个类找不到。
2. org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘redisTemplate’ defined in class path resource [applicationContext.xml]
说创建 redisTemplate bean 对象时失败了。
redis.client 2.9.0 ---- spring-data-redis 1.7.1.RELEASE
redis.client 2.9.0 ---- spring-data-redis 1.7.2.RELEASE 这两个是可以使用的
2.2 log4j2配置
2.3 jackson
<jackson.version>2.9.3jackson.version>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>${jackson.version}version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>${jackson.version}version>
dependency>
3.spring + redis 集成实现缓存功能(与mybatis无关)
3.1 添加两个redis的配置文件,并将redis.properties和applicationContext-redis.xml配置到applicationContext.xml文件中
redis.properties
applicationContext-redis.xml
注1:将redis.properties导入到applicationContext.xml文件中
spring中引入第二个属性文件会出现“找不到某个配置项”错误,这是因为spring只允许有一个
注2:通过import标签将applicationContext-redis.xml导入到applicationContext.xml文件中
redis.properties
redis.hostName=192.168.56.128
redis.port=6379
redis.password=123456
redis.timeout=10000
redis.maxIdle=300
redis.maxTotal=1000
redis.maxWaitMillis=1000
redis.minEvictableIdleTimeMillis=300000
redis.numTestsPerEvictionRun=1024
redis.timeBetweenEvictionRunsMillis=30000
redis.testOnBorrow=true
redis.testWhileIdle=true
applicationContext-redis.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxTotal" value="${redis.maxTotal}"/>
<property name="maxWaitMillis" value="${redis.maxWaitMillis}"/>
<property name="minEvictableIdleTimeMillis" value="${redis.minEvictableIdleTimeMillis}"/>
<property name="numTestsPerEvictionRun" value="${redis.numTestsPerEvictionRun}"/>
<property name="timeBetweenEvictionRunsMillis" value="${redis.timeBetweenEvictionRunsMillis}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
<property name="testWhileIdle" value="${redis.testWhileIdle}"/>
bean>
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
destroy-method="destroy">
<property name="poolConfig" ref="poolConfig"/>
<property name="hostName" value="${redis.hostName}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.password}"/>
<property name="timeout" value="${redis.timeout}"/>
bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"/>
property>
<property name="enableTransactionSupport" value="true"/>
bean>
<bean id="redisCacheTransfer" class="com.ly.util.RedisCacheTransfer">
<property name="redisTemplate" ref="redisTemplate"/>
bean>
beans>
applicationContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:property-placeholder location="classpath:jdbc.properties,classpath:redis.properties"/>
<import resource="applicationContext-mybatis.xml">import>
<import resource="applicationContext-redis.xml">import>
beans>
3.2 将redis缓存引入到mybatis中
3.1 创建mybatis的自定义缓存类“RedisCache”,必须实现org.apache.ibatis.cache.Cache接口
RedisCache.java
package com.ly.util;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache //实现类
{
private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);
private static RedisTemplate<String,Object> redisTemplate;
private final String id;
/**
* The {@code ReadWriteLock}.
*/
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
@Override
public ReadWriteLock getReadWriteLock()
{
return this.readWriteLock;
}
public static void setRedisTemplate(RedisTemplate redisTemplate) {
RedisCache.redisTemplate = redisTemplate;
}
public RedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
logger.debug("MybatisRedisCache:id=" + id);
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
try{
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>putObject: key="+key+",value="+value);
if(null!=value)
redisTemplate.opsForValue().set(key.toString(),value,60, TimeUnit.SECONDS);
}catch (Exception e){
e.printStackTrace();
logger.error("redis保存数据异常!");
}
}
@Override
public Object getObject(Object key) {
try{
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>getObject: key="+key);
if(null!=key)
return redisTemplate.opsForValue().get(key.toString());
}catch (Exception e){
e.printStackTrace();
logger.error("redis获取数据异常!");
}
return null;
}
@Override
public Object removeObject(Object key) {
try{
if(null!=key)
return redisTemplate.expire(key.toString(),1,TimeUnit.DAYS);
}catch (Exception e){
e.printStackTrace();
logger.error("redis获取数据异常!");
}
return null;
}
@Override
public void clear() {
Long size=redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection redisConnection) throws DataAccessException {
Long size = redisConnection.dbSize();
//连接清除数据
redisConnection.flushDb();
redisConnection.flushAll();
return size;
}
});
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>clear: 清除了" + size + "个对象");
}
@Override
public int getSize() {
Long size = redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection)
throws DataAccessException {
return connection.dbSize();
}
});
return size.intValue();
}
}
3.2 静态注入中间类“RedisCacheTransfer”,解决RedisCache中RedisTemplate的静态注入,从而使MyBatis实现第三方缓存
RedisCacheTransfer.java
package com.ly.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
public class RedisCacheTransfer {
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
RedisCache.setRedisTemplate(redisTemplate);
}
}
3.3 spring与mybatis整合文件中开发二级缓存
<property name="configurationProperties">
<props>
<prop key="cacheEnabled">trueprop>
<prop key="lazyLoadingEnabled">falseprop>
<prop key="aggressiveLazyLoading">trueprop>
props>
property>
3.4 在XxxMapper.xml中添加自定义cache功能
<cache type="com.ly.util.RedisCache"
eviction="LRU"
flushInterval="6000000"
size="1024"
readOnly="false">cache>
type:指定redis的二级缓存类的全路径名
eviction:二级缓存算法(FIFO/LRU/SOFT/WEAK)
flushInterval:刷新间隔,间隔多长时间清空缓存,被动触发非定时器轮询
size:缓存大小。每个缓存可以存储 1024 个列表或对象的引用
readOnly:缓存将作为“读/写”缓存,意味着获取的对象不是共享的且对调用者是安全的。不会有其它的调用者或线程潜在修改。
注意: pom对properties文件的加载!!!
资料:
MyBatis内置的二级缓存算法
Mybatis的所有Cache算法都是基于装饰器模式对PerpetualCache扩展增加功能。
1)
FIFO:先入先出,基于LinkedList实现;
2)LRU:最近最少使用,基于LinkedHashMap实现,在put的时候,自动移除最少使用缓存对象;
3)
SOFT:对Cache的value进行SoftReference包装;当缓存对象是Soft reference可达时,gc会向系统申请更多内存,而不是直接回收它,当内存不足的时候才回收它;
4)WEAK:对Cache的value进行WeakReference包装;WeakReference不会强制对象保存在内存中。它拥有比较短暂的生命周期,允许你使用垃圾回收器的能力去权衡一个对象的可达性。在垃圾回收器扫描它所管辖的内存区域过程中,一旦gc发现对象是weakReference可达,就会把它放到ReferenceQueue中,等下次gc时回收它。
测试结果:
package com.ly.service.impl;
import com.ly.SpringBaseTest;
import com.ly.model.Student;
import com.ly.service.StuService;
import com.ly.util.StringUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
/**
* @author 毅哥哥
* @site
* @company
* @create 2019-09-24 21:29
*/
public class StuServiceImplTest extends SpringBaseTest {
@Autowired
private StuService stuService;
@Test
public void insert() {
student.setSname("宜章仔");
student.setSex("女");
for (int i = 0; i < 100; i++) {
student.setSname("宜章仔"+i);
student.setSex("女");
this.stuService.insert(student);
}
}
@Test
public void selectByPrimaryKey() {
Student student = this.stuService.selectByPrimaryKey(1);
System.out.println(student);
}
@Test
public void aaa() {
Map map = new HashMap();
map.put("sname", StringUtils.toLikeStr("aa"));
List<Map> aaa = this.stuService.aaa(map, pageBean);
for (Map m : aaa) {
System.out.println(m);
}
}
@Test
public void listPager() {
Map map = new HashMap();
map.put("sname", StringUtils.toLikeStr("宜章"));
pageBean.setPage(3);
List<Map> aaa = this.stuService.listPager(map, pageBean);
for (Map m : aaa) {
System.out.println(m);
}
}
@Test
public void cacheSimgle() {
Student student = this.stuService.selectByPrimaryKey(1);
System.out.println(student);
Student student2 = this.stuService.selectByPrimaryKey(1);
System.out.println(student2);
}
@Test
public void cacheMany() {
Map map = new HashMap();
map.put("sname", StringUtils.toLikeStr("宜章"));
pageBean.setPage(3);
List<Map> aaa = this.stuService.listPager(map, pageBean);
for (Map m : aaa) {
System.out.println(m);
}
List<Map> aaa2 = this.stuService.listPager(map, pageBean);
for (Map m : aaa2) {
System.out.println(m);
}
}
}