淘宝的好券网站怎么做,马鞍山网站建设推广,富阳网站设计,网站 建设 开发 协议前言
最近在看Mybatis的源码#xff0c;搜到这篇文章Sqlsession、Connection和Transaction原理与三者间的关系#xff0c;debug之后发现有不少疑惑#xff0c;于是按照原文整理了一下#xff0c;记录下debug中的一些困惑点。
对于我们开发来讲#xff0c;不管跟任何关系…前言
最近在看Mybatis的源码搜到这篇文章Sqlsession、Connection和Transaction原理与三者间的关系debug之后发现有不少疑惑于是按照原文整理了一下记录下debug中的一些困惑点。
对于我们开发来讲不管跟任何关系型数据库打交道都无法规避这三巨头数据库的会话-Sqlsession、连接-Connection和事务-Transaction今天让我们一起来梳理下这三者之间的工作原理和关系。
1、首先来解析下会话Sqlsession
会话是Mybatis持久化层跟关系型数据库交互的基础所有的查询、数据更新包含保存、更新、删除操作都在与数据库建立会话的基础上进行的MyBatis中的会话是SqlSession默认实现是DefaultSqlSession。可以通过SqlSessionFactory的openSession来获取的。 通过SqlSessionFactory获取SqlSession的代码如下 String resource mybatis-config.xml;InputStream inputStream Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);SqlSession sqlSession sqlSessionFactory.openSession();打开一个会话的时序图大致流程如下
第一步通过new SqlSessionFactoryBuilder().build(inputStream)来构造SqlSessionFactory参数是配置文件的输入流。主要实现的代码块如下 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException(Error building SqlSession., e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}
第二步XMLConfigBuilder的parse方法会解析配置文件解析的结果就是得出一个Configuration对象。其中一步就是根据配置文件中的datasource节点解析出数据源主要实现的代码块如下
dataSource typePOOLED!--这里会替换为local-mysql.properties中的对应字段的值--property namedriver valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://127.0.0.1:3306/test?useUnicodetrue/property nameusername valueroot/property namepassword value12345678/property namepoolMaximumActiveConnections value2/property namepoolMaximumIdleConnections value2/
/dataSource第三步SqlSessionFactory的openSession会获取SqlSession。具体实现代码如下 Transaction tx null;try {final Environment environment configuration.getEnvironment();final TransactionFactory transactionFactory getTransactionFactoryFromEnvironment(environment);tx transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException(Error opening session. Cause: e, e);} finally {ErrorContext.instance().reset();}}
2、接下来再分析下Mybatis的连接Connection
MyBatis在每次执行SQL操作时在获取Statement时会去获取数据库连接(下面源码在SimpleExecutor)。因为每个sql的Statement不同所以连接也不同也就是一个Sqlsession对应多个Connection。只有在开启spring事务的的前提下当前线程才是共用一个Connection,这是因为spring在获取连接时使用ThreadLocal在DataSource层面对Connection做了缓存即你连接的数据源没变Connection不变比如没改变查询的数据库。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection getConnection(statementLog);stmt handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}
事务没有交spring管理
// 这里是单独运行mybatis,没和spring整合
public class MybatisHelloWorld {public static void main(String[] args) {String resource mybatis-config.xml;Reader reader;try {reader Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper new SqlSessionFactoryBuilder().build(reader);SqlSession session sqlMapper.openSession();try {TestAMapper mapper session.getMapper(TestAMapper.class);TestA testA mapper.getUser(1);mapper.getUser(2);System.out.println(testA);} finally {session.close();}} catch (IOException e) {e.printStackTrace();}}
}
查看mapper.getUser(1)和mapper.getUser(2)时Connection获取如下图 显然这两个Connection并不是同一个。
事务交spring管理(配置的druid数据源)
// 这里重点不是mybatis和spring的整合相关代码省略
// 自己可以在springboot中快速导入然后写单测走以下代码
// 也不关注回滚和提交只关注开启事务管理后的连接获取情况即可
DefaultTransactionDefinition transactionDefinition new DefaultTransactionDefinition();
TransactionStatus transaction transactionManager.getTransaction(transactionDefinition);
mapper.getUser(1);
mapper.getUser(2); 查看mapper.getUser(1)和mapper.getUser(2)时Connection获取如下图 显然这两个是同一个这不是复制了一遍图啊看handler地址就知道是两次Mapper方法的执行。下面是获取当前线程同一数据源连接的缓存由于不是本文重点具体如何设置进去的就不展开了。 类似的原理在SqlSessionTemplate功能之一就是帮我们管理SqlSession的新建和关闭我们单独运行Mybatis是自己手动操作的整合Spring后直接调Mapper就行了不用管SqlSession也出现了这里缓存的是当前线程的SqlSession如果当前线程的每次执行Mapper的SqlSession不一样那么底层Connection也会不一样事务就没法实现了 不好意思以上结论是错误的设置事务手动提交SqlSession session sqlMapper.openSession(false);假设把mapper.getUser(1)和mapper.getUser(2)换成两个更新语句调用session.commit()会发现提交成功。问题来了两个更新是两个不同的connectionsession.commit()底层也是调connection.commit(),一个connection不可能提交两个connection的事务啊那么一个SqlSession就是对应一个Connection
那么上面的debug截图明明显示不同啊为啥呢注意看prepareStatement方法这里生成的Connetion是带Proxy的说明这个地方的虽然每次创建不一样的Connetion但只是代理对象里面其实还是一个Connetion下图中的realConnection如下图 知道为啥叫realConnection了吧这里涉及了JDK动态代理。因为代理对象的存在导致在debug过程中发现不同Mapper方法创建的Connection和JdbcTransaction的Connection全都不一样如果基础比较劳知道Connection只能提交基于它自己生成的statement执行的sql的事务那么就不会有这个困惑了。 回到主线我们配置的数据源为POOLED这里会使用PooledDataSource来获取Connection。 Overridepublic Connection getConnection() throws SQLException {return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();}
这里进行了数据库的连接进行了池化管理MyBatis通过自身的数据源PooledDataSource来进行数据库连接的管理。
3、然后再来说下事务Transaction
在执行sqlSession.commit时会去提交事务。 UserMapperExt userMapperExt sqlSession.getMapper(UserMapperExt.class);userMapperExt.insert(new UserDTO(houliu,23));userMapperExt.findUserListByName(zhangsan);userMapperExt.update(name ,wangwu,22);sqlSession.commit();执行commit后会调用如下代码
一个sqlSession中可以进行多个事务提交 userMapperExt1.insert(new UserDTO(houliu,23));userMapperExt1.findUserListByName(zhangsan);userMapperExt1.update(name ,wangwu,22);sqlSession1.commit();//SqlSession sqlSession2 sqlSessionFactory.openSession();UserMapperExt userMapperExt2 sqlSession1.getMapper(UserMapperExt.class);userMapperExt2.insert(new UserDTO(houliu,23));userMapperExt2.findUserListByName(zhangsan);userMapperExt2.update(name ,wangwu,22);sqlSession1.commit();原生jdbc中一个connection可以执行多次commit
Class.forName(“com.mysql.cj.jdbc.Driver”); //classLoader,加载对应驱动
Connection connection DriverManager.getConnection(“jdbc:mysql://127.0.0.1:3306/test?useUnicodetrue”, “root”, “12345678”);
connection.setAutoCommit(false);
PreparedStatement preparedStatement connection.prepareStatement(“update cnt_user set age 201 where name ‘zhangsan’”);
preparedStatement.execute();connection.commit();preparedStatement connection.prepareStatement(update cnt_user set age 233 where name zhangsan);
preparedStatement.execute();
preparedStatement connection.prepareStatement(insert into cnt_user (age , name) values(100 ,liusi));
preparedStatement.execute();connection.commit();
可以看出事务是依附在SqlSession上的。好了讲完了上面三者的原理后最后我们来总结下三者的关系。
Sqlsession、Connection和Transaction之间关系
连接可以通过数据库连接池被复用。在MyBatis中不同时刻的SqlSession可以复用同一个Connection同一个SqlSession中可以提交多个事务。事务回滚与提交其实都是调Connection的相关方法即同一个事务内sql操作使用同一个数据库连接。因此连接—会话—事务的关系如下 Connection与SqlSession一 一对应Connection一对多SqlSession是SqlSession关闭了情况下又新建一个SqlSession即上面说的所谓不同时刻因为连接池的存在不可能多个SqlSession同时对应同一个连接所以只能算一 一对应。Connection与SqlSession两者与Transaction是一对多。
从经典的JDBC操作数据库的几个步骤出发再看Mybatis的源码就能明白SqlSession是对Connection的高级封装它作为Mybatis的接口层提供了一系列增删改查接口我们通过它可以直接操作数据库而原生的Connection我们还得创建PreparedStatement预处理sql,处理返回结果等等而这些操作SqlSession接口底层已经为我们实现了。