Mybatis源码分析-Mybatis是如何执行sql的
使用mybatis作为dao层框架是目前较主流的一种方案,与spring框架整合下的实践一般是先编写mapper接口,再编写mapper的xml文件,最后在service层中调用mapper接口进行数据库层面操作。举个系统岗位
实例的curd做例子:
- mapper接口
public interface SysPostMapper
{
/**
* 查询岗位数据集合
*
* @param post 岗位信息
* @return 岗位数据集合
*/
public List<SysPost> selectPostList(SysPost post);
/**
* 查询所有岗位
*
* @return 岗位列表
*/
public List<SysPost> selectPostAll();
}
- mapper的xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 命名空间使用mapper接口全路径 -->
<mapper namespace="com.ruoyi.system.mapper.SysPostMapper">
<resultMap type="SysPost" id="SysPostResult">
<id property="postId" column="post_id" />
<result property="postCode" column="post_code" />
<result property="postName" column="post_name" />
<result property="postSort" column="post_sort" />
<result property="status" column="status" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<cache></cache>
<sql id="selectPostVo">
select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark
from sys_post
</sql>
<!-- namespace + id 将组成mappedStatement的唯一标识符 -->
<select id="selectPostList" parameterType="SysPost" resultMap="SysPostResult">
<include refid="selectPostVo"/>
<where>
<if test="postCode != null and postCode != ''">
AND post_code like concat('%', #{postCode}, '%')
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="postName != null and postName != ''">
AND post_name like concat('%', #{postName}, '%')
</if>
</where>
</select>
<select id="selectPostAll" resultMap="SysPostResult">
<include refid="selectPostVo"/>
</select>
</mapper>
- service层调用
@Service
public class SysPostServiceImpl implements ISysPostService
{
@Autowired
private SysPostMapper postMapper;
@Autowired
private SysUserPostMapper userPostMapper;
/**
* 查询岗位信息集合
*
* @param post 岗位信息
* @return 岗位信息集合
*/
@Override
public List<SysPost> selectPostList(SysPost post)
{
return postMapper.selectPostList(post);
}
/**
* 查询所有岗位
*
* @return 岗位列表
*/
@Override
public List<SysPost> selectPostAll()
{
return postMapper.selectPostAll();
}
}
举例完毕,本文要探究的是为什么调用mapper接口就可以操作数据库?
1 mapper接口不是接口,是动态代理类
接口自然是无法直接调用,真正在调用的其实是增强了该接口的动态代理类。mybatis使用的动态代理是基于jdk自带的那种,即基于接口的。下面举个例子说明下:
- mapper接口
public interface StudentMapper {
void study();
}
- 增强器(即实现了InvocationHandler)
public class MapperProxy<T> implements InvocationHandler {
private final Class<T> mapperInterface;
public MapperProxy(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("虽然是个接口,没有任何实现类,我也能直接执行!");
System.out.println("我还能知道你正在调用的方法:" + method.getName());
return null;
}
}
- demo演示
public class Demo {
public static void main(String[] args) {
MapperProxy<StudentMapper> mapperProxy = new MapperProxy<>(StudentMapper.class);
StudentMapper studentMapper = (StudentMapper) Proxy.newProxyInstance(mapperProxy.getClass().getClassLoader(), new Class[]{StudentMapper.class}, mapperProxy);
studentMapper.study();
}
}
studentMapper
是个接口,也没有任何实现类,但也可直接调用,因为实际调用的是动态代理类,其具体逻辑定义在MapperProxy
的invoke
方法中。例子虽简单,但mybatis的做法其实也是类似的,下面一起探究。
首先从DefaultSqlSession
类的getMapper
方法切入,其实与spring整合的场景下一般感知不到这个步骤,因为mapper的动态代理类已经交给spring容器管理了。
// org.apache.ibatis.session.defaults.DefaultSqlSession
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// org.apache.ibatis.session.Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// org.apache.ibatis.binding.MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 核心逻辑在此
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// org.apache.ibatis.binding.MapperProxyFactory
public T newInstance(SqlSession sqlSession) {
// MapperProxy实现了InvocationHandler接口
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 基于jdk自动的动态代理生成
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
看看MapperProxy
的具体逻辑把
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// ...................省略......................
}
由上面的源码分析可知,mapper接口执行的实际逻辑定义在MapperProxy的invoke方法中
2 mapper接口执行查询类sql的过程
2.1 mapper接口的执行入口
上面说过mapper接口是个动态代理类,分析过增强的逻辑即可知道真正执行逻辑封装在类MapperMehod中,故从此类切入
// org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// command这类信息的封装在前面的节点已处理,大体可理解为从mapper的xml文件解析所得
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
// 以select类型的sql为例子
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
// ...............省略..............
}
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
// 判断是否有分页信息数据
// 实际使用的是SqlSessionTemplate处理
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
// org.mybatis.spring.SqlSessionTemplateorg.mybatis.spring.SqlSessionTemplate
// 与spring整合的环境下使用这个组件,其被SqlSessionInterceptor增强
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.selectList(statement, parameter);
}
// -----增强的逻辑----------
// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在此获取正在的sqlSession,后续详细分析
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 调用目标方法
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
2.2 获取SqlSession
说明下获取sqlSession
的过程
// org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 从事务同步管理器中观察是否已经有本线程之前放过的sqlSession对象,有则直接返回
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
// 没有则新获取一个session对象
session = sessionFactory.openSession(executorType);
// 把session绑定到线程上,当然这需要看是否开启配置 TransactionSynchronization
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
TransactionSynchronizationManager
这个对象是spring-tx包下的,与spring的事务相关,可存放很多线程相关的变量,值得关注,后期将mybatis和事务结合起来的时候再重点分析。
接下来重点分析下openSession
的过程
//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
// 其实大部分情况都是调用openSessionFromDataSource此方法获取session
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 与spring结合的话则是SpringManagedTransactionFactory,具体可看mybatisAutoConfiguration的配置
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// SpringManagedTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// CachingExecutor 代理对象SimpleExecutor 封装变量transaction configuration
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();
}
}
session
中必然要有数据库的信息,transaction
封装了dataSource
,executor
中封装Transaction
,最终在DefaultSqlSession
中封装executor
,链式调用,最终就有此信息
看看获取Executor的过程
//org.apache.ibatis.session.Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据不同类型生成,一般都是 Simple那种,然后再用Caching套上
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 = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
2.2.1 值得关注的插件增强
用过分页插件的都知道其原理是增强了executor的实现类,增强的插入点就在这里:
//org.apache.ibatis.plugin.InterceptorChain
//这个chain啊,其实myatis的configuration中addInterceptor的时候会往里头加插件
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 返回的target是个代理对象,经过遍历后就会,,,一层套一层.....
target = interceptor.plugin(target);
}
return target;
}
//org.apache.ibatis.plugin.Interceptor
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// org.apache.ibatis.plugin.Plugin
// 最核心的逻辑在这里
// plugin类实现了InvocationHandler接口,很显然有要进行动态代理增强了
// 这里的target一般是simpleExecutor
public static Object wrap(Object target, Interceptor interceptor) {
// 一般inteceptor会在类开头写下这类注解元信息,可参考PageInterceptor
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 获取type实现的接口中在signatureMap中定义的,说直白点就是判断是否要拦截这个接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//-----------------------------------分隔符-------------------------------------------
// plugin既然实现了InvocationHandler接口,那就看看他的增强逻辑吧
//org.apache.ibatis.plugin.Plugin
// 成员变量
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
// 增强的逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 只有定义的方法才拦截
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
总结:executor
是有可能被插件增强过的。
上面这一大段都是如何获取sqlSession
的逻辑,为啥这么复杂呢?因为与spring
整合后有事务这种概念,同个事务使用相同的sqlSession
,引入了spring-tx
中的组件TransactionSynchronizationManager
用于获取同个线程中先前存过的sqlSession
(如果存在)。梳理下来有这么几点:
- 获取组件:
executor
transaction
sqlsession
- 组件的依赖,
executor
需要transaction
,transaction
中封了datasource
的信息,sqlSession
需要executor
- 其他的点:
- 如果
TransactionSynchronizationManager
有SqlSession
支持直接返回,没有得新获取 executor
是可以被plugin
增强的
- 如果
2.3 sqlSession的查询逻辑
有了sqlSession
总算可以查数据了,下面开始分析!
回到逻辑:org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
通过反射的方式调用类org.apache.ibatis.session.defaults.DefaultSqlSession
的selectList
方法
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// wrapCollection的逻辑比较简单,如果parameter是collection或array类型的则套一层返回ParamMap对象来返回
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
经过多个重载方法后最终进入executor.query(ms, wrapCollection(parameter), rowBounds, handler);
如果statement
有使用cache
则会先从cache中
取,即配置二级缓存,不过目前没遇到配cache
的情况,故简化处理,具体逻辑在cachingExecutor的deltegate成员,即simpleExecutor
中
// org.apache.ibatis.executor.SimpleExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
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--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// 从数据库中取
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 本地缓存先放个占位符
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 逻辑在这
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 将结果放入缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 比较关键的组件StatementHandler,后面细讲
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 预编译statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 查询获取结果
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
总结下获取数据的过程:一般都是通过simpleExecutor
此组件获取数据,先从一级缓存取,没有则从数据库取,然后放入缓存中;
2.4 StatemnetHandler组件真干活
从获取数据到封装数据有个很重要的组件StatementHandler
,接下来重点说明:
2.4.1 获取StatemnetHandler过程
// org.apache.ibatis.session.Configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 实际上的类型是RoutingStatementHandler,根据ms.getStatementType让其包裹一个delegate做真正的活,一般是PreparedStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 一样的,statementHandler也可以被plugin增强,但很少这么搞,比如PageInterceptor就不增强statementHandler接口
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
2.4.2 基于StatementHandler获取prepareStatement
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;
}
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 一般情况都是直接 connection.prepareStatement(sql);
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
//org.apache.ibatis.scripting.defaults.DefaultParameterHandler
//从handler.parameterize(stmt); 最终将到此处的逻辑
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 这东西和预编译语句中的?一一对应
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
// 输入属性进此逻辑
Object value;
// 获取属性名
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
//这里的parameterObject就是指xxxxMapper中接口被调用时上送的参数
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// 是否有对应的类型处理器
value = parameterObject;
} else {
//这种情况指的是上送的若是个对象,一般都没有类型处理器嘛
// 此时就通过属性名获取对应对象的getxxx的返回值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 设置参数,最终底层执行jdbc的操作,如ps.setLong(i,value)
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
总结下获取statement的过程:
- 获取connection
- 通过connection和
sql
获取prepareStatement
- 将参数填充到statement中,参数已经在
mappedStatemnt
的bounedSql
的ParameterMappings
中封好,依次遍历封装进去
2.4.3 使用StatementHandler获取数据
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
// -----------------分割线--------------------
// org.apache.ibatis.executor.resultset.DefaultResultSetHandler
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
// 比较简单,调stmt.getResultSet(),然后包一层到ResultSetWrapper中
// 当然如果用到了druid这类的连接池会比较复杂,但终究用的是resultSet的接口标准
ResultSetWrapper rsw = getFirstResultSet(stmt);
// xml中的resultMap有值则封装到这里
// 一般只会有一个,不考虑多resultMap的情况
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//处理结果,结束后将结果封装到multipleResults对象中,具体分析在后面
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);
}
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
// 简化分析,不考虑父mapping的情况
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
// 使用默认的,DefaultResultHandler
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
// 处理完将defaultResultHandler封装的结果搬到multipleResults对象中
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 只分析simple的ResultMap,不考虑嵌套那种的
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
// 此循环即遍历resultSet的入口,最终结果封装到DefaultResultHandler组件中
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
// 不考虑这种
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
// 将resultSet的一个结果封装到rowValue的java对象中,具体过程后面细说
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
// 逻辑比较简单,将rowValue放入DefaultResultHandler的list变量中,为啥套了好几层就搞不懂
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 根据resultMap中的type创建返回的java对象,一般通过反射方式
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
// 一对没映射到的属性应用属性自动映射方式填充对象的属性值
// 填充的方式后面详细展开
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
// 映射属性填充到rowValue中
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
// 可以配置是返回新对象还是返回null
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
// 返回值
return rowValue;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
this.useConstructorMappings = false; // reset previous mapping result
final List<Class<?>> constructorArgTypes = new ArrayList<>();
final List<Object> constructorArgs = new ArrayList<>();
// 获取返回对象,下面细说
Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
// issue gcode #109 && issue #149
// 遍历resultMapping,如果存在懒加载属性,则通过动态代理方式增强返回对象
// 不细展开,应该挺复杂的...
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
break;
}
}
}
this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
return resultObject;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)
throws SQLException {
final Class<?> resultType = resultMap.getType();
final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
if (hasTypeHandlerForResultObject(rsw, resultType)) {
// 存在resultType对应的typeHandler则最终会调用其getResult方法获取值返回
return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
} else if (!constructorMappings.isEmpty()) {
// 目前没见过用这种的
return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
} else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
// 一般是这种,通过反射创建返回对象
return objectFactory.create(resultType);
} else if (shouldApplyAutomaticMappings(resultMap, false)) {
return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs);
}
throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
// -------------------分隔符-------------------
//--------------自动装配属性的逻辑-----------------
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// 创建未被resultMap映射列的信息,逻辑也简单,遍历column找出没在resultmap中定义过的
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
// 对不存在resultMap的属性用typeHandler获取值
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
// 填充映射的属性,即resultMap中有定义的
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
// 一般不会进入此逻辑
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
// 此逻辑较简单,metaObject中封装了目标返回对象rowValue,通过property名称寻找setProperty方法,通过方式方式将value值填充到rowValue中
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
上面分析了使用preparedStatementHandler获取对象的具体过程,总结成下面几点:
- 调用statement.execute,其实就是进行查库
- 使用
DefaultResultSetHandler
的handleResultSets方法处理数据,其实就是遍历resultset,通过反射方式创建对象,设置对象值 - 理一下几个组件:
metaObject
rowValue
DefaultResultHandler的list变量
multipleResults
,先创建对象作为rowValue
,把rowValue
封到metaObject
中,根据resultSet
封属性的时候操作的是metaObject
,那么其实也封到了rowValue
中,rowValue
后面会封到DefaultResultHandler的list变量
,从DefaultResultHandler的list变量
赋值到multipleResults
后返回
到此,获取数据的整个过程结束,后续操作一般是善后类的,如关闭sqlSession之类
3 mapper接口执行更新类sql的过程
前面介绍了查询语句的逻辑,下面介绍下更新语句的逻辑,有了上面的分析过程,看update就轻松很多。
还是从MapperProxy
切入
// org.apache.ibatis.binding.MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
// .................省略
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
// 最终调用sqlSessionTemplate进行update操作
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
// .................省略
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// -----------------------分割--------------
// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
// sqlSession此对象被SqlSessionInterceptor增强,故先入此逻辑,基本都是这种操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
mapperMethod
最终也是调用DefaultSqlSession
的update
方法,具体分析下此方法
// org.apache.ibatis.session.defaults.DefaultSqlSession
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
// caChingExecutor,但其实可能被plugin增强过
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
// org.apache.ibatis.executor.SimpleExecutor
// cachingExecutor最终使用的也是SimpleExecutor组件
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清空一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 和查询操作一样,也是使用statementHandler,具体使用的是RoutingStatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
获取prepareStatement
的过程和查询是一样的,三步走:1获取连接connection
,2基于connection
生成prepareStatement
,3使用statementHandler
填充prepareStatement
中的参数
下面具体分析statementHandler
的update
逻辑
// org.apache.ibatis.executor.statement.PreparedStatementHandler
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行jdbc的查询
ps.execute();
int rows = ps.getUpdateCount();
// 获取当初mapper中上送的参数
Object parameterObject = boundSql.getParameterObject();
// key生成器,update操作一般没配,则是 NoKeyGenerator,无逻辑的类
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
更新操作的逻辑很简单,没有查询那么复杂。关于key生成器的使用虽然update操作没用到,但是insert操作时经常使用,具体表现就是如果主键是自增的,则插入完成后可将自增生成的主键自动设置到对象中,下面具体分析下其逻辑
3.1 关于KeyGenerator
keyGenerator
是一个接口,mybatis
提供了几个实现类:
Jdbc3KeyGenerator
自增主键那一类使用这种SelectKeyGenerator
oracle
那类需要指定selectKey
的使用这种NoKeyGenerator
空操作
分析下Jdbc3KeyGenerator
的逻辑
// org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
// ms里已经封好了主键属性
final String[] keyProperties = ms.getKeyProperties();
if (keyProperties == null || keyProperties.length == 0) {
return;
}
try (ResultSet rs = stmt.getGeneratedKeys()) {
// 获取rs的元信息
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
// 映射键,逻辑较复杂,不深究了,打住
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
4 sql执行过程总结
画图总结
5 最后
本文从源码角度分析了mybatis执行sql的过程,可能有不严谨和理解不正确的地方,在以后有更多使用经验后不断修正。
参考文章: