- mabatis 如何解析mapper.xml文件里的if等标签,生成执行语句?设置参数?
- 下面通过跟踪源码我们来查看下具体是如何执行。
- 这里我们可以看到我们的mapper接口是通过MapperProxy 生成的代理对象进行调用的。即诸如的是一个动态代理对象。
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 反射执行方法
@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 (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 生成并缓存MapperMethod 对象。method 为key,value 是MapperMethod 。这样就建立了method到
//MapperMethod 的映射关系。
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行调用
return mapperMethod.execute(sqlSession, args);
}
}
public class MapperMethod {
//
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;
}
}
private class SqlSessionInterceptor implements InvocationHandler {
// 拦截并获取sqlsession执行调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
public class DefaultSqlSession implements SqlSession {
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行器调用update方法做更新
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
public class CachingExecutor implements Executor {
// 缓存执行器调用的是其他是那种执行器的一种,即简单执行器 ,批量执行器,reuse 执行器。
private final Executor delegate;
//缓存执行器,默认二级缓存打开。
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
}
// 向下继续追踪,我们可以看到sqlNode 节点是我们把标签解析为sql的地方。静态sql。
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
//静态sql直接添加
context.appendSql(text);
return true;
}
}
}
// foreach 标签解析
public class ForEachSqlNode implements SqlNode {
public static final String ITEM_PREFIX = "__frch_";
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
applyOpen(context);
int i = 0;
for (Object o : iterable) {
DynamicContext oldContext = context;
if (first || separator == null) {
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
if (o instanceof Map.Entry) {
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else {
applyIndex(context, i, uniqueNumber);
applyItem(context, o, uniqueNumber);
}
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) {
first = !((PrefixedContext) context).isPrefixApplied();
}
context = oldContext;
i++;
}
applyClose(context);
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
}
// 标签解析的根
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 循环解析
contents.forEach(node -> node.apply(context));
return true;
}
}
// DynamicSqlSource 的getBoundSql 方法
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// parse解析,将形参去除,形成带?的sql.
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
//构建携带参数,带? sql 的对象。
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
// DefaultParameterHandler 的参数设置方法
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
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);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
// 类型转换器,参数设置方法 BaseTypeHandler jdbc 类型到java类型的转换
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
- 标签解析后的:foreach 解析
- 通过以上源码来看: 在上一篇我们解析完成后,MappedStatement的对象里的 private SqlSource sqlSource; 属性里记录了DynamicSqlSource 实现SqlSource 接口,其属性rootSqlNode 记录了解析的SqlNode。
private final SqlNode rootSqlNode; //MixedSqlNode
-
public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents;
- }
- 具体的标签解析器配合具体的入参来解析生成对应的sql.具体的标签解析器实现了SqlNode接口。
-
public interface SqlNode { boolean apply(DynamicContext context); }
- 参数设置:由类型转换器来设置参数,BaseTypeHandler基础的类型转换器实现TypeHandler<T> 接口。其他的转换器继承该基类。
- 由以上代码,我们可以提取一些关键的类。
-
Configuration: 配置类及容器。mybatis启动后构建这个对象时就会把所有相关的信息作为该对象的属性保存下来。
- 可以说 :这个类是mybatis的核心基础类,是一个mybatis的容器。
- MapperProxy : mapper接口生成动态代理对象的类。该类可以生成mapper接口的动态代理对象,从而注入。该对象包含了sqlSession, mapperInterface, methodCache 这三个属性。
- MapperRegistry : 这个类维护了mapper接口和其动态代理对象工厂(MapperProxyFactory)的映射关系。
- MapperProxyFactory: Mapper代理工厂负责生成MapperProxy 对象。也记录了Method, MapperMethod的映射缓存。
- MapperMethod : 记录了了sql执行命令和方法签名。其execute 方法根据不同的命令,调用SqlSession的执行方法。
- DefaultSqlSession: 实现了SqlSession接口,记录了Executor执行器,Configuration 配置类,具体的执行交给执行器执行sql语句。
- SimpleExecutor: 简单执行器。执行简单的增删改查。
- ReuseExecutor: 复用Statement 对象的执行器。缓存了sql与Statement 对象的映射关系。
- BatchExecutor: 批量操作执行器。
- CachingExecutor: 缓存执行器,开启二级缓存后使用,该执行器是加了缓存功能。执行sql的还是上边三种的一个。典型的装饰模式应用。
- TransactionalCacheManage:二级缓存开启后,使用它保存缓存。
- 推荐大家可以看一本书:《myBatis3源码深度解析》参照书可以来自己跟踪源码,带上问题去分析。
- 通过源码我们也可以提出一些问题用于面试,比如 mybatis有哪些执行器?执行器概念?执行器的区别?
- 如何设置参数的?怎么解析我们的foreach,if等标签??
- # 和$ 的解析有啥区别?