zoukankan      html  css  js  c++  java
  • Mybatis技术原理理——整体流程理解

    前言:2018年,是最杂乱的一年!所以你看我的博客,是不是很空!

     

    网上有很多关于Mybatis原理介绍的博文,这里介绍两篇我个人很推荐的博文  Mybatis3.4.x技术内幕 MyBaits源码分析!让我找到了学习的入口,当然另外你必须要看的官方文档 MyBatis学习。那么有了这些知识,就让我们愉快的吃鸡之路吧!

    一:你首先得知道的知识点。

    1.1 JDBC

      在个人看来, Mybatis的核心就是对SQL语句的管理!那么在JAVA环境下,对SQL的管理或者其他任何的实现,肯定是离不开JAVA的数据库操作接口,包括Connrction接口、Statement接口、PreparadStatement接口以及ResultSet接口等等的属性,你可以先通过JDBC来操作一次或者更多次的数据库。这个就不多做赘述了!

    1.2 动态代理

      不知道你最初有没有和我一样,有这样的疑问。Mybatis的mapper层明明就是一个接口,都没有实现类!即使是在TMapper.xml中做了映射,也没有看到任何关于接口的实现类,那他是怎么被实例化的呢,又是怎么实现方法的功能的呢?动态代理会告诉你答案!

    先来看看jdk动态代理的一个小例子:
    建立一个实体

    public class TestDemo {
        private Integer id;
        private String name;
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

         建立一个mapper

    public interface TestDemoMapper {
        TestDemo getById(Integer id);
    }

      建立一个Mapper的代理类,需要继承一个 InvocationHandler 即可

    public class DemoMapperProxy<T> implements InvocationHandler {
    
        @SuppressWarnings("unchecked")
        public T newInstance(Class<T> clz) {
            return (T)Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            TestDemo testDemo = new TestDemo();
            testDemo.setId(1);
            testDemo.setName("proxyName");
            return testDemo;
        }
    }

    测试类

    public static void main(String[] args) {
            DemoMapperProxy<TestDemoMapper> demoMapperProxy = new DemoMapperProxy<TestDemoMapper>();
            TestDemoMapper testDemoMapper = demoMapperProxy.newInstance(TestDemoMapper.class);
            TestDemo byId = testDemoMapper.getById(1);
            System.out.println(byId);
        }
    

      结果:TestDemo{id=1, name='proxyName'},

      所以这就应该大致能够说明Mybatis中的mapper是怎么工作的了吧!

    二:关于mapper的整体流程理解

    2.1 先来看看官方网站的一个例子吧

    在官方文档中的 Mybatis 3 的入门介绍中,介绍了怎么配置一个mybatis的测试类

    mybatis-config.xml 中的一个简单配置:

    <configuration>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC" />
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver" />
                    <property name="url" value="jdbc:mysql://172.20.139.237:3306/rubber-fruit?useUnicode=true&amp;characterEncoding=utf-8&amp;autoReconnect=true" />
                    <property name="username" value="user123" />
                    <property name="password" value="u123" />
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="UserMapper.xml" />
        </mappers>
    </configuration>

      然后在写一个测试类:

      public static void main(String[] args) throws Exception {
    
            String resouse = "mybatis-config.xml";
            InputStream stream = Resources.getResourceAsStream(resouse);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            User byId = mapper.getById(1);
            sqlSession.close();
            System.out.println(byId);
        }

      其实这个逻辑很简单,首先是拿到mabatis-config.xml 中的配置对象,生成SqlSessionFactory,然后通过sqlSession拿到mapper,通过动态代理完成方法的执行,并返回结果!那么我们就可以简单的拆分成两个部分,第一:配置文件的初始化,第二:sql的执行,后面的文章也会更具这个大模块来更具细化的讲解

      其实上面的第四行代码 可以更换成config对象的,可能看的更清楚一些:

         String resouse = "mybatis-config.xml";
            InputStream stream = Resources.getResourceAsStream(resouse);
            XMLConfigBuilder configBuilder = new XMLConfigBuilder(stream,null,null);
            Configuration parse = configBuilder.parse();
            SqlSessionFactory build = new SqlSessionFactoryBuilder().build(parse);

      其实Mybatis的前期初始化的一个重要的对象就是 Configuration对象,后面会慢慢揭开它的面纱!

    2.2 整体流程图

    通过上面的这个测试方法,我们通过源码流程,大致可以拆分出这样的一个图:

     其实从这个图中我们已经可以看出Mybatis划分先两个部分:

     1:初始化过程

      首先是通过XMLConfigBuilder解析 mybatis-config.xml 对象来进行初始化,拿到Configuration对象,然后SqlSessionFactoryBuilder通过初始化的Configuration对象,生成SqlSession,SqlSession中调用getMapper对象方法,其实也就是Configuration中的getMapper方法 通过MapperProxy代理对象生成一个mapper。到这里位置就是一个简单的初初始化思路了!当然Mybatis的初始化远远不止这些。

    2:mybatia中执行一个sql的完整流程

      从图中也是能够很较清晰的看出这个执行流程的。因为Mapper是一个接口,所以不能直接实例化的!那么MapperProxy的作用,就是通过JDK的动态代理,来间接的对mapper进行实例化,不了解的可以看上面的1.2中的例子。那么我们可以看看org.apache.ibatis.binding.MapperProxy源码中的对象方法:

    @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);
      } 

    通过代理的对象方法方法,拿到了一个MapperMethod对象,并对mapperMethod对象进行来缓存。为啥要缓存呢?这个时候可以看看MapperMethod对象是什么?org.apache.ibatis.binding.MapperMethod中对两个私有方法

      private final SqlCommand command;
      private final MethodSignature method;
    
      public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, mapperInterface, method);
      }
    

         MapperMethod中的SqlCommand对象中的两个私有方法,    

    public static class SqlCommand {
        private final String name;
        private final SqlCommandType type;
    }
    public enum SqlCommandType {
      UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
    }
    

        而MapperMethod中的MethodSignature对象呢?

       private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;
        private final boolean returnsCursor;
        private final Class<?> returnType;
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final ParamNameResolver paramNameResolver;

       所以这些很清晰了,MapperMethod对象是把mapper中的sql进行了封装,获取了sql的执行类型和返回值。

      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:
            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;
      }

      通过执行的sql类型 选取不通的sqlSession进行执行。其实可以看出,转了一圈,最后的最后还是落在了SqlSession当中。而SqlSession又把这个重要的操作交个了执行器Executor。最后又到了StatementHandler来负责执行最后的sql,ResultSetHandler放回执行的结果。

      2.3 记一个知识点

      看到这里,突然是想问一下一个问题的,Mapper的方法是否支持重载呢?

      答案是不能的!mybatis是使用package+Mapper+method 全名称作为Key值 去xml中寻找一个唯一sql来执行的那么,重载方法时将导致矛盾。对于Mapper接口,Mybatis禁止方法重载。通过代码断点可以看到。

      org.apache.ibatis.session.Configuration对象中有一个方法,addMappedStatement()

    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

     /**中间部分省略掉**

     public void addMappedStatement(MappedStatement ms) {

        mappedStatements.put(ms.getId(), ms);
      }

        

      而这个地方put的ms.getId 就是通过mapper的配置文件组合成的一个唯一的key值。那我们在看看StrictMap 中的put方法源码,那么就一目了然了

     @SuppressWarnings("unchecked")
        public V put(String key, V value) {
          if (containsKey(key)) {
            throw new IllegalArgumentException(name + " already contains value for " + key);
          }
          if (key.contains(".")) {
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
              super.put(shortKey, value);
            } else {
              super.put(shortKey, (V) new Ambiguity(shortKey));
            }
          }
          return super.put(key, value);
        }

      如果有相同的key的,那么会抛出异常,这也就是为啥mapper不能重载的原因!

  • 相关阅读:
    文献阅读 | On the subspecific origin of the laboratory mouse
    文献阅读 | GenomicsDB: storing genome data as sparse columnar arrays.
    文献阅读 | The human Y chromosome: an evolutionary marker comes of age
    文献阅读 | HaploGrouper: A generalized approach to haplogroup classification
    文献阅读 | Systematic evaluation of error rates and causes in short samples in next-generation sequencing
    文献阅读 | The Wheat 660K SNP array demonstrates great potential for marker‐assisted selection in polyploid wheat
    使用HMM进行分类识别(以语音识别为例)
    文献阅读 | Fine definition of the pedigree haplotypes of closely related rice cultivars by means of genome-wide discovery of single-nucleotide polymorphisms
    GWAS学习笔记(一) | 质量控制(QC)
    python实现简单决策树(信息增益)——基于周志华的西瓜书数据
  • 原文地址:https://www.cnblogs.com/luffyu/p/10257338.html
Copyright © 2011-2022 走看看