浅谈MyBatis 如何执行一条 SQL语句

前言

Mybatis 是 Java 开发中比较常用的 ORM 框架。在日常工作中,我们都是直接通过 Spring Boot 自动配置,并直接使用,但是却不知道 Mybatis 是如何执行一条 SQL 语句的,而这篇文章就是来揭开 Mybatis 的神秘面纱。

基础组件

我们要理解 Mybatis 的执行过程,就必须先了解 Mybatis 中都有哪一些重要的类,这些类的职责都是什么?

SqlSession

我们都很熟悉,它对外提供用户和数据库之间交互需要使用的方法,隐藏了底层的细节。它默认是实现类是 DefaultSqlSession

Executor

这个是执行器,SqlSession 中对数据库的操作都是委托给它。它有多个实现类,可以使用不同的功能。

Configuration

它是一个很重要的配置类,它包含了 Mybatis 的所有有用信息,包括 xml 配置,动态 sql 语句等等,我们到处都可以看到这个类的身影。

MapperProxy

这是一个很重要的代理类,它代理的就是 Mybatis 中映射 SQL 的接口。也就是我们常写的 Dao 接口。

工作流程

初步使用

首先,我们需要得到一个 SqlSessionFactory 对象,该对象的作用是可以获取 SqlSession  对象。

// 读取配置
InputStream resourceAsStream = Resources.getResourceAsStream("config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 创建一个 SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);

当我们得到一个 SqlSessionFactory 对象之后,就可以通过它的 openSession 方法得到一个 SqlSession 对象。

 SqlSession sqlSession = sqlSessionFactory.openSession(true);

最后,我们通过 SqlSession 对象获取 Mapper ,从而可以从数据库获取数据。

// 获取 Mapper 对象
HeroMapper mapper = sqlSession.getMapper(HeroMapper.class);
// 执行方法,从数据库中获取数据
Hero hero = mapper.selectById(1);

详细流程

获取 MapperProxy 对象

我们现在主要关注的就是 getMapper 方法,该方法为我们创建一个代理对象,该代理对象为我们执行 SQL 语句提供了重要的支持。

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

getMapper  方法里面委托 Configuration 对象去获取对应的 Mapper 代理对象,之前说过 Configuration 对象里面包含了 Mybatis 中所有重要的信息,其中就包括我们需要的 Mapper 代理对象,而这些信息都是在读取配置信息的时候完成的,也就是执行sqlSessionFactoryBuilder.build 方法。

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

我们可以看到它又将获取 Mapper 代理对象的操作委托给了 MapperRegistry 对象(搁着俄罗斯套娃呢?),这个 MapperRegistry 对象里面就存放了我们想要的 Mapper 代理对象,如果你这么想,就错了,实际上,它存放的并不是我们想要的 Mapper 代理对象,而是 Mapper 代理对象的工厂,Mybatis 这里使用到了工厂模式。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

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

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

我只保留了 getMapper 方法和 addMapper 方法。

在 getMapper 方法中,它获取的是 MapperProxyFactory 对象,我们通过名称可以得出这是一个 Mapper 代理对象工厂,但是我们是要得到一个 MapperProxy 对象,而不是一个工厂对象,我们再来看 getMapper 方法,它通过 mapperProxyFactory.newInstance 来创建代理对象。

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

创建了一个 MapperProxy 对象,并且通过 Proxy.newProxyInstance 方法(不会还有人不知道这是 JDK 动态代理吧),创建一个代理对象处理,这个代理对象就是我们想要的结果。这里没有体现出来代理了哪个对象啊?其实 mapperInterface 这是一个成员变量,它引用了需要被代理的对象。而这个成员变量实在创建 MapperProxyFactory 对象的时候赋值的,所以我们每一个需要被代理的接口,在 Mybatis 中都会为它生成一个 MapperProxyFactory 对象,该对象的作用就是为了创建所需要的代理对象。

缓存执行方法

当我们获取到代理对象 mapper 之后,就可以执行它里面的方法。
这里使用一个例子:

// Myabtis 所需要的接口
public interface HeroMapper {
    Hero selectById(Integer id);
}
// HeroMapper 接口所对应的 xml 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test.HeroMapper">
    <select id="selectById" resultType="test.Hero">
        select * from hero where id = #{id}
    </select>
</mapper>

我们执行 selectById 方法,获取一个用户的信息。

// 获取 Mapper 对象
HeroMapper mapper = sqlSession.getMapper(HeroMapper.class);
// 执行方法,从数据库中获取数据
Hero hero = mapper.selectById(1);

通过上面的解析已经知道,这里的 mapper 是一个代理对象的引用,而这个代理类则是 MapperProxy,所以我们主要是去了解 MapperProxy 这个代理类做了什么事情。

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @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 {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
      return methodCache.computeIfAbsent(method, m -> {
           return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      }
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
      private final MapperMethod mapperMethod;

      public PlainMethodInvoker(MapperMethod mapperMethod) {
          super();
          this.mapperMethod = mapperMethod;
      }

      @Override
      public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
          return mapperMethod.execute(sqlSession, args);
      }
  }
}

代理对象执行方法时都是直接执行 invoke() 方法,在这个方法中,我们主要就看一条语句 cachedInvoker(method).invoke(proxy, method, args, sqlSession);

我们首先看 cachedInvoker 方法,它的参数是 Method 类型,所以这个 method 表示的就是我们执行的方法 HeroMapper.selectById,它首先从缓存中获取是否之前已经创建过一个该方法的方法执行器 PlainMethodInvoker 对象,其实这只是一个包装类,可有可无,在工程上来说,有了这个包装类,会更加易于维护。而这个执行器里面只有一个成员对象,这个成员对象就是 MapperMethod,并且这个 MapperMethod 的构造函数中需要传递  HeroMapper、HeroMapper.selectById、Cofiguration 这三个参数。

以上步骤都执行完成之后,接下来我们可以看到执行了 PlainMethodInvoker 的 invoke 方法,而它又将真正的操作委托给了 MapperMethod,执行 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);
          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());
    }
    return result;
  }

这个方法中,我们可以看到熟悉的几个关键字:select、update、delete、insert,这个就是为了找到执行方式,我们因为是 select 语句,所以分支会走向 select,并且最终会执行到 sqlSession.selectOne 方法中,所以最终饶了一大圈,依然还是回到了我们一开始就提到的 SqlSession 对象中。
在这个方法中,首先会构造参数,也就是我们看到的 convertArgsToSqlCommandParam 方法,它的内部执行方式是按照如下方式来转换参数的:

使用 @param 自定义命名
amethod(@Param int a, @Param int b)  则会构造 map  ->  [{"a", a_arg}, {"b", b_arg}, {"param1",  a_arg}, {"param2", b_arg}],a 和 param1 是对参数 a 的命名,a_arg 是传递的实际的值。
虽然只有两个参数,但是最后却会在 Map 存在四个键值对,因为 Mybatis 最后自己会生成以 param 为前缀的参数名称,名称按照参数的位置进行命名。

不使用 @param

amethod(int a, int b),则会构造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}],因为没有对参数进行自定义命名,所以 Myabtis 就对参数取了一个默认的名称,以 arg 为前缀,位置为后缀进行命名。

在参数只有一个,并且参数为集合的情况下,会存放多个键值对:

  • amethod(Collection<Integer> a),这种情况下,会构造 map -> [{"arg0", a_arg}, {"collection", a_arg}]
  • amethod(List<Integer> a),这种情况下,会构造 map -> [{"arg0", a_arg}, {"collection", a_arg}, {"list", a_arg}]
  • amethod(Integer[] a),这种情况下,会构造 map -> [{"arg0", a_arg}, {"array", a_arg}]
  • 但是,如果有两个参数,那么就不会这么存放,而是按照常规的方式:
  • amethod(List<Integer> a,List<Integer> b)  则会构造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}]
  • amethod(List<Integer> a,int b)  则会构造 map -> [{"arg0", a_arg}, {"arg1", b_arg}, {"param1", a_arg}, {"param2", b_arg}]

不会作为参数的对象
在 Mybatis 中有两个特殊的对象:RowBounds、ResultHandler,这两个对象如果作为参数则不会放入到 map 中,但是会占据位置。

amethod(int a,RowBounds rb, int b),这种情况下,会构造 map -> [{"arg0", a_arg}, {"arg2", b_arg}, {"param1", a_arg}, {"param2", b_arg}]

注意这里的 b 参数的命名分别是 arg2 和 param2,arg2 是因为它的位置在参数的第 3 位,而 param2 则是因为它是第 2 个有效参数。

获取需要执行的 SQL 对象

参数构造完成之后,我们就需要寻找需要执行的 SQL 语句了。

@Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

这里的 statement 虽然是 String 类型的,但是它并不是真正的 SQL 语句,它是一个寻找对应 MapperStatement 对象的名称,在我们的例子中,它就是 test.HeroMapper.selectById ,Mybatis 通过这个名称可以寻找到包含了 SQL 语句的对象。

我们跟踪代码的执行,最后会来到下面这个方法,这是一个包含三个参数的重载方法。

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

在第四行代码中,可以得知它通过 statement 从 Configuration 对象中获取了一个 MapperStatement 对象, MapperStatement 对象包含的信息是由 <select>、<update>、<delete> 、<insert> 元素提供的,我们在这些元素中定义的信息都会保存在该对象中,如:Sql 语句、resultMap、fetchSize 等等。

执行 SQL 语句

获取到包含 SQL 语句信息的对象之后,就会交给 Execute 执行器对象去执行后续的处理,也就是 executor.query 方法。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

获取需要自行的 Sql 语句,然后创建一个缓存使用的 key,用于二级缓存。

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // ....
    // 跟缓存有关,如果缓存中存在数据,则直接从缓存中返回,否则从数据库中查询
 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
 return list;
}

最后会执行到一个 doQuery 方法

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

这段代码创建了一个 Statement 对象的处理器 StatementHandler,这个处理器主要的工作就是完成 JDBC 中 PrepareStatement 对象的一些准备工作,包括:创建 PrepareStatement 对象,设置需要执行的 sql 语句,为 sql 语句中的参数赋值。完成这些工作之后,就开始从数据库获取数据了。

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

第四行代码即执行对应的 Sql 查询,后续则是对结果进行处理。

总结

Mybatis 通过 MapperProxy 代理了我们的 Dao 接口类,以此来帮助我们执行预定义的 Sql 语句,通过 Cache 来缓存对应的执行结果,通过 StatementHandler  创建 PrepareStatement 对象,通过 jdbc 执行 SQL 操作。

到此这篇关于浅谈MyBatis 如何执行一条 SQL语句的文章就介绍到这了,更多相关MyBatis 执行SQL语句内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MyBatis一次执行多条SQL语句的操作

    有个常见的场景:删除用户的时候需要先删除用户的外键关联数据,否则会触发规则报错. 解决办法不外乎有三个: 1.多条sql分批执行: 2.存储过程或函数调用: 3.sql批量执行. 今天我要说的是MyBatis中如何一次执行多条语句(使用mysql数据库). 1.修改数据库连接参数加上allowMultiQueries=true,如: hikariConfig.security.jdbcUrl=jdbc:mysql://xx.xx.xx:3306/xxxxx?characterEncoding=u

  • 在mybatis执行SQL语句之前进行拦击处理实例

    比较适用于在分页时候进行拦截.对分页的SQL语句通过封装处理,处理成不同的分页sql. 实用性比较强. import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Properties; import org.apache.ibatis.e

  • mybatis 通过拦截器打印完整的sql语句以及执行结果操作

    开发过程中,如果使用mybatis做为ORM框架,经常需要打印出完整的sql语句以及执行的结果做为参考. 虽然mybatis结合日志框架可以做到,但打印出来的通常都是sql和参数分开的. 有时我们需要调试这条sql的时候,就需要把参数填进去,这样未免有些浪费时间. 此时我们可以通过实现mybatis拦截器来做到打印带参数的完整的sql,以及结果通过json输出到控制台. 直接看代码和使用方法吧: MyBatis拦截器打印不带问号的完整sql语句拦截器 import java.text.DateF

  • 解决mybatis执行SQL语句部分参数返回NULL问题

    今天在写代码的时候发现一个问题:mybatis执行sql语句的时候返回bean的部分属性为null,在数据库中执行该sql语句能够正常返回,把相关代码反反复复翻了个遍,甚至都重启eclipse了,依旧没解决问题,后来网上搜了一下,还真有类似的问题. 闲话少说,直接说问题,该sql语句是自己写的,resultType直接用了该bean全名称,最终导致部分属性显示为null, 原来的写法: <select id="selectByArticle" parametertype=&quo

  • MyBatis 执行动态 SQL语句详解

    大家基本上都知道如何使用 MyBatis 执行任意 SQL,使用方法很简单,例如在一个 XXMapper.xml 中: <select id="executeSql" resultType="map"> ${_parameter} </select> 你可以如下调用: sqlSession.selectList("executeSql", "select * from sysuser where enabled

  • 浅谈MyBatis 如何执行一条 SQL语句

    前言 Mybatis 是 Java 开发中比较常用的 ORM 框架.在日常工作中,我们都是直接通过 Spring Boot 自动配置,并直接使用,但是却不知道 Mybatis 是如何执行一条 SQL 语句的,而这篇文章就是来揭开 Mybatis 的神秘面纱. 基础组件 我们要理解 Mybatis 的执行过程,就必须先了解 Mybatis 中都有哪一些重要的类,这些类的职责都是什么? SqlSession 我们都很熟悉,它对外提供用户和数据库之间交互需要使用的方法,隐藏了底层的细节.它默认是实现类

  • 浅谈Mybatis SqlSession执行流程

    目录 Mybatis执行SQL流程 SqlSession Executor Mybatis之Executor Mybatis之StatementHandler 进入ResultSetHandler Mybatis执行SQL流程 在看源码之前,我们需要了解一些基本知识,如果您没有阅读Mybatis SqlSessionFactory 初始化原理,可以先阅读Mybatis SqlSessionFactory 初始化原理这篇文章,这用更有助于我们理解接下来的文章 在看源码之前,我们需要了解一些基本知识

  • Spring 中jdbcTemplate 实现执行多条sql语句示例

    说一下Spring框架中使用jdbcTemplate实现多条sql语句的执行: 很多情况下我们需要处理一件事情的时候需要对多个表执行多个sql语句,比如淘宝下单时,我们确认付款时要对自己银行账户的表里减去订单所需的钱数,即需要更新银行账户的表,同时需要更新淘宝订单的表将订单状态改为"已付款",这就需要先后执行多个sql(仅仅用于表达执行多的SQL的举例说明,具体淘宝如何实现并不是很清楚~~~~~); 但如果这中间出现电脑断网断电等问题,仅将我们银行账户的钱扣掉了,订单状态并没有改,那我

  • 浅谈MyBatis执行SQL的两种方式

    目录 前言 准备接口和Mapper配置文件: 使用SqlSession 发送 SQL 使用 Mapper 接口发送 SQL 比较两种发送 SQL 方式 前言 本文介绍MyBatis执行SQL语句的2种方式:SqlSession和Mapper接口以及它们的区别. 准备接口和Mapper配置文件: 定义UserMapper接口: package cn.cvs.dao; import cn.cvs.pojo.User; import java.util.List; public interface U

  • 浅谈mybatis中的#和$的区别 以及防止sql注入的方法

    mybatis中的#和$的区别 1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号.如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id". 2. $将传入的数据直接显示生成在sql中.如:order by $user_id$,如果传入的值是111,那么解析成sql时的值为order by user_id,  如果传入的

  • 浅谈mybatis中SQL语句给boolean类型赋值问题

    我就废话不多说了,大家还是直接看代码吧~ <select id="getBiTree" parameterType="String" resultMap="MenuVoListMap"> SELECT m.menu_id , m.parent_id , m.`name` , 1 opens FROM menu m WHERE m.is_valid = 1 AND (m.type = 0 or m.type = 1) and m.men

  • 浅谈Mybatis版本升级踩坑及背后原理分析

    1.背景 某一天的晚上,系统服务正在进行常规需求的上线,因为发布时,提示统一的pom版本需要升级,于是从 1.3.9.6 升级至 1.4.2.1. 当服务开始上线后,开始陆续出现了一些更新系统交互日志方面的报警,属于系统辅助流程,报警下图所示, 具体系统数据已脱敏,内容是Mybatis相关的报警,在进行类型转换的时候,产生了强转错误. 更新开票请求返回日志, id:{#######}, response:{{"code":XXX,"data":{"call

  • 浅谈MyBatis原生批量插入的坑与解决方案

    目录 原生批量插入的"坑" 解决方案 分片 Demo 实战 原生批量插入分片实现 总结 前面的文章咱们讲了 MyBatis 批量插入的 3 种方法:循环单次插入.MyBatis Plus 批量插入.MyBatis 原生批量插入,详情请点击<MyBatis 批量插入数据的 3 种方法!> 但之前的文章也有不完美之处,原因在于:使用 「循环单次插入」的性能太低,使用「MyBatis Plus 批量插入」性能还行,但要额外的引入 MyBatis Plus 框架,使用「MyBati

  • 浅谈Mybatis二级缓存的缺陷

    一级缓存默认是开启的(但是整合了Spring,Mybatis的一级缓存默认就失效了) 二级缓存是要手动配置开启的(二级缓存是mapper级别的缓存,可以跨SqlSession) Mybatis二级缓存 开启二级缓存(xml方式):在*Mapper.xml中加入如下代码 <!--eviction: 清空缓存的策略 readOnly: 是否只读 flushInterval: 每个60秒刷新一次缓存 size: 内存大小,最多存储结果对象或者列表的512个引用 --> <cache readO

随机推荐