mybatis源码之mapper接口扫描原理分析

本文将通过阅读源码的方法分析session.getMapper(Class)的实现原理。

系列文档:


示例代码

mapper接口

public interface BlogMapper {

  Blog selectBlogById(Integer id);

  int insertBlog(Blog blog);

  List<Blog> selectBlogByParameter(BlogSearchParameter parameter);
}

xml mapper配置文件

就是一个普通的配置文件,有两点需要注意:

  • namespace与接口的全类名对应
  • SQL的id与接口的方法名对应。
<mapper namespace="org.net5ijy.mybatis.test.mapper.BlogMapper">
  <resultMap id="BlogResultMap" type="org.net5ijy.mybatis.test.entity.Blog">
    <id column="id" property="id"/>
    <id column="title" property="title"/>
    <id column="content" property="content"/>
    <result column="create_time" property="createTime"/>
    <result column="update_time" property="updateTime"/>
  </resultMap>

  <insert id="insertBlog" parameterType="org.net5ijy.mybatis.test.entity.Blog" 
          useGeneratedKeys="true" keyProperty="id">
    insert into blog
    (title, content, create_time, update_time)
    values
    (#{title}, #{content}, now(), now())
  </insert>

  <select id="selectBlogById" resultMap="BlogResultMap" parameterType="java.lang.Integer">
    select * from blog where id = #{id}
  </select>

  <!-- other sql configuration -->
</mapper>

主配置文件

<configuration>
  <properties resource="jdbc.properties"/>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <!-- 这里还是引入xml mapper配置文件 -->
    <mapper resource="mapper/BlogMapper.xml"/>
  </mappers>
</configuration>

测试类

public class BlogMapperTest2 {

  private SqlSession session;

  private BlogMapper blogMapper;

  @Before
  public void before() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 注意这里的session还是非自动提交事务的
    this.session = sessionFactory.openSession();
    this.blogMapper = this.session.getMapper(BlogMapper.class);
  }

  @After
  public void after() {
    this.session.close();
  }

  @Test
  public void testInsertBlog() {
    Blog blog = new Blog();
    blog.setTitle("spring学习");
    blog.setContent("spring深入 - 源码分析");

    int rows = this.blogMapper.insertBlog(blog);

    System.out.println(rows);
    System.out.println(blog.getId());
  }

  @Test
  public void testSelectBlogById() {
    Blog blog = this.blogMapper.selectBlogById(1);
    System.out.println(blog);
  }

  // other test case
}

源码分析

入口

BlogMapper blogMapper = this.session.getMapper(BlogMapper.class);

执行的是DefaultSqlSession的getMapper(Class)方法:

public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

configuration.getMapper方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

mapperRegistry.getMapper方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  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);
  }
}

方法中的knownMappers是一个成员变量,Map结构保存Class -> MapperProxyFactory映射关系:

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

此时knownMappers中是存在元素的,就是我们要get的BlogMapper.class与其对应的MapperProxyFactory的映射。

那么这个knownMappers是什么时候put值的呢?

在addMapper(Class)方法中有knownMappers的put操作,只要知道addMapper方法在哪里被调用,就可以知道knownMappers的初始化入口了。

addMapper(Class)方法

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 {
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // It's important that the type is added before the parser is run
      // otherwise the binding may automatically be attempted by the
      // mapper parser. If the type is already known, it won't try.
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

经过debug知道了addMapper(Class)方法被Configuration的addMapper(Class type)方法调用,而Configuration的addMapper(Class type)方法又被XMLMapperBuilder的bindMapperForNamespace方法和XMLConfigBuilder的mapperElement方法调用,因为我们在主配置文件不是使用mapperClass方式配置的mapper标签,所以在当前示例应该执行的是XMLMapperBuilder的bindMapperForNamespace方法,bindMapperForNamespace方法被XMLMapperBuilder.parse方法调用:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

这段代码很眼熟,就是之前初始化阶段解析主配置文件的逻辑。

解析mapper接口和statement:

addMapper(Class)方法的这两行代码值得注意一下:

MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();

// parser.parse()
public void parse() {
  String resource = type.toString();
  if (!configuration.isResourceLoaded(resource)) {
    // 此时xml mapper配置文件都解析完成了,这个方法的逻辑就不会执行了
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 缓存,不展开分析
    parseCache();
    parseCacheRef();
    // 遍历所有方法
    for (Method method : type.getMethods()) {
      // 判断方法是否可以转为statement
      // 基本都可以的
      if (!canHaveStatement(method)) {
        continue;
      }
      // 我们的接口方法没有使用注解方式,所以这个分支进不来
      // 后续的注解mapper分析再做展开
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
        parseResultMap(method);
      }
      try {
        // 方法转statement
        // 核心是根据方法标注的Select、Update、Insert、Delete、SelectProvider、
        // UpdateProvider、InsertProvider、DeleteProvider等注解创建statement
        // 我们使用的不是注解方法,不展开分析
        parseStatement(method);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}

回到mapperRegistry.getMapper方法

这段代码很重要,所以此处又一次记录:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  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);
  }
}

// mapperProxyFactory.newInstance(sqlSession)
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
  // 创建java代理
  return (T) Proxy.newProxyInstance(
                      mapperInterface.getClassLoader(),
                      new Class[] { mapperInterface },
                      mapperProxy);
}

简单介绍一下Proxy.newProxyInstance方法创建java的参数:

  • ClassLoader - 类加载器对象
  • Class<?>[] - 代理的interface
  • InvocationHandler - 处理器

重点在InvocationHandler参数,这是一个接口:

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

代理不是我们讨论的内容,我们看一下MapperProxy类的invoke方法实现,核心的代码就在这个方法里面。

MapperProxy类

核心在invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    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方法
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
  try {
    MapperMethodInvoker invoker = methodCache.get(method);
    if (invoker != null) {
      return invoker;
    }

    return methodCache.computeIfAbsent(method, m -> {
      // 因为方法都不是default的,所以执行else分支
      if (m.isDefault()) {
        try {
          if (privateLookupInMethod == null) {
            return new DefaultMethodInvoker(getMethodHandleJava8(method));
          } else {
            return new DefaultMethodInvoker(getMethodHandleJava9(method));
          }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
          throw new RuntimeException(e);
        }
      } else {
        // 执行这里,创建PlainMethodInvoker对象返回
        return new PlainMethodInvoker(
            new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
    });
  } catch (RuntimeException re) {
    Throwable cause = re.getCause();
    throw cause == null ? re : cause;
  }
}

// MapperMethodInvoker接口
interface MapperMethodInvoker {
  Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}

// PlainMethodInvoker类
private static class PlainMethodInvoker implements MapperMethodInvoker {
  private final MapperMethod mapperMethod;

  public PlainMethodInvoker(MapperMethod mapperMethod) {
    super();
    this.mapperMethod = mapperMethod;
  }

  @Override
  public Object invoke(
      Object proxy, Method method,
      Object[] args, SqlSession sqlSession) throws Throwable {
    // mapper方法在这里执行
    return mapperMethod.execute(sqlSession, args);
  }
}

MapperMethod类

MapperMethod类的execute方法负责执行mapper方法,如下:

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  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("...");
    }
    return result;
  }
}

以查询多条数据为例:

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  List<E> result;
  // 将原始的方法参数转为Statement需要的参数形式
  Object param = method.convertArgsToSqlCommandParam(args);
  // 查询数据
  if (method.hasRowBounds()) {
    RowBounds rowBounds = method.extractRowBounds(args);
    result = sqlSession.selectList(command.getName(), param, rowBounds);
  } else {
    result = sqlSession.selectList(command.getName(), param);
  }
  // 这个分支是非List返回值的处理
  if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    if (method.getReturnType().isArray()) {
      // 数组类型
      return convertToArray(result);
    } else {
      return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    }
  }
  return result;
}

可以看到内部就是使用的sqlSession.selectList方法做的查询操作,后续的逻辑我们在执行SQL源码分析中做过分析了,此处分析到这里就可以了。

其余的insert、update操作基本相同。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值