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 getInstance(Class 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 getMapper(Class<T> 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);
    }
  }

我们一句一句来看,首先第一句,很明显是得到一个代理工厂类。但是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  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 {
        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 mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

看到这里我想大家都差不多明白了吧!MapperProxy是一个实现了InvocationHandler接口的类,在这里初始化了一下,再看return newInstance(mapperProxy);方法你会更明白

protected T newInstance(MapperProxy 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标签还是其他别的标签来判断并执行其对于的方法。

好了,就这些了,不足之处请指出,谢谢!

你可能感兴趣的:(MyBatis执行sql原理)