采用redis作为独立缓存服务器(二)_spring Cache应用
2018-02-04 19:05
721 查看
请注意:该博文的环境架构和我前面的博文【http://blog.csdn.net/wx5040257/article/details/79226742】相同,不再详述!
前面提到过这个问题:如果我们的商品信息访问量特别大,那我们就把它们放到缓存里面去,如果我们更新了一个商品信息,肯定不希望把缓存里面的商品信息都给删了,而是希望在缓存中替换该商品信息。
再比方说:有一个缓存存放 list<User>,现在你执行了一个 update(user)的方法,你一定不希望清除整个缓存而想在缓存中替换掉update的元素。
要达到这个要求,要么你就自己写缓存架构,要么可以使用spring cache。当然没有人想自己写一个缓存程序,那样太难了,于是你肯定喜欢spring cache!!!
一 spring cache知识点
1. 启用Cache注解
在spring核心配置文件中加入
然后在需要用到缓存的类中加入如下注解
@EnableCaching
使用@EnableCaching启用Cache注解支持
2. @CachePut
@CachePut用法
应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存:
@CachePut源代码:
3. @CacheEvict
即应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据:
4. @Cacheable
应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中:
5. Key生成器
如果在Cache注解上没有指定key的话@CachePut(value = "user"),会使用KeyGenerator进行生成一个key:
我们也可以自定义自己的key生成器,然后通过xml风格的<cache:annotation-driven key-generator=""/>或注解风格的CachingConfigurer中指定keyGenerator。
6. 条件缓存
@Cacheable(value = "user", key = "#id", condition = "#id lt 10")
public User conditionFindById(final Long id)
根据运行流程,如下@CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,如果返回true,则放入缓存;
@CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")
public User conditionSave(final User user)
根据运行流程,如下@CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,如果返回false,则放入缓存;(即跟condition相反)
@CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'")
public User conditionSave2(final User user)
根据运行流程,如下@CacheEvict, beforeInvocation=false表示在方法执行之后调用(#result能拿到返回值了);且判断condition,如果返回true,则移除缓存;
@CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'")
public User conditionDelete(final User user)
7. @Caching
有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id-->user;username--->user;email--->user的缓存;此时就需要@Caching组合多个注解标签了。
如用户新增成功后,添加id-->user;username--->user;email--->user到缓存;
8 SpEL上下文数据
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
二 具体应用
这里我们以对用户表的增删改查为例来说明spring cache的具体应用。
关键代码
=======================redis.properties==============================
#*****************jedis连接参数设置*********************
#redis服务器ip
redis.ip=169.254.130.122
#redis服务器端口号
redis.port=6379
#redis访问密码
redis.passWord=test123
#与服务器建立连接的超时时间
redis.timeout=3000
#************************jedis池参数设置*******************
#jedis的最大活跃连接数
jedis.pool.maxActive=50
#jedis最大空闲连接数
jedis.pool.maxIdle=10
#jedis最小空闲连接数
jedis.pool.minIdle=5
#jedis池没有连接对象返回时,等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。
#如果超过等待时间,则直接抛出JedisConnectionException
jedis.pool.maxWait=1500
#从池中获取连接的时候,是否进行有效检查
jedis.pool.testOnBorrow=true
#归还连接的时候,是否进行有效检查
jedis.pool.testOnReturn=false
=========================CacheSeriUtil=======================
package com.wx.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
//序列化及反序列化
public class CacheSeriUtil {
public static byte[] toByteArray(Object obj) {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
oos.close();
bos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return bytes;
}
public static Object toObject(byte[] bytes) {
Object obj = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
obj = ois.readObject();
ois.close();
bis.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return obj;
}
}
springCache实现类
====================SpringRedisCache========================
package com.wx.utils;
import java.util.*;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
public class SpringRedisCache implements Cache {
private JedisConnectionFactory jedisConnectionFactory;
private String name;
public void setJedisConnectionFactory(
JedisConnectionFactory jedisConnectionFactory) {
this.jedisConnectionFactory = jedisConnectionFactory;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public Object getNativeCache() {
return null;
}
public ValueWrapper get(Object key) {
System.out.println("get key from cache************:"+key.toString());
final String keyf = key.toString();
RedisConnection conn=null;
Object object = null;
try {
conn=jedisConnectionFactory.getConnection();
byte[] bkey = keyf.getBytes();
byte[] value = conn.get(bkey);
if (value == null) {
System.out.println("缓存中木有,要查数据库了");
object= null;
}else{
object=CacheSeriUtil.toObject(value);
System.out.println("恭喜,从缓存中找到了:"+object.toString());
}
} catch (Exception e) {
object=null;
e.printStackTrace();
}finally{
if(conn!=null){
conn.close();
}
}
return (object!=null) ? new SimpleValueWrapper(object) : null;
}
/**
* 添加操作,如果是save方法,则添加该对象的缓存的同时,把它添加到其它list缓存中去
* @param key 形式"[className]@[methodName]@[tableName].id"
*/
public void put(Object key, Object value) {
String srcKey = key.toString();
final long liveTime = 86400; //默认缓存一天
RedisConnection conn=null;
try {
conn=jedisConnectionFactory.getConnection();
if(srcKey.indexOf('@')==-1){ //非add方法的键值对添加
byte[] keyb = srcKey.getBytes();
byte[] valueb = CacheSeriUtil.toByteArray(value);
conn.set(keyb, valueb);
if (liveTime > 0) {
conn.expire(keyb, liveTime);
}
实体类
==========================UserEntity===========================
用到缓存逻辑的类
========================UserBiz=============================
package com.wx.biz;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;
import com.wx.dao.user.IUserDao;
import com.wx.entitys.UserEntity;
@Service("userBiz")
@EnableCaching
public class UserBiz {
@Autowired
private IUserDao userDao;
public boolean isLogin(UserEntity user){
return userDao.isLogin(user);
}
@CachePut(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName",unless="#result == null")
public UserEntity save(UserEntity user){
return userDao.add(user);
}
@Cacheable(value="redis_cache",key="#root.targetClass.getName()+'.user.'+#userId")
public UserEntity getById(String userId){
return userDao.getById(userId);
}
public boolean isUserExist(String userName){
return userDao.isUserExist(userName);
}
@Cacheable(value="redis_cache", key="#root.targetClass.getName()+'.list.'+#root.methodName")
public List<UserEntity> queryMany(UserEntity user){
return userDao.queryMany(user);
}
@CacheEvict(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName+'@user.'+#id")
public Integer deleteById(String id) {
return userDao.deleteById(id);
}
@Caching(
evict = {
@CacheEvict(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName+'@user.'+#user.userId")
},
put={
@CachePut(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName")
}
)
public UserEntity modify(UserEntity user) {
return userDao.modify(user);
}
}
spring核心配置文件
=============================applicationContext.xml========================
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- 扫描包含注解类所在的包 -->
<context:component-scan base-package="com.wx.dao.*"></context:component-scan>
<context:component-scan base-package="com.wx.biz"></context:component-scan>
<bean id="config" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:configs/druidConfig.properties</value>
<value>classpath:configs/redis.properties</value>
</list>
</property>
</bean>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="filters" value="${filters}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="minIdle" value="${minIdle}" />
<property name="maxWait" value="${maxWait}" />
<property name="validationQuery" value="${validationQuery}" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${maxPoolPreparedStatementPerConnectionSize}" />
<property name="removeAbandoned" value="${removeAbandoned}" />
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="druidDataSource"></property>
<property name="configLocation">
<value>classpath:configs/mybatis-config.xml</value>
</property>
<property name="mapperLocations">
<list>
<value>classpath:configs/mappers/*.xml</value>
</list>
</property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sessionFactory"></constructor-arg>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<tx:advice id="txAdvise" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="do*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.wx.biz.*.*(..))" id="myCut"/>
<aop:advisor advice-ref="txAdvise" pointcut-ref="myCut"/>
</aop:config>
<!-- 配置redis 单机版 -->
<!-- redis数据源 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲数 -->
<property name="maxIdle" value="${jedis.pool.maxIdle}" />
<!-- 最大活跃数 -->
<property name="maxTotal" value="${jedis.pool.maxActive}" />
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="${jedis.pool.maxWait}" />
<!-- 返回连接时,检测连接是否成功 -->
<property name="testOnBorrow" value="${jedis.pool.testOnBorrow}" />
</bean>
<!-- Spring-redis连接池管理工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!-- IP地址 -->
<property name="hostName" value="${redis.ip}" />
<!-- 端口号 -->
<property name="port" value="${redis.port}" />
<!-- 密码 -->
<property name="password" value="${redis.passWord}"/>
<!-- 超时时间 -->
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<!-- redis缓存配置结束 -->
<!-- spring自己的缓存管理器,这里定义了两个缓存位置名称 ,既注解中的value -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!-- 集成第三方缓存配置 -->
<bean class="com.wx.utils.SpringRedisCache">
<property name="jedisConnectionFactory" ref="jedisConnectionFactory" />
<property name="name" value="redis_cache"/>
</bean>
</set>
</property>
</bean>
<!-- 开启Spring缓存 -->
<cache:annotation-driven cache-manager="cacheManager"/>
</beans>第一次访问用户管理界面:
会先从数据库查询,然后把查出来的结果添加进缓存
从redis desktop manager 上看也可以佐证
然后多次刷新当前页面,发现不会查询数据库了,而是从缓存里取数据,速度特别快
再来看添加数据,添加数据是从注册界面添加用户,然后回到查询界面,这里先要经过添加的数据库操作,然后再把添加的那个用户对象放到
缓存键【com.wx.biz.UserBiz.list.queryMany】中,回到查询界面就不需要从数据库查了,直接从缓存取数据
再来看修改,修改的话,要更新【com.wx.biz.UserBiz.list.queryMany】list里面的数据,而不是清空缓存。
这里采取的策略是先删除list里面的要修改的数据,然后把修改完了的数据放回list
看界面展示,修改成功了
最后来看删除
删除的逻辑是删除list里面的数据,也就是键【com.wx.biz.UserBiz.list.queryMany】中的数据,而不是清除整个缓存,然后回到用户管理首页
看界面,已经删除了
这样子,我们就实现了缓存的精确控制!!!
但是该代码比较难,所有的增删改都要单独写缓存注解,对开发人员要求高,一不小心就会出错!!!
其实我们可以采用切面编程来统一处理增删改的缓存策略!
请看下一篇博文!!!!
如果帮到了您,请点个赞吧!!!!!
前面提到过这个问题:如果我们的商品信息访问量特别大,那我们就把它们放到缓存里面去,如果我们更新了一个商品信息,肯定不希望把缓存里面的商品信息都给删了,而是希望在缓存中替换该商品信息。
再比方说:有一个缓存存放 list<User>,现在你执行了一个 update(user)的方法,你一定不希望清除整个缓存而想在缓存中替换掉update的元素。
要达到这个要求,要么你就自己写缓存架构,要么可以使用spring cache。当然没有人想自己写一个缓存程序,那样太难了,于是你肯定喜欢spring cache!!!
一 spring cache知识点
1. 启用Cache注解
在spring核心配置文件中加入
<cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true"/>另外还可以指定一个 key-generator,即默认的key生成策略 。
然后在需要用到缓存的类中加入如下注解
@EnableCaching
使用@EnableCaching启用Cache注解支持
2. @CachePut
@CachePut用法
应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存:
@CachePut(value = "user", key = "#user.id") public User save(User user) { users.add(user); return user; }即调用该方法时,会把user.id作为key,返回值作为value放入缓存;
@CachePut源代码:
public @interface CachePut { String[] value(); //缓存的名字,可以把数据写到多个缓存 String key() default ""; //缓存key,如果不指定将使用默认的KeyGenerator生成,后边介绍 String condition() default ""; //满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断 String unless() default ""; //用于否决缓存更新的,不像condition,该表达只在方法执行之后判断,此时可以拿到返回值result进行判断了 }
3. @CacheEvict
即应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据:
@CacheEvict(value = "user", key = "#user.id") //移除指定key的数据 public User delete(User user) { users.remove(user); return user; } @CacheEvict(value = "user", allEntries = true) //移除所有数据 public void deleteAll() { users.clear(); }@CacheEvict源代码:
public @interface CacheEvict { String[] value(); //请参考@CachePut String key() default ""; //请参考@CachePut String condition() default ""; //请参考@CachePut boolean allEntries() default false; //是否移除所有数据 boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除
4. @Cacheable
应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中:
@Cacheable(value = "user", key = "#id") public User findById(final Long id) { System.out.println("cache miss, invoke find by id, id:" + id); for (User user : users) { if (user.getId().equals(id)) { return user; } } return null; }@Cacheable源代码:
public @interface Cacheable { String[] value(); //请参考@CachePut String key() default ""; //请参考@CachePut String condition() default "";//请参考@CachePut String unless() default ""; //请参考@CachePut
5. Key生成器
如果在Cache注解上没有指定key的话@CachePut(value = "user"),会使用KeyGenerator进行生成一个key:
public interface KeyGenerator { Object generate(Object target, Method method, Object... params); }默认提供了DefaultKeyGenerator生成器(Spring 4之后使用SimpleKeyGenerator):
@Override public Object generate(Object target, Method method, Object... params) { if (params.length == 0) { return SimpleKey.EMPTY; } if (params.length == 1 && params[0] != null) { return params[0]; } return new SimpleKey(params); }即如果只有一个参数,就使用参数作为key,否则使用SimpleKey作为key。
我们也可以自定义自己的key生成器,然后通过xml风格的<cache:annotation-driven key-generator=""/>或注解风格的CachingConfigurer中指定keyGenerator。
6. 条件缓存
@Cacheable(value = "user", key = "#id", condition = "#id lt 10")
public User conditionFindById(final Long id)
根据运行流程,如下@CachePut将在执行完方法后(#result就能拿到返回值了)判断condition,如果返回true,则放入缓存;
@CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")
public User conditionSave(final User user)
根据运行流程,如下@CachePut将在执行完方法后(#result就能拿到返回值了)判断unless,如果返回false,则放入缓存;(即跟condition相反)
@CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'")
public User conditionSave2(final User user)
根据运行流程,如下@CacheEvict, beforeInvocation=false表示在方法执行之后调用(#result能拿到返回值了);且判断condition,如果返回true,则移除缓存;
@CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'")
public User conditionDelete(final User user)
7. @Caching
有时候我们可能组合多个Cache注解使用;比如用户新增成功后,我们要添加id-->user;username--->user;email--->user的缓存;此时就需要@Caching组合多个注解标签了。
如用户新增成功后,添加id-->user;username--->user;email--->user到缓存;
@Caching( put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.email") } ) public User save(User user) { }@Caching定义如下:
public @interface Caching { Cacheable[] cacheable() default {}; //声明多个@Cacheable CachePut[] put() default {}; //声明多个@CachePut CacheEvict[] evict() default {}; //声明多个@CacheEvict }
8 SpEL上下文数据
Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:
名字 | 位置 | 示例 | 描述 |
methodName | root对象 | #root.methodName | 当前被调用的方法名 |
method | root对象 | #root.method.name | 当前被调用的方法 |
target | root对象 | #root.target | 当前被调用的目标对象 |
targetClass | root对象 | #root.targetClass | 当前被调用的目标对象类 |
args | root对象 | #root.args[0] | 当前被调用的方法的参数列表 |
caches | root对象 | #root.caches[0].name | 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache |
argument name | 执行上下文 | #user.id | 当前被调用的方法的参数,如findById(Long id),我们可以通过#id拿到参数 |
result | 执行上下文 | #result | 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,'cache evict'的beforeInvocation=false) |
这里我们以对用户表的增删改查为例来说明spring cache的具体应用。
关键代码
=======================redis.properties==============================
#*****************jedis连接参数设置*********************
#redis服务器ip
redis.ip=169.254.130.122
#redis服务器端口号
redis.port=6379
#redis访问密码
redis.passWord=test123
#与服务器建立连接的超时时间
redis.timeout=3000
#************************jedis池参数设置*******************
#jedis的最大活跃连接数
jedis.pool.maxActive=50
#jedis最大空闲连接数
jedis.pool.maxIdle=10
#jedis最小空闲连接数
jedis.pool.minIdle=5
#jedis池没有连接对象返回时,等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。
#如果超过等待时间,则直接抛出JedisConnectionException
jedis.pool.maxWait=1500
#从池中获取连接的时候,是否进行有效检查
jedis.pool.testOnBorrow=true
#归还连接的时候,是否进行有效检查
jedis.pool.testOnReturn=false
=========================CacheSeriUtil=======================
package com.wx.utils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
//序列化及反序列化
public class CacheSeriUtil {
public static byte[] toByteArray(Object obj) {
byte[] bytes = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
oos.flush();
bytes = bos.toByteArray();
oos.close();
bos.close();
} catch (IOException ex) {
ex.printStackTrace();
}
return bytes;
}
public static Object toObject(byte[] bytes) {
Object obj = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
obj = ois.readObject();
ois.close();
bis.close();
} catch (IOException ex) {
ex.printStackTrace();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
return obj;
}
}
springCache实现类
====================SpringRedisCache========================
package com.wx.utils;
import java.util.*;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
public class SpringRedisCache implements Cache {
private JedisConnectionFactory jedisConnectionFactory;
private String name;
public void setJedisConnectionFactory(
JedisConnectionFactory jedisConnectionFactory) {
this.jedisConnectionFactory = jedisConnectionFactory;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public Object getNativeCache() {
return null;
}
public ValueWrapper get(Object key) {
System.out.println("get key from cache************:"+key.toString());
final String keyf = key.toString();
RedisConnection conn=null;
Object object = null;
try {
conn=jedisConnectionFactory.getConnection();
byte[] bkey = keyf.getBytes();
byte[] value = conn.get(bkey);
if (value == null) {
System.out.println("缓存中木有,要查数据库了");
object= null;
}else{
object=CacheSeriUtil.toObject(value);
System.out.println("恭喜,从缓存中找到了:"+object.toString());
}
} catch (Exception e) {
object=null;
e.printStackTrace();
}finally{
if(conn!=null){
conn.close();
}
}
return (object!=null) ? new SimpleValueWrapper(object) : null;
}
/**
* 添加操作,如果是save方法,则添加该对象的缓存的同时,把它添加到其它list缓存中去
* @param key 形式"[className]@[methodName]@[tableName].id"
*/
public void put(Object key, Object value) {
String srcKey = key.toString();
final long liveTime = 86400; //默认缓存一天
RedisConnection conn=null;
try {
conn=jedisConnectionFactory.getConnection();
if(srcKey.indexOf('@')==-1){ //非add方法的键值对添加
byte[] keyb = srcKey.getBytes();
byte[] valueb = CacheSeriUtil.toByteArray(value);
conn.set(keyb, valueb);
if (liveTime > 0) {
conn.expire(keyb, liveTime);
}
System.out.println("***键:"+srcKey+"*********添加进了缓存"); }else{//add方法的键值对添加 String objStr=value.toString(); String targetName=srcKey.substring(0, srcKey.indexOf('@')); String methName=srcKey.substring(srcKey.indexOf('@')+1); String theId=objStr.substring(0,objStr.indexOf('@'));//table.primarykey的形式 System.out.println(targetName+"=="+methName+"=="+theId); String keyf = targetName+"."+theId; System.out.println("****put key:" + keyf + "===value:" + value); byte[] keyb = keyf.getBytes(); byte[] valueb = CacheSeriUtil.toByteArray(value); conn.set(keyb, valueb); if (liveTime > 0) { conn.expire(keyb, liveTime); } //往其它list缓存里添加数据 //其它list缓存中删除该对象 String toAdd=targetName+".list.*"; Set<byte[]> keys= conn.keys(toAdd.getBytes()); Iterator<byte[]> it=keys.iterator() ; while(it.hasNext()){ byte[] onelistKey = it.next(); byte[] oneListValue=conn.get(onelistKey); List<Object> theList=(List<Object>)CacheSeriUtil.toObject(oneListValue); theList.add(value); //重新放回缓存中 oneListValue=CacheSeriUtil.toByteArray(theList); conn.set(onelistKey, oneListValue); if (liveTime > 0) { conn.expire(onelistKey, liveTime); } System.out.println("***put后***"+new String(onelistKey)+"****list缓存中的数据更新了*********"); } } } catch (Exception e1) { e1.printStackTrace(); }finally{ if(conn!=null){ conn.close(); } } } /** * 删除操作,如果只是根据id删除一个对象,则把其它缓存里面的该对象也删除 * 如果是删除很多对象(例如是根据性别删),则把该类下所有缓存全部清除 * @param key 形式"[className]@[methodName]@[tableName].id" */ public void evict(Object key) { String srcKey = key.toString(); String methName=srcKey.substring(srcKey.indexOf('@')+1,srcKey.lastIndexOf('@')); RedisConnection conn=null; String targetName=srcKey.substring(0, srcKey.indexOf('@')); String theId=srcKey.substring(srcKey.lastIndexOf('@')+1);//user.userId的形式 final long liveTime = 86400; //默认缓存一天 String toDel=""; try { conn=jedisConnectionFactory.getConnection(); //如果只是删除了一个对象或者更新了一个对象,则删除所有list缓存中的该对象 if(methName.equals("deleteById")||methName.startsWith("modify")||methName.startsWith("update")){ toDel=targetName+"."+theId; conn.del(toDel.getBytes()); System.out.println("*********del_key:"+toDel+"=============="); //其它list缓存中删除该对象 toDel=targetName+".list.*"; Set<byte[]> keys= conn.keys(toDel.getBytes()); Iterator<byte[]> it=keys.iterator() ; while(it.hasNext()){ byte[] onelistKey = it.next(); byte[] oneListValue=conn.get(onelistKey); List<Object> theList=(List<Object>)CacheSeriUtil.toObject(oneListValue); for(Object theObj : theList){ if(theObj.toString().startsWith(theId)){ theList.remove(theObj); //找到了,list里面的对象被去掉了 break; } } //重新放回缓存中 oneListValue=CacheSeriUtil.toByteArray(theList); conn.set(onelistKey, oneListValue); if (liveTime > 0) { conn.expire(onelistKey, liveTime); } System.out.println("***del后***"+new String(onelistKey)+"****list缓存中的数据更新了*********"); } }else{ //如果删除了很多对象,则目标类下所有的缓存全部删除 toDel=targetName+"*"; Set<byte[]> keys= conn.keys(toDel.getBytes()); Iterator<byte[]> it=keys.iterator() ; while(it.hasNext()){ byte[] onekey = it.next(); conn.del(onekey); System.out.println("***del key:"+new String(onekey)); } } } catch (Exception e) { e.printStackTrace(); }finally{ if(conn!=null){ conn.close(); } } } public void clear() { System.out.println("clear key"); RedisConnection conn=null; try { conn=jedisConnectionFactory.getConnection(); conn.flushDb(); } catch (Exception e) { e.printStackTrace(); }finally{ if(conn!=null){ conn.close(); } } } }
实体类
==========================UserEntity===========================
package com.wx.entitys; import java.io.Serializable; public class UserEntity implements Serializable { private static final long serialVersionUID = 1034088741557780953L; private Integer userId; private String userName; private String passWord; private String email; public UserEntity() { } public UserEntity(String userName, String passWord) { super(); this.userName = userName; this.passWord = passWord; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassWord() { return passWord; } public void setPassWord(String passWord) { this.passWord = passWord; } public void setEmail(String email) { this.email = email; } public String getEmail() { return email; } //缓存的删除逻辑中会依据[表名].[id]的形式查找对象,这里是自定的一个规范 public String toString() { return "user."+this.userId+"@"+this.userName; } }
用到缓存逻辑的类
========================UserBiz=============================
package com.wx.biz;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.stereotype.Service;
import com.wx.dao.user.IUserDao;
import com.wx.entitys.UserEntity;
@Service("userBiz")
@EnableCaching
public class UserBiz {
@Autowired
private IUserDao userDao;
public boolean isLogin(UserEntity user){
return userDao.isLogin(user);
}
@CachePut(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName",unless="#result == null")
public UserEntity save(UserEntity user){
return userDao.add(user);
}
@Cacheable(value="redis_cache",key="#root.targetClass.getName()+'.user.'+#userId")
public UserEntity getById(String userId){
return userDao.getById(userId);
}
public boolean isUserExist(String userName){
return userDao.isUserExist(userName);
}
@Cacheable(value="redis_cache", key="#root.targetClass.getName()+'.list.'+#root.methodName")
public List<UserEntity> queryMany(UserEntity user){
return userDao.queryMany(user);
}
@CacheEvict(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName+'@user.'+#id")
public Integer deleteById(String id) {
return userDao.deleteById(id);
}
@Caching(
evict = {
@CacheEvict(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName+'@user.'+#user.userId")
},
put={
@CachePut(value="redis_cache",key="#root.targetClass.getName()+'@'+#root.methodName")
}
)
public UserEntity modify(UserEntity user) {
return userDao.modify(user);
}
}
spring核心配置文件
=============================applicationContext.xml========================
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- 扫描包含注解类所在的包 -->
<context:component-scan base-package="com.wx.dao.*"></context:component-scan>
<context:component-scan base-package="com.wx.biz"></context:component-scan>
<bean id="config" class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:configs/druidConfig.properties</value>
<value>classpath:configs/redis.properties</value>
</list>
</property>
</bean>
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driverClassName}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
<property name="filters" value="${filters}" />
<property name="initialSize" value="${initialSize}" />
<property name="maxActive" value="${maxActive}" />
<property name="minIdle" value="${minIdle}" />
<property name="maxWait" value="${maxWait}" />
<property name="validationQuery" value="${validationQuery}" />
<property name="testWhileIdle" value="${testWhileIdle}" />
<property name="testOnBorrow" value="${testOnBorrow}" />
<property name="testOnReturn" value="${testOnReturn}" />
<property name="maxPoolPreparedStatementPerConnectionSize" value="${maxPoolPreparedStatementPerConnectionSize}" />
<property name="removeAbandoned" value="${removeAbandoned}" />
<property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
<property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
<property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
</bean>
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="druidDataSource"></property>
<property name="configLocation">
<value>classpath:configs/mybatis-config.xml</value>
</property>
<property name="mapperLocations">
<list>
<value>classpath:configs/mappers/*.xml</value>
</list>
</property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sessionFactory"></constructor-arg>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="druidDataSource"></property>
</bean>
<tx:advice id="txAdvise" transaction-manager="txManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="search*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="do*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="del*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution(* com.wx.biz.*.*(..))" id="myCut"/>
<aop:advisor advice-ref="txAdvise" pointcut-ref="myCut"/>
</aop:config>
<!-- 配置redis 单机版 -->
<!-- redis数据源 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲数 -->
<property name="maxIdle" value="${jedis.pool.maxIdle}" />
<!-- 最大活跃数 -->
<property name="maxTotal" value="${jedis.pool.maxActive}" />
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="${jedis.pool.maxWait}" />
<!-- 返回连接时,检测连接是否成功 -->
<property name="testOnBorrow" value="${jedis.pool.testOnBorrow}" />
</bean>
<!-- Spring-redis连接池管理工厂 -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<!-- IP地址 -->
<property name="hostName" value="${redis.ip}" />
<!-- 端口号 -->
<property name="port" value="${redis.port}" />
<!-- 密码 -->
<property name="password" value="${redis.passWord}"/>
<!-- 超时时间 -->
<property name="timeout" value="${redis.timeout}" />
<property name="poolConfig" ref="poolConfig" />
</bean>
<!-- redis缓存配置结束 -->
<!-- spring自己的缓存管理器,这里定义了两个缓存位置名称 ,既注解中的value -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!-- 集成第三方缓存配置 -->
<bean class="com.wx.utils.SpringRedisCache">
<property name="jedisConnectionFactory" ref="jedisConnectionFactory" />
<property name="name" value="redis_cache"/>
</bean>
</set>
</property>
</bean>
<!-- 开启Spring缓存 -->
<cache:annotation-driven cache-manager="cacheManager"/>
</beans>第一次访问用户管理界面:
会先从数据库查询,然后把查出来的结果添加进缓存
从redis desktop manager 上看也可以佐证
然后多次刷新当前页面,发现不会查询数据库了,而是从缓存里取数据,速度特别快
再来看添加数据,添加数据是从注册界面添加用户,然后回到查询界面,这里先要经过添加的数据库操作,然后再把添加的那个用户对象放到
缓存键【com.wx.biz.UserBiz.list.queryMany】中,回到查询界面就不需要从数据库查了,直接从缓存取数据
再来看修改,修改的话,要更新【com.wx.biz.UserBiz.list.queryMany】list里面的数据,而不是清空缓存。
这里采取的策略是先删除list里面的要修改的数据,然后把修改完了的数据放回list
看界面展示,修改成功了
最后来看删除
删除的逻辑是删除list里面的数据,也就是键【com.wx.biz.UserBiz.list.queryMany】中的数据,而不是清除整个缓存,然后回到用户管理首页
看界面,已经删除了
这样子,我们就实现了缓存的精确控制!!!
但是该代码比较难,所有的增删改都要单独写缓存注解,对开发人员要求高,一不小心就会出错!!!
其实我们可以采用切面编程来统一处理增删改的缓存策略!
请看下一篇博文!!!!
如果帮到了您,请点个赞吧!!!!!
相关文章推荐
- 采用redis作为独立缓存服务器(三)_spring Cache后传之aop优化
- 采用redis作为独立缓存服务器(一)
- redis作为mysql的缓存服务器(读写分离)
- redis作为mysql的缓存服务器(读写分离)
- redis作为mysql的缓存服务器(读写分离) 推荐
- 001-Springboot从数据库读取配置文件并启动Redis作为缓存服务器
- Redis作为缓存服务器
- 用Redis作为缓存服务器,加快数据库操作速度
- redis作为mysql的缓存服务器(读写分离)
- redis作为mysql的缓存服务器(读写分离) (转)
- redis作为mysql的缓存服务器(读写分离)
- redis作为mysql的缓存服务器(读写分离) (转)
- redis作为mysql的缓存服务器(读写分离,通过mysql触发器实现数据同步)
- redis作为缓存服务器
- redis作为mysql的缓存服务器(读写分离)
- springboot 整合dubbo最佳实践 (使用redis作为注册中心)
- Kafka,Mq,Redis作为消息队列使用时的差异?
- 在Apache Tomcat 7设置redis作为session store
- 初学Redis(3)——用Redis作为Mysql数据库的缓存
- spring,springmvc,mybatis整合redis,redis作为缓存使用