zoukankan      html  css  js  c++  java
  • mybatis查询语句的背后

     转载请注明出处。。。

    一、前言

    在先了解mybatis查询之前,先大致了解下以下代码的为查询做了哪些铺垫,在这里我们要事先了解,myabtis会默认使用DefaultSqlSessionFactory作为sqlSessionFactory的实现类,而sqlSession的默认实现类为DefaultSqlSession

    1  public static SqlSessionFactory getSessionFactory() throws IOException {
    2         Reader reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml");
    3         SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    4         return builder.build(reader);
    5     }

    获取mybatis的配置文件流,交给sqlSessionFactoryBuilder进行解析,在这里只会涉及到一部分,具体,请大家移步mybatis源码进行分析

    解析大致步骤(以下说的配置文件,是mybatis配置数据库连接信息的那个配置文件,不是mapper.xml文件)

    解析配置文件的核心类在XMLConfigBuilder类中,

    代码如下

     1 public Configuration parse() {
     2     if (parsed) {
     3       throw new BuilderException("Each XMLConfigBuilder can only be used once.");
     4     }
     5     parsed = true;
     6     parseConfiguration(parser.evalNode("/configuration"));
     7     return configuration;
     8   }
     9 
    10   private void parseConfiguration(XNode root) {
    11     try {
    12       // 解析properties节点信息
    13       propertiesElement(root.evalNode("properties"));
    14       // 解析settings节点配置信息,其中二级缓存的总开关就是这里配置,当然mybatis默认是开启的,详细见Configuration类中的cacheEnabled属性
    15       Properties settings = settingsAsProperties(root.evalNode("settings"));
    16       loadCustomVfs(settings);
    17       loadCustomLogImpl(settings);
    18       // 解析别名
    19       typeAliasesElement(root.evalNode("typeAliases"));
    20       // 解析插件
    21       pluginElement(root.evalNode("plugins"));
    22       // 这个节点一般不进行配置,myabtis也提供了一个默认实现类DefaultObjectFactory,除非自定义对象工厂实现,才需配置
    23       objectFactoryElement(root.evalNode("objectFactory"));
    24       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    25       reflectorFactoryElement(root.evalNode("reflectorFactory"));
    26       settingsElement(settings);
    27       // read it after objectFactory and objectWrapperFactory issue #631
    28       environmentsElement(root.evalNode("environments"));
    29       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    30       // 处理java类型和数据库类型的转换,mybatis提供了许多默认实现,详细见TypeHandlerRegistry类,如果需自定义,可在此节点中进行配置
    31       typeHandlerElement(root.evalNode("typeHandlers"));
    32       // 这也是一个核心的配置,mapperElement方法会对mapper.xml文件内容进行一个解析
    33       mapperElement(root.evalNode("mappers"));
    34     } catch (Exception e) {
    35       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    36     }
    37   }

    解析mapper.xml文件 的类XMLMapperBuilder,

     1 public void parse() {
     2     // 也就是检测配置文件配置的mapper节点有没有加载到configuration类中,防止重复加载
     3     if (!configuration.isResourceLoaded(resource)) {
     4       configurationElement(parser.evalNode("/mapper"));
     5       configuration.addLoadedResource(resource);
     6       // 这个是绑定,mapper接口的,当处理成功,在configuration类中的mapper注册器中,会添加一个mapper
     7       bindMapperForNamespace();
     8     }
     9 
    10     parsePendingResultMaps();// 解析resultMap节点
    11     parsePendingCacheRefs(); // 解析缓存节点,如<cache-ref/>
    12     parsePendingStatements();// 解析select|update等节点,并封装成mappedStatement类
    13   }

     其中bindMapperForNamespace()方法的操作会导致以下结果

    在configuration类中的MapperRegistry属性中添加一个mapper,结果存储在MapperRegistry类的一个map中,key为mapper的class value为一个代理工厂,负责产生mapper接口代理类。

     二、查询操作

    当我们使用要使用mybatis进行查询操作,无非大致就是两种方式

     1 /**
     2      * 通过mapper接口形式查询数据
     3      */
     4     @Test
     5     public void testSelectByMapper() throws IOException {
     6         SqlSession sqlSession = MybatisUtil.getSessionFactory().openSession();
     7         UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     8         User user = mapper.selectByPrimaryKey(10);
     9         System.out.println(user);
    10         sqlSession.close();
    11     }
    12 
    13     /**
    14      * 通过mapper接口的全限定名来进行查询
    15      * @throws IOException
    16      */
    17     @Test
    18     public void testSelectByString() throws IOException {
    19         SqlSessionFactory sessionFactory = MybatisUtil.getSessionFactory();
    20         SqlSession sqlSession = sessionFactory.openSession();
    21         User user = sqlSession.selectOne("com.mybatis.demo.mybatisdemo.mapper.UserMapper.selectByPrimaryKey",10);
    22         System.out.println(user);
    23         sqlSession.close();
    24     }

    先来看第一种的分析,当我们点击getMapper进去,它会去调用configuration类中getMapper方法,就如上面介绍的解析出mapper节点后,会存储在configuration类中的mapper注册器中,

     1 // defaultSqlSession类
     2 public <T> T getMapper(Class<T> type) {
     3     return configuration.<T>getMapper(type, this);
     4   }
     5 //configuration类
     6 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     7     return mapperRegistry.getMapper(type, sqlSession);
     8   }
     9 // 最终获取mapper对象的方法,其主要是创建一个mapper代理工厂,我们都知道mybatis的mapper接口是没有实现类的,
    10 // 但是我们直接查询是能获取数据,这里起作用的就是代理(采用的是jdk动态代理)
    11 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    12     final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    13     if (mapperProxyFactory == null) {
    14       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    15     }
    16     try {
    17       return mapperProxyFactory.newInstance(sqlSession);
    18     } catch (Exception e) {
    19       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    20     }
    21   }

    然后最终会经过代理类MapperProxy的invoke方法,进行返回结果。在这里为了更好的能理解这个类,举个例子,步骤如下

    先创建一个接口,再使用一个类去实现java的jdk代理的核心接口InvocationHandler,

    public interface TestMapper {
    
    
        User findByUserId(Integer id);
    }
    public class MapperProxyTest implements InvocationHandler {
    
        private Class<?> target;
    
        public MapperProxyTest(Class<?> target) {
            this.target = target;
        }
    
        public Object getProxyInstances(){
            return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[]{target},this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            User user = new User();
            user.setPassword("123");
            user.setUsername("李四");
            user.setAddress("123");
            user.setRegistertime(new Date());
            user.setCellphone("1111111");
            user.setAge(25);
            return user;
        }
    }

    测试类

    public class MapperTest {
    
        public static void main(String[] args){
            MapperProxyTest proxyTest = new MapperProxyTest(TestMapper.class);
            TestMapper testMapper = (TestMapper) proxyTest.getProxyInstances();
            System.out.println(testMapper.findByUserId(10));
        }
    }

    执行结果

    User{id=null, username='李四', password='123', age=25, address='123', cellphone='1111111', registertime=Sat Mar 09 15:02:16 CST 2019}

    由上面例子也可以看出最终结果是在invoke方法内,同理在mybatis中的MapperProxy的invoke方法也是负责返回最终结果的

     1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     2     try {
     3       if (Object.class.equals(method.getDeclaringClass())) {
     4         return method.invoke(this, args);
     5       } else if (isDefaultMethod(method)) {
     6         return invokeDefaultMethod(proxy, method, args);
     7       }
     8     } catch (Throwable t) {
     9       throw ExceptionUtil.unwrapThrowable(t);
    10     }
    11    // 交给了mpperMethod类去处理
    12     final MapperMethod mapperMethod = cachedMapperMethod(method);
    13     return mapperMethod.execute(sqlSession, args);
    14   }

     mapperMethod类中有两个重要属性,也就是它的内部类,

    也可以很清楚的了解到SqlCommand是用来存储当前执行方法的信息,如全限定名,还有该方法是属于select|update|delete|insert|flush的哪一种,

    对于methodSignature,则是纪录该方法的一些信息,如返回值类型,参数等信息,paramNameResolver处理mapper接口中的参数,下面代码中有一个大致的介绍,以后会做一个详细的介绍,这里只贴下代码,只针对select做介绍

     1 public Object execute(SqlSession sqlSession, Object[] args) {
     2     Object result;
     3     switch (command.getType()) {
     4       case INSERT: {
     5         Object param = method.convertArgsToSqlCommandParam(args);
     6         result = rowCountResult(sqlSession.insert(command.getName(), param));
     7         break;
     8       }
     9       case UPDATE: {
    10         Object param = method.convertArgsToSqlCommandParam(args);
    11         result = rowCountResult(sqlSession.update(command.getName(), param));
    12         break;
    13       }
    14       case DELETE: {
    15         Object param = method.convertArgsToSqlCommandParam(args);
    16         result = rowCountResult(sqlSession.delete(command.getName(), param));
    17         break;
    18       }
    19       case SELECT:
    20         if (method.returnsVoid() && method.hasResultHandler()) {// 返回值为void类型,但是有ResultHandler参数,并且只能有一个,不然会报错
    21           executeWithResultHandler(sqlSession, args);
    22           result = null;
    23         } else if (method.returnsMany()) {// 处理返回值类型为集合类型或者数组类型
    24           result = executeForMany(sqlSession, args);
    25         } else if (method.returnsMap()) {//处理返回值类型为Map类型
    26           result = executeForMap(sqlSession, args);
    27         } else if (method.returnsCursor()) {//返回值是否为cursor类型
    28           result = executeForCursor(sqlSession, args);
    29         } else {//其他类型
    30           Object param = method.convertArgsToSqlCommandParam(args);
    31           result = sqlSession.selectOne(command.getName(), param);
    32           if (method.returnsOptional() &&
    33               (result == null || !method.getReturnType().equals(result.getClass()))) {
    34             result = Optional.ofNullable(result);
    35           }
    36         }
    37         break;
    38       case FLUSH:
    39         result = sqlSession.flushStatements();
    40         break;
    41       default:
    42         throw new BindingException("Unknown execution method for: " + command.getName());
    43     }
    44     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    45       throw new BindingException("Mapper method '" + command.getName()
    46           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    47     }
    48     return result;
    49   }

     这里只介绍select部分中常用返回多个实例对象的情况,也就是返回值为集合类型。

     1 private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
     2     List<E> result;
     3    // 将mapper接口的参数名称和args整成一个map结构,最后在会将值赋给sql中对应的变量
     4    // 在3.5版本中,默认的mapper结构(假如没使用@param注解或者处于jdk1.8版本中在代码编译时加上 -parameters 参数),结构为
     5    // param1 -> args[0]  param2 -> args[1]
     6    // arg0 -> args[0]    arg1 -> args[1]  mybatis之前有些版本不是arg0 而是0 1 。。数字代替。
     7     Object param = method.convertArgsToSqlCommandParam(args);
     8     if (method.hasRowBounds()) {// 处理参数中带有rowBounds参数
     9       RowBounds rowBounds = method.extractRowBounds(args);
    10       result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    11     } else {// 其它情况
    12       result = sqlSession.<E>selectList(command.getName(), param);
    13     }
    14     // issue #510 Collections & arrays support
    15     // 说明返回类型不是集合List类型,而是数组类型或其它集合类型。
    16     if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    17       if (method.getReturnType().isArray()) {
    18         return convertToArray(result);
    19       } else {
    20         return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    21       }
    22     }
    23     return result;
    24   }

     从上面知道,最终还是回到了sqlSession里面,

    @Override
      public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        try {
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }

     MappedStatement存储的其实就是对每一个select|update|delete|insert 标签的解析结果

    关于MappedStatement是怎么解析得来的,又是怎么存储在Configuration中,可沿着以下路线进行查看

    SqlSessionFactoryBuilder  ---> build方法 

    XMLConfigBuilder  ---->  parse、parseConfiguration、mapperElement方法

    XMLMapperBuilder   ----> parse、parsePendingStatements、parseStatementNode

    MapperBuilderAssistant    ----> addMappedStatement

    这里不做过多介绍,详情见源码

    在selectList中executor的默认实现类是,SimpleExecutor,不过它还由Configuration类中的一个属性决定最后的类型,

     1 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     2     executorType = executorType == null ? defaultExecutorType : executorType;
     3     executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
     4     Executor executor;
     5     if (ExecutorType.BATCH == executorType) {
     6       executor = new BatchExecutor(this, transaction);
     7     } else if (ExecutorType.REUSE == executorType) {
     8       executor = new ReuseExecutor(this, transaction);
     9     } else {
    10       executor = new SimpleExecutor(this, transaction);
    11     }
    12     // 如果cacheEnabled为true,其实这个属性默认为true的,
    13     // 则由CachingExecutor进行包装,也就是常说的装饰设计模式
    14     if (cacheEnabled) {
    15       executor = new CachingExecutor(executor);
    16     }
    17     executor = (Executor) interceptorChain.pluginAll(executor);
    18     return executor;
    19   }

     最后回到selectList中来,由此可见,调用了CachingExecutor类中的query方法来执行。

     1 @Override
     2   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
     3       throws SQLException {
     4     // 如果不为空,则启用了二级缓存
     5     Cache cache = ms.getCache();
     6     if (cache != null) {
     7       flushCacheIfRequired(ms);
     8       if (ms.isUseCache() && resultHandler == null) {
     9         ensureNoOutParams(ms, boundSql);
    10         @SuppressWarnings("unchecked")
    11         List<E> list = (List<E>) tcm.getObject(cache, key);
    12         if (list == null) {
    13           list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    14           tcm.putObject(cache, key, list); // issue #578 and #116
    15         }
    16         return list;
    17       }
    18     }
    19     return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    20   }

     关于二级缓存,相信熟悉的都清楚,要想使用它,需要动两个地方,

    一个是mybatis的配置文件,将cacheEnabled设置为true,其实mybatis对这个属性的默认值就是true,所以二级缓存的总开关是打开的。

    第二个就是在mpper.xml文件中使用 <cache/>  或<cache-ref/>

    这里对缓存不做介绍。

    然后调用了BaseExecutor的query方法,这个类起的作用就是对一级缓存进行了操作,最终调用了SimpleExecutor的doQuery方法进行查询。

  • 相关阅读:
    Monkey面试整理
    Monkey测试环境搭建
    软件测试之Monkey 初步了解(入门级)
    接口测试Post和Get区别(面试题)
    软件测试测试人员遇到的问题及解决方法(面试)
    接口测试用例的基本测试点
    兼容性测试主要测的浏览器
    【转】Web测试中定位bug方法
    使用Postman做接口测试
    head中的title显示在body中
  • 原文地址:https://www.cnblogs.com/qm-article/p/10542187.html
Copyright © 2011-2022 走看看