什么是ehcache呢?
ehcache是一个非常轻量级的缓存实现,而且从1.2之后就支持了集群,而且是hibernate默认的缓存provider。EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
如何使用ehcache缓存技术呢?
在使用ehcache缓存技术的时候,首先要对它进行相应的导包和配置:
导入jar包:
<!-- 引入ehcache二级缓存相关JAR包 -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>${ehcache.version}</version>
</dependency>
配置chcache.xml(ehcache配置文件,主要是定义缓存空间及缓存地址等)
<ehcache>
<!-- 定义缓存数据存放的位置:java.io.tmpdir (默认为内存空间),如果要修改位置:/home/lihan/ d:/ehcache -->
<diskStore path="java.io.tmpdir"/>
<!-- 定义默认的缓存配置 -->
<cache
name="users"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
memoryStoreEvictionPolicy="LRU"
/>
<!-- maxElementsInMemory="10000" 定义缓存中,可以存放数据的数据量 -->
<!-- eternal="false" 设置存放的元素是否是"常量" -->
<!-- timeToIdleSeconds="120" 设置元素在缓存空间中,不使用的情况下,默认的存活时间,单位是:秒 -->
<!-- timeToLiveSeconds="120" 设置元素在缓存空间中,如果发生调用,需要重置的存活时间(只重置1次),单位是:秒 -->
<!-- overflowToDisk="true" 当内存不足存储10000多个对象时,是否启动硬盘存储 -->
<!-- memoryStoreEvictionPolicy="LRU" 设置缓存清理策略:LRU 最近最少使用 LFU 最少使用 FIFO 先进先出 -->
<cache name="roles"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
向applicationContext.xml中注册(也就是spring的配置文件)
<!-- 声明cacheManager -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"
p:cache-manager-ref="ehcache" />
<!-- cacheManager工厂类,指定ehcache.xml的位置 -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
p:config-location="classpath:ehcache.xml" />
<!-- 开启spring的注解缓存支持 -->
<cache:annotation-driven />
具体的配置参见:https://blog.csdn.net/IT_CREATE/article/details/86592388
如何在代码中使用呢?
首先该缓存技术用于方法之上,一般用于业务层service,在进行方法操作的时候对返回结果进行数据的缓存,看下面代码:
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import com.gezhi.ssh.bean.PageBean;
import com.gezhi.ssh.bean.UserBean;
import com.gezhi.ssh.usermag.dao.IUserDao;
import com.gezhi.ssh.usermag.service.IUserService;
/**
* @Cacheable 是spring框架封装的缓存注解,它的作用:就是缓存数据,
* 当使用的该注解的方法,在第一次被调用时,将该方法执行的结果,以K-V键值对的结构
* 缓存到对应的缓存区间去,key默认情况下,是传入该方法的参数以及参数的组合。
* 但是也可以自定义,key的自定义方式采用"#参数名"或者"#p参数的下标(第一个下标为0)"来完成对键的设置
*
* 方法在后续的调用过程中,将会优先从缓存区间中,以参数作为key去查找数据,如果查到了,就直接从缓存区间中返回,那么方法体就不需要执行了,如果没找到,才执行方法体,将执行的结果再丢入到缓存区间去
*
* condition 这个属性,可以用来设置缓存条件,如:"#id %2 == 0" ,表示缓存中只缓存ID是偶数的数据
*
*
* unless("除了") 该属性,可以用来设置缓存条件,如:"#result=null",表示如果返回结果是null,则不需要缓存
*
*
*
* 该注解通常定义在查询方法上
*
*/
@Service
public class UserServiceImpl implements IUserService {
@Resource
private IUserDao userDaoImpl;
/**
* 假如有人问你:当数据库数据刷新以后,怎么去做缓存同步?
*
* 1、我在做刷新时,就将缓存所有都清理了,让别人再同步一次(最low)
*
* 2、我在刷新时,重新获取一次缓存数据,并做修改,然后在向缓存中再加一次 (AOP)
*
* 3、我采用异步手段,在向数据库保存的同时,让其他的线程|程序去刷新缓存
*/
/**
* @CachePut 主要是将数据向缓存中,去更新
*
* 将传入的参数来作为键,将方法的返回来作为值,进行存储
*/
// @CachePut(value= {"users"},key="#user.id")
@Override
public UserBean saveUserBean(UserBean user) {
// TODO Auto-generated method stub
userDaoImpl.saveUserBean(user);
return user;
}
// @CachePut(value= {"users"},key="#user.id")
@Override
public UserBean updateUserBean(UserBean user) {
// TODO Auto-generated method stub
userDaoImpl.updateUserBean(user);
return user;
}
/**
* @CacheEvict 缓存的清理注解,allEntries=false 是否一次清理所有的缓存对象
* beforeInvocation=false 在什么时候去清理缓存(方法执行之前?还是之后)
* key="#user.id" 清理缓存的键
*/
@CacheEvict(value= {"users"}, allEntries=false,beforeInvocation=false,key="#user.id")
@Override
public void deleteUserBean(UserBean user) {
// TODO Auto-generated method stub
userDaoImpl.deleteUserBean(user);
}
@Cacheable(value= {"users"},key="#id")
@Override
public UserBean getUserBeanById(int id) {
// TODO Auto-generated method stub
return userDaoImpl.getUserBeanById(id);
}
@Override
public UserBean loadUserBeanById(int id) {
// TODO Auto-generated method stub
return userDaoImpl.loadUserBeanById(id);
}
@Override
public Map<String, Object> queryUserBeanById(Integer id) {
// TODO Auto-generated method stub
return userDaoImpl.queryUserBeanById(id);
}
@Override
public List<?> findUserBeanMapByObject(UserBean user) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanMapByObject(user);
}
@Override
public UserBean findUserBeanByLoginNameAndPwd(String loginName, String pwd) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanByLoginNameAndPwd(loginName, pwd);
}
@Override
public List<?> findUserBeanByObject(UserBean user) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanByObject(user);
}
@Override
public List<?> findUserBeanByMap(Map map) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanByMap(map);
}
@Override
public PageBean findUserBeanList2PageBean(PageBean page, UserBean user) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanList2PageBean(page, user);
}
@Override
public List<?> findUserBeanByIdcardAndAddress(String idcard, String address) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanByIdcardAndAddress(idcard,address);
}
}
@Cacheable :(key值不能写死,不然永远从缓存中拿出来的是同一个数据)
用于查询数据比较合适,因为只要缓存有数据,做查询的话就可以去缓存拿数据,而不用去执行查询方法了。
是spring框架封装的缓存注解,它的作用:就是缓存数据,主要这三个属性,value、key和condition。
value:指定你用的哪个缓存空间,就是你在chcache.xml配置文件中自己定义的缓存缓存空间
key:指的是你作为缓存的键,因为缓存的内容是k-v键值对的形式,存到缓存空间的数据就是你方法的返回对象,通过设置的key值去找到这个返回的对象。key中的 # 表示的是取值,比如key="#user.id" 表示取传入方法中的user对象的id作为键值
condition:指定发生的条件,如:"#id %2 == 0" ,表示缓存中只缓存ID是偶数的数据
使用@Cacheable标注的方法,Spring在每次执行前都会检查Cache中是否存在相同key的缓存元素,如果存在就不再执行该方法,而是直接从缓存中获取结果进行返回,否则才会执行并将返回结果存入指定的缓存中。
@CachePut(key值不能写死,不然永远修改缓存区中同一个键的数据)
主要是将数据存于缓存中,用于更新数据和新增数据比较合适
属性和@Cacheable一样
@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
说的直白一点就是,这个注解指定的方法每次执行它都会去进行缓存操作,这样做的好处就是在修改了数据库信息之后,需要将缓存中的数据也进行相应的修改。
在使用这个注解的时候要注意,如果修改操作同查询操作或新增操作这两个方法的返回结果都是同一个类型的对象,则它的key值必须和其他两个方法的key值一样,准确的说三个方法都应该保持一致。为什么要这样呢,因为当我们做了查询之后,那么就会有相应的缓存了,那么在执行查询相同数据的时候,就不会再去底层拿数据了,而是通过键直接拿缓存的数据。这时你做了修改,把数据修改了,那么再次执行查询时就拿不到最新的修改了的数据,所以说它的键值一定要和其他两个的键值保持一致。这样做的好处是,三个方法的键值保持了一致,那么我在每次修改数据后,都会对缓存区中相同键值的数据进行修改。
总结一句话:不管是新增、修改、查询,只要它们的返回类型一样,那么它们的key值都应该保持一致,为的是做修改时刷新缓存区;返回类型不同的,那么它们的key一定不能一样,如果键值相同,就会出现结果覆盖,拿到的结果永远不对。
比如你要获取一个对象,而你永远只能得到字符串,因为拥有相同key值的另一个方法返回的是字符串,它已经将改key值的数据存到了缓存中,你去拿缓存拿数据就只能拿到那个数据了。
什么是key一致呢,举个例子:都以 id 值为键,那么就是一致;如果用其他杂七杂八的算法弄出来的就是键值不一致。自己领悟,这只是我的观点,具体看自己。
@CacheEvict
缓存的清理注解,用在做删除操作时
allEntries=false 是否一次清理所有的缓存对象
beforeInvocation=false 在什么时候去清理缓存(方法执行之前?还是之后)false表示在之后
key、value意思和@Cacheable一样
之前说了相同返回类型的方法key值都应该一致,我们把这些相同返回的分成一组,那么每一组都应该有个删除方法,key值和它们一致,这样做了删除之后,才能清除key值相同的数据。
使用自定义缓存生成键策略
首先需要自定义自己的生成键策略(也就是自定义的键生成器):
如下面这个代码,就会生成不同的键:(它是根据当前的对象和方法名和传入参数经过一系列算法生成独一无二的键值)
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;
/**
* 缓存 键 生成器
* @author Administrator
*
*/
@Component
public class CacheKeyGenerator implements KeyGenerator,Serializable{
/**
*
*/
private static final long serialVersionUID = -6560716503034938613L;
@Override
public Object generate(Object target, Method method, Object... params) {
return new CustomKey(target.getClass(), method.getName(), params);
}
/**
* Like {@link org.springframework.cache.interceptor.SimpleKey} but considers the method.
*/
static final class CustomKey implements Serializable {
/**
*
*/
private static final long serialVersionUID = -1643380720996899548L;
private final Class<?> clazz;
private final String methodName;
private final Object[] params;
private final int hashCode;
/**
* Initialize a key.
*
* @param clazz the receiver class
* @param methodName the method name
* @param params the method parameters
*/
CustomKey(Class<?> clazz, String methodName, Object[] params) {
this.clazz = clazz;
this.methodName = methodName;
this.params = params;
int code = Arrays.deepHashCode(params);
code = 31 * code + clazz.hashCode();
code = 31 * code + methodName.hashCode();
this.hashCode = code;
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CustomKey)) {
return false;
}
CustomKey other = (CustomKey) obj;
if (this.hashCode != other.hashCode) {
return false;
}
return this.clazz.equals(other.clazz)
&& this.methodName.equals(other.methodName)
&& Arrays.deepEquals(this.params, other.params);
}
}
}
在向applicatinContext.xml注册(也就是spring配置文件,就是在之前我们配置的开启注解支持中用 key-generator 引用)
<!-- 开启spring的注解缓存支持 -->
<cache:annotation-driven key-generator="CacheKeyGenerator"/>
<bean id="CacheKeyGenerator" class="com.xxx.cache.CacheKeyGenerator"/>
最后是在方法上使用这个:
@Cacheable(keyGenerator="cacheKeyGenerator",value="users")
public UserBean findUserBeanByLoginNameAndPwd(String loginName, String pwd) {
// TODO Auto-generated method stub
return userDaoImpl.findUserBeanByLoginNameAndPwd(loginName, pwd);
}
ehcache +mybatis+spring 自定义缓存策略:https://blog.csdn.net/u013378306/article/details/52168628
springboot 缓存技术自定义key生成简单记录:https://blog.csdn.net/qq_32771571/article/details/80040861
spring整合ehcache 注解实现查询缓存,并实现实时缓存更新或删除:https://www.cnblogs.com/jianjianyang/p/4938765.html