XiaoLin's Blog

Xiao Lin

Mybatis 的 Mapper 为什么可以无实现类执行SQL?

21
2024-02-04

在 Mybatis 中,Mapper 接口是定义 SQL 映射关系的接口,通过 XML 配置文件或注解的方式将接口中定义的方法与 SQL 语句进行映射。在使用 Mybatis 进行开发时,我们可以直接通过 Mapper 接口来执行 SQL,而不需要编写对应的实现类。

这种 Mapper 可以无实现类执行 SQL 的原因在于 Mybatis 在运行时动态代理了 Mapper 接口,生成了一个代理类,代理类中包含了实际执行 SQL 的代码。当通过 Mapper 接口调用 SQL 时,实际上是调用了生成的代理类中对应的方法,然后代理类再去执行 SQL,最终返回结果给调用方。

通过这种方式,我们可以很方便地进行 SQL 的操作,同时也避免了编写冗余的 SQL 代码和实现类代码的工作。

总之,Mybatis 的 Mapper 可以无实现类执行 SQL 是因为它在运行时动态代理了 Mapper 接口,生成了一个代理类来执行 SQL,从而简化了开发者的工作。

执行原理

总结一下Mapper无实现类执行SQL的关键点:

  1. Mapper接口方法通过动态代理的方式被调用;
  2. Mapper接口方法定义了SQL语句,通过接口方法中的注解信息生成对应的MapperMethod对象;
  3. MapperMethod对象负责执行对应的SQL语句,并返回结果;
  4. MapperRegistry类维护了Mapper接口与对应的MapperProxyFactory对象的关系,用于创建MapperProxy代理对象;
  5. SqlSession.getMapper方法根据Mapper接口获取对应的MapperProxy代理对象。

在mybatis启动过程中,会创建一个Configuration 对象,Configuration 对象维护着MapperRegistry 对象,在Configuration对象初始化时,会调用MapperRegistry的addMapper方法来注册Mapper接口。

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);
        }
      }
    }
  }

该方法首先会检测该Mapper是否已经注册过,如果注册过则抛出异常,防止重复注册。然后他会创建一个MapperProxyFactory 对象并将Mapper当作参数传入,MapperProxyFactory内就只有一个参数一个参数**mapperInterface ,是反射生成的对象**。MapperAnnotationBuilder类会解析Mapper接口中的注解信息,并根据注解信息来生成对应的MapperMethod对象。

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

}

在当Mapper注册完毕后,我们在获取Mapper都是通过sqlSession获取,获取的Mapper都是被

MapperProxy实现的动态代理类对象。

SqlSession.getMapper方法调用时,MyBatis会通过MapperRegistry.getMapper方法获取MapperProxyFactory对象,并调用它的newInstance方法创建一个MapperProxy代理对象。

  • sqlSession
@Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  • MapperRegistery
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

MapperProxyFactory的newInstance方法会创建一个MapperProxy代理对象。它接受一个SqlSession对象作为参数,并调用MapperProxy的构造方法创建代理对象。


  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

MapperProxy代理对象实现了Mapper接口,并拦截Mapper接口中的方法调用。我们可以看到,当调用的mapper的构造方法,则直接执行。其他方法会调用cachedInvoker 返回的对象,并调用他的invoker方法,

@Override
  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 方法会创建一个 PlainMethodInvoker 对象,该类是一个内部类,创建该类传入的MapperMethod 对象即是我们xml文件中与mapper方法名对应的sql语句

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        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 {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
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 {
      return mapperMethod.execute(sqlSession, args);
    }
  }

最终调用PlainMethodInvoker 对象的invoke方法完成sql的执行。