spring+redis缓存配置,以及报连接池错误的可能

本文详细介绍了Spring如何集成Redis进行缓存操作,包括引入依赖、配置缓存管理器以及自定义缓存实现类。同时,文章讨论了Spring缓存注解的使用,如@Cacheable、@CachePut和@CacheEvict。在集成Redis过程中,作者提到了可能出现的连接池错误,特别是事务处理时连接的关闭和回收。最后,针对因强制关闭Redis快照导致的连接池错误,提供了解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值