zoukankan      html  css  js  c++  java
  • mybatis源码学习(一) 原生mybatis源码学习

    最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得

    个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybatis源码学习(也就是mybatis-spring这个jar包的相关源码),这边笔记,主要来学习原生mybatis;

    还是先用描述一下,原生mybatis从解析xml到执行SQL的一个流程:

      1.第一步:首先会通过sqlSessionFactoryBuilder来build一个SQLSessionFactory,在build的过程中,会对mybatis的配置文件、mapper.xml文件进行解析,

         1.1将mapper.xml中对应的select/update/delete/insert节点,包装成mappedStatement对象,然后把这个类对象,存方到了一个map中(mappedStatements),key值是namespace的value+select节点的id,value就是mappedStatement;

         1.2然后通过反射,根据当前namespace,获取到一个class,将class存到了另外一个map中,knownMappers中,key值是通过反射生成的class对象,value是根据class,生成的MapperProxyFactory对象,这两个map,在执行查询的时候,有用到

     2.根据sqlSessionFactoryBuilder生成SqlSessionFactory,再根据sqlSesionFactory,创建sqlSession,这时候,就可以调用slqSession的selectOne();selectList()来执行查询,或者通过sqlSession.getMapper()来获取到接口的代理对象,在执行增删改查sql

    下面,我们根据上述的步骤来解析mybatis源码,主要来说如何解析mybatis配置文件

     1 String resource = "mybatis-config.xml";
     2 InputStream inputStream = Resources.getResourceAsStream(resource);
     3 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
     4 SqlSession sqlSession = sqlSessionFactory.openSession();
     5 //        List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll");
     6 //        OperChannel operChannel = sqlSession.selectOne("com.springsource.study.mybatis.OperChannelMapper.selectAll",1);
     7 //        System.out.println(operChannel.toString());
     8 
     9 System.out.println("+++++++++++++++++");
    10 OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class);
    11 System.out.println(operChannelMapper.selectAll(1));

    这是我自己写的一个测试类,首先我们从sqlSessionFactoryBuilder().buidl(inputStream)说起:

     1.首先会获取到一个XMLConfigBuilder(),然后调用XMLConfigBuilder的parse()方法来解析mybatis的配置文件;需要知道的是,对mybatis配置文件的解析,最后会存放到一个configuration.class中,可以简单理解为和配置文件对应的一个类,在后面,生成的environment、mappedStatements都是configuration的一个属性

     1 public Configuration parse() {
     2   //在new XMLConfigBuilder的时候,默认置为了false,表示当前xml只能被解析一次
     3 if (parsed) {
     4   throw new BuilderException("Each XMLConfigBuilder can only be used once.");
     5 }
     6   parsed = true;
     7   //将配置文件解析成了configuration对象,在该方法中完成
     8   parseConfiguration(parser.evalNode("/configuration"));
     9   return configuration;
    10 }
    11 
    12 /**
    13 * @param root
    14 * 原生mybatis在执行的时候,解析mybatis配置文件
    15 *  这里解析的节点,都是配置文件中配置的xml信息
    16 */
    17 private void parseConfiguration(XNode root) {
    18 try {
    19   //issue #117 read properties first
    20   propertiesElement(root.evalNode("properties"));
    21   Properties settings = settingsAsProperties(root.evalNode("settings"));
    22   loadCustomVfs(settings);
    23   loadCustomLogImpl(settings);
    24   typeAliasesElement(root.evalNode("typeAliases"));
    25   pluginElement(root.evalNode("plugins"));
    26   objectFactoryElement(root.evalNode("objectFactory"));
    27   objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    28   reflectorFactoryElement(root.evalNode("reflectorFactory"));
    29   settingsElement(settings);
    30   // read it after objectFactory and objectWrapperFactory issue #631
    31   //在这里解析environment信息,获取到数据源
    32   environmentsElement(root.evalNode("environments"));
    33   databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    34   typeHandlerElement(root.evalNode("typeHandlers"));
    35   //解析mapper配置信息,将SQL封装成mapperstatement
    36   mapperElement(root.evalNode("mappers"));
    37 } catch (Exception e) {
    38   throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    39   }
    40 }

    在parseConfiguration里面,主要是对配置文件中各个节点属性来进行解析;我们着重来说mapper节点的解析,其中对environment的解析也提一下吧,在获取数据源的时候,会先获取到<DataSource>节点中type,根据type创建一个DataSourceFactory,然后把DataSource节点中配置的property属性的value和name,存到properties中,然后从dataSourceFactory中获取到一个数据源;

    我们来说对mappers的解析:

     在mybatis配置文件中,对mapper.xml的配置方式有四种,分别是package、resource、URL、mapperClass,这四种优先级就是写的前后顺序,因为在源码中,是按照这四个顺序来进行解析的

     解析的mappers节点之后,会对节点中的每个节点进行遍历,我们本次,只以resource为例:

      

     1 private void mapperElement(XNode parent) throws Exception {
     2 if (parent != null) {
     3   for (XNode child : parent.getChildren()) {
     4     //在配置文件中,配置mapper.xml文件有四种方式,这里按照优先级记进行解析
     5     if ("package".equals(child.getName())) {
     6       String mapperPackage = child.getStringAttribute("name");
     7       configuration.addMappers(mapperPackage);
     8     } else {
     9       String resource = child.getStringAttribute("resource");
    10       String url = child.getStringAttribute("url");
    11       String mapperClass = child.getStringAttribute("class");
    12       if (resource != null && url == null && mapperClass == null) {
    13         ErrorContext.instance().resource(resource);
    14         InputStream inputStream = Resources.getResourceAsStream(resource);
    15         //实例化一个mapper解析器
    16         XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    17         //解析SQL语句
    18         mapperParser.parse();
    19       } else if (resource == null && url != null && mapperClass == null) {
    20         ErrorContext.instance().resource(url);
    21         InputStream inputStream = Resources.getUrlAsStream(url);
    22         XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
    23         mapperParser.parse();
    24       } else if (resource == null && url == null && mapperClass != null) {
    25         Class<?> mapperInterface = Resources.classForName(mapperClass);
    26         configuration.addMapper(mapperInterface);
    27       } else {
    28         //配置文件的配置只能是这四种,否则会报错
    29         throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
    30       }
    31     }
    32   }
    33 }
    34 }

     在mapperParse.parse()方法中,会对<mapper>节点进行解析,然后获取到mapper节点对应xml文件中的所有属性

     1 private void configurationElement(XNode context) {
     2     try {
     3       String namespace = context.getStringAttribute("namespace");
     4       if (namespace == null || namespace.equals("")) {
     5         throw new BuilderException("Mapper's namespace cannot be empty");
     6       }
     7       builderAssistant.setCurrentNamespace(namespace);
     8       cacheRefElement(context.evalNode("cache-ref"));
     9       cacheElement(context.evalNode("cache"));
    10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    11       resultMapElements(context.evalNodes("/mapper/resultMap"));
    12       sqlElement(context.evalNodes("/mapper/sql"));
    13       //这里是解析增删改查语句的
    14       buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    15     } catch (Exception e) {
    16       throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    17     }
    18   }

    这个方法就是对mapper.xml文件中各个节点的解析,其中,比较重要的是对增删改查节点的解析:

     会获取到当前mapper中所有的增删改查节点,然后再遍历,遍历的时候,会获取到每个节点的所有参数信息,由于这个解析具体参数的篇幅比较长,就不粘贴了,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 在这个方法中;

     其中需要提的是,以select为例,解析到select节点的所有参数和参数值之后,会把所以参数build成一个mappedStatement对象,然后存放到mappedStatements这个map中,key值就是namespace+id(这里的ID就是select/update...配置的id属性,一般配置成方法名);

    把所有的增删改查存到mappedStatements之后,会进行另外一个操作,就是根据namespace,通过反射,创建一个Class,对象,然后把class对象存到另外一个map中,knownMappers

     1 private void bindMapperForNamespace() {
     2     String namespace = builderAssistant.getCurrentNamespace();
     3     if (namespace != null) {
     4       Class<?> boundType = null;
     5       try {
     6         boundType = Resources.classForName(namespace);
     7       } catch (ClassNotFoundException e) {
     8         //ignore, bound type is not required
     9       }
    10       if (boundType != null) {
    11         if (!configuration.hasMapper(boundType)) {
    12           // Spring may not know the real resource name so we set a flag
    13           // to prevent loading again this resource from the mapper interface
    14           // look at MapperAnnotationBuilder#loadXmlResource
    15           configuration.addLoadedResource("namespace:" + namespace);
    16           configuration.addMapper(boundType);
    17         }
    18       }
    19     }
    20   }

    put方法就在最后一行代码,configuration.addMapper(boundType);

    这里也比较简单,就是new MapperProxyFactory()作为value,class作为key,然后存到map中,后面会用到


    截止到这里,对配置文件,SQL的解析就完成了,下面我们来说如何执行SQL

      1.List<OperChannel> list = sqlSession.selectList("com.springsource.study.mybatis.OperChannelMapper.selectAll");

      我们先说这种方法来请求SQL,这里就是调用DefaultSqlSession 的selectList()方法,

       首先会根据如此那终端额全类名+方法名,从mappedStatements这个map中获取到mappedStatement对象,这也就是为什么namespace一定要和接口的包名+类名一直的原因

       获取到mappedStatement对象之后,就是先查询缓存,缓存中没有就从数据库查询,数据库查到之后,存到一级缓存中,这里的大致的原理是这样的,后面,会单独写一遍笔记。来记录,如何具体执行SQL的

     2.OperChannelMapper operChannelMapper = sqlSession.getMapper(OperChannelMapper.class);

        System.out.println(operChannelMapper.selectAll(1));

        这是第二种方式,和第一种方式稍微有些不同

        这里的入参是mapperInterface.class,会根据入参,从上面提到的knownMappers中获取到mapperProxyFactory对象,然后再调用mapperProxyFactory.newInstance()方法来生成代理对象

    1 protected T newInstance(MapperProxy<T> mapperProxy) {
    2         return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    3     }
    4 
    5     public T newInstance(SqlSession sqlSession) {
    6         MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
    7         return this.newInstance(mapperProxy);
    8     }

    在使用jdk代理生成代理对象时,需要传三个入参,这里重要是mapperproxy,就是实现了invocationHandler接口的实现类,生成了接口的代理对象之后,

    在执行selectAll()的时候,由于这时候,是代理对象,所以会执行mapperproxy的invoke方法,

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    if (Object.class.equals(method.getDeclaringClass())) {
    return method.invoke(this, args);
    }

    if (this.isDefaultMethod(method)) {
    return this.invokeDefaultMethod(proxy, method, args);
    }
    } catch (Throwable var5) {
    throw ExceptionUtil.unwrapThrowable(var5);
    }

    MapperMethod mapperMethod = this.cachedMapperMethod(method);
    return mapperMethod.execute(this.sqlSession, args);
    }

    在这里,mapperMethod.execute()方法中,会判断当前SQL是select?update?delete?等,然后调用DefaultSqlSession对应的方法;
    也就是说,selSession.getMapper()这种方式其实是通过代理对象来执行SQL的;

    截止到这里,基本上就是原生mybatis解析、执行的逻辑,后面有新的认识,会继续更新
  • 相关阅读:
    for,while循环嵌套二次练习
    break语句;continue语句
    实际运用for、while循环嵌套
    for,while循环嵌套
    while循环
    if语法分支
    js读取cookie,并利用encrypt和decrypt加密和解密方法
    instance of的java用法
    objectmapper使用
    日志组件slf4j介绍及配置详解
  • 原文地址:https://www.cnblogs.com/mpyn/p/11962230.html
Copyright © 2011-2022 走看看