MyBatis執行sql原理

我們知道Mybatis是通過在配置文件中配置sql文,然後對應創建一個接口,通過調用接口的形式來執行sql的。

但是接口並沒有被實例化怎麼就能被調用呢,知道動態代理的朋友肯定會想到是動態代理在背後操控這一切。

動態代理

先上一段簡單的動態代理代碼

interface ArithmeticCalculator {
    int add(int i, int j);

    int sub(int i, int j);
}

class MyInvocationHadler implements InvocationHandler {

    @SuppressWarnings("unchecked")
    public <T> T getInstance(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        System.out.println(method.getName());
        System.out.println(args[0]+"**"+args[1]);
        return 0;
    }

}

public class DynamicProxy {
    public static void main(String[] args) {
        MyInvocationHadler hadler=new MyInvocationHadler();
        ArithmeticCalculator calculator=hadler.getInstance(ArithmeticCalculator.class);
        calculator.add(2, 3);
    }

}

關於動態代理的只是就不在這裏細說了,有興趣的朋友可以自行查資料。

獲取代理實例

ICommand icommand=session.getMapper(ICommand.class);
Command command=icommand.selectOneTOMany("xhj");

ICommand是個接口,我們通過動態代理得到了一個接口代理,我們知道原理就是貼出來的第一段代碼,當然也不會那麼簡單了,我們跟一下源碼看看裏面的廬山真面目。

當我們看getMapper源碼時發現有兩個實現類,我們要看哪個呢?有兩種方法,第一種比較簡單,就是給兩個實現類都打上斷點,看看debug時跑的是哪個。第二個就是看SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(reader);中build方法,到源碼中我們會看到如下代碼

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

所以我們要看的是DefaultSqlSessionFactory的getMapper方法。我們找到如下代碼

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

我們一句一句來看,首先第一句,很明顯是得到一個代理工廠類。但是knownMappers這個又是什麼呢?我們發現這是一個Map

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

其實這個Map裏存放的都是以接口.class爲鍵,對應的代理工廠類爲值的鍵值對。它在我們初始化的時候就已經創建好了,也就是在創建SqlSessionFactory 的時候,也是在build方法中。我們進build方法一步步往下找,找到如下代碼

 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 {

就不把所有代碼貼出來了,關鍵部分,我們在配置總的配置文件的時候會使用mapper這個標籤,這個方法就是解析這個標籤用的,我們看到如果在sql配置文件中有package這個屬性時它就會走上面這個分支,其中有段代碼configuration.addMappers(mapperPackage);我們進去看,找到如下代碼

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

其中knownMappers.put(type, new MapperProxyFactory(type));在這裏我們找到了初始化knownMappers的源頭了。

好了,接下來看return mapperProxyFactory.newInstance(sqlSession);我們得到代理工廠類後就得返回代理實例了,不多說跟進去看

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

看到這裏我想大家都差不多明白了吧!MapperProxy是一個實現了InvocationHandler接口的類,在這裏初始化了一下,再看return newInstance(mapperProxy);方法你會更明白

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

很明顯這就是我們動態代理的關鍵語句了,到此爲止我們明白了Mybatis是怎麼通過配置在namespace中的屬性全類名的接口得到一個可執行的接口實例的,確切說這不是一個實例,而只是一個代理類。

執行對應sql

我們知道,通過動態代理得到的代理實例在調用方法的時候最終是調用代理工廠的invoke方法的,既然如此我們進MapperProxy的invoke方法看下

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

這裏第一個if是永遠不會進去的,我們看下面的execute方法,這裏就是執行sql文的地方了final MapperMethodmapper Method=cachedMapperMethod(method);這個方法得到我們要執行哪個sql的方法名。最後我們進execute方法看一眼

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    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 {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } 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;
  }

這個方法裏面會通過判斷你在配置文件裏配置的是select標籤還是其他別的標籤來判斷並執行其對於的方法。

好了,就這些了,不足之處請指出,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章