Всем привет! Данная статья будет полезна начинающим разработчикам на Java, для понимания работы пакетных операции и команды batchUpdate и на сколько она эффективнее по производительности в сравнении с update. Поехали!
Вначале немного теории:
С официальной документации:
12.4 JDBC batch operations
Most JDBC drivers provide improved performance if you batch multiple calls to the same prepared statement. By grouping updates into batches you limit the number of round trips to the database. This section will cover batch processing using both the JdbcTemplate and the SimpleJdbcTemplate.
Основываясь на данной документации можно сказать, что драйверы JDBC обеспечивают повышенную производительность при пакетном вызове одного и того же подготовленного оператора. Группируя обновления в пакеты, мы можем ограничивать количество обращений к базе данных и тем самым увеличивать скорость записи или обновления данных и увеличить тем самым производительность работы. Попробуем узнать на сколько можно повысить производительность работы.
Кому не очень интересен сам процесс расчета - Welcom сразу в конец статьи, где будет сам результат. А пока за работу.
Ссылка на репозиторий будет тут
Я создал spring-boot проект с помощью сайта https://start.spring.io/. Стартеры:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
В качестве базы данных буду использовать postgres. Поэтому:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
Также для генерации данных подключим следующую библиотеку:
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>1.0.2</version>
</dependency>
А для замера времени работы метода:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
Начнем с базы данных. Каждый шаг работы с БД я не буду описывать, так как это не является темой статьи. Я как, уже говорил, буду использовать postgres. Он у меня работает локально и с помощью pgAdmin4 создаю новую базу данных test_db.
Далее в IDEA подключаю данную базу и создаю в ней таблицу users. Скрипт на создание таблицы:
CREATE TABLE users(
user_id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_name varchar(100) NOT NULL,
address varchar(255) NOT NULL,
email varchar(100),
telephone varchar(13)
)
И конечно же в application.properties нужно прописать настройки к подключению к нашей БД.
spring.datasource.url=jdbc:postgresql://localhost:5432/test_db
spring.datasource.username=// тут Ваш username в базе данных
spring.datasource.password=// тут Ваш пароль к базе данным
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.database=postgresql
С базой данных закончили. Сейчас займемся сущностью, которую будем сохранять в базу данных.
public class User {
private int user_id;
private String user_name;
private String address;
private String email;
private String telephone;
public User(String user_name, String address, String email, String telephone) {
this.user_name = user_name;
this.address = address;
this.email = email;
this.telephone = telephone;
}
public int getUser_id() {
return user_id;
}
public void setUser_id(int user_id) {
this.user_id = user_id;
}
public String getUser_name() {
return user_name;
}
public void setUser_name(String user_name) {
this.user_name = user_name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
}
Пришло наконец-то время на бизнес-логику создадим интерфейс UserService двумя методами void batchUpdateTest() и void multipleUpdateTest() - первый будет делать вставки с помощью метода batchUpdate, а второй - будет просто использовать update.
Создадим сервис UserServiceImpl, заимплементим данный сервис от интерфейса UserService и реализуем два метода.
Метод batchUpdateTest():
public void batchUpdateTest() {
List<User> users= create100_000Users();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
jdbcTemplate.batchUpdate("INSERT INTO Users (user_name, address, email, telephone) VALUES(?, ?, ?, ?)", new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement preparedStatement, int i) throws SQLException {
preparedStatement.setString(1, users.get(i).getUser_name());
preparedStatement.setString(2, users.get(i).getAddress());
preparedStatement.setString(3, users.get(i).getEmail());
preparedStatement.setString(4, users.get(i).getTelephone());
}
@Override
public int getBatchSize() {
return users.size();
}
});
stopWatch.stop();
System.out.println("Time has passed with batch update, ms: " + stopWatch.getTime());
}
Метод multipleUpdateTest():
public void multipleUpdateTest() {
List<User> users= create100_000Users();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (User user: users) {
jdbcTemplate.update("INSERT INTO Users (user_name, address, email, telephone) VALUES(?, ?, ?, ?)",
user.getUser_name(),
user.getAddress(),
user.getEmail(),
user.getTelephone());
}
stopWatch.stop();
System.out.println("Time has passed with update, ms: " + stopWatch.getTime());
}
Также в данном сервисе есть еще один вспомогательный метод, который генерирует 10 000 Uses-ов:
public static List<User> create100_000Users() {
List<User> users = new ArrayList<>();
Faker faker = new Faker();
for (int i = 0; i < 10_000; i++) {
String firstName = faker.name().firstName();
String lastName = faker.name().lastName();
String sufixTel = String.valueOf(i);
String telephone = "+000290000000";
telephone = telephone.substring(0, telephone.length()-sufixTel.length()) + sufixTel;
User user = new User(
firstName + " " + lastName,
faker.address().fullAddress(),
firstName.toLowerCase() + lastName.toLowerCase() + i + "@gmail.com",
telephone);
users.add(user);
}
return users;
}
Создадим контроллер с помощью которого будем все это тэстить.
@RestController
@RequestMapping("api/v1")
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/withBatchUpdate")
@ResponseStatus(HttpStatus.OK)
public void withBatchUpdate() {
userService.batchUpdateTest();
}
@GetMapping("/withUpdate")
@ResponseStatus(HttpStatus.OK)
public void withoutBatchUpdate() {
userService.multipleUpdateTest();
}
}
Запросы буду посылать с помощью postmana.
Запускаем наше приложение.
Идем в postman и отправляем запрос на http://localhost:8080/api/v1/withBatchUpdate и ждем.
Результат: 196 мс.
Идем снова в postman и отправляем запрос на http://localhost:8080/api/v1/withUpdate и ждем.
Результат: 1932 мс.
Повторим каждый запрос еще по 2 раза и внесем все данные в таблицу.
Расчеты показали, что при 10 000 вставок в базу данных метод batchUpdate() сработал в 10,6 раза быстрее, чем просто update().
Спасибо Всем за внимание к данной статье. Пока.