通过解析mybatis的配置文件,获得了SqlSessionFactory对象。有了工厂对象,就可以创建SqlSession了。通过Sqlsession,可以对mapper接口进行代理,最终由代理对象完成拼接sql、进行参数赋值、执行sql、结果集映射等操作。在这个过程中,mybatis四大对象也会参与其中,完成各自的职责。mybatis还提供了一级、二级缓存的功能,来提高执行效率;提供了插件机制,供开发人员扩展。
//加载并解析配置文件
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
//通过工厂创建SqlSession
//DefaultSqlSessionFactory#openSession()
SqlSession sqlSession = sqlSessionFactory.openSession();
复制代码
1 SqlSession对象的创建流程
DefaultSqlSessionFactory#openSession()
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//每个SqlSession分配一个Executor
final Executor executor = configuration.newExecutor(tx, execType);
//使用Configuration和Executor创建SqlSession
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();
}
}
}
复制代码
在SqlSession的创建流程中,我们看到mybatis四大对象之一的Executor
mybatis的四大对象:
- ParameterHandler:处理SQL的参数对象
- ResultSetHandler:处理SQL的返回结果集
- StatementHandler:数据库的处理对象,用于执行SQL语句
- Executor:MyBatis的执行器,用于执行增删改查操作
1.1 Executor的创建流程
Configuration#newExecutor()
public class Configuration {
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//每个Executor分配一级缓存,一级缓存的作用域是SqlSession
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {//全局二级缓存是否开启
/**
* 全局二级缓存默认开启 但还需要每个mapper手动开启二级缓存
*/
executor = new CachingExecutor(executor);
}
//代理
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
复制代码
SimpleExecutor
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
}
复制代码
public abstract class BaseExecutor implements Executor {
//一级缓存
protected PerpetualCache localCache;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
//每个Executor分配一级缓存,一级缓存的作用域是SqlSession
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
}
复制代码
InterceptorChain#pluginAll
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
//层层包装
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
复制代码
2 mapper接口的代理
有了SqlSession对象,就可以创建mapper接口的代理了
DefaultSqlSession#getMapper
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
}
复制代码
Configuration#getMapper
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
复制代码
MapperRegistry#getMapper
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
/**
* 加载配置文件的把接口放到knownMappers中
*/
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);
}
}
}
复制代码
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
/**
* 使用jdk动态代理生成代理对象
* 关注 mapperProxy.invoke()方法
*
* MapperProxy<T> implements InvocationHandler
*/
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
}
复制代码
mapper接口的代理对象就生成了,注意Proxy.newProxyInstance方法的InvocationHandler类型的参数-MapperProxy,目标方法执行的时候,会进入到MapperProxy#invoke中。
3 MapperProxy.invoke
执行流程:
1、如果是Object类的方法,直接执行目标方法
2、如果是接口的默认方法,会把默认方法绑定到代理对象 再调用
3、开发人员写的执行sql的方法,先从缓存中拿mybatis的方法装饰对象,如果没有就使用目标方法对象创建
4、创建MapperMethod对象,构造器里有初始化操作
4.1 创建SqlCommond对象,该对象包含一些和 SQL 相关的信息
4.2 创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息
5、执行MapperMethod#execute
复制代码
MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 代理以后,所有Mapper的方法调用时,都会调用这个invoke方法 (但并不是所有方法都要被代理,所以需要排除一些方法)
*/
try {
if (Object.class.equals(method.getDeclaringClass())) {
/**
* 调用的是否是Object默认的方法 并且没有重写
*
*/
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
/**
* 是否是接口的默认方法
* 如果是默认方法 会把默认方法绑定到代理对象 再调用
*/
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
/**
* 先从缓存中拿,缓存中没有就创建 创建MapperMethod时会有一系列初始化动作
*/
final MapperMethod mapperMethod = cachedMapperMethod(method);
/**
* 我们写的一般会走这里
*/
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
/**
* 类似于反射调用方法操作类 更轻量级一些 构造方法句柄
*
* 如果是默认方法 会把默认方法绑定到代理对象 再调用
*/
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
//是否是默认方法
private boolean isDefaultMethod(Method method) {
return (method.getModifiers()
& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC
&& method.getDeclaringClass().isInterface();
}
}
复制代码
3.1 MapperMethod的创建流程
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
/**
* 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
*/
this.command = new SqlCommand(config, mapperInterface, method);
/**
* 创建 MethodSignature 对象,从类名中可知,该对象包含了被拦截方法的一些信息
*/
this.method = new MethodSignature(config, mapperInterface, method);
}
}
复制代码
3.2 SqlCommand的创建流程
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
/**
* 拿到启动时创建好的MappedStatement
*/
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
/**
* 检测当前方法是否有对应的MappedStatement
*/
if (ms == null) {
//检测当前方法是否有 @Flush 注解
if(method.getAnnotation(Flush.class) != null){
// 设置 name 和 type 变量
name = null;
type = SqlCommandType.FLUSH;
} else {
/*
* 若 ms == null 且方法无 @Flush 注解,此时抛出异常。
* 这个异常比较常见,大家应该眼熟吧
*/
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 设置 name 和 type 变量
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
}
复制代码
3.3 MethodSignature的创建流程
public static class MethodSignature {
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
/**
* 提供反射拿到方法返回类型
*/
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
// 检测返回值类型是否是 void、集合或数组、Cursor、Map 等
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
// 解析 @MapKey 注解,获取注解内容
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
/*
* 获取 RowBounds 参数在参数列表中的位置,如果参数列表中
* 包含多个 RowBounds 参数,此方法会抛出异常
*/
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 获取 ResultHandler 参数在参数列表中的位置
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
/**
* 解析参数列表 关注
*/
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
}
复制代码
3.4 ParamNameResolver的创建流程
ParamNameResolver:方法参数转为sql参数
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
public ParamNameResolver(Configuration config, Method method) {
/**
* 获取参数类型列表
*/
final Class<?>[] paramTypes = method.getParameterTypes();
/**
* 获取参数注解
*/
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 检测当前的参数类型是否为 RowBounds 或 ResultHandler
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
// name 为空,表明未给参数配置 @Param 注解
if (name == null) {
// @Param was not specified.
// 检测是否设置了 useActualParamName 全局配置
if (config.isUseActualParamName()) {
/*
* 通过反射获取参数名称。此种方式要求 JDK 版本为 1.8+,
* 且要求编译时加入 -parameters 参数,否则获取到的参数名
* 仍然是 arg1, arg2, ..., argN
*/
name = getActualParamName(method, paramIndex);
}
if (name == null) {
/*
* 使用 map.size() 返回值作为名称,思考一下为什么不这样写:
* name = String.valueOf(paramIndex);
* 因为如果参数列表中包含 RowBounds 或 ResultHandler,这两个参数
* 会被忽略掉,这样将导致名称不连续。
*
* 比如参数列表 (int p1, int p2, RowBounds rb, int p3)
* - 期望得到名称列表为 ["0", "1", "2"]
* - 实际得到名称列表为 ["0", "1", "3"]
*/
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
// 存储 paramIndex 到 name 的映射
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
}
复制代码
3.5 MapperMethod#execute
public class MapperMethod {
//包含一些和 SQL 相关的信息
private final SqlCommand command;
//包含了被拦截方法的一些信息
private final MethodSignature method;
/**
* 执行sql
* @param sqlSession
* @param args
* @return
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
/**
*
* MapperMethod的构造方法中会创建SqlCommond 和 MethodSignature对象
*
* 可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
*/
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:
/**
* 如果方法返回值为 void,但参数列表中包含 ResultHandler,表明使用者
* 想通过 ResultHandler 的方式获取查询结果,而非通过返回值获取结果
*/
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
/**
* 如果返回多条记录
*/
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
/**
* 执行查询操作,并将结果封装在 Map 中返回
*/
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;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 如果方法的返回值为基本类型,而返回值却为 null,此种情况下应抛出异常
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;
}
}
复制代码
这里我们分析executeForMany
这个分支,其他分支类似
4 执行查询
executeForMany
public class MapperMethod {
//包含一些和 SQL 相关的信息
private final SqlCommand command;
//包含了被拦截方法的一些信息
private final MethodSignature method;
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//方法参数转换为sql参数
Object param = method.convertArgsToSqlCommandParam(args);
//mybatis提供的逻辑分页,该分页操作是对ResultSet结果集进行分页,也就是人们常说的逻辑分页,而非物理分页。
/**
* 物理分页:直接从数据库中拿出我们需要的数据,例如在Mysql中使用limit。
* 逻辑分页:从数据库中拿出所有符合要求的数据,然后再从这些数据中拿到我们需要的分页数据。
*
* 物理分页每次都要访问数据库,逻辑分页只访问一次。
* 物理分页占用内存少,逻辑分页相对较多。
* 物理分页数据每次都是最新的,逻辑分页有可能滞后。
*/
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//走这里
//DefaultSqlSession
result = sqlSession.<E>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;
}
public Object convertArgsToSqlCommandParam(Object[] args) {
//参数名解析器
return paramNameResolver.getNamedParams(args);
}
}
复制代码
executeForMany首先将方法参数转换成了sql参数,看一下转换流程
4.1 方法参数转为sql参数
mybatis传参:
- 传入单个参数
mapper层
User getUserInfo(Integer userId);
复制代码
mapper.xml
select userId
from users
where userId=#{userId};
- 传入多个参数 userId,sex 使用索引对应值
mapper层
User getUserInfo(Integer userId,String sex);
复制代码
mapper.xml
select userId
from users
where userId=#{0} and sex=#{1};
- 传入多个参数 userId,sex 使用注解@Param
mapper层
User getUserInfo(@Param("userId")Integer userId,@Param("sex")String sex);
复制代码
mapper.xml
<select id="getUserInfo" resultType="com.demo.elegant.pojo.User">
select userId
from users
where userId=#{userId} and sex=#{sex};
</select>
复制代码
- 传入多个参数 使用实体类传入
mapper层
User getUserInfo(User User);
复制代码
mapper.xml
<select id="getUserInfo" parameterType="User" resultType="com.demo.elegant.pojo.User">
select userId
from users
where userId=#{userId} and sex=#{sex};
</select>
复制代码
- 传入多个参数, 使用Map类传入
mapper层
User getUserInfo(Map map);
复制代码
mapper.xml
<select id="getUserInfo" parameterType="Map" resultType="com.demo.elegant.pojo.User">
select userId
from users
where userId=#{userId} and sex=#{sex};
</select>
复制代码
ParamNameResolver.getNamedParams:结合创建ParamNameResolver对象时有一系列初始化动作。
public class ParamNameResolver {
public Object getNamedParams(Object[] args) {
/**
* 创建当前类ParamNameResolver的对象时,会去遍历方法上的参数 判断形参上是否有@Param注解 有的话加入到names中
* args;方法实参
* names:方法形参上的名字(包括使用@Param注解中的value)
*/
final int paramCount = names.size();
if (args == null || paramCount == 0) {
//没有参数
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//只有一个参数 就算参数是对象或map也会进到此分支
/**
* 如果方法参数列表无 @Param 注解,且仅有一个非特别参数,则返回该参数的值。
* 比如如下方法:
* List findList(RowBounds rb, String username)
* names 如下:
* names = {1 : "username"}
* 此种情况下,返回 args[names.firstKey()],即 args[1] -> username
*/
return args[names.firstKey()];
} else {
//否则 返回一个ParamMap,修改参数名 参数名就是其位置
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
/**
* 1、先加一个#{0}、#{1}、#{2}参数
*/
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param、
//保证使用@Param标注的参数名不会被覆盖
/**
* 检测 names 中是否包含 genericParamName,什么情况下会包含?答案如下:
*
* 使用者显式将参数名称配置为 param1,即 @Param("param1")
*/
if (!names.containsValue(genericParamName)) {
/**
* 2、再加一个#{param1}、#{param2}、#{param3}参数
*
* //默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
* //如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。
*/
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
复制代码
DefaultSqlSession#selectList
public class DefaultSqlSession implements SqlSession {
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
/**
* MappedStatement包含与当前sql包含的所有信息
*/
MappedStatement ms = configuration.getMappedStatement(statement);
/**
* 默认情况下,executor 的类型为 CachingExecutor,SqlSessionFactory.openSession():创建Sqlsesison会分配一个Executor
* 该类是一个装饰器类,用于给目标 Executor 增加二级缓存功能。
* 那目标 Executor 是谁呢?默认情况下是 SimpleExecutor
*/
//CachingExecutor
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
复制代码
创建SqlSession对象时,先创建了一个SimpleExecutor,然后创建了一个CachingExecutor包装SimpleExecutor,故这里调用的是CachingExecutor#query()。
4.2 ${}拼接操作
CachingExecutor#query()
CachingExecutor的delegate属性就是创建SqlSession时传入CachingExecutor构造器入参的SimpleExecutor,装饰者模式。
public class CachingExecutor implements Executor {
//delegate就是SimpleExecutor,装饰者,欧式
private final Executor delegate;
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/**
* getBoundSql():如果是${} 参数在这里映射,直接替换成参数值 #{}在mybatis初始化就已经替换成? 了
*/
BoundSql boundSql = ms.getBoundSql(parameterObject);
/**
* 创建cacheKey 一级缓存和二级缓存用的同一个key
*/
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//调用重载的方法 接4.3目录
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
//BaseExecutor
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
}
复制代码
MappedStatement#getBoundSql
获取最终的sql,完成${}的拼接操作
public final class MappedStatement {
public BoundSql getBoundSql(Object parameterObject) {
/**
* 调用 sqlSource 的 getBoundSql 获取 BoundSql
* SqlSource常用实现类:
* DynamicSqlSource
* RawSqlSource
* StaticSqlSource
*
* 当 SQL 配置中包含 ${}(不是 #{})占位符,或者包含 <if>、<where> 等标签时,会被认为是动态 SQL,
* 此时使用 DynamicSqlSource 存储 SQL 片段。否则,使用 RawSqlSource 存储 SQL 配置信息
*
*/
// 只要有${} ==> DynamicSqlSource
// 只要#{}没有${} ==> RowSqlSource
/**
* ${]在DynamicSqlSource.getBoundSql()中映射
*/
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
/**
* SELECT * FROM author WHERE name = #{name} AND age = #{age}
* 这个 SQL 语句中包含两个 #{} 占位符,在解析mapper时这两个占位符会被解析成两个 ParameterMapping 对象
* ParameterMapping{property='name', mode=IN, javaType=class java.lang.String, jdbcType=null, ...}
* ParameterMapping{property='age', mode=IN, javaType=class java.lang.Integer, jdbcType=null, ...}
*
*/
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
/**
* 创建新的 BoundSql,这里的 parameterMap 是 ParameterMap 类型。
* 由<ParameterMap> 节点进行配置,该节点已经废弃,不推荐使用。默认情况下,
* parameterMap.getParameterMappings() 返回空集合
*/
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
}
复制代码
BaseExecutor#createCacheKey
创建缓存的key,这个key由一级缓存和二级缓存共用
/**
* update():计算hashcode
* 判断是否的同一条sql的依据:
* 1、sql id :namespace + id
* 2、如果使用mybatis分页功能 起始位置和查询的条数相同
* 3、sql
* 4、传的参数
*/
复制代码
BaseExecutor
public abstract class BaseExecutor implements Executor {
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
/**
* update():计算hashcode
* 判断是否的同一条sql的依据:
* 1、sql id :namespace + id
* 2、如果使用mybatis分页功能 起始位置和查询的条数相同
* 3、sql
* 4、传的参数
*/
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
/**
* boundSql.getSql():此时的sql ${}已被替换成具体参数值 #{}还是?
*/
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
}
复制代码
4.3 二级缓存
CachingExecutor#query()
1、当前namespace是否开启二级缓存。如果没开启,调用BaseExecutor.query方法(SimpleExecutor继承了BaseExecutor,自然也有了该query方法)
2、如果开启了二级缓存
2.1 判断是否需要刷新缓存
2.2 根据cacheKey尝试从二级缓存中获取数据
2.3 命中缓存则返回缓存中的结果
2.4 缓存中没有的话则调用BaseExecutor.query查数据库
复制代码
public class CachingExecutor implements Executor {
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
/**
* MappedStatement是全局共享的 保存在Configuration中
*/
/**
* 二级缓存
*/
Cache cache = ms.getCache();
/**
* 若映射文件中未配置缓存或参照缓存,此时 cache = null
*/
if (cache != null) {
//当前namespace开启了二级缓存
/**
* 是否刷新缓存 默认增删改刷新 查询不刷新
*/
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
/**
* tcm:缓存事物管理器
*/
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
/**
* // 若缓存未命中,则调用被装饰类的 query 方法
*/
//BaseExecutor
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//namespace未开启二级缓存 走这里
//BaseExecutor.query()
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
复制代码
4.4 一级缓存
BaseExecutor#query()
先尝试从一级缓存中获取,缓存命中则返回缓存中的数据;否则,查询数据库
public abstract class BaseExecutor implements Executor {
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;
}
}
复制代码
查询数据库将在下文中进行介绍,涉及主要到除了Executor之外的其他三大对象、 PreparedStatment 为 ? 赋值,执行sql、处理数据库返回的结果集等。