第九篇:mybatis的接口访问原理

本文深入解析 MyBatis 中 SqlSession.getMapper 的工作原理,通过 Java 动态代理实现接口方法的调用,最终委托给 sqlSession 对象执行 SQL 操作。讲解了 MapperRegistry 如何获取代理对象,MapperProxy 如何执行方法并调用 sqlSession 的不同 CRUD 操作。
摘要由CSDN通过智能技术生成

这一篇需要有第八篇的知识,只要你懂了那个,再看这个自然就明白了,因为这个底层还是要用到sqlSession对象去与数据库交互的

第八篇:SqlSession.selectList调用过程_zxc_user的博客-CSDN博客

还是首先来看个使用例子

使用

public class SqlSessionTest {


    public static void main(String[] args) throws Exception{

        //获取SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = FactoryUtils.sqlSessionFactory();

        //打开SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        //执行接口
        User user = userMapper.selectUser(1);
        System.out.println(user);
    }
}



FactoryUtils上一篇有,这里就不细说了,看这样的使用是不是很方便,但是有个使用规范就是接口的全类名要作为xml文件的namespace,方法名要跟xml里面每个操作项的id一致,要么你也可以通过注解在接口方法上标记要执行的语句,但是一般不这么用,因为sql过于复杂的时候比xml还要难理解

   
 @Select("select * from user where id = #{id}")
 User a(@Param("id") Integer id);

使用还是比较简单的,那么mybatis是怎么做的呢,肯定是需要借助代理对象的,下面就具体来看下源码是怎么做的,入口就在于 sqlSession.getMapper(UserMapper.class)

sqlSession.getMapper

最终会到这里来

org.apache.ibatis.binding.MapperRegistry#getMapper


  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //之前注册的时候就已经把它放到这个knownMappers Map中了
    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);
    }
  }
  


这里还是比较简单的,就是从map中获取了一个代理对象创建工厂,然后再使用工厂创建代理对象,看下代理对象是怎么创建的


  protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用jdk进行动态处理
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //创建代理对象MapperProxy实现了InvocationHandler接口
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //创建代理
    return newInstance(mapperProxy);
  }



很明显就可以知道使用了jdk动态代理,那么核心就在于MapperProxy中了,我们下面再去看一些这个类的执行,因为代理过程中就会调到InvocationHandler接口的invoker方法中去

MapperProxy

该对象实现了InvocationHandler接口,并作为参数传到代理对象中了,那么当代理对象执行任意方法的时候就会到达这个类的invoke方法中,下面就来看看这个方法的实现

invoke

  @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)) {
        //如果是默认方法执行默认方法的逻辑,兼容java7方法?
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //根据方法获取MapperMethod并且进行缓存,不用重复生成
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //真正的方法执行入口
    return mapperMethod.execute(sqlSession, args);
  }



这里面核心就两个方法,我们都看一下

cachedMapperMethod

  private MapperMethod cachedMapperMethod(Method method) {
    //从缓存中获取
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      //新创建一个MapperMethod对象,里面涉及到MethodSignature的创建,这里就不细说了
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      //放到缓存中
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }



可以看到,这个逻辑还是比较简单的,除了MethodSignature的创建可能麻烦点,有兴趣的可以自己点进去看一看

mapperMethod.execute

这个位置才是我们调用接口时真正会进来的方法,也是最重要的代理方法,接下来我们再看看里面是怎么实现的

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:
        //方法没有返回值并且有ResultHandle,进行分支处理
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          //多条返回值
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          //返回类型未map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          //返回cursor
          result = executeForCursor(sqlSession, args);
        } else {
          //当条返回
          Object param = method.convertArgsToSqlCommandParam(args);
          //委托给sqlSession查询
          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;
  }



这里面逻辑就很清晰了,代理方法最终还是委托给了sqlSession对象进行处理,可以看看executeForMany


  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //转换参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      //是否需要绕过多少行
      RowBounds rowBounds = method.extractRowBounds(args);
      //委托sqlSession.selectList
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      //委托sqlSession.selectList
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }


再看进去就没啥了,是上篇的逻辑了

这篇就比较简单了,因为底层的执行逻辑在上篇就说了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值