1. 在使用Mybatis时我们通常要写一个接口,然后XML里写对应的sql语句,那我们在调用接口的时候是如何跟XML中的语句做绑定的呢 下面我们分析下。
Mapper接口和XMLSQL绑定是在binding包中实现的。通过类名Proxy我们可以想到他是通过动态代理来实现。上图中SqlSession的getMapper方法最终调用的是MapperRegistry中的getMapper方法
下面对该包的类依次分析下
2.MapperRegistry通过MapperProxyFactory代理工厂来生产Mapper代理,它的knownMappers属性存的就是我们项目里写的所有Mapper接口,key就是Mapper接口的Class对象,Vaule是接口对象的代理工厂。
3.MapperProxyFactory类 顾名思义他是Mapper代理的工厂,用来生产Mapper接口的代理。它有两个属性
(1)mapperInterface 指的就是我们写的Mapper接口, 例如我有个 UserMapper接口,mapperInterface 就是 UserMapper.class
(2) methodCache 是Mapper接口中的方法缓存, 例如我的UserMapper中有一个 selectUserById ,methodCache存的KEY就是selectUserById这个方法 value就是MapperMethod包含方法详细信息。
最终通过newInstance方法生成我们的Mapper代理。
4.然后看下MapperMethod类,他是用来记录Mapper中方法的详细信息,比如方法操作类型是 SELECT INSERT 或者 DELETE..,返回值的类型是啥,返回值是不是集合,map等 ,
那么mybatis是咋知道你的接口是查询,删除还是修改呢。如下它有俩属性 我们重点看下SqlCommand的构造器
(1)SqlCommand内部类,重点来了!resolveMappedStatement方法返回的MappedStatement对象包含的就是我们XML中的SQL语句信息,它是用methodName查的,这也就是为什么我们接口方法要和XML
中 id一样的原因。
点进resolveMappedStatement中的hasStatement方法继续看
buildAllStatements方法将XML中写的所有sql语句封装成MappedStatement存到map中,继续进去看
parseStatementNode方法解析XNM文档
解析XML文档,最后builderAssistant.addMappedStatement 添加到全局配置Configuration中。
/**
* 解析select、insert、update、delete这四类节点
*/
public void parseStatementNode() {
// 读取当前节点的id与databaseId
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 验证id与databaseId是否匹配。MyBatis允许多数据库配置,因此有些语句只对特定数据库生效
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 读取节点名
String nodeName = context.getNode().getNodeName();
// 读取和判断语句类型
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 处理语句中的Include节点
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 参数类型
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
// 语句类型
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 处理SelectKey节点,在这里会将KeyGenerator加入到Configuration.keyGenerators中
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 此时, 和 节点均已被解析完毕并被删除,开始进行SQL解析
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
// 判断是否已经有解析好的KeyGenerator
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
// 全局或者本语句只要启用自动key生成,则使用key生成
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 读取各个配置属性
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 在MapperBuilderAssistant的帮助下创建MappedStatement对象,并写入到Configuration中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
然后上文说的SqlCommand对象就知道了Mapper接口的方法对应的是SELECT UPDATE还是delete等操作了。
5.下面我们来看下代理中做了嘛操作
重点看下invoke方法,try catch部分是处理是处理继承Object或接口的默认方法,不需要参与代理。
下面cachedMapperMethod方法,先从方法缓存查,查不到new出来存上再返回。再强调一下MapperMethod就是你Mapper接口的方法,比如有一个selectUserById方法,MapperMethod就是它,最终调用MapperMethod的execute方法带着你的接口方法信息去绑定XML中的SQL语句,最后执行真正的数据库操作。
/**
* @author Clinton Begin
* @author Eduardo Macarron
*
* 参见代理模式,这是代理类本身,通过invoke方法代理被代理对象的操作
*
*/
public class MapperProxy implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
// 该Map的键为方法,值为MapperMethod对象。通过该属性,完成了MapperProxy内(即映射接口内)方法和MapperMethod的绑定
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) { // 继承自Object的方法
// 直接执行原有方法
return method.invoke(this, args);
} else if (method.isDefault()) { // 默认方法
// 执行默认方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 找对对应的MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 调用MapperMethod中的execute方法
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
5. 从execute方法点进去,从switch代码中可以看出, 会根据你的操作类型做相应的操作
假设是一个UPDATE语句,从sqlSession点进去,MappedStatement包含的就是XML中我们的sql语句信息,上文已经提到,XML文件中所有的SQL语句已经解析并存到Configuration全局配置中,
获取后会交给Executor执行器去执行数据库操作, 后边怎么组装参数连接数据库执行,怎么接收结果根据泛型封装我们需要的结果,这些要具体分析mybatis exeutor包下的代码了。exeutor包也是mybatis最核心的包。