1.前言
ExecutorType:批量操作执行器类型
在MyBatis中,ExecutorType是一种枚举类型,用于指定SQL语句执行的方式。其中,ExecutorType.BATCH和ExecutorType.SIMPLE是两种常见的执行方式
1. ExecutorType.SIMPLE:
ExecutorType.SIMPLE是默认的执行方式。
每次执行SQL语句时,都会打开一个新的PreparedStatement对象。
每条SQL语句都会立即被执行并提交到数据库。
适用于常规的单个或少量SQL操作。
PreparedStatement对象是 Java 中用于执行预编译 SQL 语句的接口PreparedStatement 具有以下特点:①预编译②参数化查询(防止SQL注入)③性能优化
2. ExecutorType.BATCH:
ExecutorType.BATCH会将一批SQL语句集中在一起批量执行,减少了与数据库的交互次数,提高性能。
多条SQL语句会一起提交到数据库执行,可以提升执行效率。
可以通过
sqlSession.flushStatements()
方法手动触发批量执行。适用于需要执行大量SQL操作的场景,如批量插入、更新或删除多条记录。
综上所述,ExecutorType.SIMPLE适用于常规的单个或少量SQL操作,而ExecutorType.BATCH适用于需要批量执行大量SQL操作的场景,效率更高。选择适当的执行方式可根据业务需求和性能要求进行衡量。
3.ExecutorType.REUSE
当多次调用相同的SQL语句时,会重用已编译的SQL语句和执行计划。
适用于单条SQL语句的重复执行,例如在一个循环中多次执行相同的SQL语句。
每次执行都会创建一个新的Statement对象,但会重用已编译的SQL语句和执行计划,以提高执行效率。
综上所述,ExecutorType.SIMPLE适用于常规的单个或少量SQL操作,而ExecutorType.BATCH适用于需要批量执行大量SQL操作的场景,效率更高。选择适当的执行方式可根据业务需求和性能要求进行衡量。
3.常见的插入方式
第一种:单条插入语句(最慢的)
insert into student(name,age) values(#{name},#{age})
第二种:<foreach>标签,循环插入(效率高)
insert into student(name,age) values
<foreach collection="students" item="student" open="" close="" separator=","> (# {student.name},#{student.age})
</foreach>
4.百万数据 批量导入数据库
4.1 从Excel中获取数据
第一种:获取Excel所有数据
ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
reader.setSheet("Sheet1"); # setSheet()方法是设置读取Excel的那一页
List<Map<String,Object>> readAll = reader.readAll();
第二种:分批读取Excel中的数据
ExcelReader reader = ExcelUtil.getReader("d:/aaa.xlsx");
List<List<Object>> readAll = reader.read();# read()
方法有三个参数:第一个参数:开始读取的行数
第二个参数:结束读取的行数
第三个参数:是否跳过第一行(因为大部分我们数据的第一行都是标题)
注意:hutool中读取的Excel数据会 将读到的数据,存入到内存中。
4.2开启批处理
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
Plain Text
ExceutorType.BATCH批处理,是批量操作sql语句,无论是单条插入的sql语句还是foreach插入的sql语句,都可以使用
目的是:将多条sql语句先拼接完成,不提交给数据库执行,等达到你设置的条件,或者某一个时刻,再统一将多条sql语句提交给数据库执行。减少了与数据库交互的次数,性能也会得到大幅度提升。
注意:在配置文件中 连接数据库的 url上加上 以下参数 `rewriteBatchedStatements=true`
rewriteBatchedStatements=true
是 MySQL JDBC驱动程序中的一个连接参数。rewriteBatchedStatements=true
是一个可以提高MySQL批量操作性能的参数
5.多线程导入数据库
CountDownLatch
CountDownLatch
是一个同步辅助类,它允许一个或多个线程等待直到一组操作完成。这个类的主要作用是协调多个线程之间的执行顺序,确保所有线程都完成了某个任务后再继续执行后续代码。在 Java 中,CountDownLatch
的构造函数接受一个整数参数,表示需要等待的线程数量。当计数器的值变为 0 时,表示所有线程都已经完成了它们的任务,可以继续执行其他线程了。
CountDownLatch 维护了一个计数器,初始值为指定的数量。当一个或多个线程调用 await() 方法时,它们会被阻塞,直到计数器的值变为 0。而其他线程可以通过调用 countDown() 方法来减小计数器的值,当计数器的值变为 0 时,所有处于等待状态的线程都会被唤醒。需要注意的是,CountDownLatch 是一次性的,即计数器的值减为 0 后就不能再重置成其他值
百万数据插入
public String upload(@RequestBody UploadInfo uploadInfo) throws Exception {
String name = uploadInfo.getName();
String base64 = uploadInfo.getBase64();
String[] strArray = StrUtil.splitToArray(base64, "base64,");
byte[] bytes = Base64.decode(strArray[1]);
//用于创建一个基于字节数组的输入流。它允许你从一个字节数组中读取数据。
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 使用Hutool读取Excel文件
ExcelReader reader = ExcelUtil.getReader(byteArrayInputStream);
//将读取到的 reader 转化为 List<Man>集合
List<Person> persons = reader.readAll(Person.class);
//StopWatch类是 Hutool 工具库中的类,用于测量代码执行时间
StopWatch stopWatch = new StopWatch();
//读取数据的结束时间同时也是写入数据库的开始时间
stopWatch.start();
//sqlSessionFactory是通过ioc容器注入的 设置其SqlSession的执行器格式ExecutorType.SIMPLE(默认)
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
//循环将List<Man>中的数据插入数据库
for (Person person : persons) {
mapper.insert(person);
}
//mapper.insertBatch(persons);
sqlSession.commit();
stopWatch.stop();
sqlSession.close();
System.out.println("插入数据库最终的结果为:" + stopWatch.getTotalTimeSeconds());
return "ok";
}
百万数据导出
@GetMapping("/write")
public void exportExcel(HttpServletResponse response) throws IOException {
// 创建Excel写入器 参数 true 表示追加数据,即在已有的 Excel 文件上追加新数据。如果设为 false,则会覆盖已有的数据。
List<Person> person = personMapper.selectAll();
// 创建ExcelWriter对象
ExcelWriter writer = ExcelUtil.getWriter(true);
int i = 0;
while (true) {
List<Person> list = person.stream().skip(i * 100000).limit(100000).parallel().collect(Collectors.toList());
if (list.isEmpty()) {
break;
}
writer.setSheet("person" + i);
// 写入表头
writer.addHeaderAlias("id", "Id");
writer.addHeaderAlias("name", "姓名");
writer.addHeaderAlias("age", "年龄");
// 写入当前批次的数据
writer.write(list, true);
i++;
}
//response为HttpServletResponse对象 设置响应的内容类型为Excel文件
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
//test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
//设置响应头,告诉浏览器以附件形式下载文件,文件名为test.xlsx。这样设置可以让浏览器弹出文件下载对话框。
response.setHeader("Content-Disposition", "attachment;filename=test.xlsx");
//获取响应输出流,它是用于将响应的数据发送给客户端的流。
ServletOutputStream out = response.getOutputStream();
//将Excel数据写入输出流。第二个参数为true表示追加写入,即将数据追加到已有的Excel文件中。
writer.flush(out, true);
writer.close();
//关闭输出流
out.close();
}
多线程插入数据
@PostMapping
public String upload(@RequestBody UploadInfo uploadInfo) throws Exception {
String base64 = uploadInfo.getBase64();
String[] strArray = StrUtil.splitToArray(base64, "base64,");
byte[] bytes = Base64.decode(strArray[1]);
//用于创建一个基于字节数组的输入流。它允许你从一个字节数组中读取数据。
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// 使用Hutool读取Excel文件
ExcelReader reader = ExcelUtil.getReader(byteArrayInputStream);
ExecutorService executor = Executors.newFixedThreadPool(10);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
try {
//存储读取的数据
List<Person> students = null;
Map<Integer, List<Person>> map = null;
int start = 1;
int end = 10000;
//计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
while (end <= 200000) {
students = reader.read(0, start, end, Person.class);
int i = 0;
map = new HashMap<>();
while (true) {
List<Person> list = students.stream().skip(i * 2000).limit(2000).parallel().collect(Collectors.toList());
if (list.isEmpty()) {
break;
}
map.put(i + 1, list);
i++;
}
CountDownLatch latch = new CountDownLatch(5);
for (List<Person> students1 : map.values()) {
executor.submit(() -> {
mapper.insertBatch(students1);
latch.countDown();
});
}
latch.await();
sqlSession.commit();
start += 10000;
end += 10000;
}
stopWatch.stop();
System.out.println("插入数据库的时间为:" + stopWatch.getTotalTimeSeconds());
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
} finally {
executor.shutdown();
}
return null;
}