select for update不交由spring事务管理的正确姿势
2020/3/18 5:01:20
本文主要是介绍select for update不交由spring事务管理的正确姿势,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
代码https://github.com/shuangyueliao/mybatisexplore
SqlSession sqlSession = null; try { InputStream in = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in); // 不开启自动提交事务 sqlSession = sqlSessionFactory.openSession(false); List<User> list = sqlSession.selectList("com.shuang.test.findAllUsers"); if (list.size() > 0) { sqlSession.update("xxx"); } } catch (Exception e) { e.printStackTrace(); } finally { // 强制提交事务,如果不设置的话,只有在insert或update才会提交事务,如果selectForUpdate结果为空,无法进行update操作是无法提交事务的 sqlSession.commit(true); sqlSession.close(); }
上面的代码是在oracle数据库中进行的,数据库连接池使用druid,代码看起来无任何问题。而实际是当查询为空时,不会执行下面的update语句,而事务还是没有提交,导致相应行被锁住了。也就是sqlSessionFactory.openSession(false)中的发生autoCommit不起作用。debug源码,跟踪executor.query中的方法
这里的queryFromDatabase就是查询数据库了
debug里面的doQuery,感觉距离真相越来越近了
这里的prepareStatement是关键
getConnection获取连接就能进行数据库操作了
这里的Connection中的setAudoCommit才是决定是否自动提交事务的关键,而sqlSessionFactory.openSession(false)设置autoCommit最终没有正确赋值给Connection才会导致事务没有正确提交,而我们使用Druid,对应的DruidPooledConnection的autoCommit默认是true,即自动提交事务
既然自动提交事务,那么sqlSession.commit(true)就无法强制提交事务了
最终是调用transaction.commit
因为它是自动commit,所以就没必要执行connection.commit(),最终导致在查询结果为空没有执行下面的update语句时,selectForUpdate会没有提交事务锁住相应行。尽管查询结果为空,但它仍可以锁住行,比如这个例子中的sql是select * from user where age=18
,尽管查询结果为空,但对于age=18的行锁仍然存在,当其它的sql插入age=18的数据时会被阻塞
解决办法有三种:
- 用sqlSession.getConnection().setAutoCommit(false);来设置autoCommit属性为false
- 提交时用直接调用connection的commit方法:sqlSession.getConnection().commit();
- 使用spring来开启事务
需要注意的是,上面的例子,在oracle数据库会阻塞,但mysql不会,原因在于mysql会为每条执行sql语句自动提交事务,无须显式提交commit,每条sql就是一个事务,即在autoCommit=true时,selectForUpdate也会提交事务,而oracle不同于mysql,oracle数据库底层需要显示提交事务
最后给出4个思考题梳理一下:
- 开启事务,事务不提交,for update悲观锁不会释放。(这个没什么好说的)
public void forupdateByTransaction() throws Exception { // 主线程获取独占锁 reentrantLock.lock(); new Thread(() -> transactionTemplate.execute(transactionStatus -> { // select * from forupdate where name = #{name} for update this.forupdateMapper.findByName("testforupdate"); System.out.println("==========for update=========="); countDownLatch.countDown(); // 阻塞不让提交事务 reentrantLock.lock(); return null; })).start(); countDownLatch.await(); System.out.println("==========for update has countdown=========="); this.forupdateMapper.updateByName("testforupdate"); System.out.println("==========update success=========="); reentrantLock.unlock(); }
- 开启事务,但不加spring事务并发执行for update,会发生阻塞,而mysql不会阻塞。(不加spring事务就是connection是自动提交事务,而mysql相应的自动提交事务没问题,但oracle底层需要显示提交事务,它不会根据connection的自动提交事务就能做到真正的提交事务,它需要显示提交事务)
public void forupdateByConcurrent() { AtomicInteger atomicInteger = new AtomicInteger(); for (int i = 0; i < 100; i++) { new Thread(() -> { // select * from forupdate where name = #{name} for update this.forupdateMapper.findByName("testforupdate"); System.out.println("========ok:" + atomicInteger.getAndIncrement()); }).start(); } }
- 不加spring事务并发for update操作,并且druid连接池autocommit=false,不会发生阻塞(不开启事务当然不会阻塞)
- 加spring事务并发for update操作,不会发生阻塞(事务的commit都交给spring管理了,不会发生阻塞)
private void forupdateByConcurrentAndTransaction() { AtomicInteger atomicInteger = new AtomicInteger(); for (int i = 0; i < 100; i++) { new Thread(() -> transactionTemplate.execute(transactionStatus -> { // select * from forupdate where name = #{name} for update this.forupdateMapper.findByName("testforupdate"); System.out.println("========ok:" + atomicInteger.getAndIncrement()); return null; })).start(); } }
这篇关于select for update不交由spring事务管理的正确姿势的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)
- 2024-05-31全网首发!软考2024年5月《软件设计师》真题+解析+答案!(21-30题)
- 2024-05-30【Java】百万数据excel导出功能如何实现
- 2024-05-30我们小公司,哪像华为一样,用得上IPD(集成产品开发)?
- 2024-05-30java excel上传--poi
- 2024-05-30安装笔记本应用商店的pycharm,再安排pandas等模块,说是没有打包工具?
- 2024-05-29java11新特性
- 2024-05-29哪些无用敏捷指标正在破坏敏捷转型?
- 2024-05-29鸿蒙原生应用再新丁!新华社 入局鸿蒙
- 2024-05-29设计模式 之 迭代器模式(Iterator)