添加maven依赖

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.1</version>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>

只需在spring boot项目中添加mybatis-spring-boot-starter依赖即可,我们示例使用的是mysql所以添加了mysql的数据库驱动依赖。可根据自身的需求替换。

编写MybatisConfig配置类

import com.yyoo.boot.config.MybatisBaseConfig;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;

import javax.sql.DataSource;


@Configuration
@Slf4j
@Data
// mapper* 表示mapper包及其子包
@MapperScan(value = {
        "com.yyoo.boot.mybatis.mapper*"
})
@ConfigurationProperties("my.mybaties")
public class MybatisConfig extends MybatisBaseConfig{

    /**
     * Mybatis别名文件夹
     */
    private String aliasesPackage;
    /**
     * 映射的 mapper文件
     */
    private String[] mapperLocations;

    @Bean
    @ConfigurationProperties("my.hikari")
    public HikariConfig getHikariConfig(){
        return new HikariConfig();
    }

    @Bean
    public HikariDataSource hikariDataSource(HikariConfig hikariConfig){
        return new HikariDataSource(hikariConfig);
    }

    @Bean(name = "sessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("hikariDataSource") DataSource ds) throws Exception {
        log.info("数据源:{},aliasesPackage:{}",ds,this.aliasesPackage);
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        if(this.aliasesPackage != null && !"".equals(this.aliasesPackage)) {
            factoryBean.setTypeAliasesPackage(this.aliasesPackage);
        }
        //指定mapper xml目录
        Assert.notEmpty(this.mapperLocations,"扫描的Mapper xml不能为空");
        factoryBean.setMapperLocations(resolveMapperLocations(this.mapperLocations));
        return factoryBean.getObject();
    }

    @Bean("sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) {
        // 使用上面配置的Factory 
        // 要设置Template为BATCH方式,请使用new SqlSessionTemplate(factory,ExecutorType.BATCH);
        SqlSessionTemplate template = new SqlSessionTemplate(factory);
        return template;
    }

}

@MapperScan扫描Mapper接口所在的包,可以列多个

本文使用HikariDataSource所为数据源,可根据自身情况替换

resolveMapperLocations方法在MybatisBaseConfig中定义,下面贴出代码,实现可扫描classpath*:mapper/Mapper.xml,classpath:mapper/*/*Mapper.xml这样的配置,所以此处没有直接使用Resource[]数组。

事务配置,本示例中没有自定义配置事务管理器PlatformTransactionManager,spring会自动根据我们的数据源配置相应的事务管理器,一般情况下是使用DataSourceTransactionManager或JtaTransactionManager。

MybatisBaseConfig

package com.yyoo.boot.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class MybatisBaseConfig {

    protected Resource[] resolveMapperLocations(String[] mapperLocations) {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<Resource> resources = new ArrayList<Resource>();
        if (mapperLocations != null) {
            for (String mapperLocation : mapperLocations) {
                log.debug("数据源扫描的映射文件地址{}",mapperLocation);
                try {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                } catch (IOException e) {
                    // ignore
                    e.printStackTrace();
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

}

Mapper接口和Mapper.xml文件

package com.yyoo.boot.mybatis.mapper;

import com.yyoo.boot.mybatis.beans.MyEmp;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface MyEmpMapper {

    int insert(MyEmp myEmp);

    int insertForeach(@Param("myEmpList") List<MyEmp> myEmpList);
}

注意包名@MapperScan中配置的包名需要包含到

<?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">
<!-- namespace我们对应到了我们的EmpMapper接口 -->
<mapper namespace="com.yyoo.boot.mybatis.mapper.MyEmpMapper">

    <insert id="insert" parameterType="MyEmp">
        insert into t_my_emp(name,age,sex)
        values(#{name},#{age},#{sex})
    </insert>

    <insert id="insertForeach">
        insert into t_my_emp(name,age,sex)
        values
        <foreach item="myEmp" collection="myEmpList" separator=",">
        (#{myEmp.name},#{myEmp.age},#{myEmp.sex})
        </foreach>
    </insert>

</mapper>

注意xml文件所在的路径,必须是mapperLocations配置所在的文件夹(此处在resource目录下的mapper文件夹)

定义Service以及相应的测试代码

package com.yyoo.boot.mybatis.service;

public interface MyEmpService {

    void insert(int num);

    void insertForeach(int num);

}
package com.yyoo.boot.mybatis.service;

import com.yyoo.boot.mybatis.AutoNameUtil;
import com.yyoo.boot.mybatis.beans.MyEmp;
import com.yyoo.boot.mybatis.mapper.MyEmpMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@Service
public class MyEmpServiceImpl implements MyEmpService{

    @Resource
    MyEmpMapper myEmpMapper;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insert(int num) {
        for (int i = 0;i < num;i++) {
            Random random = new Random();
            MyEmp myEmp = new MyEmp();
            myEmp.setName(AutoNameUtil.autoSurAndName());
            myEmp.setAge(random.nextInt(50) + 15);// 15岁及以上
            myEmp.setSex(random.nextInt(2));
            myEmpMapper.insert(myEmp);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertForeach(int num) {
        List<MyEmp> list = new ArrayList<>();
        for (int i = 0;i < num;i++) {
            Random random = new Random();
            MyEmp myEmp = new MyEmp();
            myEmp.setName(AutoNameUtil.autoSurAndName());
            myEmp.setAge(random.nextInt(50) + 15);// 15岁及以上
            myEmp.setSex(random.nextInt(2));
            list.add(myEmp);
        }
        myEmpMapper.insertForeach(list);
    }
}

insert方法其实就是直接插入,根据SqlSessionTemplate创建是是否使用BATCH模式来区分直接插入和BATCH插入。

package com.yyoo.boot.mybatis;

import com.yyoo.boot.Application;
import com.yyoo.boot.mybatis.service.MyEmpService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class,webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class Test1 {

    @Resource
    private MyEmpService myEmpService;

    @Test
    public void t1(){

        long start = System.currentTimeMillis();
        myEmpService.insertForeach(1000000);
        long end = System.currentTimeMillis();
        System.out.println("直接insert执行时间:"+(end - start));

    }

    @Test
    public void t2(){

        long start = System.currentTimeMillis();
        myEmpService.insertForeach(1000000);
        long end = System.currentTimeMillis();
        System.out.println("foreach执行时间:"+(end - start));

    }

}

在SpringBoot下再次进行一次执行时间对比

执行方式

100条

1000条

1w条

10w条

100w条

循环insert

282ms

1557ms

6714ms

48256ms

450864ms

foreach插入

212ms

279ms

840ms

4915ms

报错

BATCH插入

173ms

510ms

2989ms

17579ms

174985ms

可以看到foreach方式插入优势明显,只是数据量过大时会报错而已。建议还是使用foreach方式,然后通过代码控制foreach一次执行的数据体量。