批处理步骤概述
- 收集用户数据:每次用户查询时,将查询行为数据存储在内存中的
List
结构中。
- 定时批量写入:每5分钟触发一次批量写入操作,将内存中的数据(即
List
中的数据)一次性写入数据库。
- 使用JDBC批处理:在写入数据库时,使用JDBC的批处理(
batch insert
)来一次性插入多条记录,从而避免频繁的数据库连接和写操作。
JDBC 批处理步骤
- 创建数据库连接:使用数据库连接池来管理连接,提高效率。
- 创建
PreparedStatement
:使用批量插入的 PreparedStatement
来减少数据库交互的次数。
- 收集数据:将内存中的
List
数据逐条添加到批处理中。
- 执行批处理:执行一次批量提交,将数据写入数据库。
- 清理资源:操作完成后,释放相关资源。
业务代码示例
import java.sql.*;
import java.util.List;
public class UserQueryBatchProcessor {
private static final int BATCH_SIZE = 1000;
private static final int FLUSH_INTERVAL = 5 * 60 * 1000;
private List<UserQueryData> queryDataList;
private DataSource dataSource;
public UserQueryBatchProcessor(DataSource dataSource) {
this.dataSource = dataSource;
}
public void processUserQueries() {
while (true) {
try {
storeUserQueryData();
Thread.sleep(FLUSH_INTERVAL);
batchInsertUserQueries();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void storeUserQueryData() {
queryDataList.add(new UserQueryData("user1", "query1", System.currentTimeMillis()));
queryDataList.add(new UserQueryData("user2", "query2", System.currentTimeMillis()));
}
private void batchInsertUserQueries() {
if (queryDataList.isEmpty()) {
return;
}
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = dataSource.getConnection();
connection.setAutoCommit(false);
String insertSQL = "INSERT INTO user_query_logs (user_id, query, query_time) VALUES (?, ?, ?)";
preparedStatement = connection.prepareStatement(insertSQL);
int count = 0;
for (UserQueryData data : queryDataList) {
preparedStatement.setString(1, data.getUserId());
preparedStatement.setString(2, data.getQuery());
preparedStatement.setLong(3, data.getQueryTime());
preparedStatement.addBatch();
if (++count % BATCH_SIZE == 0) {
preparedStatement.executeBatch();
}
}
preparedStatement.executeBatch();
connection.commit();
queryDataList.clear();
} catch (SQLException e) {
e.printStackTrace();
if (connection != null) {
try {
connection.rollback();
} catch (SQLException rollbackEx) {
rollbackEx.printStackTrace();
}
}
} finally {
try {
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static class UserQueryData {
private String userId;
private String query;
private long queryTime;
public UserQueryData(String userId, String query, long queryTime) {
this.userId = userId;
this.query = query;
this.queryTime = queryTime;
}
public String getUserId() {
return userId;
}
public String getQuery() {
return query;
}
public long getQueryTime() {
return queryTime;
}
}
}
代码解释
processUserQueries()
:这个方法会模拟不断收到用户查询请求,每隔5分钟将积累的查询数据批量写入数据库。
storeUserQueryData()
:模拟用户查询数据的存储,可以替换为你实际的用户行为采集方式。
batchInsertUserQueries()
:这是核心的批量写入过程,使用了JDBC的批处理(PreparedStatement.addBatch()
)和事务(connection.setAutoCommit(false)
)来确保高效的批量插入。每次批量插入后,都会清空内存中的数据列表。
UserQueryData
类:封装了查询数据的基本结构,存储用户ID、查询内容及查询时间。
相关性能优化
- 批量大小调整:
BATCH_SIZE
参数可以根据你的系统的负载情况进行调整。如果数据量非常大,可以适当增大批量插入的大小,但需要注意过大的批量可能导致内存占用过高。
- 数据库连接池:为了避免频繁地打开和关闭数据库连接,应该使用数据库连接池来管理连接。常见的连接池有 HikariCP、Druid 等。
- 事务管理:使用事务确保批量插入的原子性。如果批量插入的过程中出现错误,整个操作将会回滚。