Mybatis优化:使用batch模式实现批量插入/更新

目录

demo

 工具类

Mybatis原理简介

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

Mybatis开启Batch模式

Batch模式效率高的原因

参考文档


 

 


 

 

Mybatis中有两种批量插入的方式:

  1. 动态SQL使用foreach标签进行批量插入
  2. 使用Mybatis的batch模式进行批量插入

相比之下,数据量较大的场景中后者效率更高

demo

public void demo(){
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
    try {
        for (Dept dept : deptList) {
            baseManager.insert(dept);
        }
        session.commit();
    }catch (Exception e){
        session.rollback();
    }finally {
        session.close();
    }
}

 工具类

package com.utils.sql;

import com.utils.SpringUtils;
import java.util.List;
import java.util.function.BiConsumer;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

/**
 * @author yms
 * @description:  Mybatis 批量insert、update工具类
 * @date: 2023/7/3 17:21
 */
@Slf4j
public class BatchDmlUtil {

  /**
   * 批量新增方法
   *
   * @param list       要新增的集合
   * @param clazz      Mapper类
   * @param biConsumer 对应的单条新增方法
   * @param <M>        mapper类型
   * @param <T>        结合元素类型
   */
  public static  <M, T> void batchOperate(List<T> list, Class<M> clazz, BiConsumer<M, T> biConsumer) {
    if (list == null || list.size() == 0) {
      log.info("BatchDmlUtil batchInsert list data is null!");
      return;
    }
    SqlSessionFactory sqlSessionFactory = SpringUtils.getBean(SqlSessionFactory.class);
    SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
    try {
      M mapper = session.getMapper(clazz);
      list.forEach(a -> {
        biConsumer.accept(mapper, a);
      });
      session.commit();
      session.clearCache();
    } catch (Exception e) {
      e.printStackTrace();
      log.error("BatchDmlUtil batchInsert is exception!clazz={}", clazz.getName(), e);
      session.rollback();
    } finally {
      session.close();
    }
  }

}


package com.utils;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

/**
 * spring工具类 方便在非spring管理环境中获取bean
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor {

    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws org.springframework.beans.BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws org.springframework.beans.BeansException
     */
    public static <T> T getBean(Class<T> clz) throws BeansException {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name) {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
     * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     *
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker) {
        return (T) AopContext.currentProxy();
    }
}

Mybatis原理简介

       每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

//使用SqlSessionFactoryBuilder创建SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

//使用SqlSessionTemplate创建SqlSessionFactory
@Autowired
private SqlSessionTemplate sqlSessionTemplate;

SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();

SqlSessionFactoryBuilder

        SqlSessionFactoryBuilder可以理解为SqlSessionFactory的建造者,其作用是用来创建SqlSessionFactory,需要注意的是SqlSessionFactoryBuilder不是线程安全的。在spring项目中我们可以使用SqlSessionTemplate来创建SqlSessionFactory,SqlSessionTemplate是线程安全的。

SqlSessionFactory

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

SqlSession

        SqlSession是mybatis的顶层API,其作用是访问数据库,完成增删改查以及事务的提交和回滚等操作。SqlSession实例不是线程安全的,不能被多个线程共享。

Mybatis开启Batch模式

Mybatis在枚举中提供了三种类型的执行器

package org.apache.ibatis.session;

/**
 * @author Clinton Begin
 */
public enum ExecutorType {
  SIMPLE, //mybatis的默认执行器,它为每个语句的执行创建一个新的预处理语句
  REUSE,  //会复用预处理语句
  BATCH   //会批量执行所有更新语句,不需要对同样的SQL进行多次预编译
}

从org.apache.ibatis.session.Configuration类中可以看到Mybatis模式是simple模式

b6f262f102a84f0abd3966e33235a4cf.png

 

使用SqlSessionFactory创建一个Batch类型的SqlSession实例

SqlSessionFactory sqlSessionFactory = sqlSessionTemplate.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

SqlSessionFactory中有多个创建SqlSession实例的方法,我们可以根据自己的实际需求调用不同的方法

package org.apache.ibatis.session;

import java.sql.Connection;

/**
 * Creates an {@link SqlSession} out of a connection or a DataSource
 * SqlSessionFactory源码
 * @author Clinton Begin
 */
public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

}
SqlSessionFactory有两个实现类:DefaultSqlSessionFactory和SqlSessionManager

  最终在DefaultSqlSessionFactory.openSessionFromDataSource()方法中生成Mybatis的执行器

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

 org.apache.ibatis.session.Configuration.newExecutor()

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //判断,如果是BATCH模式,则创建BatchExecutor
    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);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Batch模式效率高的原因

        Batch中减少了SQL预编译的次数,单次插入数据量特别大,字段特别多的情况下更适合使用Mybatis的Batch模式。

BatchExecutor.doUpdate()

  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    final Configuration configuration = ms.getConfiguration();
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
    final BoundSql boundSql = handler.getBoundSql();
    final String sql = boundSql.getSql();
    final Statement stmt;
    //判断如果当前SQL与上一次传入的SQL一样则不会创建新的Statement
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      int last = statementList.size() - 1;
      //直接取上次的Statement
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      handler.parameterize(stmt);// fix Issues 322
      BatchResult batchResult = batchResultList.get(last);
      batchResult.addParameterObject(parameterObject);
    } else {
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      handler.parameterize(stmt);    // fix Issues 322
      currentSql = sql;
      currentStatement = ms;
      statementList.add(stmt);
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
SimpleExecutor.doUpdate()
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      //创建新的Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

    private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

参考文档

MyBatis中文网

mybatis-spring –

 

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 MyBatis 实现批量插入,可以通过以下步骤完成: 1. 首先,需要在 MyBatis 配置文件中开启 JDBC 的批处理功能,可以在 `<configuration>` 标签下添加以下配置: ```xml <configuration> <settings> <setting name="jdbc.batch_size" value="100"/> </settings> </configuration> ``` 上面的配置表示批处理的大小为 100 条。 2. 在 Mapper 接口中定义批量插入的方法,可以使用 `List` 或 `Array` 作为参数类型,例如: ```java public interface UserMapper { void batchInsert(List<User> userList); } ``` 3. 在对应的 Mapper XML 文件中编写 SQL 语句,使用 `foreach` 标签循环插入数据,例如: ```xml <insert id="batchInsert"> INSERT INTO user (id, name, age) VALUES <foreach collection="list" item="item" separator=","> (#{item.id}, #{item.name}, #{item.age}) </foreach> </insert> ``` 上面的 SQL 语句中,使用MyBatis 的 `foreach` 标签,遍历传入的 `List` 或 `Array`,将数据插入到数据库中。 4. 最后,在 Java 代码中调用批量插入的方法,例如: ```java List<User> userList = new ArrayList<>(); // 添加多条数据到 userList 中 userMapper.batchInsert(userList); ``` 调用 `batchInsert` 方法时,传入包含多条数据的 `List` 参数,即可批量插入数据。 通过以上步骤,就可以使用 MyBatis 实现批量插入了。需要注意的是,在实际使用中,批处理的大小需要根据具体的场景和数据量进行调整,以达到最优性能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值