Mybatis源码浅析

作者: wxxhfg | 来源:发表于2021-03-28 16:44 被阅读0次

    Mybatis xml解析流程

    Xml解析的常见方式:DOM SAX Xpath ,Mybatis使用的时Xpath,因其足够简单。

    对应代码:

     String resource="mybatis-config.xml";
     InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    

    执行流程:
    1、 执行SqlSessionFactoryBuilder 中的build

    //以下3个方法都是调用下面第8种方法
      public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
      }
    
      public SqlSessionFactory build(InputStream inputStream, String environment) {
        return build(inputStream, environment, null);
      }
    
      public SqlSessionFactory build(InputStream inputStream, Properties properties) {
        return build(inputStream, null, properties);
      }
    
      //第8种方法和第4种方法差不多,Reader换成了InputStream
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          //创建一个XMLConfigBuilder对象
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          //解析xml文件各个结点返回Configuration对象,传入build对象返回DefaultSqlSessionFactory
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    
      //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    
    }
    

    2、执行XMLConfigBuilder的构造方法

     public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
        //构建XPathParser对象  进行xml解析
        this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    
    
      //上面6个构造函数最后都合流到这个函数,传入XPathParser
      private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        //首先调用父类初始化Configuration
        super(new Configuration());
        //错误上下文设置成SQL Mapper Configuration(XML文件配置),以便后面出错了报错用吧
        ErrorContext.instance().resource("SQL Mapper Configuration");
        //将Properties全部设置到Configuration里面去
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
    

    3、new Configuration() (属性都为默认值)

    设置configuration的属性参数

    设置XMLConfigBuilder的其他参数(document对象也在其中创建 即将xml 转化为Document对象)

    此时XMLConfigBuilder构建完成

     public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
        commonConstructor(validation, variables, entityResolver);
        //调用createDocument() 构建document ,底层使用SAX解析  将进来的xml文件  转换为document
        this.document = createDocument(new InputSource(inputStream));
      }
    

    4、调用parser.parse()获得Configuration 对象

    //解析配置
      public Configuration parse() {  //只解析一次
        //如果已经解析过了,报错
        if (parsed) {
          throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;  //未解析过  将标志位置为true
    //  <?xml version="1.0" encoding="UTF-8" ?>
    //  <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    //  "http://mybatis.org/dtd/mybatis-3-config.dtd">
    //  <configuration>
    //  <environments default="development">
    //  <environment id="development">
    //  <transactionManager type="JDBC"/>
    //  <dataSource type="POOLED">
    //  <property name="driver" value="${driver}"/>
    //  <property name="url" value="${url}"/>
    //  <property name="username" value="${username}"/>
    //  <property name="password" value="${password}"/>
    //  </dataSource>
    //  </environment>
    //  </environments>
    //  <mappers>
    //  <mapper resource="org/mybatis/example/BlogMapper.xml"/>
    //  </mappers>
    //  </configuration>
    
        //根节点是configuration   parser Xpath解析器
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
      }
    
      //解析配置
      private void parseConfiguration(XNode root) {
        try {
          //分步骤解析
          //issue #117 read properties first
          //1.properties
          propertiesElement(root.evalNode("properties"));
          //2.类型别名
          typeAliasesElement(root.evalNode("typeAliases"));
          //3.插件
          pluginElement(root.evalNode("plugins"));
          //4.对象工厂
          objectFactoryElement(root.evalNode("objectFactory"));
          //5.对象包装工厂
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          //6.设置
          settingsElement(root.evalNode("settings"));
          // read it after objectFactory and objectWrapperFactory issue #631
          //7.环境
          environmentsElement(root.evalNode("environments"));
          //8.databaseIdProvider
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          //9.类型处理器
          typeHandlerElement(root.evalNode("typeHandlers"));
          //10.映射器
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    parser是XPathParser对象,使用parser解析xml的configuration结点,得到Xnode结点。返回Xnode结点,Xnod中包含了其他结点的信息

    5、解析对应结点下的各个结点内容,放进Configuration

    //1.properties
      //<properties resource="org/mybatis/example/config.properties">
      //    <property name="username" value="dev_user"/>
      //    <property name="password" value="F2Fa3!33TYyg"/>
      //</properties>
      private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          //如果在这些地方,属性多于一个的话,MyBatis 按照如下的顺序加载它们:
    
          //1.在 properties 元素体内指定的属性首先被读取。
          //2.从类路径下资源或 properties 元素的 url 属性中加载的属性第二被读取,它会覆盖已经存在的完全一样的属性。
          //3.作为方法参数传递的属性最后被读取, 它也会覆盖任一已经存在的完全一样的属性,这些属性可能是从 properties 元素体内和资源/url 属性中加载的。
          //传入方式是调用构造函数时传入,public XMLConfigBuilder(Reader reader, String environment, Properties props)
    
          //1.XNode.getChildrenAsProperties函数方便得到孩子所有Properties
          Properties defaults = context.getChildrenAsProperties();
          //2.然后查找resource或者url,加入前面的Properties
          String resource = context.getStringAttribute("resource");
          String url = context.getStringAttribute("url");
          if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
          }
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }
          //3.Variables也全部加入Properties
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          parser.setVariables(defaults);
          configuration.setVariables(defaults);
        }
      }
    

    mapperElement节点解析

    //10.映射器
    //  10.1使用类路径
    //  <mappers>
    //    <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    //    <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
    //    <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    //  </mappers>
    //
    //  10.2使用绝对url路径
    //  <mappers>
    //    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    //    <mapper url="file:///var/mappers/BlogMapper.xml"/>
    //    <mapper url="file:///var/mappers/PostMapper.xml"/>
    //  </mappers>
    //
    //  10.3使用java类名
    //  <mappers>
    //    <mapper class="org.mybatis.builder.AuthorMapper"/>
    //    <mapper class="org.mybatis.builder.BlogMapper"/>
    //    <mapper class="org.mybatis.builder.PostMapper"/>
    //  </mappers>
    //
    //  10.4自动扫描包下所有映射器
    //  <mappers>
    //    <package name="org.mybatis.builder"/>
    //  </mappers>
      private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              //10.4自动扫描包下所有映射器
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
                //10.1使用类路径
                ErrorContext.instance().resource(resource);
                InputStream inputStream = Resources.getResourceAsStream(resource);
                //映射器比较复杂,调用XMLMapperBuilder
                //注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url != null && mapperClass == null) {
                //10.2使用绝对url路径
                ErrorContext.instance().resource(url);
                InputStream inputStream = Resources.getUrlAsStream(url);
                //映射器比较复杂,调用XMLMapperBuilder
                XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                mapperParser.parse();
              } else if (resource == null && url == null && mapperClass != null) {
                //10.3使用java类名
                Class<?> mapperInterface = Resources.classForName(mapperClass);
                //直接把这个映射加入配置
                configuration.addMapper(mapperInterface);
              } else {
                throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
              }
            }
          }
        }
      }
    
    //Configuration类下
    public void addMappers(String packageName) {
        mapperRegistry.addMappers(packageName);
      }
    
      //查找包下所有类   MapperRegistry类下
      public void addMappers(String packageName) {
        addMappers(packageName, Object.class);
      }
    
      //MapperRegistry类下
      public void addMappers(String packageName, Class<?> superType) {
        //查找包下所有是superType的类
        ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
        resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
        Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
        for (Class<?> mapperClass : mapperSet) {
          addMapper(mapperClass);    //将set里面的xxxMapper使用动态代理创建出来
        }
      }
    
      //看一下如何添加一个映射   
      public <T> void addMapper(Class<T> type) {
        //mapper必须是接口!才会添加
        if (type.isInterface()) {
          if (hasMapper(type)) {
            //如果重复添加了,报错
            throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
          }
          boolean loadCompleted = false;
          try {
            //创建mapper的动态代理,使用JDK的动态代理实现
            knownMappers.put(type, new MapperProxyFactory<T>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
            parser.parse();  //解析成对应的mapper.xml 文件
            loadCompleted = true;
          } finally {
            //如果加载过程中出现异常需要再将这个mapper从mybatis中删除
            if (!loadCompleted) {
              knownMappers.remove(type);
            }
          }
        }
      }
    

    6、调用build得到会话工厂DefaultSqlSessionFactory

    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          //创建一个XMLConfigBuilder对象
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          //解析xml文件各个结点返回Configuration对象,传入build对象返回DefaultSqlSessionFactory
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }
    
      //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    

    流程图:

    源码分析流程图

    SqlSession创建流程

    SqlSessionFactoryBuilder

        使用**建造者模式** 
    

    这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

    SqlSessionFactory

    使用工厂模式,单例模式

    SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式

    SqlSession

    每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。


    Mybatis核心对象

    mybatis核心对象分为两类:存储类对象和 操作类对象
    SqlSesson封装了JDBC(Statement,Prepared Statement ,Callable Statement ,ResultSet),还包含了SqlSessionFactory,mybatis-config,.xml,Mapper.xml的信息

    Mybatis的核心对象及其作用

    mybatis核心对象

    存储类对象

    概念:在java中(jvm)对Mybatis相关的配置信息进行封装

    Configrution

    mybatis-config.xml ----------------->Configuration

    1. 封装了mybatis-config.xml
    2. 封装了mapper文件 MappedStatement
    3. 创建了Mybatis其他相关的对象
    public class Configuration {
    
      //环境
      protected Environment environment;
    
      //---------以下都是<settings>节点-------
      protected boolean safeRowBoundsEnabled = false;
      protected boolean safeResultHandlerEnabled = true;
      protected boolean mapUnderscoreToCamelCase = false;   //数据库表字段驼峰
      protected boolean aggressiveLazyLoading = true;
      protected boolean multipleResultSetsEnabled = true;
      protected boolean useGeneratedKeys = false;          //自增主键
      protected boolean useColumnLabel = true;
      //默认启用缓存
      protected boolean cacheEnabled = true;
      protected boolean callSettersOnNulls = false;
    
      protected String logPrefix;
      protected Class <? extends Log> logImpl;
      //缓存作用域  本地缓存使用范围为SESSION  对相同SqlSession的不同调用将不会共享数据
      protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
      protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
      protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
      protected Integer defaultStatementTimeout;
      //默认为简单执行器   实际上因为cacheEnabled = true 缓存开启而使用了CachingExecutor
      protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
      protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
      //---------以上都是<settings>节点-------
    
      protected Properties variables = new Properties();
      //对象工厂和对象包装器工厂
      protected ObjectFactory objectFactory = new DefaultObjectFactory();
      protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
      //映射注册机
      protected MapperRegistry mapperRegistry = new MapperRegistry(this);
    
      //默认禁用延迟加载
      protected boolean lazyLoadingEnabled = false;
      protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
    
      protected String databaseId;
      /**
       * Configuration factory class.
       * Used to create Configuration for loading deserialized unread properties.
       *
       * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300</a> (google code)
       */
      protected Class<?> configurationFactory;
      //拦截器链  在Executor创建的时候赋值在
      protected final InterceptorChain interceptorChain = new InterceptorChain();
      //类型处理器注册机
      protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
      //类型别名注册机
      protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
      protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    
      //映射的语句,存在Map里   对应这各个xxxDAOMapper.xml文件,将各个Mapper信息进行了汇总
      protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
      //缓存,存在Map里
      protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
      //结果映射,存在Map里    所有的ResultMap 全都放在resultMaps中
      protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
      protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
      protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
    
      //路径  对应xml中的mapper
      protected final Set<String> loadedResources = new HashSet<String>();
      protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
    
      //不完整的SQL语句
      protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
      protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
      protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
      protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
    
      /*
       * A map holds cache-ref relationship. The key is the namespace that
       * references a cache bound to another namespace and the value is the
       * namespace which the actual cache is bound to.
       */
      protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
    
      public Configuration(Environment environment) {
        this();
        this.environment = environment;
      }
    
      public Configuration() {
        //注册更多的类型别名,至于为何不直接在TypeAliasRegistry里注册,还需进一步研究
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); //事务处理器
        typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    
        typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);  //这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用
        typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
        typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
        typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); // 缓存策略
        typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
        typeAliasRegistry.registerAlias("LRU", LruCache.class);
        typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
        typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
    
        typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
    
        typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
        typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);  //raw 图像格式
    
        typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);  //日志
        typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
        typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
        typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
        typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
        typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
        typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
    
        typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); //动态代理
        typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
    
        languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);   //默认使用xml
        languageRegistry.register(RawLanguageDriver.class);
      }
    //创建元对象
      public MetaObject newMetaObject(Object object) {
        return MetaObject.forObject(object, objectFactory, objectWrapperFactory);
      }
    
      //创建参数处理器
      public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        //创建ParameterHandler
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        //插件在这里插入
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    
      //创建结果集处理器
      public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
        //创建DefaultResultSetHandler(稍老一点的版本3.1是创建NestedResultSetHandler或者FastResultSetHandler)
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        //插件在这里插入
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
      }
    
      //创建语句处理器
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        //创建路由选择语句处理器
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        //插件在这里插入
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
      }
    
      //产生执行器
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        //这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        //然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        //此处调用插件,通过插件可以改变Executor行为
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
    
       ************************** get()和set()方法省略**************
    }
    

    Configrution小结:

    1. 设置配置文件中的对应属性

    2. 设置默认执行器 (简单执行器)

      protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
      
    3. 创建拦截器链 InterceptorChain

    4. 创建类型处理器注册机 TypeHandlerRegistry

    5. 创建类型别名注册机 TypeAliasRegistry

    6. 将各个mapper文件中的信息进行汇总存放

      protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
      
    7. 为系统内部使用的类定义别名注册(在构造函数中设置)

    8. 创建元对象

    9. 创建参数处理器 parameterHandler

    10. 创建结果集处理器resultSetHandler

    11. 创建语句处理器statementHandler

    12. 创建执行器executor

    MappedStatement

    xxxDaoMapper.xml -----------------> MappedStatement

    MappedStatement对象

    对应的就是Mapper文件中的一个一个的配置

    <select id 和 < delete id 对应不同的MappedStatement对象 Mapper文件中的语句都被打散了,不以整个mapper出现

     //映射的语句,存在Map里   对应这各个xxxDAOMapper.xml文件,将各个Mapper信息进行了汇总
      protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
    
    <select id="getRole" parameterType="long" resultMap="roleMap" >
            select
            id,role_name as roleName,note from role where id=#{id}
        </select>
    

    mapper中的一条标签语句对应着一个MappedStatement
    MappedStatment 中使用BoundSql来封装sql语句

    public final class MappedStatement {
    
      //对应的属性
      private String resource;
      private Configuration configuration;
      private String id;    //sql的id namespace.id
      private Integer fetchSize;
      private Integer timeout;
      private StatementType statementType;
      private ResultSetType resultSetType;
    
      //SQL源码
      private SqlSource sqlSource;   //解析出来的sql   对应一条sql语句
      private Cache cache;
      private ParameterMap parameterMap;   //以废弃
      private List<ResultMap> resultMaps;   //对应的ResultMap
      private boolean flushCacheRequired;
      private boolean useCache;
      private boolean resultOrdered;
      private SqlCommandType sqlCommandType;
      private KeyGenerator keyGenerator;
      private String[] keyProperties;
      private String[] keyColumns;
      private boolean hasNestedResultMaps;
      private String databaseId;  //数据库ID,用来区分不同环境
      private Log statementLog;
      private LanguageDriver lang;
      private String[] resultSets;
    
      MappedStatement() {
        // constructor disabled
      }
    
      //静态内部类,建造者模式
      public static class Builder {
        private MappedStatement mappedStatement = new MappedStatement();
    
        public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
          mappedStatement.configuration = configuration;
          mappedStatement.id = id;
          mappedStatement.sqlSource = sqlSource;
          mappedStatement.statementType = StatementType.PREPARED; //默认使用PREPARED
          mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
          mappedStatement.resultMaps = new ArrayList<ResultMap>();
          mappedStatement.timeout = configuration.getDefaultStatementTimeout();
          mappedStatement.sqlCommandType = sqlCommandType;   //select ||delete|| update ||insert || UNKNOWN
          mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
          String logId = id;
          if (configuration.getLogPrefix() != null) {
            logId = configuration.getLogPrefix() + id;
          }
          mappedStatement.statementLog = LogFactory.getLog(logId);
          mappedStatement.lang = configuration.getDefaultScriptingLanuageInstance();
        }
    
        public Builder resource(String resource) {
          mappedStatement.resource = resource;
          return this;
        }
    
        public String id() {
          return mappedStatement.id;
        }
    
        public Builder parameterMap(ParameterMap parameterMap) {
          mappedStatement.parameterMap = parameterMap;
          return this;
        }
    
        public Builder resultMaps(List<ResultMap> resultMaps) {
          mappedStatement.resultMaps = resultMaps;
          for (ResultMap resultMap : resultMaps) {
            mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
          }
          return this;
        }
    
        public Builder fetchSize(Integer fetchSize) {
          mappedStatement.fetchSize = fetchSize;
          return this;
        }
    
        public Builder timeout(Integer timeout) {
          mappedStatement.timeout = timeout;
          return this;
        }
    
        public Builder statementType(StatementType statementType) {
          mappedStatement.statementType = statementType;
          return this;
        }
    
        public Builder resultSetType(ResultSetType resultSetType) {
          mappedStatement.resultSetType = resultSetType;
          return this;
        }
    
        public Builder cache(Cache cache) {
          mappedStatement.cache = cache;
          return this;
        }
    
        public Builder flushCacheRequired(boolean flushCacheRequired) {
          mappedStatement.flushCacheRequired = flushCacheRequired;
          return this;
        }
    
        public Builder useCache(boolean useCache) {
          mappedStatement.useCache = useCache;
          return this;
        }
    
        public Builder resultOrdered(boolean resultOrdered) {
          mappedStatement.resultOrdered = resultOrdered;
          return this;
        }
    
        public Builder keyGenerator(KeyGenerator keyGenerator) {
          mappedStatement.keyGenerator = keyGenerator;
          return this;
        }
    
        public Builder keyProperty(String keyProperty) {
          mappedStatement.keyProperties = delimitedStringtoArray(keyProperty);
          return this;
        }
    
        public Builder keyColumn(String keyColumn) {
          mappedStatement.keyColumns = delimitedStringtoArray(keyColumn);
          return this;
        }
    
        public Builder databaseId(String databaseId) {
          mappedStatement.databaseId = databaseId;
          return this;
        }
    
        public Builder lang(LanguageDriver driver) {
          mappedStatement.lang = driver;
          return this;
        }
    
        public Builder resulSets(String resultSet) {
          mappedStatement.resultSets = delimitedStringtoArray(resultSet);
          return this;
        }
    
        public MappedStatement build() {
          assert mappedStatement.configuration != null;
          assert mappedStatement.id != null;
          assert mappedStatement.sqlSource != null;
          assert mappedStatement.lang != null;
          mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
          return mappedStatement;
        }
      }
    ***************************get()和set()方法省略********************************************
    }
    

    MappedStatement 小结:

    1. 使用建造者模式构建
    2. 内部有configuration便于操作和使用
    3. 含有xml某个结点中的完整信息

    操作类对象

    Executor:Mybatis的执行器

    主要功能:

    1. 增删改 update
    2. 查 query
    3. 事务 提交 回滚
    4. 缓存


      Executo功能
    Executo的实现类

    SimpleExecutor 常用Excutor
    ReuseExecutor 复用Excutor 条件和参数都不改变的情况下可以使用
    BatchExecutor JDBC中批处理 批处理;一个连接上进行多个sql

    Executor小结 :

    1. 调用StatementHandler来进行数据库相关的改、查操作
    2. 维护缓存

    StatementHandler

    是Mybatis真正封装JDBC Statement 的部分 , 是Mybatis访问数据库的核心。实现了Excutor中增删改查的功能

    ParameterHandler

    解决sql中的参数类型和占位符(?,?)

    ResultSetHandler

    对JDBC中 查询结果集ResultSet封装

    TypeHandler

    类型转化
    例如:将java类型转化为数据库类型(String varchar)


    Mybatis工作流程源码分析

    将核心对象与SqlSession建立联系

    对应代码:

    RoleMapper roleMapper=sqlSession.getMapper(RoleMapper.class);
    //sqlSession.selectList("com.wxx.mapper.RoleMapper.getRole");  上面语句是下面语句的封装
    

    执行流程:

    1、DefaultSqlSession(sqlSession默认实现方式)调用getMapper()方法 将xxxMapper.class 和 当前进行会话的sqlsession传入进去

    //DefaultSqlSession类
      public <T> T getMapper(Class<T> type) {
        //最后会去调用MapperRegistry.getMapper
        return configuration.<T>getMapper(type, this);
      }
    
    

    2、调用mapper注册机的getMapper()方法

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    
    1. 程序中的mapper都放在一个hashmap中,先查找这个hashmap中有没有key为当前类的代理工厂类。如果有则为当前mapper创建一个代理对象,并返回;如果没有则进行异常处理。
     @SuppressWarnings("unchecked")
      //返回代理类
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        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);
        }
      }
    

    代理实现原理分析:

    对应代码:

    mapperProxyFactory.newInstance(sqlSession)
    
    1. 动态代理创建具体实现类
    2. 具体实现类内部调用对应的sqlsession的方法
    mybatis源码中的这些核心对象,在sqlsession调用对应功能时候建立联系
    例如:
        SqlSession.insert()
            DefaultSqlSession
                Exctutor
                    StatementHandler
    

    MyBatis 完成代理创建核心类型 ---> DAO接口的实现类
    执行顺序如下:

    MapperProxy  implements InvocationHandler
                    invoke(类加载器,方法名,参数)
                            sqlSession.insert
                                                update
                                                delete
                                                selectOne
                                                selectList
    MapperProxyFactory
                Proxy.newProxyInstrance()
    

    MapperProxy 实现InvocationHandler接口
    实现invoke()方法

    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
    
      public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {   //生成xxxDAO的实现类
        //用JDK自带的动态代理生成映射器
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    
      public T newInstance(SqlSession sqlSession) {     //传进sqlSession和对应mapperInterface 生成代理对象  methodCache是方法缓存
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
    }
    
    public class MapperProxy<T> implements InvocationHandler, Serializable {
    
      private static final long serialVersionUID = -6424540398559729838L;
      private final SqlSession sqlSession;
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache;
    
      public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
      }
    
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
        //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
        if (Object.class.equals(method.getDeclaringClass())) {
          try {
            return method.invoke(this, args);
          } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
        //这里优化了,去缓存中找MapperMethod
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行
        return mapperMethod.execute(sqlSession, args);
      }
    
      //去缓存中找MapperMethod
      private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = methodCache.get(method);
        if (mapperMethod == null) {
          //找不到才去new
          mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
          methodCache.put(method, mapperMethod);
        }
        return mapperMethod;
      }
    
    }
    

    invoke()中的method.invoke(this, args); 底层对应的相应的SqlSession.insertupdatedeleteselectOneselectList方法。

    public class MapperMethod {
    
      private final SqlCommand command;  //对应的标签 是什么类型   才能具体的知道调用对应crud所对应的方法
      private final MethodSignature method;   //返回值  和 参数
        //需求Configuration对象
      public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new SqlCommand(config, mapperInterface, method);
        this.method = new MethodSignature(config, method);
      }
    
      //执行
      public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        //可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
        if (SqlCommandType.INSERT == command.getType()) {   //insert
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.insert(command.getName(), param));
        } else if (SqlCommandType.UPDATE == command.getType()) { //update
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.update(command.getName(), param));
        } else if (SqlCommandType.DELETE == command.getType()) { //delete
          Object param = method.convertArgsToSqlCommandParam(args);
          result = rowCountResult(sqlSession.delete(command.getName(), param));
        } else if (SqlCommandType.SELECT == command.getType()) { //select
          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 {
            //否则就是一条记录   selectOne
            Object param = method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(command.getName(), param);
          }
        } else {
          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命令,静态内部类
      public static class SqlCommand {
    
        private final String name;  //name.id
        private final SqlCommandType type; //select || update || insert || delete
    
        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
          String statementName = mapperInterface.getName() + "." + method.getName();
          MappedStatement ms = null;
          if (configuration.hasStatement(statementName)) {
            ms = configuration.getMappedStatement(statementName);
          } else if (!mapperInterface.equals(method.getDeclaringClass().getName())) { // issue #35
            //如果不是这个mapper接口的方法,再去查父类
            String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
            if (configuration.hasStatement(parentStatementName)) {
              ms = configuration.getMappedStatement(parentStatementName);
            }
          }
          if (ms == null) {
            throw new BindingException("Invalid bound statement (not found): " + statementName);
          }
          name = ms.getId();
          type = ms.getSqlCommandType();   //select delete update insert
          if (type == SqlCommandType.UNKNOWN) {
            throw new BindingException("Unknown execution method for: " + name);
          }
        }
    
        public String getName() {
          return name;
        }
    
        public SqlCommandType getType() {
          return type;
        }
      }
    
    //方法签名,静态内部类
      public static class MethodSignature { //对应sql语句中的参数
    
        private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;
        private final Class<?> returnType;
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final SortedMap<Integer, String> params;
        private final boolean hasNamedParameters;
    
        public MethodSignature(Configuration configuration, Method method) {
          this.returnType = method.getReturnType();
          this.returnsVoid = void.class.equals(this.returnType);
          this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
          this.mapKey = getMapKey(method);
          this.returnsMap = (this.mapKey != null);
          this.hasNamedParameters = hasNamedParams(method);
          //以下重复循环2遍调用getUniqueParamIndex,是不是降低效率了
          //记下RowBounds是第几个参数
          this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
          //记下ResultHandler是第几个参数
          this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
          this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
        }
    

    StatementHandler执行流程:
    时序图(图片来自于http://www.coderead.cn/

    mybatis执行时序图

    核心对象总结

    configuration

    默认初始化时,默认构造函数为各种类型起别名:

                    1. 事务处理器
                    2. 数据源类型 
                    3. 缓存策略
                    4. 图像格式
                    5. 日志类型
                    6. 动态代理类型
                    7. 默认驱动类(xml方式)
    

    其他配置:

    1. 默认使用简单执行器ExecutorType.SIMPLE
    2. 缓存作用域在session范围内LocalCacheScope.SESSION
    3. 创建拦截器链
    4. 创建类型处理器注册机
    5. 创建类型别名注册机

    mappedStatement

    用来表示xml中的一个sql,例如:

     <select id="findRole" parameterType="long" resultMap="roleMap">
            select
            id,role_name,note from role where role_name like CONCAT('%',#{roleName
            javaType=string,
            jdbcType=VARCHAR,typeHandler=com.wxx.handler.MyStringHandler},'%')
        </select>
    
    用来描述<select|update|insert|delete>或者@Select、@Update等注解配置的SQL信息。
    

    sqlsession

    使用门面模式,只设定api,具体参数让executor来执行

    executor

    简单执行器(SimpleExecutor),可实现sqlsession中的全部功能 一次一次的执行

    重用执行器(ReuseExecutor),先查缓存中时候有相应的sql语句,若有,则不再进行sql的预编译

    批处理执行器(BatchExecutor),一次插入一批数据,只对操作有效,对查询操作和简单执行器一样

    四大对象

    StatementHandler

    ParameterHandler 用来处理StatementHandler中的参数

    ResultSetHandler 用来处理StatementHandler 中的结果集

    TypeHandler 用来帮助ParameterHandlerResultSetHandler处理javaType和数据库类型的相互转换


    缓存

    一级缓存在BaseExecutor中实现 二级缓存使用装饰器模式在CachingExecutor中实现

    缓存逻辑图

    mybaits缓存逻辑图

    一级缓存

    命中场景(会话级别缓存)

    运行时参数相关:

    1. sql语句 和参数相同
    2. statementid需要相同
    3. sqlsession也必须相同
    4. RowBounds返回行范围必须相同

    操作与配置相关:

    ①②③ 都会调用clearLocalCache()来对缓存进行清空

    1. 未手动清空 (提交 回滚)
    2. 未调用flushCache == true 的查询
    3. 未执行update操作 update操作会清空全部缓存
    4. 缓存作用域 不是STATEMENT

    spring整合mybatis 以及缓存失效原因


    使用拦截器(动态代理)来控制方法的调用,最终达到实现事务的效果

    二级缓存

    作用范围是整个应用,而且可以跨线程调用



    使用流程:


    二级缓存执行流程

    使用场景

    运行时参数相关:

    1. 会话提交之后
    2. sql语句 和参数相同
    3. statement的id需要相同
    4. RowBounds 返回行范围必须相同

    为何提交之后才能命中缓存

    会话1和会话2本来是不可见的,但是使用二级缓存后边的可见了。例如: 会话2进行了查询、修改操作,若此时将操作填充到二级缓存中,会话1此时进行查询,拿到了这条缓存数据。而后会话2 进行了回滚操作,会话1拿到的数据就是数据库中不存在的,导致了脏读

    解决方案:


    二级缓存存储图

    为每个会话创建一个事务缓存管理器(transactionCaches),每次访问一个新的xxxMapper都会创建一个暂存区,对应着相应的缓存区


    Mybatis中使用的设计模式

    单例模式

    configuration 使用单例 保证整个程序运行期间都可使用,且不重复创建,放置浪费资源

    构造器模式

            在Mybatis的初始化的主要工作是加载并解析mybatis-config.xml的配置文件、映射配置文件以及相关的注解信息。因为使用了建造者模式,BashBuilder抽象类即为建造者接口的角色

    public abstract class BaseBuilder {
        //需要配置,类型别名注册,类型处理器注册3个东西
      protected final Configuration configuration;
      protected final TypeAliasRegistry typeAliasRegistry;
      protected final TypeHandlerRegistry typeHandlerRegistry;
    
      public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
      }
       ………………
    }
    

    适配器模式

    (Excutor 等核心对象,使用缺省适配器模式) 使用extends关键字来继承,只用实现想实现的功能

    缺省适配模式 为一个接口提供缺省实现,这样的类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。当原接口中定义的方法太多,而其中大部分又不被需要时,这种模式非常实用。由缺省适配器类直接实现目标(Target)角色接口,并为所有方法提供缺省的空实现。用户类就只需要继承适配器类,只实现自己需要实现的方法就行了。

    interface Test{
       void eat();
       void drink();
       void walk();
    }
    
    
    abstract class abstractClass implements Test{
       public void eat() {
           // TODO Auto-generated method stub
           
       }
       @Override
       public void drink() {
           // TODO Auto-generated method stub
           
       }
    
       @Override
       public void walk() {
           // TODO Auto-generated method stub
           
       }
    }
    
    class eatClass extends abstractClass{  
           @Override
           public void eat() {
               // TODO Auto-generated method stub
               super.eat();
           }
       }
       class drinkClass extends abstractClass{
           @Override
           public void eat() {
               // TODO Auto-generated method stub
               super.eat();
           }
       
       }
    

    代理模式(动态代理)

     RoleMapper roleMapper=sqlSession.getMapper(RoleMapper.class);  实现类使用动态字节码技术来创建,在jvm中运行时创建
        等价于
    Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{RoleMapper.class},
                                                                new MyMapperProxy(sqlSession,RoleMapper.class));
    

    工厂模式

    使用mapper工厂来创建mapper

    MapperProxyFactory  和  MapperProxy
        
    protected T newInstance(MapperProxy<T> mapperProxy) {
        //用JDK自带的动态代理生成映射器
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    

    门面模式

    sqlsession使用门面模式,只定义api 具体的使用让Executor来进行实现
    

    装饰器模式

    在不改变原有类结构和继承的情况下,通过包装原对象去扩展一个新功能

    Caching Executor 实现二级缓存,而其他的操作,如同获取连接等操作BaseExecutor已经做了,直接使用BaseExecutor

    责任链模式

    二级缓存中的责任链模式


    二级缓存责任链

    相关文章

      网友评论

        本文标题:Mybatis源码浅析

        本文链接:https://www.haomeiwen.com/subject/kuyphltx.html