Mybatis的底层原理及其动态代理学习

原理:
解析及初始化过程

根据mybatis配置文件(xml,mapper),通过XPath将xml转化为Resource,将Resource转化为Document。将Document转化为Configuration。这个过程包括了对 Mapper 接口、Mapper.xml文件的解析,将当前映射文件所对应的DAO接口的Class对象,也就是节点上定义的namespace属性(该Class对象存储在configuration对象里的属性MapperRegistry中有一个叫做knownMappers的map中,这里是关键后面为动态代理使用)解析成Class对象注册进configuration中,这主要是为了给DAO接口创建代理对象,并将解析结果(包含了sql参数,返回类型)注册进configuration中,然后sqlsessionFactoryBuilder会根据configuration对象获取sqlsessionFactory ,用来开启sqlsession。解析xml是一系列非常臃肿的代码,随便过一下就好,后面动态代理才是关键点

Configuration的addMapper方法用于注册对应的dao层接口类及动态代理工厂,实际上Configuration类里面通过MapperRegistry对象维护了所有要生成动态代理类的XxxMapper接口信息,可见Configuration类确实是相当重要一个类

public class Configuration {
    ...
    protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    ...
    public  void addMapper(Class type) {
      mapperRegistry.addMapper(type);
    }
    public  T getMapper(Class type, SqlSession sqlSession) {
      return mapperRegistry.getMapper(type, sqlSession);
    }
    ...
}

通过addMapper注册dao层需要代理的类及工厂MapperProxyFactory到MapperRegistry 中

 public  void addMapper(Class type) {
    // 这个class必须是一个接口,因为是使用JDK动态代理,所以需要是接口,否则不会针对其生成动态代理
    if (type.isInterface()) {
    ...
      try {
        // 生成一个MapperProxyFactory,用于之后生成动态代理类
        knownMappers.put(type, new MapperProxyFactory<>(type));
        //以下代码片段用于解析我们定义的XxxMapper接口里面使用的注解,这主要是处理不使用xml映射文件的情况
    ...
}

MapperRegistry中有一个map叫knownMappers,类型为Map, MapperProxyFactory>,它是一个Map,key为dao接口的class对象,而value为该dao接口代理对象的工厂,这里是为了后面动态代理做准备的

public class MapperRegistry {
  private final Configuration config;
  private final Map, MapperProxyFactory> knownMappers = new HashMap, MapperProxyFactory>();
}
动态代理及执行过程:

当MyBatis初始化及解析完毕后,configuration对象中存储了所有DAO接口的Class对象和相应的MapperProxyFactory对象,接下来,就到了使用DAO接口中函数的阶段了

当调用dao对应的api时会调用sqlsession的getMapper内部是调用configuration中getMapper方法,将dao接口做为被代理对象传入内部的MapperRegistry中,MapperRegistry又会调用getMapper的方法,getMapper内部是会获取knownMappers中对应的MapperProxyFactory,然后调用MapperProxyFactory的newIntance方法,这里是使用jdk动态代理的方式为 dao层的Mapper接口生成 MapperProxy。

MapperProxy调用invoke()方法获取MapperMethod类,MapperMethod类是整个代理机制的核心类,MapperMethod主要是对SqlSession中的操作进行了封装和使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点,每个节点都会生成一个MappedStatement类,MapperMethod类的execute()内部使用switch case语句根据SqlCommand的getType()方法,判断要执行的sql类型,比如INSET、UPDATE、DELETE、SELECT和FLUSH,然后分别调用SqlSession的增删改查等方法。MethodSignature用来封装方法的参数以及返回类型。MapperMethod类的execute()内部通过判断SqlCommand的操作类型调用sqlsession相应方法完成和数据库的交互,根据MethodSignature解析的参数和返回类型传入sqlsession相应方法中作为对应的参数。

获取到MapperProxy类的代理对象后,mapper接口调用相应的方法时其实就是调用MapperMethod类的execute()方法,execute()调用已经传入相应参数sqlsession的相应方法来完成对数据库的操作。

总结整个逻辑是:SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> MapperProxy#invoke()——> MapperMethod#execute()

sqlSession.getMapper()调用了configuration.getMapper(),那我们再看一下configuration.getMapper():

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

configuration.getMapper()又调用了mapperRegistry.getMapper(),根据dao层的接口类从knownMappers中获取对应的MapperProxyFactory,然后调用newInstance创建代理:

public  T getMapper(Class type, SqlSession sqlSession) {
//根据dao层的接口类从knownMappers中获取对应的MapperProxyFactory
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    ...
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory中newInstance方法,这里就是典型的动态代理了,被代理接口是mapperInterface ,代理对象是MapperProxy

  protected T newInstance(MapperProxy mapperProxy) {
    //这里使用JDK动态代理,通过Proxy.newProxyInstance生成动态代理类
    // newProxyInstance的参数:类加载器、接口类、InvocationHandler接口实现类
    // 动态代理可以将所有接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

InvocationHandler接口的实现类是MapperProxy,最后是调用了mapperMethod.execute(sqlSession, args),其源码如下:

public class MapperProxy implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class mapperInterface;
  private final Map methodCache;

  public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @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 if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //调用XxxMapper接口自定义的方法,进行代理
    //首先将当前被调用的方法Method构造成一个MapperMethod对象,然后掉用其execute方法真正的开始执行。
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
  ...
}

核心类是MapperMethod,是返回的代理对象最终要执行的是MapperMethod的execute方法

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()) {
      //insert语句的处理逻辑
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      //update语句的处理逻辑
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      //delete语句的处理逻辑
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      //select语句的处理逻辑
      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);
          //调用sqlSession的selectOne方法
          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;
  }
  ...
}

参考:https://www.cnblogs.com/hopeofthevillage/p/11384848.html,https://segmentfault.com/a/1190000019130222,https://www.cnblogs.com/qingchen521/p/10327440.html

  1. 用户程序调用 mybatis 中 API 的接口后,sqlsession 根据 statement ID 找到 mapperstatement。通过 exactuator 执行器对 mapperstatement 进行解析,包括动态 sql 拼接,参数转换,结果映射等,获得 jdbcstatement。jdbc 执行 jdbcstatement
  2. 最后根据 mapperstatement 中的结果映射对返回结果进行转换为对应的 map 或者 JAVAbean。
一级缓存和二级缓存
  1. Mybatis 一级缓存作用域是 session,session commit 之后缓存就失效了。
  2. Mybatis 二级缓存作用域是 sessionfactory,该缓存是以 namespace 为单位的(也就是一个 Mapper.xml 文件),不同 namespace 下的操作互不影响。
  3. 所有对数据表的改变操作都会刷新缓存。但是一般不要用二级缓存,例如在 UserMapper.xml 中有大多数针对 user 表的操作。但是在另一个 XXXMapper.xml 中,还有针对 user 单表的操作。这会导致 user 在两个命名空间下的数据不一致。
  4. 如果在 UserMapper.xml 中做了刷新缓存的操作,在 XXXMapper.xml 中缓存仍然有效,如果有针对 user 的单表查询,使用缓存的结果可能会不正确,读到脏数据。

你可能感兴趣的:(Mybatis的底层原理及其动态代理学习)