1. 程式人生 > >Mybatis原始碼分析(4)—— Mapper的建立和獲取

Mybatis原始碼分析(4)—— Mapper的建立和獲取

Mybatis我們一般都是和Spring一起使用的,它們是怎麼融合到一起的,又各自發揮了什麼作用?

就拿這個Mapper來說,我們定義了一個介面,聲明瞭一個方法,然後對應的xml寫了這個sql語句, 它怎麼就執行成功了?這傢伙是怎麼實現的,帶著這個好奇心,我一步步跟蹤,慢慢揭開了它的面紗。

一、初始化時的埋點

MapperFactoryBean的父類SqlSessionDaoSupport中setSqlSessionFactory方法構建了一個sqlSession:

public abstract class SqlSessionDaoSupport extends
DaoSupport {
private SqlSession sqlSession; private boolean externalSqlSession; public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } } //...... }

將會呼叫這個構造器:

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

產生一個sqlSession代理,SqlSessionTemplate的內部類:

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      final 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) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
}

二、獲取Mapper

MapperFactoryBean是MapperScannerConfigurer在掃描包後往每個Mapper的beanDefine中新增給BeanClass屬性的:

definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);

definition.getPropertyValues().add("addToConfig", this.addToConfig);

那麼當我們獲取每個Mapper都會走MapperFactoryBean:

 public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
 }

這個就是在前面提到的SqlSessionTemplate中根據Class型別來獲取:

 public <T> T getMapper(Class<T> type) {
    return getConfiguration().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) {
    if (!knownMappers.contains(type))
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    try {
      return MapperProxy.newMapperProxy(type, sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
 }

public class MapperProxy implements InvocationHandler, Serializable

 public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    ClassLoader classLoader = mapperInterface.getClassLoader();
    Class<?>[] interfaces = new Class[]{mapperInterface};
    MapperProxy proxy = new MapperProxy(sqlSession);
    return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
 }

通過上面一系列方法,獲取的是一個MapperProxy。

雖然我們只定義了介面沒有實現類,但縱觀這個dao層,做的都是和資料庫打交道的事,唯一不同的是sql語句不同,為了便於管理,將所有的sql寫在配置檔案中,然後根據配置的規則和相應介面生成代理類。

走到這裡,共經過了兩次代理:

  • 第一次是在SqlSessionTemplate中,持有一個內部類SqlSessionInterceptor,將所有基於SqlSession的操作轉移給DefaultSqlSession。

  • 第二次是針對Mapper的代理,為介面生成代理類。這個代理類持有了上面的SqlSessionTemplate(也間接持有了DefaultSqlSession)。

第一次可以說是為了融入Spring而做的代理,讓每個Mapper在建立之初就自然而然地持有 了一個SqlSession,後面的操作就是水到渠成。第二次的代理是必然的,根據Mybatis的設計,介面和Xml配置組合的方式,框架在背後為我們生成了代理類,這才符合Java規範嘛。

三、方法呼叫

拋開Spring的呼叫棧,從service層來看,例如下面的某個service的某個方法:

@Autowired
private XxMapper xxMapper;

@Override
public List<ComResVo> getListByXxId(Integer xxId) {
    if(null == xxId){
        return null;
    }
    return this.xxMapper.selectXxListById(xxId);
}

mapper作為屬性注入到service中,當時通過MapperFactoryBean的getObject方法獲取的就是一個代理類,這個時候呼叫就會轉移到MapperProxy的invoke方法:

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (method.getDeclaringClass() == Object.class) {
      return method.invoke(this, args);
    }
    final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
    final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
    final Object result = mapperMethod.execute(args);
    if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
      throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
 }

MapperMethod#execute

public Object execute(Object[] args) {
    Object result = null;
    if (SqlCommandType.INSERT == type) {
        Object param = getParam(args);
        result = sqlSession.insert(commandName, param);
    } else if (SqlCommandType.UPDATE == type) {
        Object param = getParam(args);
        result = sqlSession.update(commandName, param);
    } else if (SqlCommandType.DELETE == type) {
        Object param = getParam(args);
        result = sqlSession.delete(commandName, param);
    } else if (SqlCommandType.SELECT == type) {
        if (returnsVoid && resultHandlerIndex != null) {
            executeWithResultHandler(args);
        } else if (returnsMany) {
            result = executeForMany(args);
        } else if (returnsMap) {
            result = executeForMap(args);
        } else {
            Object param = getParam(args);
            result = sqlSession.selectOne(commandName, param);
        }
    } else {
        throw new BindingException("Unknown execution method for: " + commandName);
    }
    return result;
}

execute方法會根據方法型別選擇對應的sqlSession方法,在這裡至少把增刪改查給區分開了,查詢方法還給細化了。而傳入的sqlSession是一個SqlSessionTemplate,它相關的呼叫又會轉移至它持有的一個sqlSessionProxy(DefaultSqlSession)。

DefaultSqlSession持有給定的Executor,將所有方法最終繫結到Executor的query和update方法。

然後就是分別執行doQuery和doUpdate方法,構造StatementHandler,doQuery方法還要傳入一個ResultHandler,處理返回的結果集。

從上面的分析也大致知道了整個流程,對Mybatis的處理方式也有了一定的瞭解。如果叫我寫這麼一個框架,我會怎麼寫?

我想最好的方法就是先不看這個原始碼,把它的功能全部搞懂,這個可以叫需求分析了,看看它實現了哪些功能,智慧到什麼程度,然後我自己去實現,這個過程可能會遇到很多問題,搞不出來可以適當參考下,全部搞完還要對比下,看看人家設計的高明之處。

今天貌似是愚人節,節日快樂!