锁在事务中的错误使用
错误示例:
@Override
@Transactional(rollbackFor = Exception.class)
public Item add(ItemDTO dto) throws Exception {
//锁商品,防并发
String lockKey = StringUtil.format(Constants.LOCK_ACTIVITY_PRODUCT, dto.getItemId());
//加锁
boolean isLock = redisClient.trylock(lockKey, 2 * 60);
AssertUtil.isTrue(isLock, "该商品正在被处理中,请稍后再试");
try {
Item item = itemMapper.selectByItemId(dto.getItemId());
if(item==null){
...
itemMapper.insert(newItem);
}else{
...
itemMapper.updateItem(item);
}
return item;
} finally {
//释放锁,此时事务还未提交
redisClient.unlock(lockKey);
}
}
事务的范围大于锁的范围,锁释放的瞬间可能事务还没来的及提交,看源码:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 执行完目标方法返回 (public Item add(ItemDTO dto) throws Exception)
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
所以要保证线程安全锁应该加在事务之外