问题复现:
(1) 在service中,要生成主键,我们采取的策略是,先在主键工厂表中把当前最大主键查询出来,并让他加一,使这个值为新插入业务表数据的主键:
TIdFactory idfactory= tIdFactoryDao.findById(map);
Long id= KEYConstants.initial_id+idfactory.getIncrement()+1;
(2) 封装业务数据,在业务表中进行数据插入
User u= new User();
u.setId(id);
userDao.save(u);
( 3) 更新主键工厂表,让主键加一,供下次使用
tIdFactoryDao.updateIncrement(id);
说明: 上面1,2,3,在一个service方法中实现,service方法加上事务注解@Transactional;
现在有两个controller同时调用该service的方法,当第一个controller在调用service方法运行到第2步的时候,第二个controller也调用了该service同时运行了第1步,那么当第二个controller运行第2步时,问题产生了,主键重复异常。
失败的解决方法:
用synchronized锁把1,2,3锁在一个代码块中,使得在controller在执行1,2,3时,controller2是要等待的,本以为,事情得到解决,但是,由于整个service方法是有事务的,虽然加了锁,但是锁的级别是在spring的事务内,因此无法控制controller2去读取当前已经被controller1拿走的工厂内的当前的主键,也就是无法控制controller2去读取controller1已经读取过的id。
成功的解决方法:
通过分析,要解决这个问题首先想到是把synchronized,放在controller层,这样锁的级别就大于了service的事务级别,但是我并没有这样做。
分析了一下,当前我们使用的mysql数据库的事务隔离级别是可重复读
也就意味着,这个数据库的隔离级别是可以控制脏读,和不可重复读的,那么,如果让步骤1的查询加上锁(带上事务),那么在controller1执行完步骤1,一直到当前service执行完事务被提交,这段时间,controller2是不能执行第一步的,它不能查询到当前这个在被更新的数据,因为它如果读取了数据,执行了第一步这就算脏读,这是mysql当前的隔离级别已经控制了的,所以controller2只能等待service事务提交,那么等到controller1的事务提交后,后面的步骤3自然也已经被提交,那么,controller2拿到的工厂中的id只能是最新的。自此,问题解决,解决方法就是 在步骤1的查询操作的sql上面加上for update。
关于mysql的事务隔离级别:https://blog.csdn.net/lcx390549721/article/details/81082361