mybatis的Mapper接口调用原理

2020-04-20  本文已影响0人  樱花舞

注意:若不理解代理模式和jdk动态代理请先了解再接下去阅读,效果更好。

1、首先举个简单例子来说明

1.1先创建Mapper

比作mybatis的xxxMapper,就是Mapper.xml对应的接口类。

public interface Mapper {
    String call();
}
1.2创建MapperXml

MapperXml相当于mybatis执行数据库操作的代理类,每一个方法对应一个MapperXml。

public class MapperXml {
    public String call() {
        return "xxxx";
    }
}
1.3创建代理的Handler类

jdk代理的实现就是实现InvocationHandler接口,实现invoke方法,当调用Mapper的方法时,实际是调用了MapperHandler的invoke方法。而mybatis的Mapper接口不能有重载方法名,就是因为在初始化时方法名作为key值,不能存在相同的方法名。

public class MapperHandler implements InvocationHandler {
    //根据方法名保存一份对应的操作类。
    private static final Map<String, MapperXml> mapperXmlMap = new HashMap<>();

    static {
        //初始化默认
        mapperXmlMap.put("call", new MapperXml());
    }

    public void addMapperProxy(String method, MapperXml mapperXml) {
        mapperXmlMap.put(method, mapperXml);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //根据方法名取到对应的实现类,然后调用对应的方法。
        return mapperXmlMap.get(method.getName()).call();
    }
}
1.4创建SqlSession

此SqlSession相当于mybatis的SqlSession,根据Mapper的类型获取到相应的代理类。

public class SqlSession {
    public static <T> T getMapper(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperHandler());
    }
}
1.5测试例子
public static void main(String[] args) {
        Mapper test = SqlSession.getMapper(Mapper.class);
        System.out.println(test.call());
        //输出xxxx
    }

总结:mybatis调用mapper接口其实就是返回的代理类,由代理类去执行数据库操作。

2、mybatis对应的源码解释

当执行自己的Mapper接口时,实际调用的是MapperProxy的invoke方法,而MapperProxy相当于上面提到的MapperHandler,MapperMethod相当于MapperXml,下面是它的主要方法:

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //根据方法名找到对应的操作类
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行操作
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    //看map是否存在这个类,若没有则新建
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

最后看看MapperMethod的execute方法,根据sql类型执行不同操作,最后都会调用sqlSession的方法,对数据库操作。若想了解更多可以看mybatis下的org.apache.ibatis.binding包,MapperProxyFactory主要创建Mapper的代理类,MapperRegistry主要缓存相应的代理对象,并提供查找添加等方法。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        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);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        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;
  }
上一篇下一篇

猜你喜欢

热点阅读