在开发中使用mybatis,是将Mapper接口生成的代理类存入到spring IOC容器中。然后,通过注解获取代理对象,调用对应接口方法,间接调用到 mapper xml中的SQL。
如果想了解mybatis是如何通过接口实现SQL操作可以看一下这个:https://www.toutiao.com/article/7206541912168268343
一般接口都会有入参,和返回对象。那么mybatis是如何进行封装的呢?
mapper xml 文件中的语句 经常会使用一些标签 if 、foreach、set 、where 等,是如何实现的呢?
参数解析这一步骤,通常是在执行SQL语句之前就要完成的,因此,需要查看SQL执行之前的代码。问题是从哪里开始查看源码呢?既然不知道具体是哪里进行了参数解析,那就从 调用代理对象开始 。
代理对象调用方法执行 MapperProxy.invoke 方法,MapperProxy.invoke方法中调用 MapperProxy.cachedInvoker 方法,并且该方法中创建一个 MapperMethod 实例对象。
MapperMethod对象中有两个成员常量,SqlCommand 和 MethodSignature对象。后面会用到。
最终MapperProxy.cachedInvoker 方法返回一个 PlainMethodInvoker 对象,接着PlainMethodInvoker对象调用自己的 invoke 方法(注意:这就是一个invoke方法,跟代理没关系,不要被迷惑了)
调用方法后,执行 MapperMethod对象的 execute 方法。该方法中
method.convertArgsToSqlCommandParam(args),就是我们要找的参数解析方法。
这块代码可以不看。
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);
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());
}
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;
}
convertArgsToSqlCommandParam 方法,MethodSignature 对象是创建MapperMethod时一个成员常量。
ParamNameResolver 是 MethodSignature中的一个成员常量。
ParamNameResolver 对象中 ParamNameResolver 方法。该方法获取 带有@param注解的参数。
ParamNameResolver是用来解析 @param 类型的
public ParamNameResolver(Configuration config, Method method) {
//默认使用 实际参数名
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations 获取@Param注解的参数
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
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;
}
}
if (name == null) {
// @Param was not specified.
if (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);
}
用来解析实体参数 (param1, param2, ...)
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
}
//如果只有一个参数,并且没有使用@Param注解,就直接返回第一个参数
else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
}
//有多个参数,则封装成一个map,key为参数参数名称,
//参数使用@Param注解,名称就是注解中的值,否则key为arg0、arg1这种类型,
//同时,一定含有key为param1、param2的参数,值就是传入的值
else {
final Map param = new ParamMap<>();
int i = 0;
for (Map.Entry entry : names.entrySet()) {
//没有@Param注解,key为arg0、arg1这种类型
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
小结:
ParamNameResolver 对象解析参数,两种方法:
ParamNameResolver方法:解析形参,判断是否使用了@Param注解。
getNamedParams方法:封装实参,如果只有一个,并且没有使用@Param注解,就直接返回第一个参数值,否则封装成map。 多个参数,key为参数参数名称,使用@Param注解,key值就是注解中的值,否则key为arg0、arg1这种类型,并且,一定含有key为param1、param2。。。的参数,作为值传入。
上面的这一部分主要是将参数解析。
参数解析完成后,执行SqlSession中的方法。
以selectList方法为例,首先获取MappedStatement对象,给对象包含SQL。
wrapCollection(parameter) 方法对解析后的参数进行了再次封装。
对集合、数组、列表进行了二次封装,其他类型原样返回。
执行 executor.query(ms, wrapCollection(parameter), rowBounds, handler) 代码后,进入BaseExecutor类中query方法。
BoundSql boundSql = ms.getBoundSql(parameter);
通过 mappedStatement 对象,获取 BoundSql对象。
然后,根据mappedStatement 成员变量 sqlSource 调用 getBoundSql 。
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
ParameterObject :传入的参数。
一个参数对象时:为该参数的类型;
多个参数对象时:为ParamMap对象。
ParameterMapping:#{name}形式的引用变量,变量会在解析Mapper.xml文件中的语句时,就被替换成占位符“?”。ParameterMapping类记录对应的变量信息。
additionalParameters:使用Criteria对象的sql。
MetaObject:Mybatis在sql参数设置和结果集映射里经常使用到这个对象。
DynamicSqlSource或者RowSqlSource,前者表示动态SQL,后者表示静态SQL。
动态SQL:行该sql相关操作的时候才根据传入的参数进行解析。(mybatis 标签 if 、where 等)
以StaticSqlSource为例:
getBoundSql直接返回BoundSql对象。
代码返回到:BaseExecutor 类query方法中。
query 方法调用 queryFromDatabase 方法,queryFromDatabase 再调用
doQuery(ms, parameter, rowBounds, resultHandler, boundSql)方法。
doQuery 方法中 prepareStatement 就是给预编译SQL进行赋值的。
@Override
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//预编译sql,并且给参数赋值
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
prepareStatement方法中 handler.parameterize(stmt) 就是设置参数值。
调用PreparedStatementHandler 类的 parameterize方法。
parameterize方法中的 parameterHandler.setParameters((PreparedStatement) statement) 调用 DefaultParameterHandler setParameters 方法。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
千辛万苦终于是把解析的参数与SQL语句进行融合了。
本篇篇幅有点长,后期拆分一下。
页面更新:2024-04-30
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号