美文网首页
mybatis的Mapper接口调用原理

mybatis的Mapper接口调用原理

作者: 樱花舞 | 来源:发表于2020-04-20 23:24 被阅读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;
      }
    

    相关文章

      网友评论

          本文标题:mybatis的Mapper接口调用原理

          本文链接:https://www.haomeiwen.com/subject/twwsihtx.html