一.spring集成redis
1.引入jar包
jedis-2.9.0.jar
spring-data-redis-1.7.11.RELEASE.jar
2.redis配置文件
<!-- 记住要把配置的缓存管理器的id放进来 -->
<cache:annotation-driven cache-manager="cacheMenager"/>
<!-- scanner redis properties -->
<!--(1)如果你有多个数据源需要通过<context:property-placeholder管理,且不愿意放在一个配置文件里,那么一定要加上ignore-unresolvable=“true" -->
<!--(2)注意新版的(具体从哪个版本开始不清楚,有兴趣可以查一下)JedisPoolConfig的property name,不是maxActive而是maxTotal,而且没有maxWait属性,建议看一下Jedis源码。 -->
<!-- redis连接池 -->
<bean id="jedisConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大连接jedis数 -->
<property name="maxTotal" value="${redis.maxActive}"></property>
<!-- 最大空闲数 -->
<property name="maxIdle" value="${redis.maxIdle}"></property>
<!-- 最长等待时间 -->
<property name="maxWaitMillis" value="${redis.maxWait}"></property>
<!-- 获得一个jedis实例的时候是否检查连接可用性;如果为true,则得到的jedis实例均是可用的; -->
<property name="testOnBorrow" value="${redis.testOnBorrow}"></property>
</bean>
<!-- redis连接工厂 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"></property>
<property name="port" value="${redis.port}"></property>
<property name="password" value="${redis.password}"></property>
<property name="poolConfig" ref="jedisConfig"></property>
</bean>
<!-- redis操作模板,这里采用尽量面向对象的模板 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
<!-- 如果不配置Serializer,那么存储的时候只能使用String,如果用对象类型存储,那么会提示错误 can't cast to String!!! -->
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<!--开启事务 -->
<property name="enableTransactionSupport" value="true" />
</bean>
<!-- 缓存管理器 -->
<bean id="cacheMenager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="com.caodaxing.redis.cache.MyCache" p:name="selectUserInfo" p:timeout="120" />
<bean class="com.caodaxing.redis.cache.MyCache" p:name="selectRole" p:timeout="120" />
</set>
</property>
</bean>
缓存管理器中我set了两个模块,分别叫selectUserInfo和selectRole,设置过期时间为120秒,这两个模块接下来会用到,com.caodaxing.redis.cache.MyCache为实现Cache接口的自定义实现类,
下面为缓存自定义实现类代码:
@Component
public class MyCache implements Cache {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private String name;
private long timeout;
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this.redisTemplate;
}
@Override
public ValueWrapper get(Object key) {
if(key == null || StringUtils.isEmpty(key.toString())) {
return null;
}else {
final String finalKey = formatStr(key);
redisTemplate.multi();
Object object = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] newKey = finalKey.getBytes();
byte[] value = connection.get(newKey);
if(value == null) {
return null;
}
return SerializableUtil.unserializeList(value);
}
});
redisTemplate.exec();
return object != null?new SimpleValueWrapper(object):null;
}
}
@Override
public void put(Object key, Object value) {
if (key==null || value==null) {
return;
} else {
final String finalKey = formatStr(key);
if (!StringUtils.isEmpty(finalKey)) {
final Object finalValue = value;
redisTemplate.multi();
redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) {
connection.set(finalKey.getBytes(), SerializableUtil.serialize(finalValue));
// 设置超时间
connection.expire(finalKey.getBytes(), timeout);
return true;
}
});
redisTemplate.exec();
}
}
}
@Override
public void evict(Object key) {
if(key == null) {
return;
}else {
final String finalKey = formatStr(key);
redisTemplate.multi();
redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
connection.del(finalKey.getBytes());
return true;
}
});
redisTemplate.exec();
}
}
@Override
public void clear() {
redisTemplate.multi();
redisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
connection.flushDb();
return true;
}
});
redisTemplate.exec();
}
private String formatStr(Object obj) {
if(obj instanceof String) {
return (String)obj;
}else {
return obj.toString();
}
}
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public void setName(String name) {
this.name = name;
}
}
记住存储对象时,一定要序列化,不然会报错,name为配置文件中的配置,getNativeCache()方法为设置你的缓存对象,我这里用的是redis,当然你也集成mongo和memcache等,timeout为设置数据的过期时间.
3.缓存方法
下面是我对service层中两个方法进行缓存
@Service
public class LoginUserServiceImpl implements LoginUserService {
@Autowired
private LoginUserMapper loginUserMapper;
/**
* 调用该方法时,会先触发Cache接口的get方法,如果缓存中查到数据,则直接返回数据,不再走该方法
* 如果没有查到数据,执行该方法,结束后调用Cache接口的put方法,将返回的数据存入缓存中
* 注意:key的值是字符串的话,使用单引号括起来,例:key="'userName'",如果不加单引号默认做Spel表达式识别
* 会报SpelEvaluationException错误,spel表达式:#方法参数
*/
@Cacheable(value="selectUserInfo",key="'userinfo_'+#pageNumber")
@Override
public List<LoginUser> getUserInfoList(int pageNumber) {
System.out.println("select userInfo start...");
return getUser(pageNumber);
}
/**
* 删除缓存数据,value为要删除的哪个模块数据,key为要删除的数据键值,调用Cache类的evict方法
* 当allEntries为true时,删除value模块里的所有数据,调用Cache类的clear方法,key可不填
* allEntries默认为false,此时key值必填,否则会以默认key查询,有可能报错
*/
@CacheEvict(value="selectUserInfo",key="'userinfo_1'",allEntries=false)
@Override
public void clearData() {
System.out.println("clear all data...");
}
private List<LoginUser> getUser(int pageNumber) {
System.out.println("query database");
LoginUserExample example = new LoginUserExample();
example.setOrderByClause(" id asc limit "+((pageNumber-1)*1)+","+(pageNumber*1));
List<LoginUser> selectByExample = loginUserMapper.selectByExample(example);
return selectByExample;
}
}
可以看到,我在方法的上面加入spring自带的@Cacheable和@CacheEvict注解,并且用到了上面配置文件中配置的name,我这里是将从数据库查询的用户信息放入名字为"selectUserInfo"模块的redis缓存中,key为"userinfo_"加当前页pageNumber参数
对于spring缓存注解不了解的可百度一下,这里就不多做介绍了,在这说一下该类中两个方法与上面自定义缓存类的联系,当调用getUserInfoList()方法的时候,会先调用Cache类中的get()方法,根据key值查询是否有数据,如果有,则直接返回数据,不执行getUserInfoList()方法中的代码,如果没有查到数据,则执行该方法中的代码,在方法执行完成后,会调用Cache类中的put()方法,将返回的数据放入到redis缓存中,这就是@Cacheable注解执行的全过程,
另外再讲下@CachePut注解,它在方法执行完后直接调用Cache类中的put()方法,不会调用get()方法,用于更新数据等使用
至于@CacheEvict注解,为清除数据使用,它有两种情况,当你配置allEntries=true时,他执行的是Cache类中的clear()方法,为false时,执行evict()方法,具体使用哪种情况,可根据需要自行选择.
到这里spring集成redis做缓存就讲到这里了,下面我们讲一下集成redis出现的连接池错误:
二.redis连接池问题:
一般使用redis连接池后,系统会帮你分配资源以及管理连接,但是当你打开了redis的事务时,有事务的连接池是不会自动关闭redis对象连接的,需自己手动关闭并释放连接,而且想要事务生效必须使用RedisTemplate中的multi()和exec()包住,如上面代码所示:
至于手动关闭连接,回收到redis连接池中可在exec()方法之后使用
RedisConnectionUtils.unbindConnection(redisTemplate.getConnectionFactory());
具体是否有用没有测试,可以参考:https://blog.youkuaiyun.com/LiuHanFanShuang/article/details/52136438
还有一种情况会报跟连接池相关的错误,但并不是连接数满了,而是强制关闭Redis快照导致不能持久化。
这种情况我们只需要在redis服务器上执行以下代码就可:
127.0.0.1:6379> CONFIG SET stop-writes-on-bgsave-error no