上篇说到了getwapper方法获取到了Mapper接口的代理对象,现在分析下Mybatis执行Sql的相关内容原理。
伴随着问题深入分析:
- Mybatis是如何通过Mapper接口找到对应的mapper.xml文件的sql执行语句?原理是什么?
- Mybatis怎么创建的数据库连接?什么方式创建的?
- Mybatis怎么进行参数处理的,原理是什么?
- 怎么执行sql语句的?
- 执行sql后结果集是怎么映射到实体类的?
伴随着以上问题,我们渐渐深入源码探究:
通过之两篇文章的介绍,我们知道了getMapper会返回一个MapperProxy#JDK代理对象。既然是代理对象,执行时就会执行代理对象的invoke方法 MapperProxy#invoke
进入源码:
执行Sql获取
@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 if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 获取Mapper方法,这里就是主要处理获取Mapper接口所对应的含有sql的mapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 拿到mapperMethod后执行sql
return mapperMethod.execute(sqlSession, args);
}
进入cachedMapperMethod(method),参数method是接口中方法的全名。
com.cmm.mybatissource.project.mapper.SysUserMapper.queryList(java.lang.String)
private MapperMethod cachedMapperMethod(Method method) {
// 方法缓存中获取mapper方法
MapperMethod mapperMethod = methodCache.get(method);
// 没有获取到,则取配置configuration中获取mapperMethod
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
// 将获取到的mapperMethod 加入方法缓存
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
那我们就看看是如果获取到mapperMethod的。进入 new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// sql命令初始化,也就是找到sql内容的步骤
this.command = new SqlCommand(config, mapperInterface, method);
// 方法签名
this.method = new MethodSignature(config, mapperInterface, method);
}
这里我们主要看下new SqlCommand(配置信息Configuration,接口全限定名,方法全限定名)
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 得到方法名称
final String methodName = method.getName();
// 获取接口的class类对象
final Class<?> declaringClass = method.getDeclaringClass();
// 决定MapperStatment
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
进入resolveMappedStatement(接口全限定名,方面名称,接口class对象,配置configuration)
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 生成statemengID = 接口全名 + 方法名
String statementId = mapperInterface.getName() + "." + methodName;
// 判断configuration中是否含有改statementId 内容的MappedStatement,之前章节已经说过了
// mapper.xml解析的时候,会将解析的配置信息全部放入 configuration,其中就包括namespace+ID
if (configuration.hasStatement(statementId)) {
// 获取改接口方法所定义的MappedStatement
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
// 处理父类接口
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
这样就很清楚了,mybatis底层是通过 statemengID = 接口 + 方法名,与configuration中配置信息xml中namespace + id 进行匹配,如果有就去除含有sql信息的mappedstatement。
数据库链接
等于说现在Mybatis已经知道了要执行哪个sql语句了接下来就是执行处理了。
那回过头进入 mapperMethod.execute(sqlSession, args)
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据sql语句类型执行
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:
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);
}
break;
case FLUSH:
那就进入executeForMany(sqlsession 内含所有配置信息,args参数),直接进入改法中关键方法 sqlSession.<E>selectList(command.getName(), param)
进入 executor.query ;
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 根据参数获取需要执行的具体SQL语句
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 因为一级缓存默认开启的,所以需要创建一个惟一的缓存键
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 调用查询方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
进入query
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//获取MappedStatement的缓存对象
Cache cache = ms.getCache();
//因为是第一次查询,所以缓存里面肯定是空的,
//如果是第二次查询的话,且当前数据未被改动,那么这个缓存对象肯定不为空,
//就直接根据StateMent从缓存中取出结果,不需要进行数据连接、查询等一系列操作
//极大的提高了查询的响应速率
if (cache != null) {
//根据StateMent对象查询是否需要刷新缓存,其实就是验证数据是否修改过,
//如果数据已经被修改,则MyBatis会删除缓存
flushCacheIfRequired(ms);
//判断条件:是否需要使用缓存以及处理处理器是否为空
if (ms.isUseCache() && resultHandler == null) {
//查询是否是Callable的StateMent对象
ensureNoOutParams(ms, key, parameterObject, boundSql);
//数据是否已经更改?没有更改则直接查询缓存,否则查询数据库
if (!dirty) {
//利用读锁进行加锁,因为DefaultSqlSession是线程不安全的
cache.getReadWriteLock().readLock().lock();
try {
//根据前面创建的缓存key值,获取缓存值
@SuppressWarnings("unchecked")
List<E> cachedList = (List<E>) cache.getObject(key);
//如果缓存不为空则返回缓存值
if (cachedList != null) return cachedList;
} finally {
//释放读锁
cache.getReadWriteLock().readLock().unlock();
}
}
//这一步其实就是线程进来的时候还有缓存,但是在查询缓存的时候,由于数据变更导致缓存被删
//所以只能悲催的去查询数据库
List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//然后将这个查询过程放到事务管理器中
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
//返回结果
return list;
}
}
public abstract class BaseExecutor implements Executor {
/**
* query()方法入口
* 此方法只是做一些条件判断,并且再一次查询局部缓存容器,看是否有缓存值
* 因为DefaultSqlSession是线程不安全的
**/
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());
//判断当前会话是否已关闭,关闭则抛出异常,因为DefaultSqlSession是线程不安全的,
//所以多线程情况下会有这种情况
if (closed) throw new ExecutorException("Executor was closed.");
//判断是否需要刷新缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
//初始化返回结果
List<E> list;
try {
//这个变量类似于版本号,当前线程执行数据库操作前加1,结束后减1
queryStack++;
//如果结果处理器为空,则再一次查询缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//如果缓存结果不为空,则更新此次Statement的缓存参数
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//如果为空,则直接查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
//将变量减1,变成0,表示操作完成
queryStack--;
}
//....省略部分代码
//返回结果
return list;
}
/**
*queryFromDatabase()方法查询数据
* 这个方法调用的doQuery()就是我们本次要找寻的答案了。
**/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//返回结果
List<E> list;
//存储当前的缓存唯一键key和执行器持有者放入localCache对象
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查询数据库
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//移除前面存储的key-value对象
localCache.removeObject(key);
}
//将缓存唯一键key和查询结果放入本地缓存中
localCache.putObject(key, list);
//判断StatementType是否是CALLABLE类型的,就是是否需要调用存储过程
if (ms.getStatementType() == StatementType.CALLABLE) {
//如果是则将缓存键key和参数放入localOutputParameterCache对象中
localOutputParameterCache.putObject(key, parameter);
}
//返回结果
return list;
}
}
进入doQuery
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
// 获取Statement处理器,StateMent处理器包含了:SQL语句、执行器、结果处理器、参数处理器等等
// 是四大对象之一
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 进行sql语句预编译
Statement stmt = prepareStatement(handler, ms.getStatementLog());
//Statement处理器执行SQL语句进行查询
return handler.<E>query(stmt, resultHandler);
}
StatementHandler 是四大对象之一,用来进行sql语句的预编译和相关参数处理工作,我们看下StatementHandler的创建:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建StatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 拦截链调用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
同样的在StatementHandler创建时我们也看到了插件的调用过程,所以我们也可以自定义插件对statementHandler 进行修改。
继续 sql语句预编译 :Statement stmt = prepareStatement(handler, ms.getStatementLog());
/**
* 进行数据库连接,同时也进行Sql语句预编译
**/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
//Statement对象
Statement stmt;
//进行数据库连接
Connection connection = getConnection(statementLog);
//进行SQL预编译处理
stmt = handler.prepare(connection);
//设置sql参数
handler.parameterize(stmt);
//返回StateMent对象
return stmt;
}
n.net/qq_36526036/article/details/105647571
进入 getConnection;
/**
* 下面的这四个方法就是数据库连接的整个过程,
* 可以看到最后一个方法就是调用JDBC的连接方法,进行数据库连接的
* 到此也证明了我们猜想的正确性
**/
protected Connection getConnection(Log statementLog) throws SQLException {
//在当前事务中创建一个数据库连接
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog);
} else {
//返回连接
return connection;
}
}
//打开一个连接
public Connection getConnection() throws SQLException {
//如果连接为空
if (connection == null) {
//新建一个连接
openConnection();
}
//返回这个连接对象
return connection;
}
/**
*根据数据源信息进行数据库连接
**/
protected void openConnection() throws SQLException {
//用数据源进行数据库连接
connection = dataSource.getConnection();
//事务级别设置
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
//设置自动提交事务,默认为false
setDesiredAutoCommit(autoCommmit);
}
/**
* 调用JDBC方法进行数据库连接
**/
public Connection getConnection() throws SQLException {
//调用数据库驱动创建一个连接
return DriverManager.getConnection(this.jdbcUrl, this.createProps((String)null, (String)null));
}
进入handler.parameterize(stmt);看下,底层通过parameterHandler处理器进行了参数设置
@Override
public void parameterize(Statement statement) throws SQLException {
// 参数设置
parameterHandler.setParameters((PreparedStatement) statement);
}
SQL执行
最终的sql语句查询是在 doQuery return handler.<E>query(stmt, resultHandler);
/**
* 执行SQL语句入口
**/
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//将Statement对象强转成PreparedStatement对象
PreparedStatement ps = (PreparedStatement) statement;
//执行SQL语句,查询结果保存在PreparedStatement对象中:
//这个执行过程确实和JDBC执行过程一致
//JDBC: ps.executeQuery(sql)
ps.execute();
//解析查询出来的结果:将JDBC类型转换成JAVA类型
return resultSetHandler.<E> handleResultSets(ps);
}
最终将查询到的数据通过 resultSetHandler处理器先进映射处理:
/**
* 解析查询结果: 已省略部分代码
**/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
//实例化结果集
final List<Object> multipleResults = new ArrayList<Object>();
//获取执行SQL返回的结果类型集合, 比如测试类返回的是:BaseUserInfoDO类
final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//SQL执行返回的结果类型集合长度
int resultMapCount = resultMaps.size();
//结果集计数器,遍历结果集用
int resultSetCount = 0;
//获取执行SQL的结果,返回的结果集都在这个对象里面
ResultSet rs = stmt.getResultSet();
//遍历结果集
while (rs != null && resultMapCount > resultSetCount) {
//获取第一种结果类型
final ResultMap resultMap = resultMaps.get(resultSetCount);
//获取查询结果列的缓存,里面存放的是查询的列和列的JDBC类型及对应的JAVA类型
ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
//解析返回的结果
handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
//获取下一个结果类型,测试类只有一个结果类型:BaseUserInfoDO类
rs = getNextResultSet(stmt);
resultSetCount++;
}
//返回结果
return collapseSingleResultList(multipleResults);
}
/**
* 解析查询结果 已省略部分代码
**/
protected void handleResultSet(ResultSet rs, ResultMap resultMap, List<Object> multipleResults, ResultColumnCache resultColumnCache) throws SQLException {
//新建结果集对象
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//解析SQL查询出来的所有的行信息:即所有的结果集,这个时候结果集还是JDBC类型
handleRowValues(rs, resultMap, defaultResultHandler, rowBounds, resultColumnCache);
//将解析后的结果集添加到这个结果对象中:这个时候结果集对应的就是JAVA类型了
multipleResults.add(defaultResultHandler.getResultList());
}
/**
* 遍历结果集合
**/
protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
//实例化一个结果对象
final DefaultResultContext resultContext = new DefaultResultContext();
//遍历查询出来的所有结果:即数据库表的所有行数据
while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
//获取结果集对象:即BaseUserInfoDO类
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
//解析每一行的结果(一个BaseUserInfoDO对象):
//即把数据表中每一行的每一个JDBC字段转换成JAVA字段并赋值
Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
//将这个对象添加到结果集中
callResultHandler(resultHandler, resultContext, rowValue);
}
}
/**
* 解析每一行的值:即把数据表中每一行的每一个JDBC字段转换成JAVA字段并赋值
**/
protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
//获取BaseUserInfoDO类对象
Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
//获取BaseUserInfoDO类对象
final MetaObject metaObject = configuration.newMetaObject(resultObject);
//获取BaseUserInfoDO类对象的成员变量
final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
//解析BaseUserInfoDO对象每一个成员变量值
applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache)
//返回结果
return resultObject;
}
/**
*把数据表中每一行的每一个JDBC字段转换成BaseUserInfoDO类的成员变量并且赋值
**/
protected boolean applyAutomaticMappings(ResultSet rs, List<String> unmappedColumnNames, MetaObject metaObject, String columnPrefix, ResultColumnCache resultColumnCache) throws SQLException {
//核心代码:遍历BaseUserInfoDO类的每一个成员变量,然后进行赋值
for (String columnName : unmappedColumnNames) {
//根据成员变量类型获取相应的类型解析器
final TypeHandler<?> typeHandler = resultColumnCache.getTypeHandler(propertyType, columnName);
//根据相应的类型解析器,将查询出来的JDBC类型值转换成对应的JAVA类型值
final Object value = typeHandler.getResult(rs, columnName);
//对字段进行赋值
metaObject.setValue(property, value);
}
}
}
以上就是Mybatis整个流程,补充下我们看下最后parameterHandler(参数处理) resultSetHandler(结果集处理)的创建源码:
可以看到这两个处理器也是支持插件处理的。
总结: