mybatis mapper 底层原理

发布时间 2023-07-26 17:51:23作者: 黄光跃

使用 SqlSession 的接口查询比较麻烦,MappedStatement 的 id 也是字符串容易出错,也不符合面向接口的编程方式。所以 mybatis 也支持使用 mapper 接口的方法来简化操作

初始化

前面分析初始化过程的时候有说到 MappedStatement 的维护,这一步是在解析映射文件的时候完成的,mapper 的注册也是在这一步,看下关键源码:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 就会走这里了
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else { 
                
                ...
                
            }
        }
    }
}

@MapperScan 注解会扫描所有的 mapper 接口文件,是根据配置的包名来扫描的,关键方法就是 addMappers(),看下这个方法:

// org.apache.ibatis.session.Configuration#addMapper
public void addMappers(String packageName, Class<?> superType) {
    // 维护 configuration.mapperRegistry
	mapperRegistry.addMappers(packageName, superType);
}

// org.apache.ibatis.binding.MapperRegistry#addMappers
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
        // 调用了 addMapper 方法
    	addMapper(mapperClass);
    }
}

// org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
    	if (hasMapper(type)) {
    		throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    	}
    	boolean loadCompleted = false;
    	try {
            // 维护 configuration.mapperRegistry.knownMappers 属性(创建代理对象的包装工厂)
            // SqlSession.getMapper(type) 获取一个包装工厂发,再根据包装工厂生成代理对象
    		knownMappers.put(type, new MapperProxyFactory<>(type));
    		MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            // 主要是这里
    		parser.parse();
    		loadCompleted = true;
    	} finally {
    		if (!loadCompleted) {
      			knownMappers.remove(type);
    		}
    	}
    }
}

parser.parse() 做了什么

// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse  
public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        // 这个方法会读取映射文件,维护 configuration.mappedStatements,原理和前面分析的一样
    	loadXmlResource();
    	configuration.addLoadedResource(resource);
    	assistant.setCurrentNamespace(type.getName());
    	parseCache();
    	parseCacheRef();
        // 遍历 mapper 接口中的方法,type 就是接口的 class
    	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 {
                parseStatement(method);
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new MethodResolver(this, method));
            }
    	}
    }
    parsePendingMethods();
}

至此 configuration.mappedStatements、configuration.mapperRegistry 都维护好了

工作流程

先看 SqlSession.getMapper(xxxMapper.class) 底层

// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
	return configuration.<T>getMapper(type, this);
}

// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	return mapperRegistry.getMapper(type, sqlSession);
}

// org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 获取 mapper 接口的代理工厂
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
     // 通过工厂生成一个代理对象返回
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

怎么生成代理对象?

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
    // 拿到工厂对象
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 调用下面的方法生成代理对象
    return newInstance(mapperProxy);
}

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

对象生成好了,就是调用方法了,其实调用的是代理对象的方法,显而易见会通过反射执行 invoke 方法

// org.apache.ibatis.binding.MapperProxy#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 先判断执行的方法是不是 Object 类的方法,比如tostring,hashcode 等方法,如果是,直接执行
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (isDefaultMethod(method)) {
            // 判断执行的方法是否为默认方法(jdk8的新东西),如果是则直接执行反射执行
            return invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
	// 利用接口、方法等信息,构造 mapperMethod 并进行缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 最终通过 MapperMethod 代理执行具体的数据库操作
    return mapperMethod.execute(sqlSession, args);
}

再看下 execute 方法源码:

// org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;	// 返回结果
    if (SqlCommandType.INSERT == command.getType()) { // INSERT 操作
      // 处理参数
      Object param = method.convertArgsToSqlCommandParam(args);
      // 调用 sqlSession 的 insert 方法 
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) { // UPDATE 操作 
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) { // DELETE 操作
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) { // SELECT 操作
      // 如果返回 void 并且参数有 resultHandler,调用 sqlSession.select() 
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        // 如果返回不是 void,是多个结果,就调用 sqlSession.selectList()  
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        // 如果返回类型是 MAP 则调用 executeForMap 方法 
        result = executeForMap(sqlSession, args);
      } else {
        // 否则就是查询单个对象
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else {
        // 接口方法没有和sql命令绑定
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // 如果返回值为空 并且方法返回值类型是基础类型 并且不是 VOID 则抛出异常  
    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;
}

可以看到最终还是根据 SqlSession 来操作的,感觉是饶了一下,SqlSession 可以完成的工作,多饶了一圈 mapper,最终还是使用 SqlSession,SqlSession 的工作原理前面分析过,这里就不分析了

总结

  1. 根据配置扫描 mapper 所在的包,根据 configuration.addMappers() 方法来维护 configuration.mappedStatements、configuration.mapperRegistry 属性
    1. mappedStatements 就是 MappedStatement 对象集合
    2. mapperRegistry 属性里面维护了一个 knownMappers 属性,knownMappers 是个 map,key 是 接口,value 是创建接口代理对象的工厂对象
  2. sqlSession.getMapper() 就是获取 configuration.mapperRegistry.knownMappers 的值,然后创建一个代理对象
  3. mapper.method() 就是执行代理对象的方法,底层还是调用了 sqlSession 的方法
  4. sqlSession 又到 configuration.mappedStatements 找到具体的 MappedStatement
  5. 然后又是 Executor 根据 MappedStatement 查询数据(缓存、处理参数、创建连接、映射结果)