1.这里只是讲述CRUD的执行,在CRUD之前,你服务启动的时候,mybtis会把mapper和xml中的节点SQL通过反射加载到Configuration中,这个不做描述了,后面再补;
2.我们直接开始启动工程跟着代码一点点跟,看是如何执行的;
3.我的springframework版本是5.3.7 mybatis版本3.4.6 springboot版本2.5.0 jdk1.8;
注意:当版本不同时,执行的逻辑肯定是不同的,因为spring和mybatis都会进行不断的优化升级
当我们执行一个SQL的时候,会先通过org.springframework.aop.support包中的AopUtils类,来执行处理,调用了方法invokeJoinpointUsingReflection
这里入参有三个,贴下入参
1.可以看到这里的入参target是MapperProxy类,method是你的SQL方法,args是你的请求入参2.invokeJoinpointUsingReflection 方法里面,主要有两行代码
// 这里面就做了一件事情使用 method.setAccessible(true) 改变了访问权限 ReflectionUtils.makeAccessible(method); // 这个里面invoke了一堆代码,主要看这里 return method.invoke(target, args);
这里可以看到invoke会进入到Java Method类中的invoke,我们主要看mybatis就不介绍java的这些方法了(有的有点复杂,我也没看懂),这里可以看到进来之后继续到底下执行invoke,这个时候是调用的DelegatingMethodAccessorImpl类中的invoke来进行的执行
可以看到DelegatingMethodAccessorImpl类中,这里的invoke是调用了MethodAccessorImpl的invoke,这里实际指向的对象是NativeMethodAccessorImpl这个类
可以看到这里最后跑到了NativeMethodAccessorImpl类中,执行的invoke0,invoke0方法是用native修饰的,说明这是调用的底层非java方法,调用的C,C++写的东西了(我也看不懂底层到底干了啥,JAVA设计的真负责,C我也木有学习过)
接下来我们终于可以看框架源码了,框架看起来还是比较简单的,没java那么复杂了,我们可以看到这里最终调用的是MapperProxy类中的invoke方法,里面入参有三个,就是上面介绍invokeJoinpointUsingReflection方法的三个入参,一摸一样
// 代码直接贴出来吧,这里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);
// 这里跳到连接【https://blog.csdn.net/qq_43555164/article/details/108254738】看下原因
} 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);
}
// cachedMapperMethod方法,入参是你的method
private MapperMethod cachedMapperMethod(Method method) {
// 这里先根据methodCache拿,看能不能拿出来,methodCache是一个Map
// private final Map methodCache;
MapperMethod mapperMethod = methodCache.get(method);
// 为空就去new对象去了
if (mapperMethod == null) {
// 别小看这一行代码,里面写了一堆(我yue你仙人,我还以为就new了个对象,写那么复杂干啥?脑子被门挤了吗?)
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
// 把这俩玩意丢的map缓存起来
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
--------------------------------------------华丽的分割线--------------------------------------------
// 这里介绍下new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); 我再yue你一次仙人,G2加油,干翻T1
// 这里实例化的时候会给对象里面的俩属性new一下,new的底下这两个东西
// private final SqlCommand command;
// private final MethodSignature method;
// 介绍下三个入参
// mapperInterface: 这个就是你的mapper接口
// method: 这个是你执行的mapper里面的哪个SQL方法
// config: mybatis初始化的时候,把一堆东西丢config缓存里面了
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
// 这里new对象里面写的东西有点多,底下贴吧
this.command = new SqlCommand(config, mapperInterface, method);
// 这里new对象里面写的东西有点多,底下贴吧
this.method = new MethodSignature(config, mapperInterface, method);
}
可以看到这个SqlCommand对象,是在MapperMethod类里面的,还是个public类,估计那会想的能给其他地方也用
可以看到这里面写了一堆东西,接下来贴代码,看看这里面干了点啥
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {
// 获取method的名字的 我这里方法名字是【queryUser】
final String methodName = method.getName();
// 获取method是属于哪个接口的 我这里是【interface com.example.demo.Mapper.UserMapper】
final Class> declaringClass = method.getDeclaringClass();
// 调用了私有方法,又拿了个对象,看看这里面干了点啥,底下贴代码 G2牛逼,干翻了T1,nice
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
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 = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
// 这里源码的私有方法,入参四个,上面都讲了,这里不再讲了
// 需要注意【mapperInterface】和【declaringClass】,虽然我感觉是一摸一样,但是应该是有区别的(我现在还没发现哪里有区别)
private MappedStatement resolveMappedStatement(Class> mapperInterface, String methodName,
Class> declaringClass, Configuration configuration) {
// 拿到俩name组成一个ID,我的是【com.example.demo.Mapper.UserMapper.queryUser】
String statementId = mapperInterface.getName() + "." + methodName;
// 这里configuration参数的作用,判断了下是否有这个ID和返回对应的缓存中的对象,底下看下这个方法干了点啥
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
// 需要注意下这个俩方法在configuration类里面
public boolean hasStatement(String statementName) {
// 调了内部的方法hasStatement
return hasStatement(statementName, true);
}
public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.containsKey(statementName);
}
// 先陪女朋友过520,后面再补



