前两期,分别研究了下mybatis配置文件的加载源码分析和mybatis的映射文件源码分析,当两个文件加载完毕后,会通过MapperRegister的addMappers方法为每一个mapper接口生成一个代理对象,
这样,前期工作就准备就绪了,接下来,就是我们开始执行业务逻辑啦,当我们开始访问的service调用mapper接口的时候首先回去调用代理对象的invoke方法,执行相关的逻辑,接下来,我们从源码角度去分析下
之前,在分析加载映射文件的时候,已经说过,在为mapper绑定命名空间的时候,会调用MapperRegister的addMapper方法,再来重温下下这个方法
方法很简单主要做了以下几件事
1:判断maper是不是接口,
2:为mapper接口生成代理对象存入到map集合
3:处理mapper接口方法上注解sql流程
public <T> void addMapper(Class<T> type) {
//判断mapper是不是接口
if (type.isInterface()) {
//knowMappers是否已经存在这个类型的mapper接口
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//将mapper对应的类型作为key,mapper代理对象作为value存入到map集合中
knownMappers.put(type, new MapperProxyFactory<T>(type));
//以下代码为处理mapper接口上使用sql注解的语句解析,都是差不多的,就不做分析了
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
接下来,进入主题,咱们开始为mapper生成代理对象逻辑,为mapper生成代理对象的逻辑封装在MapperRegster的getMapper中,看源码
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从KnowMappers中获取对应的类型的代理对象
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);
}
}
在获取到 MapperProxyFactory 对象后,即可调用工厂方法为 Mapper 接口生成代理对象了,继续看源码,这里使用了jdk的动态代理
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//创建mapper代理对象
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//创建mapperPrxy代理对象
return newInstance(mapperProxy);
}
接下来进入创建mapperProxy代理对象的方法,查看invoke方法的执行逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果该方法是object类型,直接执行
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);
}
//1:获取mapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//2:执行sql
return mapperMethod.execute(sqlSession, args);
}
来看一下方法一:主要作用是获取mapperMethod对象,不存在,就创建后,添加到methodCache集合中,比较简单,就不做分析了
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
我们进入到MapperMethod的创建逻辑中
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//1:创建sql语句
this.command = new SqlCommand(config, mapperInterface, method);
//2:创建方法签名
this.method = new MethodSignature(config, mapperInterface, method);
}
1:以下是构建sqlCommond的方法.构建sql语句
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//获取方法名
final String methodName = method.getName();
//获取类型
final Class<?> declaringClass = method.getDeclaringClass();
//获取映射语句
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
//如果映射语句为空,查看是否存在Flush注解,这里简单说一下这个注解的作用
//当存在事物的时候,这个注解起到了一种预插入的作用,当你需要插入一行记录,这个时候由于事物没有结束,你想使用插入的这个对象的主键,是拿不到的,这个时候加入这个注解
//就会拿到这个主键,这时候,就可以执行其他操作了
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属性和type属性
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
2:MethodSignatured的主要作用是用于检测目标方法的返回类型,以及解析目标方法参数列表
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();
}
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.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);
}
//接下来看看参数列表的解析过程
public ParamNameResolver(Configuration config, Method method) {
// 获取参数类型列表
final Class<?>[] paramTypes = method.getParameterTypes();
//获取参数注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
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;
// 获取 @Param 注解内容
name = ((Param) annotation).value();
break;
}
}
// name 为空,表明未给参数配置 @Param 注解
if (name == null) {
// @Param was not specified.
if (config.isUseActualParamName()) {
// 检测是否设置了 useActualParamName 全局配置
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
解析完毕后,可得到参数下标到参数名的映射关系,这些映射关系存储在names集合中,
也就是说,加入有一个方法是getUser(@Param("id")Integer id,@Param("name")String name,RowBounds rb,User user)
那样names集合中最终存储的值为 names:{0=id,1=name,3=user}
接下来进入重头戏了看看eecute方法,return mapperMethod.execute(sqlSession, args);经过漫长的路程,终于感觉要看到希望了,继续往下走
有没有一种很熟悉的感觉,看到了INSERT,UPDATE,DELETE,SELECT...这里就是用来处理各种sql语句的地方了,接下来咱们一个个分析,
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
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:
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;
}
不难看出,INSERT,UPDATE,DELETE三个sql语句都是两个方法,
//构建参数
Object param = method.convertArgsToSqlCommandParam(args);
//返回结果
result = rowCountResult(sqlSession.insert(command.getName(), param));
咱们来一个个分析下,从insert开始
INSERT:
进入method.convertArgsToSqlCommandParam(args);方法
主要逻辑封装在ParamNameResolver的getNamedParams中
public Object getNamedParams(Object[] args) {
//获取mapper接口中动态参数的个数
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
//没有Param注解,并且参数个数是1个的情况直接返回这个参数
return args[names.firstKey()];
} else {
//处理多个参数的情况
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//将值和对应的索引添加到param集合中
param.put(entry.getValue(), args[entry.getKey()]);
// 添加通用参数名称 (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
rowCountResult方法比较简单,就是返回具体影响的行数,代码比较简答就不做具体的分析了,
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
后面的DELETE,UPDATE,都是差不多的,咱们这里就不做具体的分析了,接下来,我们主要分析下SELECT语句的,查询也是在平时的业务中使用额最平凡的语句
相对来说,也比较复杂,涉及到结果集的分装,也是一个难点,虽然咱们用起来比较简单,但是myabtis处理起来可是一点都不简单,接下来,我们一起来学习下
SELECT中涉及到的方法比较多:有如下四个方法,select方法中返回值类型的不同,导致对每种返回结果的处理也不一样,
executeWithResultHandler
executeForMany
executeForMap
executeForCursor
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);
}
咱们分析个selectOne方法吧,通过源码可以看出,selectOne和selectList是一样的,只不过,返回值不一样,selectOne是从selectList里面取一行数据,所以,咱们分析
selectOne就是分析了selectList方法,这些具体的方法被封装在DefaultSqlSession类中
@Override
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对象
MappedStatement ms = configuration.getMappedStatement(statement);
//调用querry方法
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();
}
}
我们发现这里多了个executor变量,这是一个接口,有很多实现类,我们先去看一下这个接口,我们可以看一下他的调用关系,一共有BaseExecuter和BatchExecuter两个
实现了Executer接口,有三个是实现类实现了基类,分别是BatchExecutor(批量执行器),ReuseExecutor(重复执行器),SimpleExecutor(简单执行器.mybatis的默认执行器)
这里简单说一下,这三个执行器,后续还会深入去研究下这三个执行器的
BatchExecutor(批量处理sql语句,性能会提升)
ReuseExecutor()大致意思是说:定义了一个map<String,Statement>,key为sql语句,value为执行的statement,这样执行相同的sql的时候,就不用重复创建了.
SimpleExecutor():myabtis默认的执行器,一条条执行sql语句执行处理.
简单说道这里,我会专门准备一期写一下这三个执行器的用法,下面,我们一起看看默认执行器的创建过程,SimpleExecutor作为mybatis的默认执行器,是在哪里创建的那
经过一系列分析,原来是在DefaultSqlSessionFactoy的openSession方法中创建的,咱们看看源码
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
//获取事物工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//获取事物
final Transaction tx = transactionFactory.newTransaction(connection);
//获取Executor
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
configuration.newExecutor:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//定义了defaultExecutorType变量 protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
executorType = executorType == null ? defaultExecutorType : executorType;
/**
*ExecutorType是一个枚举,定义了三个枚举类型,这里默认就是SimpleExecutor
/
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 = new SimpleExecutor(this, transaction);
}
//启用缓存的话,默认为CacheExecuter
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
我们从CachingExecutor进行分析,我们分析的是select,所以分析querry方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取BoundSql
BoundSql boundSql = ms.getBoundSql(parameterObject);
//获取缓存
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
//执行querry重载方法
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//获取MappedStatement中的缓存,如果映射文件中没有配置的话,cache为null
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
//判断是否在缓存中存在
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
//缓存中不存在.调用BaseExecuter的querry方法
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
调用BaseExecuter的querry方法
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
这里提到了BaseExecuter,咱们再跳转到BaseExecuter中的querry方法看看
@SuppressWarnings("unchecked")
@Override
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;
}
queryFromDatabase:咱们看看这个方法的具体逻辑
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 {
//调用doQuery执行查询
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;
}
又出现了新的方法doQuery,它是一个抽象方法,由于之前默认的执行器是SimpleExecuter,所以跳转到SimpleExecuter的doQuerry中看到这里,突然想起了一种之前写jdbc源码的感觉,一起来复习下,
1:加载jdbc所需的四个参数
2:加载驱动
3:创建数据库连接
4:创建perparedStatement
5:执行sql语句
6:遍历结果集
7:关闭连接
@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();
//创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//4:创建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
//执行数据库查询
return handler.<E>query(stmt, resultHandler);
} finally {
//7:关闭Statement
closeStatement(stmt);
}
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
//5执行sql语句
statement.execute(sql);
//6:遍历结果集
return resultSetHandler.<E>handleResultSets(statement);
}
和以上的七个步骤就算对应上啦!接下来,咱们来分析下BondSql,在上一篇中,我们在分析映射文件的时候,说道mybatis在解析映射文件的sql的时候会根据不同的关键字解析sql
那马接下来,我们一起看下,mybatis的到底是怎样将参数赋值到到sql语句中的,我们一起看一下CaxcheExecuter中的getBoundSql,bondSql中存在5个参数,
private final String sql;(处理动态内容后,获取的实际sql字符串)
private final List<ParameterMapping> parameterMappings;(解析sql中的每一个占位符,并设置到parameterMappings)
private final Object parameterObject;(运行时参数,比如传入的对象信息)
private final Map<String, Object> additionalParameters;(每个参数的附加信息)
private final MetaObject metaParameters;(additionalParameters 的元信息对象)
public BoundSql getBoundSql(Object parameterObject) {
//获取BoundSql对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
//获取boundSql中的参数映射列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
//不存在参数映射list,创建boundSql对象
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// 处理嵌套查询逻辑
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
sqlSource.getBoundSql一共有四个实现类,分别是
DynamicSqlSource
RawSqlSource
StaticSqlSource
ProviderSqlSource
我们先看一下DynamicSqlSource
@Override
public BoundSql getBoundSql(Object parameterObject) {
//创建DynamicContext对象
DynamicContext context = new DynamicContext(configuration, parameterObject);
//解析sql
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
//创建staticSqlSource并将 sql 语句中的占位符 #{} 替换为问号 ?
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// 调用 StaticSqlSource 的 getBoundSql 获取 BoundSql
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 将 DynamicContext 的 ContextMap 中的内容拷贝到 BoundSql 中
for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
}
return boundSql;
}
接下来看下rootSqlNode.apply(context);这个方法有多个实现类,针对映射文件中的不同的标签,定义了不同的解析方法,我们看一下uml图
image.png
StaticTextSqlNode 用于存储静态文本,
TextSqlNode 用于存储带有 ${} 占位符的文本,
IfSqlNode 则用于存储 <if> 节点的内容。
MixedSqlNode 内部维护了一个 SqlNode 集合,用于存储各种各样的 SqlNode。
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 遍历 SqlNode 集合
for (SqlNode sqlNode : contents) {
// 调用 salNode 对象本身的 apply 方法解析 sql
sqlNode.apply(context);
}
return true;
}
}
MixedSqlNode 可以看做是 SqlNode 实现类对象的容器,凡是实现了 SqlNode 接口的类都可以存储到 MixedSqlNode 中,
包括它自己。MixedSqlNode 解析方法 apply 逻辑比较简单,即遍历 SqlNode 集合,并调用其他 SalNode 实现类对象的 apply 方法解析 sql
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
下面分析下TextSqlNode
public class TextSqlNode implements SqlNode {
private final String text;
private final Pattern injectionFilter;
@Override
public boolean apply(DynamicContext context) {
// 创建 ${} 占位符解析器
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
// 解析 ${} 占位符,并将解析结果添加到 DynamicContext 中
context.appendSql(parser.parse(text));
return true;
}
private GenericTokenParser createParser(TokenHandler handler) {
// 创建${}占位符解析器,
return new GenericTokenParser("${", "}", handler);
}
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
private Pattern injectionFilter;
public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
}
@Override
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
// 通过 ONGL 从用户传入的参数中获取结果
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value));
// 通过正则表达式检测 srtValue 有效性
checkInjection(srtValue);
return srtValue;
}
}
}
GenericTokenParser 是一个通用的标记解析器,用于解析形如 ${xxx},#{xxx} 等标记。GenericTokenParser 负责将标记中的内容抽取出来,并将标记内容交给相应的 TokenHandler 去处理。
BindingTokenParser 负责解析标记内容,并将解析结果返回给 GenericTokenParser,用于替换 ${xxx}
通常情况下,${}的解析是不经过转义的,而是传什么,解析成什么这就是为什么我们不应该在 SQL 语句中是用 ${} 占位符,风险太大
其他的就不做分析了,接下来,主要分析下#{}的解析
#{} 占位符的解析逻辑是包含在 SqlSourceBuilder 的 parse 方法中,该方法最终会将解析后的 SQL 以及其他的一些数据封装到 StaticSqlSource 中。下面,一起来看一下 SqlSourceBuilder 的 parse 方法。
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
//创建 #{} 占位符处理器
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
//创建#{}解析器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 解析 #{} 占位符,并返回解析结果
String sql = parser.parse(originalSql);
// 封装解析结果到 StaticSqlSource 中,并返回
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这里就进行具体的分析了,大概说下意思,就是会将#{}替换为?,然后将里面的内容解析成为map并存入到parmeterMappng中,
解析 content
解析 propertyType,
构建 ParameterMapping 对象,
以上就是sql的解析,
我们来总结下以上步骤(以查询语句为例)
1:加载完映射文件后,MapperRegister为每一个mapper接口生成一个代理对象
2:当执行调用的时候,会执行代理对象的invoke方法,
3:获取sql命令,是查询语句,还是update语句或者是insert语句
4:解析方法的返回类型
5通过ParamNameResolver方法解析传递的参数,并设置到map集合中
6:执行execute方法(mybatis有三种执行器方法,默认为simple)
7:根据之前的sqlCommond确定sql的语句类型(select,insert,update).执行不同的方法
8:执行cacheingExecuter方法
9:解析sql中的各个标签,比如${}直接解析为字符串,#{}先解析为?,然后将内容一一解析后封装到map中,并将结果封装到bondSql中
10:创建statement对象
11:执行sql语句
12:处理返回的结果集
13:关闭连接
现在解析完了sql,剩余额工作就是创建statement对象,对返回结果集的处理,我们下一期继续接着分析
Thanks!
网友评论