Мало знаний, большой вызов! Эта статья участвует в "Необходимые знания для программистов«Творческая деятельность.
Функция пакетной вставки — одна из наиболее распространенных бизнес-функций в нашей повседневной работе, я также написал статью о «Функция вставки пакетных данных MyBatis Plus, yyds!», но отзывы в области комментариев не очень хорошие, есть две основные проблемы: во-первых, многие люди неправильно поняли функцию пакетной вставки MyBatis Plus (далее — MP) и думают, что MP также использует циклы для вставлять данные один раз, так что производительность не улучшилась, во-вторых, у нативного метода пакетной вставки на самом деле есть подводные камни, но мало кто об этом знает.
Поэтому, принимая во внимание описанную выше ситуацию, брат Лей решил составить еще один обзор пакетной вставки MyBatis и в то же время провести тест производительности для трех методов реализации, а также соответствующий принципиальный анализ.
Кратко поговорим о трех функциях пакетной вставки:
- Одинарная вставка петли;
- функция пакетной вставки MP;
- Встроенная функция пакетной вставки.
Готов к работе
Прежде чем мы начнем, давайте создадим базу данных и проверим данные.Исполняемый SQL-скрипт выглядит следующим образом:
-- ----------------------------
-- 创建数据库
-- ----------------------------
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP DATABASE IF EXISTS `testdb`;
CREATE DATABASE `testdb`;
USE `testdb`;
-- ----------------------------
-- 创建 user 表
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
`createtime` datetime NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;
-- ----------------------------
-- 添加测试数据
-- ----------------------------
INSERT INTO `user` VALUES (1, '赵云', '123456', '2021-09-10 18:11:16');
INSERT INTO `user` VALUES (2, '张飞', '123456', '2021-09-10 18:11:28');
INSERT INTO `user` VALUES (3, '关羽', '123456', '2021-09-10 18:11:34');
INSERT INTO `user` VALUES (4, '刘备', '123456', '2021-09-10 18:11:41');
INSERT INTO `user` VALUES (5, '曹操', '123456', '2021-09-10 18:12:02');
SET FOREIGN_KEY_CHECKS = 1;
Окончательный эффект базы данных выглядит следующим образом:
1. Одиночная петля
Далее мы будем использовать проект Spring Boot для вставки фрагментов данных по 10 Вт в пакетах, чтобы проверить время выполнения каждого метода по отдельности.
Основной (тестовый) код для зацикливания одной вставки выглядит следующим образом:
import com.example.demo.model.User;
import com.example.demo.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class UserControllerTest {
// 最大循环次数
private static final int MAXCOUNT = 100000;
@Autowired
private UserServiceImpl userService;
/**
* 循环单次插入
*/
@Test
void save() {
long stime = System.currentTimeMillis(); // 统计开始时间
for (int i = 0; i < MAXCOUNT; i++) {
User user = new User();
user.setName("test:" + i);
user.setPassword("123456");
userService.save(user);
}
long etime = System.currentTimeMillis(); // 统计结束时间
System.out.println("执行时间:" + (etime - stime));
}
}
Выполнение вышеуказанной программы заняло 88574 миллисекунды, как показано на следующем рисунке:
2.Пакетная вставка MP
Существует три основных класса реализации функции пакетной вставки MP: UserController (контроллер), UserServiceImpl (класс реализации бизнес-логики) и UserMapper (класс отображения базы данных). Процесс их вызова выглядит следующим образом:Обратите внимание, что для реализации этого метода необходимо сначала добавить платформу MP, открыть файл pom.xml и добавить следующее содержимое:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>mybatis-plus-latest-version</version>
</dependency>
Примечание: mybatis-plus-latest-version представляет номер последней версии платформы MP, к которой можно получить доступ.Внутри репозитория MV.com/artifact/co…Запросите номер последней версии, но не забудьте заменить указанную выше «mybatis-plus-latest-version» на конкретный номер версии при ее использовании, например 3.4.3, чтобы нормально импортировать фреймворк.
Для получения более подробной информации о структуре MP посетите ее официальный веб-сайт:baomidou.com/guide/
① Реализация контроллера
import com.example.demo.model.User;
import com.example.demo.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/u")
public class UserController {
@Autowired
private UserServiceImpl userService;
/**
* 批量插入(自定义)
*/
@RequestMapping("/mysavebatch")
public boolean mySaveBatch(){
List<User> list = new ArrayList<>();
// 待添加(用户)数据
for (int i = 0; i < 1000; i++) {
User user = new User();
user.setName("test:"+i);
user.setPassword("123456");
list.add(user);
}
return userService.saveBatchCustom(list);
}
}
② Реализация уровня бизнес-логики
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User>
implements UserService {
@Autowired
private UserMapper userMapper;
public boolean saveBatchCustom(List<User> list){
return userMapper.saveBatchCustom(list);
}
}
③ Реализация уровня сохраняемости данных
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User>{
boolean saveBatchCustom(List<User> list);
}
После реализации приведенного выше кода мы можем использовать MP для реализации функции пакетной вставки данных, но в этой статье, в дополнение к конкретному коду реализации, нам также необходимо знать эффективность выполнения каждого метода, поэтому давайте напишем тест MP. следующий код.
Тест производительности МП
import com.example.demo.model.User;
import com.example.demo.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class UserControllerTest {
// 最大循环次数
private static final int MAXCOUNT = 100000;
@Autowired
private UserServiceImpl userService;
/**
* MP 批量插入
*/
@Test
void saveBatch() {
long stime = System.currentTimeMillis(); // 统计开始时间
List<User> list = new ArrayList<>();
for (int i = 0; i < MAXCOUNT; i++) {
User user = new User();
user.setName("test:" + i);
user.setPassword("123456");
list.add(user);
}
// MP 批量插入
userService.saveBatch(list);
long etime = System.currentTimeMillis(); // 统计结束时间
System.out.println("执行时间:" + (etime - stime));
}
}
Выполнение вышеуказанной программы заняло в общей сложности 6088 миллисекунд, как показано на следующем рисунке:Из приведенных выше результатов видно, что при использовании функции пакетной вставки MP (вставка фрагментов данных мощностью 10 Вт) ее производительность в 14,5 раз выше, чем производительность одиночной вставки в цикл.
Анализ исходного кода MP
По времени выполнения MP и одиночной вставке цикла мы видим, что использование MP не выполняется в одном цикле, как думают некоторые друзья.Чтобы более четко объяснить эту проблему, мы проверили исходный код MP .
Основным кодом реализации MP является метод saveBatch, исходный код которого выглядит следующим образом:Продолжаем следить за перегруженным методом saveBatch:Как видно из приведенного выше исходного кода, MP делит данные для выполнения на N частей, каждая из 1000 записей, и выполняет пакетную вставку каждые 1000 записей, поэтому его производительность намного выше, чем производительность одиночной вставки в цикле. .
Так зачем делать это партиями, а не сразу? Не волнуйтесь, мы поймем, когда рассмотрим третий способ реализации.
3. Родная пакетная вставка
Собственный метод пакетной вставки использует тег foreach в MyBatis для объединения данных в собственный оператор вставки для однократного выполнения.Основной код реализации выглядит следующим образом.
① Расширение уровня бизнес-логики
Добавьте метод saveBatchByNative в UserServiceImpl, и код реализации будет следующим:
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
@Autowired
private UserMapper userMapper;
public boolean saveBatchByNative(List<User> list) {
return userMapper.saveBatchByNative(list);
}
}
② Расширение уровня сохраняемости данных
Добавьте метод saveBatchByNative в UserMapper, и код реализации будет следующим:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper extends BaseMapper<User> {
boolean saveBatchByNative(List<User> list);
}
③ Добавьте UserMapper.xml
Создайте файл UserMapper.xml и используйте тег foreach для соединения SQL. Конкретный код реализации выглядит следующим образом:
<?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="com.example.demo.mapper.UserMapper">
<insert id="saveBatchByNative">
INSERT INTO `USER`(`NAME`,`PASSWORD`) VALUES
<foreach collection="list" separator="," item="item">
(#{item.name},#{item.password})
</foreach>
</insert>
</mapper>
После вышеперечисленных шагов наша нативная функция пакетной вставки почти реализована, далее мы используем модульные тесты для проверки эффективности выполнения этого метода.
Тест производительности встроенной пакетной вставки
import com.example.demo.model.User;
import com.example.demo.service.impl.UserServiceImpl;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
class UserControllerTest {
// 最大循环次数
private static final int MAXCOUNT = 100000;
@Autowired
private UserServiceImpl userService;
/**
* 原生自己拼接 SQL,批量插入
*/
@Test
void saveBatchByNative() {
long stime = System.currentTimeMillis(); // 统计开始时间
List<User> list = new ArrayList<>();
for (int i = 0; i < MAXCOUNT; i++) {
User user = new User();
user.setName("test:" + i);
user.setPassword("123456");
list.add(user);
}
// 批量插入
userService.saveBatchByNative(list);
long etime = System.currentTimeMillis(); // 统计结束时间
System.out.println("执行时间:" + (etime - stime));
}
}
Однако, когда мы запускаем программу, происходит следующее:Нани? Выполнение программы фактически сообщило об ошибке.
Анализ недостатков
Из приведенного выше сообщения об ошибке видно, что когда мы используем собственный метод для объединения 10 Вт фрагментов данных в одно выполнение SQL, объединенный SQL слишком велик (4,56 МБ), что приводит к ошибке выполнения программы, потому что по умолчанию MySQL может выполнить максимальный SQL (размер) 4M, поэтому программа сообщает об ошибке.
Это недостаток собственного метода пакетной вставки, и причина, по которой MP необходимо выполнять пакетами, состоит в том, чтобы предотвратить ошибку выполнения программы из-за запуска максимального выполнения SQL базы данных при выполнении программы.
решение
Конечно, мы также можем решить проблему сообщения об ошибках, установив максимальное выполнение SQL MySQL, Команда настройки выглядит следующим образом:
-- 设置最大执行 SQL 为 10M
set global max_allowed_packet=10*1024*1024;
Как показано ниже:
Примечание. Приведенные выше команды необходимо выполнять в клиенте, подключенном к MySQL.
Но приведенное выше решение все же является временным решением, потому что мы не можем предсказать, насколько велик самый большой исполняемый SQL в программе, поэтому наиболее распространенным методом является выделение метода выполнения пакетных вставок (то есть, как это реализует MP).
Когда мы установим максимальное выполнение SQL MySQL на 10M и запустим приведенный выше код модульного теста, результаты выполнения будут следующими:
Суммировать
В этой статье мы представляем 3 метода пакетной вставки MyBatis.Среди них производительность одиночной вставки цикла является самой низкой, а также наименее желательной.Метод использования MyBatis для объединения собственного SQL для одноразовой вставки имеет самая высокая производительность, но этот метод может вызвать ошибки выполнения программы (срабатывает ограничение максимального размера исполняемого SQL базы данных), поэтому, учитывая вышеописанную ситуацию, вы можете рассмотреть возможность использования функции пакетной вставки MP.
Подпишитесь на официальный аккаунт «Сообщество китайского языка Java», чтобы увидеть больше серий статей о MyBatis и Spring Boot.