RedisTemplate是springboot对redis操作的封装
第一步就是介绍RedisTemplate
/**
* @param redisConnectionFactory
* @return 自定义redisTemplate,自带的bean没有序列化器
*/
@Bean(name = "redisObjectTemplate")
public RedisTemplate<Object, Object> redisObjectTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new RedisConverter());//设置key的序列化器
redisTemplate.setValueSerializer(new RedisConverter());//设置值的序列化器
// redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
/**
* @param redisConnectionFactory
* @return 自定义redisTemplate,自带的bean没有序列化器
* LettuceConnectionFactory
*/
@Bean(name = "redisStringTemplate")
public RedisTemplate<String, Object> redisStringTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());//设置key的序列化器
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);//设置值的序列化器
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
// redisTemplate.setEnableTransactionSupport(true);//支持事务
redisTemplate.afterPropertiesSet();//非spring注入使用RedisTemplate,需先调用afterPropertiesSet()方法
return redisTemplate;
}
这里设置了二个不同的 RedisTemplate,序列化的方式有区别
RedisConverter为实现了RedisSerializer<Object> 的自定义序列化工具
LettuceConnectionFactory集群 RedisConnectionFactory单机 根据实际情况配置
redisTemplate.setEnableTransactionSupport(true);这个需要特殊注意,如果需要使用@Transaction的一定需要有这个配置
这里用的默认 RedisTemplate 用户也可以定义自己的 RedisTemplate,如下
public class RedisTemplateDelegate<K, V> extends RedisTemplate<K, V> {
@Resource(name = "notSupportTransactionRedisTemplate")
private RedisTemplate<K, V> notSupportTransactionRedisTemplate;
@Override
public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
//判断是否有@Transactional注解,如果有就用支持事务的RedisTemplate
if (TransactionSynchronizationManager.isActualTransactionActive()) {
return super.execute(action, exposeConnection, pipeline);
} else {
return notSupportTransactionRedisTemplate.execute(action, exposeConnection, pipeline);
}
}
@Override
public <T> T execute(SessionCallback<T> session) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
return super.execute(session);
} else {
return notSupportTransactionRedisTemplate.execute(session);
}
}
@Override
public List<Object> executePipelined(final SessionCallback<?> session, final RedisSerializer<?> resultSerializer) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
return super.executePipelined(session, resultSerializer);
} else {
return notSupportTransactionRedisTemplate.executePipelined(session, resultSerializer);
}
}
}
第二步 : 如何使用 redisTemplate
bean中注入定义好的 redisTemplate
@Autowired private RedisTemplate<String, Object> redisStringTemplate;
1、如果已经设置redisTemplate.setEnableTransactionSupport(true),直接使用@Transaction 系统在切面已经自动做了封装
特别需要注意 Transaction 方法中不能使用get去取值或者去watch(watch需要在事务之前),也不要去mutli和exec了(注解已经自动帮我们mutli了)。
如果在使用中不加@Transaction注解,会造成Redis连接不会释放的问题。
2、如果没有设置redisTemplate.setEnableTransactionSupport(true)
redisStringTemplate.watch(Arrays.asList(amoutId, ordersId));
redisStringTemplate.multi();
amoutIdValue = (String)redisStringTemplate.opsForValue().get(amoutId);
LogUtil.info(logger, "amoutIdValue:"+amoutIdValue);
redisStringTemplate.opsForValue().set(amoutId, "300");
redisStringTemplate.delete(ordersId);
List<Object> res = redisStringTemplate.exec();
if (res == null || res.isEmpty()) {
LogUtil.info(logger, "redis失败");
} else {
LogUtil.info(logger, "redis成功");
}
以上代码会报错 No ongoing transaction. Did you forget to call multi?
org.springframework.dao.InvalidDataAccessApiUsageException: No ongoing transaction. Did you forget to call multi?
at org.springframework.data.redis.connection.jedis.JedisConnection.exec(JedisConnection.java:775)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:57)
at com.sun.proxy.$Proxy143.exec(Unknown Source)
因为每次redisTemplate都会获取新的redis连接,所以推荐做法重写execute进行操作
res = redisStringTemplate.execute(new SessionCallback<List<Object>>() {
/**
* Executes all the given operations inside the same session.
*
* @return return value
*/
@Override
public List<Object> execute(RedisOperations ro){
ro.watch(Arrays.asList(amoutId, ordersId));
ro.multi();
String amoutIdValue =
(String)redisStringTemplate.opsForValue().get(amoutId);
LogUtil.info(logger, "amoutIdValue:"+amoutIdValue);
ro.opsForValue().set(amoutId, "300");
ro.delete(ordersId);
List<Object> res = ro.exec();
ro.unwatch();
return res;
}
});