前面讲了创建SqlSession,注册Mapper接口,创建接口代理对象,终于来到执行查询的语句
接上文Demo:
public class MybatisDemo {
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try (SqlSession session = sqlSessionFactory.openSession()) {
// 添加Mapper接口,并解析对应的XML文件
session.getConfiguration().addMapper(BlogMapper.class);
// 通过sqlSession获取Mapper接口代理对象
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
// 通过代理对象调用接口
blogMapper.selectBlog(101);
}
}
}
示例中BlogMapper接口采用JDK动态代理技术创建代理对象,那么这个代理对象调用的时候,必然会执行一个InvocationHandler#invoke,这是JDK动态代理技术实现增强代理的关键逻辑,Mybatis提供了MapperProxy,它实现了InvocationHandler接口,通过代理接口的方法,执行数据库操作
public class MapperProxy<T> implements InvocationHandler, Serializable {
// Mapper接口方法的缓存信息
private final Map<Method, MapperMethod> methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
// 通过methodCache获取被拦截方法的解析信息
// 如果缓存没有那么创建一个放入缓存并返回
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
// 从methodCache获取MapperMethod,
// 如果没有就创建一个存到methodCache,并返回
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
MapperProxy中,使用一个Map集合缓存接口方法的解析信息MapperMethod对象,并通过它执行后续操作
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// SqlCommand记录了当前方法执行的方法名称和操作类型
this.command = new SqlCommand(config, mapperInterface, method);
// MethodSignature记录了当前调用方法入参、出参信息
this.method = new MethodSignature(config, mapperInterface, method);
}
}
SqlCommand记录了当前接口的方法名称,以及数据库操作类型等信息
public static class SqlCommand {
private final String name; // 记录名称,如com.holybell.mapper.BlogMapper.selectBlog
private final SqlCommandType type; // 操作类型
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 如果当前接口没有解析XML文件,这里会尝试重新解析
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName,
declaringClass, configuration);
if (ms == null) {
// ...
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
// 省略抛异常...
}
}
}
}
MethodSignature记录了当前调用方法入参、出参等信息
public static class MethodSignature {
private final boolean returnsMany; // 是否返回多个
private final boolean returnsMap; // 是否返回resultMap
private final boolean returnsVoid; // 是否返回空
private final boolean returnsCursor;// 是否返回游标
private final boolean returnsOptional;
private final Class<?> returnType; // 返回结果类型
// 入参解析器 保存了接口入参下标 -> 入参名称 的关系映射
private final ParamNameResolver paramNameResolver;
}
回到MapperProxy,执行MapperMethod#execute,来到如下方法,可以看到它根据SqlCommand记录的操作类型执行不同的操作,最终还是应用SqlSession做进一步的执行
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// ...
break;
}
case UPDATE: {
// ...
break;
}
case DELETE: {
// ...
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 {
// 本例会执行这个分支
// 通过MethodSignature缓存的方法信息解析入参
Object param = method.convertArgsToSqlCommandParam(args);
// 最终还是应用SqlSession发起数据库操作
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
// ... 省略异常抛出
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
// ... 省略异常抛出
}
return result;
}
}
执行SqlSession#selectOne,跟进它会来到DefaultSqlSession#selectList,Mybatis中执行查询一条记录的selectOne最终也是通过查询多条记录的selectList实现,只是它在查询一条记录的时候,返回列表第一个元素
public class DefaultSqlSession implements SqlSession {
private final Executor executor;
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从Configuration中获取解析XML中生成的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds,
Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
// ...省略异常处理
} finally {
ErrorContext.instance().reset();
}
}
}
接着,Mybatis抽象出一个数据库操作执行器Executor,随着调用的深入,来到CachingExecutor,它应用装饰器模式组合了一个名为delegate的Executor对象,顾名思义为其他Executor增加从缓存查询的功能,这是Mybatis的二级缓存
public class CachingExecutor implements Executor {
private final Executor delegate;
public <E> List<E> query(MappedStatement ms, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取解析好的SQL,将#{}之类的占位符替换为?,记录每个可变参数的信息
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成Mybatis缓存的key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
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);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
// 如果缓存没有查询到,委托给delegate执行查询
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds,
resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 如果不能使用二级缓存,委托给delegate执行查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
如果通过CachingExecutorMybatis未能从二级缓存中获取到数据,只能通过被它装饰的delegate对象执行数据库查询操作,来到被装饰的SimpleExecutor执行器中,它继承了BaseExecutor,在BaseExecutor中Mybatis再次尝试从一级缓存查询数据,如果没有查询到最终才会去执行数据库操作
public abstract class BaseExecutor implements Executor {
// 一级缓存
protected PerpetualCache localCache;
public <E> List<E> query(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, CacheKey key,
BoundSql boundSql) throws SQLException {
// ...
List<E> list;
try {
// ...
// 尝试从一级缓存加载数据
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 {
// ...
}
// ...
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;
}
// 具体执行查询的方法,交由子类实现
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
}
一级缓存中仍然没有获取到数据,接着执行BaseExecutor#queryFromDatabase,进而执行了子类SimpleExecutor#doQuery方法,在这里会创建一个StatementHandler处理器处理JDBC原生的Statement对象
public class SimpleExecutor extends BaseExecutor {
public <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
// 来到这里,看到Statement应该很熟悉了,这就是JDBC里面的Statement
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 通过Configuration返回一个RoutingStatementHandler对象
// 创建StatementHandler的同时会为它执行拦截器调用进行增强
StatementHandler handler = configuration.newStatementHandler(
wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 在这里会执行JDBC中为SQL中的?设置具体参数值操作
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
// 如果是PreparedStatement为?占位符设置属性
handler.parameterize(stmt);
return stmt;
}
}
上述代码中创建StatementHandler的逻辑,包含对它执行拦截器操作
public class Configuration {
public StatementHandler newStatementHandler(Executor executor,
MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
StatementHandler statementHandler
= new RoutingStatementHandler(executor, mappedStatement,
parameterObject, rowBounds, resultHandler, boundSql);
// 拦截器执行处
statementHandler
= (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
}
SimpleExecutor#doQuery创建的StatementHandler对象是RoutingStatementHandler,它实现了StatementHandler,跟CachingExecutor一样,它也组合了一个实现了相同接口的delegate对象,然后根据要执行不同的操作类型,生成SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler之一
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
// 根据不同的StatementType,生成不同的delegate对象
// 具体的操作交由delegate执行
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:
// ...省略异常
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
// ...
}
如果使用了#{}占位符,Mybatis会执行PREPARED类型的PreparedStatementHandler,通过DefaultParameterHandler为SQL语句设置参数值
public class PreparedStatementHandler extends BaseStatementHandler {
// ...
protected final ParameterHandler parameterHandler; // 参数处理器
// ...
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
}
DefaultParameterHandler使用TypeHandler为?占位符设置属性值
public class DefaultParameterHandler implements ParameterHandler {
public void setParameters(PreparedStatement ps) {
// ...
try {
// 设置preparedStatement的参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
// 省略异常...
}
}
}
继续往下,来到Mybatis的TypeHandler,通过它将不同的类型的参数值设置到SQL语句的?占位符上,由于本例使用整型参数ID,因此采用IntegerTypeHandler
blogMapper.selectBlog(101);
public abstract class BaseTypeHandler<T>
extends TypeReference<T> implements TypeHandler<T> {
public void setParameter(PreparedStatement ps, int i, T parameter,
JdbcType jdbcType) throws SQLException {
if (parameter == null) {
// ...
} else {
try {
// 这是个抽象方法,根据不同的类型,有不同的TypeHandler
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
// ...
}
}
}
}
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
// 设置Integer类型的参数
public void setNonNullParameter(PreparedStatement ps, int i,
Integer parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter);
}
}
为SQL语句设置完参数,再次回到SimpleExecutor#doQuery开始执行数据库查询,它又通过前面讨论过的RoutingStatementHandler装饰的PreparedStatementHandler执行查询
public class SimpleExecutor extends BaseExecutor {
public <E> List<E> doQuery(MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
// 来到这里,看到Statement应该很熟悉了,这就是JDBC里面的Statement
Statement stmt = null;
try {
// ...
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
}
RoutingStatementHandler委托它装饰的delegate对象执行操作
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
return delegate.query(statement, resultHandler);
}
}
PreparedStatementHandler实现了StatementHandler接口,通过它调用JDBC的PreparedStatement执行数据库操作,之后就是数据库驱动完成的逻辑了
public class PreparedStatementHandler extends BaseStatementHandler {
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 执行数据库操作
return resultSetHandler.handleResultSets(ps);
}
}
至此,Mybatis已经完成数据的查询操作