JdbcTemplate 批量插入
在使用Spring框架的JdbcTemplate
进行数据库操作时,批量插入是一种常见的需求,尤其是在需要高效地插入大量数据时。JdbcTemplate
提供了多种方法来支持批量插入,下面我将介绍几种常用的实现方式:
1. 使用batchUpdate
方法
batchUpdate
是JdbcTemplate
提供的一个用于执行批量更新操作的方法,它也可以用于批量插入。这种方法适用于插入结构相同的多条数据。
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
public void batchInsert(List<MyObject> objects) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "INSERT INTO my_table (column1, column2) VALUES (?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
MyObject object = objects.get(i);
ps.setString(1, object.getColumn1());
ps.setString(2, object.getColumn2());
}
@Override
public int getBatchSize() {
return objects.size();
}
});
}
2. 使用NamedParameterJdbcTemplate
进行批量插入
当你的插入语句中有很多参数,并且希望通过名称而不是索引来指定它们时,可以使用NamedParameterJdbcTemplate
。这样可以使代码更加清晰易读。
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
public void batchInsertUsingNamedParameterJdbcTemplate(List<MyObject> objects) {
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
String sql = "INSERT INTO my_table (column1, column2) VALUES (:column1, :column2)";
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(objects.toArray());
namedParameterJdbcTemplate.batchUpdate(sql, batch);
}
3. 使用SimpleJdbcInsert
进行批量插入
SimpleJdbcInsert
是一个简化插入操作的工具类,它可以自动处理表名和列名的映射。对于批量插入,可以与SqlParameterSourceUtils
一起使用来简化操作。
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
public void batchInsertUsingSimpleJdbcInsert(List<MyObject> objects) {
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource)
.withTableName("my_table")
.usingColumns("column1", "column2");
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(objects.toArray());
simpleJdbcInsert.executeBatch(batch);
}
注意事项
1. 数据库连接池配置
假设你正在使用HikariCP作为数据库连接池,为了支持大量的并发插入操作,你需要合理配置数据库连接池的参数,比如maximumPoolSize
(最大池大小)、idleTimeout
(空闲超时时间)等。
在Spring框架中,配置Datasource是连接数据库的基础。Spring提供了多种方式来配置Datasource,包括在XML文件中配置、使用Java配置类,以及利用SpringBoot的自动配置功能。这里,我将介绍如何通过SpringBoot的application.properties文件以及使用Java配置类来配置Datasource。
使用SpringBoot的application.properties
当你使用SpringBoot时,配置Datasource非常简单。SpringBoot的自动配置功能能够根据classpath中的库和application.properties文件中的配置自动设置Datasource。以下是一个MySQL
数据库的配置示例:
# 数据库基本配置
spring.datasource.url=jdbc:mysql://localhost:3306/yourDatabase?useSSL=false&serverTimezone=UTC
spring.datasource.username=user
spring.datasource.password=password
# 指定HikariCP作为连接池 (Spring Boot 2.x 默认使用 HikariCP)
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# HikariCP 连接池配置
spring.datasource.hikari.maximum-pool-size=20 # 默认值: 10
spring.datasource.hikari.minimum-idle=10 # 默认值: 和maximumPoolSize相同
spring.datasource.hikari.idle-timeout=300000 # 默认值: 600000(10分钟)
spring.datasource.hikari.connection-timeout=30000 # 默认值: 30000(30秒)
spring.datasource.hikari.max-lifetime=1800000 # 默认值: 1800000(30分钟),0表示无限寿命
spring.datasource.hikari.pool-name=HikariCP # 默认是自动生成的
spring.datasource.hikari.auto-commit=true # 默认值: true
# 数据库驱动类(根据实际数据库类型更改)
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 连接测试查询 (Hikari会自动检测是否需要这个查询,对于某些数据库不是必须的)
# 如果数据库驱动支持JDBC4的Connection.isValid()方法,HikariCP会使用它来验证,而不需要指定这个
# HikariCP优先使用Connection.isValid()方法来检测连接的有效性
# MySQL JDBC驱动(Connector/J 5.1及以上版本)支持JDBC 4规范
# 此项可以帮助提升应用的稳定性和用户体验
spring.datasource.hikari.connection-test-query=SELECT 1
在这个例子中,maximum-pool-size
被设置为20,意味着连接池最多可以同时管理20个数据库连接。这样可以在高负载时提供足够的数据库连接,但也需要确保数据库服务器能够处理这么多并发连接。
注意事项
当配置idleTimeout时,需要考虑到maxLiffetime的设置。maxLifetime是一个连接最长生命周期的设置,HikaricP推荐maxLifetime的值应该比idleTimeout精微短一些。这是为了确保连接在被服务器端强制关闭之前,连接池就已经将其回收。
设置过短的idleTimeout可能会导致频繁地创建和销毁数据库连接,这可能会对性能产生负面影响。
在调整这些参数时,建议进行适当的测试,以确保配置的修改能够带来预期的效果,并且不会对应用用性能产生不利影响。
总的来说,是否需要手动配置idleTimeout取决于你对应用性能和资源利用的具体需求。大多数情况下,默认值已经足够好,只有在特定场景下才需要调整。
使用Java配置类
如果你希望通过Java代码来配置Datasource,可以创建一个配置类,并使用@Bean注解来定义Datasource bean。这种方式提供了更高的灵活性,允许你在创建DataSource时执行复杂的逻
辑。
以下是使用HikariCP作为连接池的一个配置示例:
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.url}")
private String dbUrl
@Value("${spring.datasource.username}")
private String dbUsername;
@Value("${spring.datasource.password}")
private String dbPassword;
@Value("${spring.datasource.driver-class-name}")
private String dbDriverClassName;
@Bean
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(dbUrl);
dataSource.setUsername(dbUsername);
dataSource.setPassword(dbPassword);
dataSource.setDriverClassName(dbDriverClassName);
//可以根据需要设置其他Hikaricp特有的配置
return dataSource;
}
}
在这个配置类中,我们使用@value注解来注入application·properties中定义的配置值,然后创建了一个HikariDatasourrce实例并返回。这个Datasource bean随后可以被Spriing容器管理,供应用中需要进行数据库操作的组件使用
结论
以上就是在SpringBoot项目中配置Datasource的几种常见方法。选择哪种方法取决于你的具体需求和偏好。无论哪种方式,SpringBoot都能够简化数据库连接和操作的过程,让你能够专注于业务逻辑的实现。
2. 分批次插入
考虑到内存限制和数据库压力,当需要插入的数据量非常大时,应该将数据分批次进行插入。例如,如果你有10万条数据需要插入,可以每1000条数据作为一个批次,逐批插入。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.sql.DataSource;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
@Service
public class MyService {
private final JdbcTemplate jdbcTemplate;
@Autowired
public MyService(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Transactional
public void batchInsertInChunks(List<MyObject> allObjects, int chunkSize) {
String sql = "INSERT INTO my_table (column1, column2) VALUES (?, ?)";
for (int i = 0; i < allObjects.size(); i += chunkSize) {
final List<MyObject> chunk = allObjects.subList(i, Math.min(allObjects.size(), i + chunkSize));
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int j) throws SQLException {
MyObject object = chunk.get(j);
ps.setString(1, object.getColumn1());
ps.setString(2, object.getColumn2());
}
@Override
public int getBatchSize() {
return chunk.size();
}
});
}
}
}
@RestController
public class MyController {
private final MyService myService;
@Autowired
public MyController(MyService myService) {
this.myService = myService;
}
@PostMapping("/batchInsert")
public ResponseEntity<?> batchInsert(@RequestBody List<MyObject> allObjects) {
// 假设chunkSize为1000,这个值可以根据您的实际需要调整
int chunkSize = 1000;
myService.batchInsertInChunks(allObjects, chunkSize);
return ResponseEntity.ok().build();
}
}
3. 调整批量大小以获得最佳性能
不同数据库对批量操作的最优批量大小(batch size)可能有所不同。过大的批量大小可能导致内存溢出或者长时间的事务锁定,而过小的批量大小则可能导致频繁的数据库交互,影响性能。
实际应用中,你可能需要根据数据库的响应时间、CPU和内存使用率等指标,通过测试来确定一个最佳的批量大小。例如,你可以从1000开始测试,逐步调整,观察性能变化,找到一个最适合你的应用场景的批量大小。
结论
通过上述例子,我们可以看到,在使用JdbcTemplate
进行批量插入时,合理配置数据库连接池、分批次插入以及调整批量大小都是非常重要的。这些措施能够帮助我们有效管理资源,提高应用性能,避免潜在的问题。在实际开发中,应该根据具体情况灵活应用这些技巧。