Mybatis源码系列文章
手写源码(了解源码整体流程及重要组件)
Mybatis源码解析(一):环境搭建
Mybatis源码解析(二):全局配置文件的解析
Mybatis源码解析(三):映射配置文件的解析
Mybatis源码解析(四):sql语句及#{}、${}的解析
Mybatis源码解析(五):SqlSession会话的创建
Mybatis源码解析(六):缓存执行器操作流程
Mybatis源码解析(六):查询数据库主流程
Mybatis源码解析(七):Mapper代理原理
@Test
public void test2() throws IOException {// 1. 通过类加载器对配置文件进行加载,加载成了字节输入流,存到内存中 注意:配置文件并没有被解析InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");// 2. (1)解析了配置文件,封装configuration对象 (2)创建了DefaultSqlSessionFactory工厂对象SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);// 3. (1)创建事务对象 (2)创建了执行器对象cachingExecutor (3)创建了DefaultSqlSession对象SqlSession sqlSession = sqlSessionFactory.openSession();// 4. JDK动态代理生成代理对象UserMapper mapperProxy = sqlSession.getMapper(UserMapper.class);// 5.代理对象调用方法User user = mapperProxy.findUserById(100);System.out.println("MyBatis源码环境搭建成功....");sqlSession.close();
}
先说个结论,后续源码验证:如果不指定xml,则会在Mapper接口同目录下寻找
进入解析
标签方法
private void mapperElement(XNode parent) throws Exception {if (parent != null) {// 获取标签的子标签for (XNode child : parent.getChildren()) {// 子标签if ("package".equals(child.getName())) {// 获取mapper接口和mapper映射文件对应的package包名String mapperPackage = child.getStringAttribute("name");// 将包下所有的mapper接口以及它的代理工厂对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂configuration.addMappers(mapperPackage);} else {// 子标签// 获取子标签的resource属性String resource = child.getStringAttribute("resource");// 获取子标签的url属性String url = child.getStringAttribute("url");// 获取子标签的class属性String mapperClass = child.getStringAttribute("class");// 它们是互斥的if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);try(InputStream inputStream = Resources.getResourceAsStream(resource)) {// 专门用来解析mapper映射文件XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());// 通过XMLMapperBuilder解析mapper映射文件mapperParser.parse();}} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);try(InputStream inputStream = Resources.getUrlAsStream(url)){XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());// 通过XMLMapperBuilder解析mapper映射文件mapperParser.parse();}} else if (resource == null && url == null && mapperClass != null) {Class> mapperInterface = Resources.classForName(mapperClass);// 将指定mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}
}
进入configuration的addMappers方法
public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);
}
public class MapperRegistry {private final Configuration config;private final Map, MapperProxyFactory>> knownMappers = new HashMap<>();...
}
进入mapperRegistry的addMappers方法
public void addMappers(String packageName) {addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class> superType) {ResolverUtil> resolverUtil = new ResolverUtil<>();// 根据package名称,加载该包下Mapper接口文件(不是映射文件)resolverUtil.find(new ResolverUtil.IsA(superType), packageName);// 获取加载的Mapper接口Set>> mapperSet = resolverUtil.getClasses();for (Class> mapperClass : mapperSet) {// 将Mapper接口添加到MapperRegistry中addMapper(mapperClass);}
}
resolverUtil.find方法

public ResolverUtil find(Test test, String packageName) {String path = getPackagePath(packageName);try {List children = VFS.getInstance().list(path);for (String child : children) {if (child.endsWith(".class")) {addIfMatching(test, child);}}} catch (IOException ioe) {log.error("Could not read package: " + packageName, ioe);}return this;
}
resolverUtil.getClasses()
private Set> matches = new HashSet<>();
...
public Set> getClasses() {return matches;
}
addMapper方法
public void addMapper(Class type) {if (type.isInterface()) {// 如果Map集合中已经有该mapper接口的映射,就不需要再存储了if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {// 将mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂knownMappers.put(type, new MapperProxyFactory<>(type));// 用来解析注解方式的mapper接口MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);// 解析注解方式的mapper接口parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}
}
public void parse() {// 获取mapper接口的全路径String resource = type.toString();// 是否解析过该mapper接口if (!configuration.isResourceLoaded(resource)) {// 先解析mapper映射文件loadXmlResource();// 设置解析标识configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());// 解析CacheNamespace注解parseCache();// 解析CacheNamespaceRef注解parseCacheRef();for (Method method : type.getMethods()) {if (!canHaveStatement(method)) {continue;}if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()&& method.getAnnotation(ResultMap.class) == null) {parseResultMap(method);}try {// 每个mapper接口中的方法,都解析成MappedStatement对象parseStatement(method);} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();
}
进入上步骤的xml解析方法loadXmlResource方法
private void loadXmlResource() {if (!configuration.isResourceLoaded("namespace:" + type.getName())) {String xmlResource = type.getName().replace('.', '/') + ".xml";// #1347InputStream inputStream = type.getResourceAsStream("/" + xmlResource);if (inputStream == null) {// Search XML mapper that is not in the module but in the classpath.try {inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);} catch (IOException e2) {// ignore, resource is not required}}if (inputStream != null) {XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());xmlParser.parse();}}
}
sqlSession.getMapper(UserMapper.class)通过Mapper接口Class对象生成代理对象
@Override
public T getMapper(Class type) {// 从Configuration对象中,根据Mapper接口,获取Mapper代理对象return configuration.getMapper(type, this);
}
public T getMapper(Class type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);
}
public T getMapper(Class type, SqlSession sqlSession) {// 根据Mapper接口的类型,从Map集合中获取Mapper代理对象工厂final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {// 通过MapperProxyFactory生产MapperProxy,通过MapperProxy产生Mapper代理对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}
}
进入newInstance方法
public T newInstance(SqlSession sqlSession) {// InvocationHandler接口的实现类final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);
}protected T newInstance(MapperProxy mapperProxy) {// 使用JDK动态代理方式,生成代理对象return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
根据jdk动态代理可知,调用接口方法则会进入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 {// 代理逻辑在这return cachedInvoker(method).invoke(proxy, method, args, sqlSession);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}
}
进入cachedInvoker(method).invoke方法(代理逻辑)
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {Object result;// 判断mapper中的方法类型switch (command.getType()) {// 添加case INSERT: {// 转换参数Object param = method.convertArgsToSqlCommandParam(args);// 最终调用的还是sqlSession中的方法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:// 无返回结果,并且有ResultHandler方法参数,将查询结果交给ResultHandler进行处理if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;// 执行查询、返回列表} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);// 执行查询、返回Map} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);// 执行查询、返回Cursor} 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;
}
补充说明下SqlCommand: command对象
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {// 当前调用的方法名称final String methodName = method.getName();// 当前执行的方法对应的Classfinal Class> declaringClass = method.getDeclaringClass();// 获取对应的MappedStatementMappedStatement 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);}}
}