本文只讲解mybatis的源码 ,不涉及过多的用法讲解,用法请百度。
mybatis查询执行步骤(举SELECT分析)
第一步:获取mapperProxy
第二步:获取mapperMethod
第三步:找到对应的执行类型(SELECT\UPDATE\INSERT\DELETE)
第四步:查询前的缓存处理,判断是否需要从二级、一级缓存中取数据(先二级再一级)
第五步:真正调用用jdbc调数据库,先预编译、再注入参数
第六步:进行结果集解析填充
开发mybatis涉及的坑
一二级缓存最好都关闭掉
select多个报selectOne操作
mapper.xml与mapper.java的类型不一致导致返回类型错误
#与$区别
看源码前各个名词介绍
1 mappedStatement
在程序启动扫描加载的时候,会把所有mapper的信息存储在mappedStatements上。可通过 全限定类名+方法名 或 方法名 获取对应mappedStatement
``````
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
............
// 这里 id = com.example.app.mapper.UserMapper.selectById
return mappedStatements.get(id);
}
``````
需要注意什么?
- userMapper.java接口里面不能实现同名函数重载,不然MappedStatement会冲突
mappedStatement里面有什么数据?
查询类型、数据来源、xml位置、id、参数类型信息、结果类型信息
2 TypeHandler
处理参数和结果集
可自定义实现一个typeHandler,继承BaseTypeHandler来自定义一个typeHandler类型
其默认常见的功能是自动映射参数类型(映射枚举类:JdbcType.java),举例如下:
- select user_id,user_name from lw_user where user_name=#{userName}
- 其实会自动映射成:select user_id,user_name from lw_user where user_name=#{userName,jdbcType=VARCHAR}
下面举一个mybatis 查询demo来进行讲解:
public static void
main
(String[] args)
throws
IOException {
String resource =
"mybatis-config.xml"
;
InputStream inputStream = Resources.
getResourceAsStream
(resource)
;
SqlSessionFactory sqlSessionFactory =
new
SqlSessionFactoryBuilder().build(inputStream)
;
// 通过SqlSessionFactory获取sqlSession
try
(SqlSession session = sqlSessionFactory.openSession()) {
// 通过SqlSession对象获取UserMapper接口的代理对象,
// 然后赋值给UserMapper接口
UserMapper mapper = session.getMapper(UserMapper.
class
)
;
// 调用UserMapper接口的方法来执行指定的数据库操作
System.
out
.println(mapper.selectById(
1
))
;
}
}
第一步:获取mapperProxy
触发时机:UserMapper mapper = session.getMapper(UserMapper.class)
其加载过程为:
第一步: 从configuration中获取mapperProxy代理对象
第二步: 用knownMappers.get(type)获取对应的代理工厂,这里拿到的的是UserMapper代理工厂,然后用jdk动态代理生成UserMapper的MapperProxy代理对象并返回。
接下来我们一步步进行解析:
// 1 从configuration中获取mapperProxy代理对象
// - 所在类:DefaultSqlSession
public
<
T
>
T
getMapper
(Class<
T
> type) {
return this
.
configuration
.getMapper(type
, this
)
;
}
..........
// 2 用
knownMappers.get(type)获取对应的代理工厂,这里拿到的的是UserMapper代理工厂
// 然后用jdk动态代理生成UserMapper的MapperProxy代理对象并返回。。
// - 所在类:
MapperRegistry.java
public
<
T
>
T
getMapper
(Class<
T
> type
,
SqlSession sqlSession) {
/**
*
加载
mybatis-config.xml
配置的
<mapper>
配置,根据指定
type
,查找对应的
MapperProxyFactory
对象
**/
// eg1:
获得
UserMapper
的
mapperProxyFactory
final
MapperProxyFactory<
T
> mapperProxyFactory = (MapperProxyFactory<
T
>)
knownMappers
.get(type)
;
/**
*
如果没配置
<mapper>
,则找不到对应的
MapperProxyFactory
,抛出
BindingException
异常
*/
if
(mapperProxyFactory ==
null
) {
throw new
BindingException(
"Type "
+ type +
" is not known to the MapperRegistry."
)
;
}
try
{
/**
使用该工厂类生成
MapperProxy
的代理对象
*/
return
mapperProxyFactory.
newInstance
(sqlSession)
;
}
catch
(Exception e) {
throw new
BindingException(
"Error getting mapper instance. Cause: "
+ e
,
e)
;
}
}
/**
*
通过动态代理,创建
mapperInterface
的代理类对象
*
*
@param
mapperProxy
*/
@SuppressWarnings
(
"unchecked"
)
protected
T
newInstance
(MapperProxy<
T
> mapperProxy) {
// 这里就是JDK动态代理生成的userMapper的mapperProxy代理对象
return
(
T
) Proxy.
newProxyInstance
(
mapperInterface
.getClassLoader()
, new
Class[] {
mapperInterface
}
,
mapperProxy)
;
}
public
T
newInstance
(SqlSession sqlSession) {
/**
*
创建
MapperProxy
对象,每次调用都会创建新的
MapperProxy
对象,
MapperProxy implements InvocationHandler
*/
final
MapperProxy<
T
> mapperProxy =
new
MapperProxy<
T
>(sqlSession
,
mapperInterface
,
methodCache
)
;
return
newInstance(mapperProxy)
;
}
第一步核心是:获取MapperProxy。其时序图如下:

第二步:
获取
mapperMethod
触发时机:List<User> users = mapper.selectByIds(1);
调用上面,就会触发到MapperProxy#invoke()方法。
为什么调用mapper.selectByIds(1)会触发到invoke()方法?
因为获取到的mapper是UserMapper的MpaerProxy代理,并且MaperProxy实现了InvocationHandler接口,实现了invoke方法,我们就可以在invoke方法自定义加载逻辑。
- public class MapperProxy<T> implements InvocationHandler, Serializable {....}
其加载过程为:
第一步:从methodCache缓存中获取,没有就创建一个put进去
第二步:创建mapperMethod过程中,会涉及到两个重要成员属性:
- 从反射的method中获取信息,然后存放到mapperMethod中。MapperMethod结构如下:
``````java
public class MapperMethod {
// 记录了SQL语句的名称和类型
private final SqlCommand command;
// Mapper接口中对应方法的相关信息
private final MethodSignature method;
................
}
``````
- commond:
-- name属性:MappedStatement的唯一标识id
-- type属性:SQL的命令类型UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
- method(总体是 判断返回类型、入参、下标号、注解值):
-- returnsMany:判断返回类型是集合或者数组吗
-- returnsMap:判断返回类型是Map类型吗
-- returnsVoid:判断返回类型是集void吗
-- returnsCursor:判断返回类型是Cursor类型吗
-- Class<?> returnType:方法返回类型
-- String mapKey:获得@MapKey注解里面的value值
-- Integer resultHandlerIndex:入参为ResultHandler类型的下标号
-- Integer rowBoundsIndex:入参为RowBounds类型的下标号
-- ParamNameResolver paramNameResolver:入参名称解析器
接下来我们一步步进行解析:
// 第一步:
从
methodCache缓存中获取,没有就创建一个put进去
@Override
public
Object
invoke
(Object proxy
,
Method method
,
Object[] args)
throws
Throwable {
try
{
/**
如果被代理的方法是
Object
类的方法,如
toString()
、
clone()
,则不进行代理
*/
// eg1: method.getDeclaringClass()==interface mapper.UserMapper
由于被代理的方法是
UserMapper
的
getUserById
方法,而不是
Object
的方法,所以返回
false
if
(Object.
class
.equals(method.getDeclaringClass())) {
return
method.invoke(
this,
args)
;
}
/**
如果是接口中的
default
方法,则调用
default
方法
*/
else if
(isDefaultMethod(method)) {
// eg1:
不是
default
方法,返回
false
return
invokeDefaultMethod(proxy
,
method
,
args)
;
}
}
catch
(Throwable t) {
throw
ExceptionUtil.
unwrapThrowable
(t)
;
}
// eg1: method = public abstract vo.User mapper.UserMapper.getUserById(java.lang.Long)
/**
初始化一个
MapperMethod
并放入缓存中 或者 从缓存中取出之前的
MapperMethod */
final
MapperMethod mapperMethod = cachedMapperMethod(method)
;
/ eg1: sqlSession = DefaultSqlSession@1953 args = {2L}
/**
调用
MapperMethod.execute()
方法执行
SQL
语句
*/
// 这里是后面要说的逻辑,先不看
return
mapperMethod.execute(
sqlSession
,
args)
;
}
//
第二步:创建mapperMethod过程中,会涉及到两个重要成员属性。 从反射的method中获取信息,然后存放到mapperMethod中
private
MapperMethod
cachedMapperMethod
(Method method) {
/**
*
在缓存中查找
MapperMethod
,若没有,则创建
MapperMethod
对象,并添加到
methodCache
集合中缓存
*/
// eg1:
因为
methodCache
为空,所以
mapperMethod
等于
null
MapperMethod mapperMethod =
methodCache
.get(method)
;
if
(mapperMethod ==
null
) {
// eg1:
构建
mapperMethod
对象,并维护到缓存
methodCache
中
mapperMethod =
new
MapperMethod(
mapperInterface
,
method
,
sqlSession
.getConfiguration())
;
// eg1: method = public abstract vo.User mapper.UserMapper.getUserById(java.lang.Long)
methodCache
.put(method
,
mapperMethod)
;
}
return
mapperMethod
;
}
第二步核心是:获取MapperMethod。其时序图如下:

第三步:找到对应的执行类型(SELECT\UPDATE\INSERT\DELETE)
触发时机:MapperProxy.invoke()方法里面执行 mapperMethod.execute(sqlSession, args);
``````java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
.........其他执行逻辑
// 前面已经获取到了MaperProxy和MapperMethod
/** 调用MapperMethod.execute()方法执行SQL语句 */
return mapperMethod.execute(sqlSession, args);
}
``````
其加载过程为(下面举例
SELECT, 所以下面只会讲解SELECT怎么查询,其他类型基本差不多
):
第一步:调用MapperMethod.execute()方法执行SQL语句
第二步:在switch(command.type)中找到对应的类型(SELECT\UPDATE\DELETE\INSERT\FLUSH),并执行
接下来我们一步步进行解析:
//
第一步:
调用MapperMethod.execute()方法执行SQL语句
@Override
public
Object
invoke
(Object proxy
,
Method method
,
Object[] args)
throws
Throwable {
........
/**
调用
MapperMethod.execute()
方法执行
SQL
语句
*/
// 真正出发选择执行对应类型的入口
return
mapperMethod.execute(
sqlSession
,
args)
;
}
/**
第二步:在switch(command.type)中找到对应的类型(SELECT\UPDATE\DELETE\INSERT\FLUSH),并执行
* MapperMethod
采用命令模式运行,根据上下文跳转,它可能跳转到许多方法中。实际上它最后就是通过
SqlSession
对象去运行对象的
SQL
。
*/
public
Object
execute
(SqlSession sqlSession
,
Object[] args) {
Object result
;
// eg1: command.getType() = SELECT
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;
}
case
SELECT
:
// eg1: method.returnsVoid() = false method.hasResultHandler() = false
if
(
method
.returnsVoid() &&
method
.hasResultHandler()) {
executeWithResultHandler(sqlSession
,
args)
;
result =
null;
}
else if
(
method
.returnsMany()) {
//
查询多条数据
List<User> users = mapper.selectByIds(1);
result = executeForMany(sqlSession
,
args)
;
}
else if
(
method
.returnsMap()) {
//
查询返回
map
,一般不建议这样用:
Map map = mapper.selectMapById(1);
result = executeForMap(sqlSession
,
args)
;
}
else if
(
method
.returnsCursor()) {
// 游标,一般也不用,不用管
result = executeForCursor(sqlSession
,
args)
;
}
else
{
//
mapper.selectById(1)会快走到这一步
//
查询单个数据:
User user = mapper.selectById(1);
/**
将参数转换为
sql
语句需要的入参
*/
Object param =
method
.convertArgsToSqlCommandParam(args)
;
/**
执行
sql
查询操作
*/
result = sqlSession.selectOne(
command
.getName()
,
param)
;
}
break;
case
FLUSH
:
result = sqlSession.flushStatements()
;
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
;
}
// 发现selectOne最终也会调用到selectList,不过限制了只能返回一条记录。所以如果不确定是否只有一条,查询单条数据的时候最好加上limit 1
@Override
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(
"Expected one result (or null) to be returned by selectOne(), but found: "
+ list.size())
;
}
else
{
return null;
}
}
第三步核心是:
找到对应的执行类型(SELECT\UPDATE\INSERT\DELETE)
。其时序图如下:

第四步:查询前的缓存处理,
判断是否需要从二级、一级缓存中取数据(先二级再一级)
触发时机:DefaultSqlSession的方法
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)
其加载过程为
:
第一步:根据mappedStatements.get(id)获取对应的mappedStatement
第二步:对参数进行处理,对Collection、Array加多一层映射。下面举两个例子:
-- selectById(@Param("id") Integer id):默认会给我们加一个 {"param1":1, "id":1}映射,其中param1是自动生成的
-- selectById(@Param("ids") List<Integer> ids):默认会给我们加一个 {"list":[1], "ids":[1]}映射,其中list是自动生成的
第三步:解析出boundSql,里面包含参数映射,待执行sql等。并获取缓存cacheKey,进入待行方法query()
-- boundSql:包含待执行sql,参数映射等
-- cacheKey:会拼接出大概这种"619109510:2666897233:com.example.app.mapper.UserMapper.selectById:0:2147483647:select id,name from userinfo where id = ? limit 1:1:development"
第四步:进行缓存处理。先处理二级,再处理一级缓存
-- 二级缓存:需要xml配置了<cache/>来开启
-- 一级缓存:默认开启,如果开启statement模式则会每次都刷出不进行缓存存储


接下来我们一步步进行解析:
//
第一步:根据
mappedStatements.get(id)获取对应的mappedStatement。
@Override
public
<
E
> List<
E
>
selectList
(String statement
,
Object parameter
,
RowBounds rowBounds) {
.........
MappedStatement ms =
configuration
.getMappedStatement(statement)
;// 获取mappedStatements
return
executor
.query(ms
,
wrapCollection(parameter)
,
rowBounds
,
Executor.
NO_RESULT_HANDLER
)
;
.........
}
//
第二步:对参数进行处理,对
Collection、Array加多一层映射
private
Object
wrapCollection
(
final
Object object) {
if
(object
instanceof
Collection) {
StrictMap<Object> map =
new
StrictMap<Object>()
;
map.put(
"collection"
,
object)
;
// 如果是Collection类型,就给他加一个list映射,所以平时我们就算不给类似selectById(List<Integer> list)加一个@Param("list")都能用默认的list访问
if
(object
instanceof
List) {
map.put(
"list"
,
object)
;
}
return
map
;
}
else if
(object !=
null
&& object.getClass().isArray()) {
// 同上。不过这里加的是array
StrictMap<Object> map =
new
StrictMap<Object>()
;
map.put(
"array"
,
object)
;
return
map
;
}
// 其他情况,举例:selectById(@Param("id") Integer id);默认会给我们加一个 {"param1":1, "id":1}映射,其中param1是自动生成的
return
object
;
}
//
第三步:解析出boundSql,里面包含参数映射,待执行sql等。并获取缓存cacheKey,进入待行方法query()
public
<
E
> List<
E
>
query
(MappedStatement ms
,
Object parameterObject
,
RowBounds rowBounds
,
ResultHandler resultHandler)
throws
SQLException {
/**
获得
boundSql
对象,承载着
sql
和对应的参数
*/
BoundSql boundSql = ms.getBoundSql(parameterObject)
;
/**
生成缓存
key */
CacheKey key = createCacheKey(ms
,
parameterObject
,
rowBounds
,
boundSql)
;
/**
执行查询语句
*/
return
query(ms
,
parameterObject
,
rowBounds
,
resultHandler
,
key
,
boundSql)
;
}
// 第四步:进行缓存处理。先处理二级,再处理一级缓存
// -- 二级缓存:需要xml配置了<cache/>来开启
// -- 一级缓存:默认开启,如果开启statement模式则会每次都刷出不进行缓存存储
public
<
E
> List<
E
>
query
(MappedStatement ms
,
Object parameterObject
,
RowBounds rowBounds
,
ResultHandler resultHandler
,
CacheKey key
,
BoundSql boundSql)
throws
SQLException {
Cache cache = ms.getCache()
;
// eg1: cache = null
/**
如果在
UserMapper.xml
配置了
<cache/>
开启了二级缓存,则
cache
不为
null*/
if
(cache !=
null
) {
/**
*
如果
flushCacheRequired=true
并且缓存中有数据,则先清空缓存
*
*
<select id="save" parameterType="XXXXXEO" statementType="CALLABLE" flushCache="true" useCache="false">
* ……
*
</select>
* */
flushCacheIfRequired(ms)
;
/**
如果
useCache=true
并且
resultHandler=null*/
if
(ms.isUseCache() && resultHandler ==
null
) {
ensureNoOutParams(ms
,
parameterObject
,
boundSql)
;
@SuppressWarnings
(
"unchecked"
)
List<
E
> list = (List<
E
>)
tcm
.getObject(cache
,
key)
;
if
(list ==
null
) {
/**
执行查询语句
*/
list =
delegate
.<
E
>query(ms
,
parameterObject
,
rowBounds
,
resultHandler
,
key
,
boundSql)
;
/**
以
cacheKey
为主键,将结果维护到缓存中
*/
tcm
.putObject(cache
,
key
,
list)
;
// issue #578 and #116
}
return
list
;
}
}
return
delegate
.<
E
>query(ms
,
parameterObject
,
rowBounds
,
resultHandler
,
key
,
boundSql)
;
}
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())
;
// eg1: closed = false
if
(
closed
) {
throw new
ExecutorException(
"Executor was closed."
)
;
}
// eg1: queryStack = 0 ms.isFlushCacheRequired() = false
/**
如果配置了
flushCacheRequired=true
并且
queryStack=0
(没有正在执行的查询操作),则会执行清空缓存操作
*/
if
(
queryStack
==
0
&& ms.isFlushCacheRequired()) {
clearLocalCache()
;
}
List<
E
> list
;
try
{
/**
记录正在执行查询操作的任务数
*/
queryStack
++
;
// eg1: queryStack=1
// eg1: resultHandler=null localCache.getObject(key)=null
/** localCache
维护一级缓存,试图从一级缓存中获取结果数据,如果有数据,则返回结果;如果没有数据,再执行
queryFromDatabase */
list = resultHandler ==
null
? (List<
E
>)
localCache
.getObject(key) :
null;
// eg1: list = 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()
;
/**
如果设置了
<setting name="localCacheScope" value="STATEMENT"/>
,则会每次执行完清空缓存。即:使得一级缓存失效
*/
if
(
configuration
.getLocalCacheScope() == LocalCacheScope.
STATEMENT
) {
// issue #482
clearLocalCache()
;
}
}
return
list
;
}
第四步:查询前的缓存处理,
判断是否需要从二级、一级缓存中取数据(先二级再一级)
。其时序图如下:

第五步:真正调用用jdbc调数据库,先预编译、再注入参数
触发时机:list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)
-> doQuery(ms, parameter, rowBounds, resultHandler, boundSql)
走到这步,就是真的查询,过程为:先预编译、再注入参数、然后调用数据库查询
接下来我们一步步进行解析:
@Override
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()
;
/**
根据
Configuration
来构建
StatementHandler */
StatementHandler handler = configuration.newStatementHandler(
wrapper
,
ms
,
parameter
,
rowBounds
,
resultHandler
,
boundSql)
;
/**
然后使用
prepareStatement
方法,对
SQL
进行预编译并设置入参
*/
stmt = prepareStatement(handler
,
ms.getStatementLog())
;
/**
开始执行真正的查询操作。将包装好的
Statement
通过
StatementHandler
来执行,并把结果传递给
resultHandler */
return
handler.<
E
>query(stmt
,
resultHandler)
;
}
finally
{
closeStatement(stmt)
;
}
}
/**
*
使用
prepareStatement
方法,对
SQL
编译并设置入参
*
*
@param
handler
*
@param
statementLog
*
@return
*
@throws
SQLException
*/
private
Statement
prepareStatement
(StatementHandler handler
,
Log statementLog)
throws
SQLException {
Statement stmt
;
/**
获得
Connection
实例
*/
Connection connection = getConnection(statementLog)
;
/**
第一步:调用了
StatementHandler
的
prepared
进行了【
sql
的预编译】
*/
stmt = handler.prepare(connection
,
transaction
.getTimeout())
;
/**
第二步:通过
PreparedStatementHandler
的
parameterize
来给【
sql
设置入参】
*/
handler.parameterize(stmt)
;
return
stmt
;
}
第五步:真正调用用jdbc调数据库,先预编译、再注入参数
。其时序图如下:

第六步:进行结果集解析填充
这一步就属于结果集填充。可通过实现typeHandler来实现结果集填充的自定义填充功能。
入口源码如下:
/**
*
处理结果集
*/
private void
handleResultSet(ResultSetWrapper rsw
, ResultMap resultMap
, List<Object> multipleResults
,
ResultMapping parentMapping)
throws SQLException {
try {
if
(parentMapping !=
null
) {
handleRowValues(rsw
, resultMap
, null, RowBounds.
DEFAULT
, parentMapping)
;
}
else {
if
(
resultHandler
==
null
) {
/**
初始化
ResultHandler
实例,用于解析查询结果并存储于该实例对象中
*/
DefaultResultHandler defaultResultHandler =
new DefaultResultHandler(
objectFactory)
;
/**
解析行数据
*/
handleRowValues(rsw
, resultMap
, defaultResultHandler
,
rowBounds
, null)
;
multipleResults.add(defaultResultHandler.getResultList())
;
}
else {
handleRowValues(rsw
, resultMap
,
resultHandler
,
rowBounds
, null)
;
}
}
}
finally {
/**
关闭
ResultSet */
closeResultSet(rsw.getResultSet())
;
}
}
时序图如下:
