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的数据时会被阻塞
解决办法有三种:
需要注意的是,上面的例子,在oracle数据库会阻塞,但mysql不会,原因在于mysql会为每条执行sql语句自动提交事务,无须显式提交commit,每条sql就是一个事务,即在autoCommit=true时,selectForUpdate也会提交事务,而oracle不同于mysql,oracle数据库底层需要显示提交事务
最后给出4个思考题梳理一下:
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();
}
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();
}
}
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();
}
}