Mybatis-语句执行
本章讲述的内容
本章主要讲解Mybatis在启动后是如何执行Mapper文件中对应SQL语句的。带着下面几个问题我们正式进入Mybatis语句执行分析。
1、Mybatis是如何实现一级缓存的?
2、Mybatis是如何执行SQL的?有几种执行器?
3、Mybatis是如何处理查询结果的?
想知道如何使用Mybatis请参考Mybatis官方文档。
相关配置
配置XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <mapper namespace="io.better.mybatis.mapper.UserMapper"> <resultMap id="BaseResultMap" type="io.better.mybatis.model.User"> <id column="id" jdbcType="BIGINT" property="id"/> <result column="username" jdbcType="VARCHAR" property="username"/> <result column="phone" jdbcType="VARCHAR" property="phone"/> <result column="address" jdbcType="VARCHAR" property="address"/> </resultMap> <sql id="Name_Column"> username </sql> <sql id="Base_Column_List"> id, <include refid="Name_Column"/>, phone, address </sql> <select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap"> select <include refid="Base_Column_List"/> from tbl_user where id = #{id,jdbcType=BIGINT} </select> </mapper>
|
全局配置XML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <configuration> <properties resource="jdbc.properties"/> <settings> <setting name="logImpl" value="SLF4J"/> <setting name="useGeneratedKeys" value="true"/> <setting name="defaultExecutorType" value="BATCH"/> </settings>
<typeAliases> <package name="io.better.mybatis.model"/> </typeAliases>
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/source-code-analysis"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments>
<mappers> <mapper resource="mybatis/mappers/UserMapper.xml"/> </mappers> </configuration>
|
入口
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class MybatisTest {
SqlSessionFactory sqlSessionFactory;
@Before public void before() throws IOException { String resource = "mybatis/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); }
@Test public void testList() { SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.selectByPrimaryKey(1L); System.out.println(user); } }
|
从代码可以看出一共执行了三个操作:
1、调用SqlSessionFactory
创建SqlSession
对象。
2、调用SqlSession获取Mapper代理对象。
3、调用Mapper代理对象执行目标方法获取结果。
获取SqlSession
调用openSession()
创建SqlSession
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 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) { } }
|
上诉代码中我们需要重点关注一个点:1、configuration.getDefaultExecutorType():
它用于指定Executor的类型,默认即ExecutorType.SIMPLE
。
在openSessionFromDataSource
方法中,Mybatis做了下面几个操作:
- 步骤①:创建TransactionFactory,类型为:
JdbcTransactionFactory
。
- 步骤②:创建Transaction事务对象,并对
autoCommit,IsolationLevel
属性赋值。
- 步骤③:根据
execType
创建执行类型的Executor
执行器。
- 步骤④:创建
DefaultSqlSession
对象。
创建Transaction
源代码如下:
1 2 3
| public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit); }
|
代码中直接调用JdbcTransaction
构造器进行Transaction创建。那么JdbcTransaction将就能干什么事情呢?让我们来看看JdbcTransaction的类结构图。
从类图可以看出JdbcTransaction主要是负责提交、回滚、关闭Transaction
,打开、获取Connection
等操作。
创建Executor
Configuration#newExecutor(Transaction, ExecutorType)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
|
代码中可以看出,执行器的类型一共有三种:批量->BatchExecutor,可重用->ReuseExecutor,简单->SimpleExecutor
。
而CachingExecutor
是一个装饰类,包装了具体的Executor,主要用于做一些缓存操作。
默认为SimpleExecutor
执行器,我们这里使用是BatchExecutor
执行器。可以通过下面的配置修改:
1
| <setting name="defaultExecutorType" value="BATCH"/>
|
最后将创建好的执行器交给interceptorChain
,此处代码是为了扩展。
因为代码最终会走向Plugin.wrap
方法中,并为其创建 Plugin
这个代理对象(如果有拦截器处理则创建,没有则无
)。
创建DefaultSqlSession
源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class DefaultSqlSession implements SqlSession {
private final Configuration configuration; private final Executor executor;
private final boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; } }
|
DefaultSqlSession的构造比较简单,仅仅是对类中部分属性进行赋值。
总结
在获取SqlSession阶段主要是初始化了Transaction,Executor,SqlSession
三个对象。
获取指定Mapper
调用getMapper()
获取Mapper
1 2 3 4 5 6 7 8
| public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); }
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
|
代码最终调用到了Configuration对象中。
因为Mybatis在启动加载阶段将所有的Mapper接口都注册到了Configuration.mapperRegistry
对象中。
创建MapperProxy
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { } }
public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
|
从代码可以看出Mybatis在获取每一个Mapper接口时都为其创建了一个MapperProxy代理对象。最终调用JDK创建代理。
总结
**在获取Mapper接口阶段主要是操作是:为指定Mapper接口创建代理对象MapperProxy
**。
执行SQL
调用MapperProxy.invoke
当调用SqlSession返回Mapper接口中的方法时,会调用到MapperProxy对象的invoke
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } }
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { return methodCache.computeIfAbsent(method, m -> { if (m.isDefault()) { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } else { return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); }
|
Mybatis会先调用cachedInvoker
方法,判断当前执行的方法是否已经创建过MethodInvoker
:
1、如果没有则创建MapperMethod
封装当前执行的方法:
1、如果执行的方法不是Default方法,则创建PlainMethodInvoker
并赋值。
2、如果执行的是Default方法,则创建DefaultMethodInvoker
并赋值。
2、如果已经创建,则直接从methodCache
中获取。
调用MapperMethodInvoker.invoke
源代码如下:
1 2 3 4
| public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { return mapperMethod.execute(sqlSession, args); }
|
调用上一步创建的MapperMethod
对象执行目标Mapper方法。
调用MapperMethod.execute
源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); case INSERT: { result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } return result; }
|
我们可以看出MapperMethod
类是执行Mapper方法的核心类。
在这个方法中我们分别看到了Mybatis对新增,更新,删除,查询所执行的操作。
通过判断command
指令类型来断定SQL语句的类型。改对象(command
)是在MapperMethod构造器中被初始化。
新增,更新,删除,查询
四个操作最终都调用了sqlSession
中对应的方法。
而executeForMany,executeForMap,executeForCursor
三个方法最终分别调用了SqlSession
的selectList,selectMap,selectCursor
方法。
**可以看出Mybatis的SQL执行最终是在SqlSession中完成的
**。
SqlSession
获取单个结果-selectList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public <T> T selectOne(String statement, Object parameter) { List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException(); } else { return null; } }
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } finally { } }
|
在selectOne
方法中调用了selectList
方法。并对结果集进行了判断,超过一个抛出异常。
在selectList
方法中Mybatis
获取到了解析Mapper
文件时生成的MappedStatement
对象。并将其传递给了Executor
。
而上面的executeForMany,executeForMap,executeForCursor
三个方法查询时最终都调用的是selectList
方法。
执行查询-Executor
CachingExecutor.query
1 2 3 4 5 6 7
| public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
|
CachingExecutor.query
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
|
BaseExecutor.query
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } return list; }
|
可以看出三个方法中都是做了很多缓存操作,提高效率,
核心查询-doQuery
BatchExecutor.doQuery (Core)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { flushStatements(); Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql); Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
|
该方法主要的作用是操作Statement
对象。1、创建StatementHandler处理对象。2、获取Connection连接。3、初始化Statement对象。4、进行参数替换。5、执行SQL完成查询
。
处理查询结果
ResultSetHandler.handleResultSets
源代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public List<Object> handleResultSets(Statement stmt) throws SQLException { final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } }
return collapseSingleResultList(multipleResults); }
|
未完待续