zoukankan      html  css  js  c++  java
  • mybatis源代码分析之binding包

    在使用ibatis执行数据库访问时,会调用形如

    getSqlMapClientTemplate().queryForObject("getCityByCityId", cityId);

    这样的代码。这样的形式要求调用方选择需要使用的函数(queryForObject、queryForList、update),还需要告诉这个函数具体执行哪一个statement(上文中是“getCityByCityId”),在这个过程中如果有一个地方选择错误或者拼写错误,不仅没有办法达到自己的期望值,可能还会出现异常,并且这种错误只有在运行时才能够发现。

    mybatis对此进行了改进,只要先声明一个接口,就可以利用IDE的自动完成功能帮助选择对应的函数,简化开发的同时增加了代码的安全性:

    SqlSession session = sqlSessionFactory.openSession();
    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = 
    mapper.selectBlog(101
    );
    } finally {
      session.close();
    }

    这个功能就是在利用java的动态代理在binding包中实现的。对动态代理不熟悉的读者可以查看(http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html)。

    一、binding包整体介绍

    这个包中包含有四个类:

    1. BindingException 该包中的异常类
    2. MapperProxy   实现了InvocationHandler接口的动态代理类
    3. MapperMethod   代理类中真正执行数据库操作的类
    4. MapperRegistry   mybatis中mapper的注册类及对外提供生成代理类接口的类

    二、MapperMethod

    MapperProxy关联到了这个类,MapperRegistry又关联到了MapperProxy,因而这个类是这个包中的基础类,我们首先介绍这个类的主要功能以及该类主要解决了那些问题。

    这个类包含了许多属性和多个函数,我们首先来了解下它的构造函数。

    //declaringInterface  已定义的mapper接口
      //method 接口中具体的一个函数
      //sqlSession 已打开的一个sqlSession
      public MapperMethod(Class<?> declaringInterface, Method method, SqlSession sqlSession) {
        paramNames = new ArrayList<String>();
        paramPositions = new ArrayList<Integer>();
        this.sqlSession = sqlSession;
        this.method = method;
        this.config = sqlSession.getConfiguration();//当前的配置
        this.hasNamedParameters = false;
        this.declaringInterface = declaringInterface;
        setupFields();//确定这个方法在mapper中的全配置名:declaringInterface.getName() + "." + method.getName();
        setupMethodSignature();//下文进行详细介绍
        setupCommandType();//设置命令类型,就是确定这个method是执行的那类操作:insert、delete、update、select
        validateStatement();
      }

    在构造函数中进行了基本属性的设置和验证,这里面稍微复杂的操作是setupMethodSignature,我们来看其具体的功能:

    //在具体实现时利用了Java的反射机制去获取method的各项属性
    private void setupMethodSignature() {
        //首先判断方法的返回类型,这里主要判断三种:是否有返回值、返回类型是list还是map
        if( method.getReturnType().equals(Void.TYPE)){
          returnsVoid = true;
        }
        //isAssignableFrom用来判定两个类是否存在父子关系;instanceof用来判断一个对象是不是一个类的实例
        if (List.class.isAssignableFrom(method.getReturnType())) {
          returnsList = true;
        }
        if (Map.class.isAssignableFrom(method.getReturnType())) { 
          //如果返回类型是map类型的,查看该method是否有MapKey注解。如果有这个注解,将这个注解的值作为map的key
          final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
          if (mapKeyAnnotation != null) {
            mapKey = mapKeyAnnotation.value();
            returnsMap = true;
          }
        }
        
        //确定函数的参数
        final Class<?>[] argTypes = method.getParameterTypes();
        for (int i = 0; i < argTypes.length; i++) {
          //是否有为RowBounds类型的参数,如果有设置第几个参数为这种类型的  
          if (RowBounds.class.isAssignableFrom(argTypes[i])) {
            if (rowBoundsIndex == null) {
              rowBoundsIndex = i;
            } else {
              throw new BindingException(method.getName() + " cannot have multiple RowBounds parameters");
            }
          } else if (ResultHandler.class.isAssignableFrom(argTypes[i])) {//是否有为ResultHandler类型的参数,如果有设置第几个参数为这种类型的  
            if (resultHandlerIndex == null) {
              resultHandlerIndex = i;
            } else {
              throw new BindingException(method.getName() + " cannot have multiple ResultHandler parameters");
            }
          } else {
            String paramName = String.valueOf(paramPositions.size());
            //如果有Param注解,修改参数名;如果没有,参数名就是其位置
            paramName = getParamNameFromAnnotation(i, paramName);
            paramNames.add(paramName);
            paramPositions.add(i);
          }
        }
      }

    前面介绍了MapperMethod类初始化相关的源代码,在初始化后就是向外提供的函数了,这个类向外提供服务主要是通过如下的函数进行:

    public Object execute(Object[] args) {
        Object result = null;
        //根据初始化时确定的命令类型,选择对应的操作
        if (SqlCommandType.INSERT == type) {
          Object param = getParam(args);
          result = sqlSession.insert(commandName, param);
        } else if (SqlCommandType.UPDATE == type) {
          Object param = getParam(args);
          result = sqlSession.update(commandName, param);
        } else if (SqlCommandType.DELETE == type) {
          Object param = getParam(args);
          result = sqlSession.delete(commandName, param);
        } else if (SqlCommandType.SELECT == type) {//相比较而言,查询稍微有些复杂,不同的返回结果类型有不同的处理方法
          if (returnsVoid && resultHandlerIndex != null) {
            executeWithResultHandler(args);
          } else if (returnsList) {
            result = executeForList(args);
          } else if (returnsMap) {
            result = executeForMap(args);
          } else {
            Object param = getParam(args);
            result = sqlSession.selectOne(commandName, param);
          }
        } else {
          throw new BindingException("Unknown execution method for: " + commandName);
        }
        return result;
      }

    这个函数整体上还是调用sqlSession的各个函数进行相应的操作,在执行的过程中用到了初始化时的各个参数。

    三、MapperProxy

    这个类继承了InvocationHandler接口,我们主要看这个类中的两个方法。一是生成具体代理类的函数newMapperProxy,另一个就是实现InvocationHandler接口的invoke。我们先看newMapperProxy方法。

    public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
        //先初始化生成代理类所需的参数
        ClassLoader classLoader = mapperInterface.getClassLoader();
        Class<?>[] interfaces = new Class[]{mapperInterface};
        MapperProxy proxy = new MapperProxy(sqlSession);//具体要代理的类
        //利用Java的Proxy类生成具体的代理类
        return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
      }

    我们再来看invoke方法:

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
        if (!OBJECT_METHODS.contains(method.getName())) {
          final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
          //生成MapperMethod对象
          final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
          //执行MapperMethod对象的execute方法
          final Object result = mapperMethod.execute(args);
          //对处理结果进行校验
          if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
            throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
          }
          return result;
        }
        return null;
      }

    四、MapperRegistry

    这个类会在Configuration类作为一个属性存在,在Configuration类初始化时进行初始化:

    protected MapperRegistry mapperRegistry = new MapperRegistry(this);

    从类名就可以知道这个类主要用来mapper的注册,我们就首先来看addMapper函数:

    public void addMapper(Class<?> type) {
        //因为Java的动态代理只能实现接口,因而在注册mapper时也只能注册接口
        if (type.isInterface()) {
          //如果已经注册过了,则抛出异常,而不是覆盖
          if (knownMappers.contains(type)) {
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            //先将这个mapper添加到mybatis中,如果加载过程中出现异常需要再将这个mapper从mybatis中删除
            knownMappers.add(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);
            }
          }
        }
      }

    下面我们来看下其向外提供生成代理对象的函数:

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //如果不存在这个mapper,则直接抛出异常
        if (!knownMappers.contains(type))
          throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        try {
          //返回代理类
          return MapperProxy.newMapperProxy(type, sqlSession);
        } catch (Exception e) {
          throw new BindingException("Error getting mapper instance. Cause: " + e, e);
        }
      }

    五、代理类生成的顺序图

    我们在开篇时提到了如下的代码:

    SqlSession session = sqlSessionFactory.openSession();
    try {
      BlogMapper mapper = session.getMapper(BlogMapper.class);
      Blog blog = 
    mapper.selectBlog(101
    );
    } finally {
      session.close();
    }

    image

  • 相关阅读:
    oracle添加字段,备注
    oracle对日期date类型操作的函数
    查询效率例子收藏
    webuploader.min.js 简单例子
    select 数字/字符串/count(参数)/sum(数字) from table
    oracle常用分析函数 over(partition by xxx order by xxx)
    LigerUi遮罩的两个方法
    LigerUI子父窗口之间传参问题
    LigerUi自动检索输入
    LigerUi折叠与展开
  • 原文地址:https://www.cnblogs.com/sunzhenchao/p/3075854.html
Copyright © 2011-2022 走看看