Mybati源码解读|缓存介绍

写在前面:

mybatis原理的话就几个技术点。反射,动态代理,缓存。

一,原理分析

最原始的mybatis示例代码。

mybatis-config.xml:

<?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>
    <settings>
        <!-- 这个配置使全局的映射器启用或禁用 缓存 -->
        <setting name="cacheEnabled" value="true" />
        <!-- 允许 JDBC 支持生成的键。 需要适合的 驱动。 如果设置为 true 则这个设置强制 生成的键被使用, 尽管一些驱动拒绝兼 容但仍然有效(比如
            Derby) -->
        <setting name="useGeneratedKeys" value="false" />
        <!-- 配置默认的执行器。SIMPLE 执行器没 有什么特别之处。REUSE 执行器重用 预处理语句。BATCH 执行器重用语句 和批量更新 -->
        <setting name="defaultExecutorType" value="SIMPLE" />
        <!-- 设置超时时间, 它决定驱动等待一个数 据库响应的时间 -->
        <setting name="defaultStatementTimeout" value="120" />
        <setting name="safeRowBoundsEnabled" value="false" />
        <setting name="mapUnderscoreToCamelCase" value="false" />
        <setting name="localCacheScope" value="SESSION" />
        <setting name="jdbcTypeForNull" value="OTHER" />
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString" />
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <typeAliases>
        <package name="com.equaker.demo.bean"/>
    </typeAliases>

    <environments default="default">

        <environment id="default">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/tb?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;userSSL=false&amp;serverTimezone=CTT&amp;allowMultiQueries=true"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>

    </environments>

    <mappers>
        <mapper resource="mapper/StudentMapper.xml"></mapper>
    </mappers>


</configuration>
Mybatis_Test.java:
package com.equaker.demo.test;

import com.equaker.demo.bean.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class Mybatis_Test {

    public static void main(String[] args) throws IOException {


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

        SqlSession sqlSession = sqlSessionFactory.openSession();
        Student student1 = sqlSession.selectOne("com.equaker.demo.mapper.StudentMapper.getById", 1);
        System.out.println(student1);

        Student student2 = sqlSession.selectOne("com.equaker.demo.mapper.StudentMapper.getById", 1);
        System.out.println(student2);

        System.out.println(student1 ==student2);

    }

}

1.1,原理解析

①,SqlSessionFactory:

我们来一步步解析这几行代码,就知道mybatis从启动到运行的构建过程了。

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//这一行代码正是初始化工作的开始。
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

进入源码分析:

// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream) {
	//调用了重载方法
    return build(inputStream, null, null);
  }

// 2.调用的重载方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //  XMLConfigBuilder是专门解析mybatis的配置文件的类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //这里又调用了一个重载方法。parser.parse()的返回值是Configuration对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } //省略部分代码
  }

我们知道parser.parse() 返回的是Configuration对象。

Configuration对象的结构和xml配置文件的对象几乎相同。

回顾一下xml中的配置标签有哪些:

properties(属性),settings(设置),typeAliases(类型别名),typeHandlers(类型处理器),objectFactory(对象工厂),mappers(映射器)等

也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部的属性中。

接下来我们进入parser.parse()方法内部看看。

//在创建XMLConfigBuilder时,它的构造方法中解析器XPathParser已经读取了配置文件
//3. 进入XMLConfigBuilder 中的 parse()方法。
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件中的顶层标签
    parseConfiguration(parser.evalNode("/configuration"));
    //最后返回的是Configuration 对象
    return configuration;
}

//4. 进入parseConfiguration方法
//此方法中读取了各个标签内容并封装到Configuration中的属性中。
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

这里面解析的节点就是mybatis-config.xml里面的标签。我们主要来看看mapperElement(root.evalNode("mappers"));。也就是解析mapper.xml的过程。这里会生成后面执行需要的对象MappedStatement。

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          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) {
            //由于我们的mybatis-config.xml只包好resource属性,所以我们只分析一种情况。
            //这里resource=mapper/StudentMapper.xml
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else {
           。。。
          }
        }
      }
    }
  }
mapperParser.parse():
 public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}
private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }

这里面都是在组装Configuration对象。那么这么类到底是什么呢?

Configuration.java:

public class Configuration {
  ...
  protected Environment environment;
  protected boolean mapUnderscoreToCamelCase;

  protected boolean useGeneratedKeys;
  //二级缓存,默认开启,但如果想使用 需要到特定的mapper.xml下面开启<cache/>标签
  protected boolean cacheEnabled = true;
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  //MappedStatement集合,继承 HashMap
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
 
 ...
}

这里面我只列举了几个我们常用到的。比如缓存,主键,mappedstatement

到此对xml配置文件的解析就结束了(下文会对部分解析做详细介绍),回到步骤 2. 中调用的重载build方法。

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

最后构造了一个DefaultSqlSessionFactory。进入类的内部 你会很惊讶的。

public class DefaultSqlSessionFactory implements SqlSessionFactory {

  private final Configuration configuration;

  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
  ...
}

内部就一个刚刚生成的Configuration。其余的都是生成SqlSession的方法。

在这里我们现总结一下目前初始化完成了那些东西。扫描了Mapper.xml映射文件,生成了MappedStatement 保存在StrictMap内。等等,并默认配置了一些执行器。

protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;这个定义了后面Sqlsession执行的时候 使用的执行器类型。

②,SqlSession:

SqlSession sqlSession = sqlSessionFactory.openSession();

很贱的一行代码就拿到了SqlSession对象。

进入源码:

//6. 进入openSession方法。
  public SqlSession openSession() {
  	//getDefaultExecutorType()传递的是SimpleExecutor
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

//7. 进入openSessionFromDataSource。
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //根据参数创建指定类型的Executor
      final Executor executor = configuration.newExecutor(tx, execType);
      //返回的是DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

根据上一步生成的Configuration对象。再根据默认执行器类型,创造了执行器。Configuration也传递到了SqlSession内部。

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
  ...
}

现在我们获取到了Sqlsession对象。下一步利用这个对象进行查询操作。

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //根据传入的全限定名+方法名从映射的Map中取出MappedStatement对象
      MappedStatement ms = configuration.getMappedStatement(statement);
      //调用Executor中的方法处理
      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 :

  • 作用: MappedStatement与Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。
  • **初始化过程:**回顾刚开始介绍的加载配置文件的过程中,会对mybatis-config.xml中的各个标签都进行解析,其中有 mappers标签用来引入mapper.xml文件或者配置mapper接口的目录。
 <select id="getUser" resultType="user" >
    select * from user where id=#{id}
  </select>

 

这样的一个select标签会在初始化配置文件时被解析封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements属性中,mappedStatements 是一个HashMap,存储时key = 全限定类名 + 方法名,value = 对应的MappedStatement对象

 继续源码中的步骤,进入 executor.query()。

//excutor是CacheExecutor
@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();
    }
  }


@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //缓存键,牵扯几个缓存命中的因素(事务,statementid, sql, params,environment)
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

//delegate是SimpleExecutor,继承自BaseExecutor.

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //在ms里面的缓存当然是二级缓存了。在这里我们也可以看到是先走二级缓存,在走一级缓存的。
    //由于未开启二级缓存。我们使用delegate。query()方法。
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }



public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //这里的localcache就是一级缓存了。
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //我们走数据库查询的方法
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }

这些就是mybatis执行的全过程了。从初始化Configuration对象。到后面再生成Sqlsession对象进行数据库查询。总感觉少了点什么???没错,就是关于动态代理的Mapper接口

 

1.2,mapper动态代理

我们把上面的例子换一种写法。

package com.equaker.demo.test;

import com.equaker.demo.bean.Student;
import com.equaker.demo.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class Mybatis_Test {

    public static void main(String[] args) throws IOException {

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

        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        Student student = studentMapper.getById(1);
        System.out.println(student);
    }
}

我们都知道接口是不存在实例化的。而我们确确实实没有实现这个mapper接口,那他的执行是咋回事呢???关于动态代理的内容请查看我的另外一篇文章《设计模式 | 结构型 - 代理模式与动态代理》

我们这里直接介绍mybatis是怎么利用动态代理的。

我们从 StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);这里说起吧。

进入源码:

DefaultSession.java:

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

 Configuration.java:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

 MapperRegistry.java:

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
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);
    }
  }

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(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);
        }
      }
    }
  }

好像有点东西出来了。没错,就是MapperRegistry里面的 knownMappers。

这里为了方便我还贴了addmapper方法进去。其实我们在初始化configuration对象的时候,已经看到这个东西了。在扫描mapper.xml映射文件的时候,会顺便生成这样的对象mapper代理工厂,利用它可以获取代理对象。话不多说我们来看看这个MapperProxyFactory是什么东西把。

MapperProxyFactory.java:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

//重载的方法,由动态代理创建新示例返回。
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

//MapperProxyFactory类中的newInstance方法
 public T newInstance(SqlSession sqlSession) {
 	// 创建了JDK动态代理的Handler类
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    // 调用了重载方法
    return newInstance(mapperProxy);
  }


}

mapperproxy.java:

//MapperProxy类,实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
  
  //省略部分源码	

  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;
  
  // 构造,传入了SqlSession,说明每个session中的代理对象的不同的!
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
  
  //省略部分源码
}

在动态代理返回了示例后,我们就可以直接调用mapper类中的方法了,说明在MapperProxy中的invoke方法中已经为我们实现了方法。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
      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);
    // 重点在这:MapperMethod最终调用了执行的方法
    return mapperMethod.execute(sqlSession, args);
  }


public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    //判断mapper中的方法类型,最终调用的还是SqlSession中的方法
    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);
          if (method.returnsOptional() &&
              (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

二,一级缓存与二级缓存 

当你们把上面的内容看懂之后,你就不会追踪一二级缓存的问题了。

共勉,加油。。。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis是一个开源的持久层框架,它的配置文件包含了会深深影响MyBatis行为的设置和属性信息。配置文件的顶层结构包括properties(属性)、settings(设置)、typeAliases(类型别名)、typeHandlers(类型处理器)、objectFactory(对象工厂)、plugins(插件)、environments(环境配置)、mappers(映射器)等。\[3\] 在MyBatis源码中,有一个方法build,该方法用于解析动态脚本并生成SqlSource对象。在该方法中,首先会调用parseDynamicTags方法解析动态标签,得到一个包含多个SqlNode的列表contents。然后,通过将contents传入MixedSqlNode的构造函数,创建一个MixedSqlNode对象rootSqlNode。接下来,根据是否为动态脚本,分别创建DynamicSqlSource或RawSqlSource对象,并将configuration、rootSqlNode和parameterType作为参数传入构造函数。最后,返回创建的SqlSource对象。\[2\] 另外,还有一个方法configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql),该方法用于创建StatementHandler对象。具体的解读需要查看该方法的实现代码。 #### 引用[.reference_title] - *1* [mybatis源码深度解析](https://blog.csdn.net/qq_31359923/article/details/126582701)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] - *2* [【MybatisMybatis源码解读](https://blog.csdn.net/keepfriend/article/details/124356649)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] - *3* [mybatis源码解析](https://blog.csdn.net/weixin_43189971/article/details/125418419)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v4^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值