线上运行一段时间后就NPE,本地又重现不了。一开始以为是框架的bug,后面发现还是人为导致的...
场景说明
这里的方法放回null
public Long increment(String key, long i) {
return redisTemplate.opsForValue().increment(key,i);
}
这是另一个接口,将全局的事务支持设置为了true。非常怀疑是这段代码导致的。因为项目启动后一段时间还运行良好,过一段时间就NPE了。
public List rightPop(String key, Class clazz, int num){
Long size = redisTemplate.opsForList().size(key);
if(num > size){
return null;
}
redisTemplate.watch(key);
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.multi();
try {
redisTemplate.opsForList().range(key, 0, num-1);
redisTemplate.opsForList().trim(key, num, -1);
List exec = redisTemplate.exec();
if(null != exec){
List list = (List) exec.get(0);
return list;
}
} catch (Exception e) {
e.printStackTrace();
redisTemplate.discard();
}finally {
redisTemplate.unwatch();
}
return null;
}
怀疑的问题代码: redisTemplate.setEnableTransactionSupport(true);
查看源码
在事务环境下会返回null
@return {@literal null} when used in pipeline / transaction.
/**
* Increment an integer value stored as string value under {@code key} by {@code delta}.
*
* @param key must not be {@literal null}.
* @param delta
* @return {@literal null} when used in pipeline / transaction.
* @see Redis Documentation: INCRBY
*/
@Nullable
Long increment(K key, long delta);
但是引发空指针的这段代码并没有显式地添加redis事务啊。难不成业务方法的 @Transactional传播过来了?
重现
test1方法
@GetMapping("/test1")
public void test1() throws IOException {
redisTemplate.watch("1");
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.multi();
try {
redisTemplate.opsForList().range("1", 0, 1-1);
redisTemplate.opsForList().trim("1", 1, -1);
List exec = redisTemplate.exec();
} catch (Exception e) {
}finally {
redisTemplate.unwatch();
}
}
test2方法,必须加 @Transactional(rollbackFor = Exception.class)注解!!
@GetMapping("/test2")
public void test2() throws IOException {
productService.test();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void test() {
Long increment = redisTemplate.opsForValue().increment("22", 1);
System.out.println(increment);
}
修正
在finally中关闭事务
finally {
redisTemplate.setEnableTransactionSupport(false);
redisTemplate.unwatch();
}
结论
1、 @Transactional、redisTemplate.setEnableTransactionSupport(true); 同时被设置,那么将以事务的方式执行,这时候 opsForValue().increment 返回null
2、redis事务不要瞎用,有坑的