Mybatis源碼概覽(二) ---Plugin擴展與Spring結合原理

    本文主要介紹Mybatis經過動態代理避免對sqlSession直接調用,而是經過MapperProxy代理技術生成了具體dao接口的Mapper實例,裏面封裝了對sqlSession的調用;Mybatis預留了Interceptor接口,用戶能夠擴展該接口,實現自定義插件;Mybatis與Spring結合主要經過Spring 的FactoryBean技術實現;
java


MapperProxy

    把Mybatis源碼概覽(一)中的helloWorld例子的註釋代碼打開 以下git

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

    這樣經過session.getMapper獲取某個Dao接口的Mapper代理實例,這樣後面查詢就不須要直接對sqlSession來操做,在具體應用中會省略掉不少代碼。具體生產Mapper代理原理咱們能夠Debug一步一步分析。github

    第一加載解析Mybatis配置文件時 跟進代碼,下面這個片斷是解析Mybatis幾大屬性 其中最後有個mappersspring

//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文件的方式
        //<mapper resource="mapper/BlogMapper.xml"/>
        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 <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 {
      //爲每一個mapper接口添加一個MapperProxyFacotry
      knownMappers.put(type, new MapperProxyFactory<T>(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這四個類之間的關係:
sql

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代理中session

    下面主要看下MapperProxy和MapperMethod源碼mybatis

 MapperProxy類app

//實現了InvocationHandler,並把每一個mapper方法對應的MapperMethod緩存起來
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> 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類ide

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

至此經過動態代理技術返回代理對象,實現經過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

相關文章
相關標籤/搜索