diff --git a/src/test/java/xyz/zhouxy/jdbc/test/AccountPO.java b/src/test/java/xyz/zhouxy/jdbc/test/AccountPO.java
deleted file mode 100644
index 97ed2f3..0000000
--- a/src/test/java/xyz/zhouxy/jdbc/test/AccountPO.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright 2025 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package xyz.zhouxy.jdbc.test;
-
-import java.time.LocalDateTime;
-import java.util.Objects;
-
-public class AccountPO {
- Long id;
- String username;
- String accountStatus;
- LocalDateTime createTime;
- Long createdBy;
- LocalDateTime updateTime;
- Long updatedBy;
- Long version;
-
- public AccountPO() {
- }
-
- public AccountPO(Long id, String username, String accountStatus,
- Long createdBy, Long updatedBy) {
- this.id = id;
- this.username = username;
- this.accountStatus = accountStatus;
- this.createdBy = createdBy;
- this.updatedBy = updatedBy;
- }
-
- public AccountPO(Long id, String username, String accountStatus,
- LocalDateTime createTime, Long createdBy,
- LocalDateTime updateTime, Long updatedBy,
- Long version) {
- this.id = id;
- this.username = username;
- this.accountStatus = accountStatus;
- this.createTime = createTime;
- this.createdBy = createdBy;
- this.updateTime = updateTime;
- this.updatedBy = updatedBy;
- this.version = version;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public String getAccountStatus() {
- return accountStatus;
- }
-
- public void setAccountStatus(String accountStatus) {
- this.accountStatus = accountStatus;
- }
-
- public LocalDateTime getCreateTime() {
- return createTime;
- }
-
- public void setCreateTime(LocalDateTime createTime) {
- this.createTime = createTime;
- }
-
- public Long getCreatedBy() {
- return createdBy;
- }
-
- public void setCreatedBy(Long createdBy) {
- this.createdBy = createdBy;
- }
-
- public LocalDateTime getUpdateTime() {
- return updateTime;
- }
-
- public void setUpdateTime(LocalDateTime updateTime) {
- this.updateTime = updateTime;
- }
-
- public Long getUpdatedBy() {
- return updatedBy;
- }
-
- public void setUpdatedBy(Long updatedBy) {
- this.updatedBy = updatedBy;
- }
-
- public Long getVersion() {
- return version;
- }
-
- public void setVersion(Long version) {
- this.version = version;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, username, accountStatus, createTime, createdBy, updateTime, updatedBy, version);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (this == obj)
- return true;
- if (obj == null)
- return false;
- if (getClass() != obj.getClass())
- return false;
- AccountPO other = (AccountPO) obj;
- return Objects.equals(id, other.id) && Objects.equals(username, other.username)
- && Objects.equals(accountStatus, other.accountStatus) && Objects.equals(createTime, other.createTime)
- && Objects.equals(createdBy, other.createdBy) && Objects.equals(updateTime, other.updateTime)
- && Objects.equals(updatedBy, other.updatedBy) && Objects.equals(version, other.version);
- }
-
- @Override
- public String toString() {
- return "AccountPO [id=" + id + ", username=" + username + ", accountStatus=" + accountStatus + ", createTime="
- + createTime + ", createdBy=" + createdBy + ", updateTime=" + updateTime + ", updatedBy=" + updatedBy
- + ", version=" + version + "]";
- }
-}
diff --git a/src/test/java/xyz/zhouxy/jdbc/test/BaseH2Test.java b/src/test/java/xyz/zhouxy/jdbc/test/BaseH2Test.java
new file mode 100644
index 0000000..c8eee59
--- /dev/null
+++ b/src/test/java/xyz/zhouxy/jdbc/test/BaseH2Test.java
@@ -0,0 +1,91 @@
+package xyz.zhouxy.jdbc.test;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
+import java.sql.Statement;
+import java.util.stream.Collectors;
+
+import org.h2.jdbcx.JdbcConnectionPool;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
+
+/**
+ * 测试基类,提供 H2 内存数据库连接池和模板实例。
+ *
+ *
使用 H2 内置 JdbcConnectionPool 作为连接池,符合项目"基本仅考虑数据库连接池"的设计。
+ */
+public abstract class BaseH2Test {
+
+ protected static final Logger logger = LoggerFactory.getLogger(BaseH2Test.class);
+
+ protected static JdbcConnectionPool dataSource;
+
+ protected SimpleJdbcTemplate createTemplate() {
+ return new SimpleJdbcTemplate(dataSource);
+ }
+
+ @BeforeAll
+ static void initDatabase() throws Exception {
+ dataSource = JdbcConnectionPool.create(
+ "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE;DATABASE_TO_UPPER=FALSE",
+ "sa", "");
+ dataSource.setMaxConnections(10);
+
+ // 加载并执行 SQL 初始化文件
+ try (Connection conn = dataSource.getConnection();
+ Statement stmt = conn.createStatement()) {
+
+ String sqlContent = loadSqlFile("init_tables.sql");
+ // 按分号拆分并逐条执行
+ for (String singleSql : sqlContent.split(";")) {
+ String trimmed = singleSql.trim();
+ if (!trimmed.isEmpty()) {
+ stmt.execute(trimmed);
+ }
+ }
+ }
+
+ logger.info("H2 数据库初始化完成,连接池已就绪");
+ }
+
+ @AfterAll
+ static void closeDatabase() {
+ if (dataSource != null) {
+ dataSource.dispose();
+ logger.info("H2 数据库连接池已关闭");
+ }
+ }
+
+ /**
+ * 重新执行初始化脚本,恢复数据到初始状态。
+ */
+ protected static void resetDatabase() throws Exception {
+ try (Connection conn = dataSource.getConnection();
+ Statement stmt = conn.createStatement()) {
+
+ String sqlContent = loadSqlFile("init_tables.sql");
+ for (String singleSql : sqlContent.split(";")) {
+ String trimmed = singleSql.trim();
+ if (!trimmed.isEmpty()) {
+ stmt.execute(trimmed);
+ }
+ }
+ }
+
+ logger.info("数据库已重置为初始状态");
+ }
+
+ private static String loadSqlFile(String fileName) throws Exception {
+ try (InputStream is = BaseH2Test.class.getClassLoader().getResourceAsStream(fileName);
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+ return reader.lines().collect(Collectors.joining("\n"));
+ }
+ }
+}
diff --git a/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java b/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
new file mode 100644
index 0000000..66de33e
--- /dev/null
+++ b/src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
@@ -0,0 +1,292 @@
+package xyz.zhouxy.jdbc.test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static xyz.zhouxy.jdbc.ParamBuilder.*;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Lists;
+
+import xyz.zhouxy.jdbc.BatchUpdateErrorInfo;
+import xyz.zhouxy.jdbc.BatchUpdateResult;
+import xyz.zhouxy.jdbc.BatchUpdateStatus;
+import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
+
+/**
+ * 批量更新 API 测试:batchUpdate。
+ */
+@DisplayName("SimpleJdbcTemplate 批量更新操作")
+class BatchUpdateTest extends BaseH2Test {
+
+ private static final Logger logger = LoggerFactory.getLogger(BatchUpdateTest.class);
+
+ private static final String INSERT_SQL =
+ "INSERT INTO users (username, email, age, balance, active) VALUES (?, ?, ?, ?, ?)";
+
+ @BeforeEach
+ void setUp() throws Exception {
+ resetDatabase();
+ }
+
+ // ================================
+ // #region - 正常批量操作
+ // ================================
+
+ @Test
+ @DisplayName("batchUpdate:正常批量插入,单批次")
+ void testBatchUpdateSingleBatch() throws SQLException {
+ SimpleJdbcTemplate template = createTemplate();
+
+ List users = new ArrayList<>();
+ users.add(new User("user01", "u01@test.com", 20, 1000L, true));
+ users.add(new User("user02", "u02@test.com", 21, 2000L, false));
+ users.add(new User("user03", "u03@test.com", 22, 3000L, true));
+
+ List