@Transactional 事务加了 锁 为什么还有并发问题?

@Transactional 事务加了 锁 为什么还有并发问题?

一、原因分析

Spring 中通过在方法上添加注解 @Transactional 可以很好的处理事务问题。Spring对此的处理原理是对 加了 @Transactional 注解的方法 添加 AOP切面来时先事务管理的。

而 synchronized 最大范围也就是方法级别的。 事务和synchronized 关系如下所示

由上图可以看出,当线程1 释放了锁,还未提交事务之前,线程2 已经获取锁并提前提交了事务,从而导致了并发的问题。

二、解决方法

1、方法一 增强事务隔离级别

可以把事务的隔离级别设置为 SERIALIZABLE 不允许事务并发执行,而必须串行化执行,最安全,不可能出现更新、脏读、不可重复读、幻读,但是效率最低。

@Transactional(isolation = Isolation.SERIALIZABLE)

public synchronized void update(Long id, Long seq){

Test1Entity entity = test1Mapper.selectById(id);

test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));

}

2、方法二 提升加锁位置

如果业务逻辑非常简单,对高并发行要求不高的话,可以把 锁操作添加到 控制层,如下所示:

// Controller

@PutMapping("/update/{id}/{seq}")

public Test1Entity update(@PathVariable("id") Long id,

@PathVariable("seq") Long seq){

// 添加锁操作。

synchronized (Class.class){ // 此处为了示例,要根据业务合理加锁

testService.update(id, seq);

}

return new Test1Entity(id, seq);

}

// Service

@Service

public class TestServiceImpl {

@Resource

private Test1Mapper test1Mapper;

@Transactional

public void update(Long id, Long seq){

Test1Entity entity = test1Mapper.selectById(id);

test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));

}

}

3、方法三 抽离事务代码

方法一和方法二的效率都比较低,另一种方式可以把,可以把需要 把需要并发控制的业务,单独抽离出来,进行事务控制操作。如下所示:

public void complex(Long id, Long seq){

// 其他业务处理

lock.lock();

try {

service.update(id, seq);

} finally {

lock.unlock();

}

// 其他业务处理

}

@Transactional

public void update(Long id, Long seq){

Test1Entity entity = test1Mapper.selectById(id);

test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));

}

4、方法四 手动开启事务

方法三中可能会增加一个类的编写,也可以在同一个方法中通过手动开启事务的方式实现。如下所示:

@Service

public class TestServiceImpl {

@Resource

private Test1Mapper test1Mapper;

private static final Lock lock = new ReentrantLock();

@Resource

private DataSourceTransactionManager transactionManager;

@Resource

TransactionDefinition transactionDefinition;

public void update(Long id, Long seq){

// 其他业务处理

lock.lock();

// 开启事务

TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);;

try {

Test1Entity entity = test1Mapper.selectById(id);

test1Mapper.updateById(new Test1Entity(id, seq + entity.getSeq()));

// 提交事务

transactionManager.commit(transaction);

}catch (Exception e){

// 回滚事务

transactionManager.rollback(transaction);

}finally {

lock.unlock();

}

// 其他业务处理

}

}

这个手动开启事务,需要每个方法都需要实现,这个也是比较繁琐,这种方式可以抽象出一个公共类,统一来实现事务的处理。 可以自己脑补

你可能也喜欢

巫师3:狂猎爱丽丝钢剑多少级拿最好
365bet繁体中文

巫师3:狂猎爱丽丝钢剑多少级拿最好

📅 07-10 👀 9849
城市英雄
365bet繁体中文

城市英雄

📅 07-07 👀 9188
輳攏的解釋
365体育网址备用

輳攏的解釋

📅 07-08 👀 7890
洪都拉斯国家足球队
365bet皇冠体

洪都拉斯国家足球队

📅 07-27 👀 9471
英雄联盟手游峡谷先锋详解攻略推荐 峡谷先锋详情
365体育网址备用

英雄联盟手游峡谷先锋详解攻略推荐 峡谷先锋详情

📅 07-02 👀 1304
挑什么样的西瓜好吃?教你选购又甜又脆的西瓜秘诀!
率土之滨有哪些国号
365bet皇冠体

率土之滨有哪些国号

📅 06-29 👀 853
俄罗斯工匠用387把金属剑创造出真正的“铁王座”
手机充电电压是多少伏?及充电安全注意事项
365体育网址备用

手机充电电压是多少伏?及充电安全注意事项

📅 07-02 👀 6334