Mybatis源码概览(二) ---Plugin扩展与Spring结合原理

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

    本文主要介绍Mybatis通过动态代理避免对sqlSession直接调用,而是通过MapperProxy代理技术生成了具体dao接口的Mapper实例,里面封装了对sqlSession的调用;Mybatis预留了Interceptor接口,用户可以扩展该接口,实现自定义插件;Mybatis与Spring结合主要通过Spring 的FactoryBean技术实现;


MapperProxy

    把Mybatis源码概览(一)中的helloWorld例子的注释代码打开 如下

BlogDao mapper = session.getMapper(BlogDao.class);
List blogs= mapper.selectAll();

    这样通过session.getMapper获取某个Dao接口的Mapper代理实例,这样后面查询就不需要直接对sqlSession来操作,在具体应用中会省略掉很多代码。具体生产Mapper代理原理我们可以Debug一步一步分析。

    第一加载解析Mybatis配置文件时 跟进代码,下面这个片段是解析Mybatis几大属性 其中最后有个mappers

//XMLConfigBuilder类中
private void parseConfiguration(XNode root) {
  try {
    Properties settings = settingsAsPropertiess(root.evalNode("settings"));
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectionFactoryElement(root.evalNode("reflectionFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    //解析具体的mapper映射,转到下断代码
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

   

//XMLConfigBuilder类中
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 {
        //解析通过xml resource引用其他mapper文件的方式
        //
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
           //获取到mapper映射文件 然后进行解析到configuration,
          //这样以后可以直接通过configuration取到该mapper
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //执行解析,此步过后,可以在configuration中看到MapperRegistry属性里面已经有实例,
          //而且其knownMappers已经有值,这一步主要调用MapperRegistry类中的addMapper方法
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

//MapperRegistry类

//解析到mapper对应的dao接口 添加进去,并为该接口实例一个MapperProxyFacotry
//(里面就是通过JDK Proxy动态生产一个具体接口实例)
public  void addMapper(Class type) {
  if (type.isInterface()) {
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      //为每个mapper接口添加一个MapperProxyFacotry
      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);
      }
    }
  }
}


    这里详细介绍下MapperMethod,MapperProxy,MapperProxyFactory,MapperRegistry这四个类之间的关系:

MapperRegistry可以理解为一个map ,维护了Mapper接口与具体的MapperProxyFactory的关系,registry一般直接放在congfiguration中,可以直接用来查询;获得到具体MapperProxyFactory之后,MapperProxyFactory主要是在newInstance中调用Proxy.newInstance来生产对应Mapper接口的具体MapperProxy,MapperProxy实现了InvocationHandler接口,这个MapperProxy为了提高效率,里面为该Mapper对应的每一个方法维护了一个对应的MapperMethod,这样实现了对MapperMethod的重复利用;MapperProxy在invoke方法中会根据调用的mapper方法名找一个对应的MapperMethod来执行具体的调用sqlSession发起SQL请求;如果没找到就new一个 ,并放到map缓存起来;

这里主要是通过动态代理技术把MapperMethod对sqlSession的执行操作封装到Mapper代理中

    下面主要看下MapperProxy和MapperMethod源码

 MapperProxy类

//实现了InvocationHandler,并把每个mapper方法对应的MapperMethod缓存起来
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 {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    //通过mapperMethod发起具体的请求
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  //缓存MapperMethod实例,实现对象重复利用
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

    MapperMethod类

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

 //---------省略代码-----//
 //可以看到最后还是转换到对sqlSession的调用
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断具体SQL类型 执行增删改查操作
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      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);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      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;
  }
  
  //-------省略代码--------//
 }


    上面代理生产的准备工作完成后,我们在执行mapper接口具体方法时会到configuration->mapperRegistry中查找MapperProxy,返回具体的mapperProxy实例

//MapperRegistry类
public  T getMapper(Class type, SqlSession sqlSession) {
  final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
  }
}

至此通过动态代理技术返回代理对象,实现通过mapper接口直接调用就完成了。


Plugin

    Mybatis预留了Interceptor接口来实现Plugin技术,这里只简单介绍一下

    首先实现一个Interceptor接口类,然后在配置xml中配置plugin。这样就可以根据Interceptor拦截我们需要的执行过程,比如可以动态修改MappedStatement,实现对SQL,SQL参数的修改。

    这里介绍一个比较好用的分页插件 就是利用该原理实现:Mybatis分页插件 github https://github.com/pagehelper/Mybatis-PageHelper


Mybatis-Spring

    一般扩展spring 都会用到spring schema技术,并实现相应的handler 解析类,解析相应的扩展配置,这里不多介绍。

Mybatis-Spring jar中最重要的几个类就是MapperFactoryBean,SqlSessionFactoryBean,SqlSessionTemplate。

    先看看SqlSessionTemplate实现了SqlSession接口,对sqlSession做了一层包装。这个类构造时候需要SqlSessionFactory参数,主要还是用来生成SqlSession的,但是这里生产的是对SqlSession做了代理的,实现了对方法调用的异常处理,事务,session关闭等处理。

    看下面SqlSessionTemplate关键代码

//其构造方法,需要传入sqlSessionFactory来生产sqlSession
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  notNull(executorType, "Property 'executorType' is required");

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;
  //生成代理
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class },
      new SqlSessionInterceptor());
}
//内部类 实现invocationHandler接口
private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //utils 类的静态方法
    SqlSession sqlSession = getSqlSession(
        SqlSessionTemplate.this.sqlSessionFactory,
        SqlSessionTemplate.this.executorType,
        SqlSessionTemplate.this.exceptionTranslator);
    try {
      //实现具体调用
      Object result = method.invoke(sqlSession, args);
      //提交事务
      if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
        // force commit even on non-dirty sessions because some databases require
        // a commit/rollback before calling close()
        sqlSession.commit(true);
      }
      return result;
    } catch (Throwable t) {
      //异常处理
      Throwable unwrapped = unwrapThrowable(t);
      if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
        // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        sqlSession = null;
        Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
        if (translated != null) {
          unwrapped = translated;
        }
      }
      throw unwrapped;
    } finally {
       //关闭session
      if (sqlSession != null) {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }
}

    SqlSessionFactoryBean主要是实现了spring FactoryBean,里面属性配置包含了datasource(数据源),configLocation(mybatis配置信息,比如plugin等),mapperLocations(我们写的sql mapper映射xml地址);有了这些我们就可以通过SqlSessionFactoryBean 为我们提供SqlSessionFactory实例。我们就可以把SqlSessionFactory交给SqlSessionTemplate为我们生成session代理。

    MapperFactoryBean类主要实现了spring FactoryBean接口,通过getObject为我们提供Mapper代理,避免对sqlSession的手工操作。MapperFactoryBean扩展了抽象类SqlSessionDaoSupport,里面有对SqlSessionTemplate的封装,为我们提供session代理。

    MapperFactoryBean中的关键代码

@Override
public T getObject() throws Exception {
  //借助sqlSessiontTemplate生成的session代理,查找出对应我们dao接口对应的mapper实例
  //这样我们在spring中只需要定义相应的dao接口方法参数 即可,不用手工一一来操作sqlsession
  return getSqlSession().getMapper(this.mapperInterface);
}

到这mybatis-spring大概分析完毕,里面还有很多细枝末叶,这里就不介绍了。

本文链接:http://my.oschina.net/robinyao/blog/645886

转载于:https://my.oschina.net/robinyao/blog/645886

你可能感兴趣的:(java,python)