mybatis作为一款持久层框架,最为主要的职责,当然是执行我们在mapper映射文件中写的sql语句,但是sql语句也分两种:
(1)普通sql语句
(2)动态sql语句,包含了<if | foreach | choose | when | otherwise | where | set | trim>等可以动态拼接sql的标签
本篇博客将以普通的sql语句执行,根据debug源码的方式,跟踪sql是如何解析,以及最后是如何执行的。测试代码,将以之前博客 MyBatis入门之一对多、多对多查询 的代码为例。首先看看mapper映射文件及测试代码:
UserMapper.xml:
<mapper namespace="com.qxf.mapper.UserMapper">
<!-- 自定义结果映射:
id:是主键
property:是Java对象的属性名
column:是数据库中列名,或者查询中取的列的别名
-->
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
<result property="isValid" column="is_valid"></result>
<!-- 一对多关联查询,使用collection,其中:
collection中的property属性blogs:是User类中的属性名称,对应就可以了(private List<Blog> blogs);
javaType:是User类中的blogs这个属性,属于哪个Java类型,这里当然是属于java.util.List
ofType: 是List泛型中的类型,这里是Blog类型
id中的column是t_blog表的id,只是在为了避免重名冲突,查询的时候取了别名blog_id
-->
<collection property="blogs" javaType="java.util.List" ofType="Blog">
<id property="id" column="blog_id"></id>
<result property="title" column="title"></result>
<result property="userId" column="user_id"></result>
</collection>
</resultMap>
<!--id 与接口的方法名相同,注意:
这里的返回值类型不再是resultType了,而是resultMap
而且resultMap的值,就是上面<resultMap>标签的id属性值
-->
<select id="getUserAndBlogByUserId" parameterType="string" resultMap="userMap">
select u.id,u.username,u.password,u.is_valid,b.id as blog_id,b.title,b.user_id
from t_user u,t_blog b
where u.id = b.user_id
and u.id=#{id}
</select>
</mapper>
测试代码:
public class One2ManyQuery {
public static void main(String[] args) throws IOException {
//读取配置信息
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//根据配置信息,创建SqlSession工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession工厂创建SqlSession
SqlSession sqlSession = factory.openSession();
//获取接口的代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserAndBlogByUserId("123");
System.out.println(user);
}
}
这里,将以下面这句代码为起点,我的想法是将mybatis的原理进行划分成几个模块分析,首先从结果分析,然后再倒推。所以现在,关于mybatis如何读取并解析配置文件的、如何创建SqlSession的、如何创建接口的代理对象的,后面有时间再写,敬请关注。
User user = mapper.getUserAndBlogByUserId("123");
step into跟进去,发现会调用MapperProxy的invoke()方法,证实了上面的mapper是接口的代理对象:
再跟进去:发现execute()方法,会先判断sql的类型:INSERT、UPDATE、DELETE、SELECT、FLUSH,我们这里是select查询,所以我们只需要关注select分支语句:
//execute()方法的2个全局变量
//result:是方法的返回值,Object类型
//param: 是方法参数,Object类型
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
//最终会走到这里,先进行参数解析,其中args是我们传入的参数,Object[]类型
param = this.method.convertArgsToSqlCommandParam(args);
//然后执行sqlSession.selectOne获得sql语句执行结果
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
所以这里又分为2步:
(1)解析sql参数为Object类型
(2)执行sqlSession.selectOne获得sql语句执行结果
一、sql参数是如何解析的?
重点关注这句代码:
param = this.method.convertArgsToSqlCommandParam(args);
一路跟进去,发现最终执行的是ParamNameResolver的getNamedParams()方法
public Object getNamedParams(Object[] args) {
int paramCount = this.names.size();
if (args != null && paramCount != 0) {
//没有参数注解,并且只有一个参数,则直接返回第一个参数
if (!this.hasParamAnnotation && paramCount == 1) {
return args[(Integer)this.names.firstKey()];
} else {
//否则,返回一个ParamMap对象
Map<String, Object> param = new ParamMap();
int i = 0;
for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
//下面的意思是,将传进来的参数封装成Map对象
//其中key为:arg0、arg1....以及param1、param2...
//而value就是我们依次传入的参数值
Entry<Integer, String> entry = (Entry)var5.next();
param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
String genericParamName = "param" + String.valueOf(i + 1);
if (!this.names.containsValue(genericParamName)) {
param.put(genericParamName, args[(Integer)entry.getKey()]);
}
}
return param;
}
} else {
return null;
}
}
那么,ParamMap又是什么鬼?其源码如下:
public static class ParamMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -2212268410512043556L;
public ParamMap() {
}
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
} else {
return super.get(key);
}
}
}
根据源码,ParamMap继承了HashMap,只是重写了get()方法,所以完全可以将它看做是一个HashMap。
根据上面分析:
(1)如果只有一个参数的话,则直接返回一个Object参数
(2)如果有多个参数的话,则会返回一个Map对象,其中key为:arg0、arg1....以及param1、param2...
而value就是我们依次传入的参数值
二、sqlSession.selectOne()方法是如何执行的?
result = sqlSession.selectOne(this.command.getName(), param);
其中,第一个参数是namespace+id组成的字符串,这里是"com.qxf.mapper.UserMapper.getUserAndBlogByUserId"
第二个参数,是我们传入的字符串参数,"123"
先看看selectOne的样子:
发现,它最终还是调用了DefaultSqlSession的selectList()方法,然后根据返回结果判断:
(1)如果返回的list大小只有一个,则符合预期,直接返回list的第一个元素
(2)如果list的大小,大于1个,那么就会抛出异常
(3)返回null值
所以,现在的目标,自然是转向了DefaultSqlSession的selectList()方法,最终执行重载的方法如下:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
//根据statement=namespace+id=com.qxf.mapper.UserMapper.getUserAndBlogByUserId
//在配置信息中查找对应的MappedStatement
//每个MappedStatement,其实就是我们的<select|update|insert|delete>等标签及其内容
MappedStatement ms = this.configuration.getMappedStatement(statement);
//通过执行器executor的query()方法执行
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
其中,RowBounds是一个内存分页对象,包含offset和limit两个属性。
目光转移到,executor的query()方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//通过传入的参数,构造BoundSql对象,就是给它相应的属性赋值,这里不细究
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存的key值
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
//执行重载方法
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
这里边有很多重载的方法,就不一一列举了,因为是第一次查询,不存在缓存,所以后面会执行到BaseExecutor的queryFromDatabase()方法,顾名思义,就是从数据库查询
继而转向doQuery()方法:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
//构建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//执行查询
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
这里有几个关键的地方:
(1)StatementHandler 是什么?
(2)参数是如何设置的?
(3)sql是怎样执行的?
(4)结果集是怎样处理的?
(一)StatementHandler 是什么?
对这行代码进行跟踪:
//构建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
先看下继承关系(图片来源于https://www.cnblogs.com/cxuanBlog/p/11295488.html):
StatementHandler的默认实现类是RoutingStatementHandler,而RoutingStatementHandler持有一个StatementHandler的对象,也就是变量delegate,而变量delegate则会根据statementType来创建,这里会创建PreparedStatementHandler:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
StatementHandler主要负责操作 Statement 对象与数据库进行交互。
而创建PreparedStatementHandler对象,会调用父类的构造方法:
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
//创建参数处理器对象ParameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//创建结果处理器对象ResultSetHandler
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
重点关注参数处理器和结果处理器,后面都会用到:
//创建参数处理器对象ParameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//创建结果处理器对象ResultSetHandler
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
(二)参数是如何设置的?
回到doQuery方法:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
//构建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//执行查询
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
我们重点关注这句:
//创建Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
跟进去,会发现调用一个方法 handler.parameterize(stmt);
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取连接
Connection connection = getConnection(statementLog);
//创建一个Statement对象,这里创建的是PreparedStatement对象
stmt = handler.prepare(connection, transaction.getTimeout());
//处理参数
handler.parameterize(stmt);
return stmt;
}
那么,我们现在看看, handler.parameterize(stmt)方法的实现,点进去发现有很多重载的方法,后面会调用在
(一)StatementHandler 是什么? 中,指到的参数处理器的setParameters()方法:
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
//属性名
String propertyName = parameterMapping.getProperty();
Object value;
if (this.boundSql.hasAdditionalParameter(propertyName)) {
value = this.boundSql.getAdditionalParameter(propertyName);
} else if (this.parameterObject == null) {
value = null;
} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
value = this.parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = this.configuration.getJdbcTypeForNull();
}
try {
//设置参数,value是属性值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (SQLException | TypeException var10) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
}
}
}
}
}
重点关注:
//设置参数,value是属性值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
最终会发现,它调用的JDBC中PreparedStatement的对应的setXXX(i, parameter);
至此,我们传递的参数就设置好了。
(三)sql是怎样执行的?
还是回到doQuery方法:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
//构建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//执行查询
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
重点看这句:
//执行查询
var9 = handler.query(stmt, resultHandler);
可以看到,调用的是StatementHandler的query方法,更为具体的来说,是调用PreparedStatementHandler的query方法:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
层层跟进之后,发现,还是调用JDBC中PreparedStatement的execute()方法执行sql语句的。
至此,sql语句也已经执行完了。
(四)结果集是怎样处理的?
紧接着上面这行代码:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行预编译的sql语句
ps.execute();
//处理结果集
return resultSetHandler.handleResultSets(ps);
}
所以,我们要看的是:
//处理结果集
return resultSetHandler.handleResultSets(ps);
看看它的具体实现:
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;
//获取第一个结果集(ps.getResultSet()即可获取),并将结果集进行包装
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取我们配置的ResultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
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);
}
我们重点关注这句,主要看看是怎么给实体类的属性赋值的:
//处理结果集
handleResultSet(rsw, resultMap, multipleResults, null);
看看它的具体实现:
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) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//处理行值
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
然后又会调用 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
//这里会判断,有没有嵌套的resultMap
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
//代码会执行到这里
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
执行到 handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
该方法内部会调用:
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
继而调用applyPropertyMappings()方法,对属性进行赋值:
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) {
//根据列名获取该列的值,resultSet.getXXX(列名) 获取
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')
//给属性赋值,property是实体类的属性名,value则是查询的值
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
具体表现为:
(1)获取列值:
//根据列名获取该列的值,resultSet.getXXX(列名) 获取
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
(2)给属性赋值:
//给属性赋值,property是实体类的属性名,value则是查询的值
metaObject.setValue(property, value);
基本上是这样了。
第一次写原理类的东西,感觉一下子追源码追得太深,并不好。