test: 重新编写测试用例完成必要单元测试

This commit is contained in:
2026-05-27 03:59:09 +08:00
parent ee68cb4d00
commit b5ed69a31b
14 changed files with 1735 additions and 589 deletions

View File

@@ -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 + "]";
}
}

View File

@@ -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 内存数据库连接池和模板实例。
*
* <p>使用 H2 内置 JdbcConnectionPool 作为连接池,符合项目"基本仅考虑数据库连接池"的设计。</p>
*/
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"));
}
}
}

View File

@@ -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<User> 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<Object[]> params = buildBatchParams(users, a -> new Object[] { a.getUsername(), a.getEmail(), a.getAge(), a.getBalance(), a.getActive() });
BatchUpdateResult result = template.batchUpdate(INSERT_SQL, params, 10);
logger.info("batchUpdate 结果: {}", result);
assertEquals(BatchUpdateStatus.SUCCESS, result.getStatus());
assertEquals(3, result.getTotal());
assertEquals(1, result.getBatchCount());
assertEquals(1, result.getCompleteBatchCount());
assertEquals(1, result.getSuccessBatchCount());
assertEquals(0, result.getErrorBatchCount());
assertEquals(10, result.getBatchSize());
assertEquals(0, result.getRemainingBatchCount());
// 验证数据已插入
int count = template.query("SELECT COUNT(*) FROM users",
rs -> { rs.next(); return rs.getInt(1); });
assertEquals(8, count); // 5 初始 + 3
}
@Test
@DisplayName("batchUpdate多批次batchSize 恰好整除")
void testBatchUpdateMultipleBatchesExact() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<User> paramsList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
paramsList.add(new User("user" + i, "u" + i + "@test.com", 20 + i, 1000L, true));
}
List<Object[]> params = buildBatchParams(paramsList, a -> new Object[] { a.getUsername(), a.getEmail(), a.getAge(), a.getBalance(), a.getActive() });
BatchUpdateResult result = template.batchUpdate(INSERT_SQL, params, 5);
logger.info("多批次 batchUpdate 结果: {}", result);
assertEquals(BatchUpdateStatus.SUCCESS, result.getStatus());
assertEquals(10, result.getTotal());
assertEquals(2, result.getBatchCount());
assertEquals(2, result.getSuccessBatchCount());
assertEquals(0, result.getErrorBatchCount());
}
@Test
@DisplayName("batchUpdatebatchSize 不整除")
void testBatchUpdateBatchSizeNotDivisible() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Object[]> paramsList = new ArrayList<>();
for (int i = 0; i < 7; i++) {
paramsList.add(new Object[]{"user" + i, "u" + i + "@test.com", 20, 1000L, true});
}
BatchUpdateResult result = template.batchUpdate(INSERT_SQL, paramsList, 3);
logger.info("不整除 batchUpdate 结果: {}", result);
assertEquals(7, result.getTotal());
assertEquals(3, result.getBatchCount());
assertEquals(BatchUpdateStatus.SUCCESS, result.getStatus());
}
// ================================
// #endregion - 正常批量操作
// ================================
// ================================
// #region - 边界情况
// ================================
@Test
@DisplayName("batchUpdate空 params 集合")
void testBatchUpdateEmptyParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Object[]> params = buildBatchParams(Collections.<User>emptyList(),
a -> new Object[] { a.getUsername(), a.getEmail(), a.getAge(), a.getBalance(), a.getActive() });
assertEquals(Collections.emptyList(), params);
BatchUpdateResult result = template.batchUpdate(
INSERT_SQL, params, 10);
assertEquals(0, result.getTotal());
assertEquals(0, result.getBatchCount());
assertEquals(10, result.getBatchSize());
}
@Test
@DisplayName("batchUpdatenull params")
void testBatchUpdateNullParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
BatchUpdateResult result = template.batchUpdate(
INSERT_SQL, null, 10);
assertEquals(0, result.getTotal());
assertEquals(0, result.getBatchCount());
}
@Test
@DisplayName("batchUpdate单条数据")
void testBatchUpdateSingleItem() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Object[]> paramsList = Collections.singletonList(
new Object[]{"single", "single@test.com", 30, 5000L, true});
BatchUpdateResult result = template.batchUpdate(INSERT_SQL, paramsList, 5);
assertEquals(BatchUpdateStatus.SUCCESS, result.getStatus());
assertEquals(1, result.getTotal());
assertEquals(1, result.getBatchCount());
}
// ================================
// #endregion - 边界情况
// ================================
// ================================
// #region - 包含错误数据
// ================================
final List<User> userListContainingInvalidData = Lists.newArrayList(
// batch 0
new User("test_0001", "test_0001@example.com", 1, 1L, true),
new User("test_0002", "test_0002@example.com", 1, 1L, true),
new User("test_0003", "test_0003@example.com", 1, 1L, true),
// batch 1
new User("test_0004", "test_0004@example.com", 1, 1L, true),
new User("test_0005", "test_0005@example.com", 1, 1L, true),
new User("test_0006", "test_0006@example.com", 1, 1L, true),
// batch 2
new User("test_0007", "test_0007@example.com", 1, 1L, true),
new User("test_0007", "test_*0007@example.com", 1, 1L, true),
// new User("test_0008", "test_0008@example.com", 1, 1L, true),
new User("test_0009", "test_0009@example.com", 1, 1L, true),
// batch 3
new User("test_0009", "test_*0009@example.com", 1, 1L, true),
// new User("test_0010", "test_0010@example.com", 1, 1L, true),
new User("test_0011", "test_0011@example.com", 1, 1L, true),
new User("test_0012", "test_0012@example.com", 1, 1L, true),
// batch 4
new User("test_0013", "test_0013@example.com", 1, 1L, true)
);
// ==================== quietly=false 中断模式 ====================
@Test
@DisplayName("batchUpdatequietly=false中间出错中断返回 INTERRUPTED")
void testBatchUpdateQuietlyFalseInterrupted() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int count0 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class)
.orElse(0);
List<Object[]> params = buildBatchParams(userListContainingInvalidData, a -> new Object[] { a.getUsername(), a.getEmail(), a.getAge(), a.getBalance(), a.getActive() });
BatchUpdateResult result = template.batchUpdate(INSERT_SQL, params, 3);
assertEquals(BatchUpdateStatus.INTERRUPTED, result.getStatus());
assertEquals(13, result.getTotal());
assertEquals(5, result.getBatchCount());
assertEquals(3, result.getCompleteBatchCount());
assertEquals(2, result.getSuccessBatchCount());
assertEquals(1, result.getErrorBatchCount());
assertEquals(2, result.getRemainingBatchCount());
assertArrayEquals(new int[] { 2 }, result.getErrorBatchIndexes());
assertEquals(1, result.getAllErrorsInfo().size());
assertTrue(result.getAllErrorsInfo().containsKey(2));
BatchUpdateErrorInfo batchUpdateErrorInfo = result.getBatchUpdateErrorInfo(2);
assertNotNull(batchUpdateErrorInfo);
assertEquals(2, batchUpdateErrorInfo.getBatchIndex());
assertNotNull(batchUpdateErrorInfo.getCause());
assertInstanceOf(SQLException.class, batchUpdateErrorInfo.getCause());
assertTrue(SQLException.class.isAssignableFrom(batchUpdateErrorInfo.getErrorType()));
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(0));
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(1));
assertArrayEquals(new int[] { 1, Statement.EXECUTE_FAILED, 1 }, result.getUpdateCounts(2));
assertNull(result.getUpdateCounts(3));
assertNull(result.getUpdateCounts(4));
Optional<Integer> count8 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(count0 + 8, count8.get().intValue());
}
// ==================== quietly=true 静默模式 ====================
@Test
@DisplayName("batchUpdatequietly=true出错继续返回 COMPLETED_WITH_ERRORS")
void testBatchUpdateQuietlyTrue() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int count0 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class)
.orElse(0);
List<Object[]> params = buildBatchParams(userListContainingInvalidData, a -> new Object[] { a.getUsername(), a.getEmail(), a.getAge(), a.getBalance(), a.getActive() });
BatchUpdateResult result = template.batchUpdate(INSERT_SQL, params, 3, true);
assertEquals(BatchUpdateStatus.COMPLETED_WITH_ERRORS, result.getStatus());
assertEquals(13, result.getTotal());
assertEquals(5, result.getBatchCount());
assertEquals(5, result.getCompleteBatchCount());
assertEquals(3, result.getSuccessBatchCount());
assertEquals(2, result.getErrorBatchCount());
assertEquals(0, result.getRemainingBatchCount());
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(0));
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(1));
assertArrayEquals(new int[] { 1, Statement.EXECUTE_FAILED, 1 }, result.getUpdateCounts(2));
assertArrayEquals(new int[] { Statement.EXECUTE_FAILED, 1, 1 }, result.getUpdateCounts(3));
assertArrayEquals(new int[] { 1 }, result.getUpdateCounts(4));
Optional<Integer> count11 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(count0 + 11, count11.get().intValue());
}
// ================================
// #endregion - 包含错误数据
// ================================
// ================================
// #region - wrong batchSize
// ================================
@Test
@DisplayName("batchUpdatebatchSize < 0参数校验不通过")
void testBatchUpdateWithWrongBatchSize() {
SimpleJdbcTemplate template = createTemplate();
List<Object[]> params = buildBatchParams(userListContainingInvalidData, a -> new Object[] { a.getUsername(), a.getEmail(), a.getAge(), a.getBalance(), a.getActive() });
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
() -> template.batchUpdate(INSERT_SQL, params, -1, true));
assertEquals("The batch size must be greater than 0.", e.getMessage());
}
// ================================
// #endregion - wrong batchSize
// ================================
}

View File

@@ -1,151 +0,0 @@
/*
* Copyright 2026-present 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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static xyz.zhouxy.jdbc.ParamBuilder.buildBatchParams;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Optional;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.google.common.collect.Lists;
import com.google.common.io.Resources;
import xyz.zhouxy.jdbc.BatchUpdateResult;
import xyz.zhouxy.jdbc.BatchUpdateStatus;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
public class BatchUpdateTests {
private static SimpleJdbcTemplate jdbcTemplate;
@BeforeAll
static void initH2() throws IOException, SQLException {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE;MODE=MySQL");
dataSource.setUser("sa");
dataSource.setPassword("");
jdbcTemplate = new SimpleJdbcTemplate(dataSource);
// 建表
executeSqlFile("schema.sql");
}
@BeforeEach
void initData() throws SQLException {
// 初始化数据
jdbcTemplate.update("truncate table sys_account");
}
static void executeSqlFile(String filePath) throws IOException, SQLException {
String[] sqls = Resources
.toString(Resources.getResource(filePath), StandardCharsets.UTF_8)
.split(";");
for (String sql : sqls) {
jdbcTemplate.update(sql);
}
}
final List<AccountPO> accountPOs = Lists.newArrayList(
// batch 0
new AccountPO(10001L, "test_0001", "1", 1L, 1L),
new AccountPO(10002L, "test_0002", "1", 1L, 1L),
new AccountPO(10003L, "test_0003", "1", 1L, 1L),
// batch 1
new AccountPO(10004L, "test_0004", "1", 1L, 1L),
new AccountPO(10005L, "test_0005", "1", 1L, 1L),
new AccountPO(10006L, "test_0006", "1", 1L, 1L),
// batch 2
new AccountPO(10007L, "test_0007", "1", 1L, 1L),
new AccountPO(10007L, "test_*0007", "1", 1L, 1L),
// new AccountPO(10008L, "test_0008", "1", 1L, 1L),
new AccountPO(10009L, "test_0009", "1", 1L, 1L),
// batch 3
new AccountPO(10009L, "test_*0009", "1", 1L, 1L),
// new AccountPO(10010L, "test_0010", "1", 1L, 1L),
new AccountPO(10011L, "test_0011", "1", 1L, 1L),
new AccountPO(10012L, "test_0012", "1", 1L, 1L),
// batch 4
new AccountPO(10013L, "test_0013", "1", 1L, 1L)
);
@Test
void testBatchUpdate() throws SQLException {
Optional<Integer> count0 = jdbcTemplate.queryFirst("SELECT COUNT(*) FROM sys_account", (rs, i) -> rs.getInt(1));
assertEquals(0, count0.get().intValue());
BatchUpdateResult result = jdbcTemplate.batchUpdate(
"INSERT INTO sys_account (id, username, account_status, created_by, updated_by) VALUES (?, ?, ?, ?, ?)",
buildBatchParams(accountPOs,
a -> new Object[] { a.getId(), a.getUsername(), a.getAccountStatus(), a.getCreatedBy(), a.getUpdatedBy() }),
3);
assertEquals(BatchUpdateStatus.INTERRUPTED, result.getStatus());
assertEquals(13, result.getTotal());
assertEquals(5, result.getBatchCount());
assertEquals(3, result.getCompleteBatchCount());
assertEquals(2, result.getSuccessBatchCount());
assertEquals(1, result.getErrorBatchCount());
assertEquals(2, result.getRemainingBatchCount());
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(0));
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(1));
assertArrayEquals(new int[] { 1, Statement.EXECUTE_FAILED, 1 }, result.getUpdateCounts(2));
assertNull(result.getUpdateCounts(3));
assertNull(result.getUpdateCounts(4));
Optional<Integer> count8 = jdbcTemplate.queryFirst("SELECT COUNT(*) FROM sys_account", (rs, i) -> rs.getInt(1));
assertEquals(8, count8.get().intValue());
}
@Test
void testBatchUpdateQuietly() throws SQLException {
Optional<Integer> count0 = jdbcTemplate.queryFirst("SELECT COUNT(*) FROM sys_account", (rs, i) -> rs.getInt(1));
assertEquals(0, count0.get().intValue());
BatchUpdateResult result = jdbcTemplate.batchUpdate(
"INSERT INTO sys_account (id, username, account_status, created_by, updated_by) VALUES (?, ?, ?, ?, ?)",
buildBatchParams(accountPOs,
a -> new Object[] { a.getId(), a.getUsername(), a.getAccountStatus(), a.getCreatedBy(), a.getUpdatedBy() }),
3,
true);
assertEquals(BatchUpdateStatus.COMPLETED_WITH_ERRORS, result.getStatus());
assertEquals(13, result.getTotal());
assertEquals(5, result.getBatchCount());
assertEquals(5, result.getCompleteBatchCount());
assertEquals(3, result.getSuccessBatchCount());
assertEquals(2, result.getErrorBatchCount());
assertEquals(0, result.getRemainingBatchCount());
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(0));
assertArrayEquals(new int[] { 1, 1, 1 }, result.getUpdateCounts(1));
assertArrayEquals(new int[] { 1, Statement.EXECUTE_FAILED, 1 }, result.getUpdateCounts(2));
assertArrayEquals(new int[] { Statement.EXECUTE_FAILED, 1, 1 }, result.getUpdateCounts(3));
assertArrayEquals(new int[] { 1 }, result.getUpdateCounts(4));
Optional<Integer> count11 = jdbcTemplate.queryFirst("SELECT COUNT(*) FROM sys_account", (rs, i) -> rs.getInt(1));
assertEquals(11, count11.get().intValue());
}
}

View File

@@ -0,0 +1,441 @@
package xyz.zhouxy.jdbc.test;
import static org.junit.jupiter.api.Assertions.*;
import static xyz.zhouxy.jdbc.ParamBuilder.buildParams;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.zhouxy.jdbc.ParamBuilder;
import xyz.zhouxy.jdbc.ResultHandler;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
/**
* 查询 API 测试query、queryList、queryFirst、queryBoolean。
*/
@DisplayName("SimpleJdbcTemplate 查询操作")
class QueryTest extends BaseH2Test {
private static final Logger logger = LoggerFactory.getLogger(QueryTest.class);
// ==================== query(ResultHandler) ====================
@Test
@DisplayName("query + ResultHandler统计总行数")
void testQueryWithResultHandlerCount() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Integer count = template.query(
"SELECT COUNT(*) FROM users",
new Object[0],
(ResultHandler<Integer>) rs -> {
rs.next();
return rs.getInt(1);
});
logger.info("query 返回总行数: {}", count);
assertEquals(5, count);
}
@Test
@DisplayName("query + ResultHandler聚合求和")
void testQueryWithResultHandlerAggregation() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Long totalBalance = template.query(
"SELECT SUM(balance) FROM users",
new Object[0],
(ResultHandler<Long>) rs -> {
rs.next();
return rs.getLong(1);
});
logger.info("query 聚合 balance 总和: {}", totalBalance);
assertNotNull(totalBalance);
}
@Test
@DisplayName("query无参数查询default 方法)")
void testQueryNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Integer count = template.query(
"SELECT COUNT(*) FROM users",
rs -> {
rs.next();
return rs.getInt(1);
});
assertEquals(5, count);
}
// ==================== queryList(RowMapper) ====================
@Test
@DisplayName("queryList + RowMapper查询全部用户")
void testQueryListWithRowMapper() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
UserRowMapper rowMapper = new UserRowMapper();
List<User> users = template.queryList("SELECT * FROM users ORDER BY id",
new Object[0], rowMapper);
logger.info("queryList(RowMapper) 返回 {} 条记录", users.size());
assertEquals(5, users.size());
assertEquals("alice", users.get(0).getUsername());
}
@Test
@DisplayName("queryList + RowMapper带参数条件查询")
void testQueryListWithParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
UserRowMapper rowMapper = new UserRowMapper();
List<User> users = template.queryList(
"SELECT * FROM users WHERE active = ? ORDER BY id",
new Object[]{true}, rowMapper);
logger.info("queryList 活跃用户: {} 条", users.size());
assertEquals(4, users.size());
users.forEach(u -> assertTrue(u.getActive()));
}
@Test
@DisplayName("queryList + RowMapper无参数重载")
void testQueryListRowMapperNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<User> users = template.queryList(
"SELECT * FROM users ORDER BY id",
new UserRowMapper());
assertEquals(5, users.size());
}
// ==================== queryList(Class) ====================
@Test
@DisplayName("queryList(Class):单列查询返回 String 列表")
void testQueryListWithClassString() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<String> usernames = template.queryList(
"SELECT username FROM users ORDER BY id",
String.class);
logger.info("queryList(Class) 返回用户名: {}", usernames);
assertEquals(5, usernames.size());
assertTrue(usernames.contains("alice"));
}
@Test
@DisplayName("queryList(Class):空结果集返回空列表")
void testQueryListEmptyResult() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<String> result = template.queryList(
"SELECT username FROM users WHERE id = ?",
buildParams(999), String.class);
assertNotNull(result);
assertTrue(result.isEmpty());
}
// ==================== queryList(Map) ====================
@Test
@DisplayName("queryList(Map):返回 List<Map>")
void testQueryListAsMap() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Map<String, Object>> users = template.queryList(
"SELECT id, username, email FROM users WHERE id = ?",
new Object[]{1});
logger.info("queryList(Map) 返回: {}", users);
assertEquals(1, users.size());
assertEquals("alice", users.get(0).get("username"));
}
@Test
@DisplayName("queryList(Map):无参数重载")
void testQueryListMapNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Map<String, Object>> users = template.queryList(
"SELECT id, username FROM users ORDER BY id");
assertEquals(5, users.size());
}
// ==================== queryFirst(RowMapper) ====================
@Test
@DisplayName("queryFirst + RowMapper查询第一条记录")
void testQueryFirstWithRowMapper() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
UserRowMapper rowMapper = new UserRowMapper();
Optional<User> user = template.queryFirst(
"SELECT * FROM users ORDER BY id",
rowMapper);
assertTrue(user.isPresent());
assertEquals("alice", user.get().getUsername());
}
@Test
@DisplayName("queryFirst + RowMapper空结果返回 Optional.empty()")
void testQueryFirstEmpty() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<User> user = template.queryFirst(
"SELECT * FROM users WHERE id = ?",
buildParams(999), new UserRowMapper());
assertFalse(user.isPresent());
}
@Test
@DisplayName("queryFirst + RowMappernull 参数")
void testQueryFirstWithNullParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<User> user = template.queryFirst(
"SELECT * FROM users ORDER BY id",
null, new UserRowMapper());
assertTrue(user.isPresent());
}
// ==================== queryFirst(Class) ====================
@Test
@DisplayName("queryFirst(Class):查询第一行第一列")
void testQueryFirstWithClass() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<String> username = template.queryFirst(
"SELECT username FROM users ORDER BY id",
String.class);
assertTrue(username.isPresent());
assertEquals("alice", username.get());
}
@Test
@DisplayName("queryFirst(Class):空结果返回 Optional.empty()")
void testQueryFirstClassEmpty() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<String> result = template.queryFirst(
"SELECT username FROM users WHERE id = ?",
buildParams(999), String.class);
assertFalse(result.isPresent());
}
@Test
@DisplayName("queryFirst + Class统计总行数")
void testQueryFirstWithClass_queryCount() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int count = template.queryFirst(
"SELECT COUNT(*) FROM users",
new Object[0],
Integer.class)
.orElse(0);
logger.info("query 返回总行数: {}", count);
assertEquals(5, count);
}
@Test
@DisplayName("queryFirst + Class聚合求和")
void testQueryFirstWithClass_queryAggregation() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Long totalBalance = template.queryFirst(
"SELECT SUM(balance) FROM users",
new Object[0],
Long.class)
.orElse(0L);
logger.info("query 聚合 balance 总和: {}", totalBalance);
assertNotNull(totalBalance);
}
// ==================== queryFirst(Map) ====================
@Test
@DisplayName("queryFirst(Map):返回 Optional<Map>")
void testQueryFirstAsMap() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<Map<String, Object>> user = template.queryFirst(
"SELECT id, username FROM users WHERE id = ?",
buildParams(2));
assertTrue(user.isPresent());
assertEquals("bob", user.get().get("username"));
}
@Test
@DisplayName("queryFirst(Map):返回 Optional<Map> 无参数重载")
void testQueryFirstAsMapNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<Map<String, Object>> user = template.queryFirst(
"SELECT id, username FROM users ORDER BY id");
assertTrue(user.isPresent());
assertEquals("alice", user.get().get("username"));
}
@Test
@DisplayName("queryFirst(Map):空结果返回 Optional.empty()")
void testQueryFirstMapEmpty() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<Map<String, Object>> result = template.queryFirst(
"SELECT * FROM users WHERE id = ?",
new Object[]{-1});
assertFalse(result.isPresent());
}
// ==================== queryBoolean ====================
@Test
@DisplayName("queryBoolean存在返回 true")
void testQueryBooleanTrue() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
boolean exists = template.queryBoolean(
"SELECT TRUE FROM users WHERE username = ?",
new Object[]{"alice"});
assertTrue(exists);
}
@Test
@DisplayName("queryBoolean不存在返回 false")
void testQueryBooleanFalse() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
boolean exists = template.queryBoolean(
"SELECT TRUE FROM users WHERE username = ?",
new Object[]{"nobody"});
assertFalse(exists);
}
@Test
@DisplayName("queryBoolean无参数重载")
void testQueryBooleanNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
boolean exists = template.queryBoolean(
"SELECT COUNT(*) > 0 FROM users");
assertTrue(exists);
}
@Test
@DisplayName("queryBoolean结果集为空返回 false")
void testQueryBooleanEmptyResult() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
boolean exists = template.queryBoolean(
"SELECT active FROM users WHERE id = ?",
new Object[]{999});
assertFalse(exists);
}
// ==================== 边界情况 ====================
@Test
@DisplayName("边界:查询包含 null 字段的数据")
void testQueryWithNullFields() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<User> user = template.queryFirst(
"SELECT * FROM users WHERE username = ?",
new Object[]{"charlie"}, new UserRowMapper());
assertTrue(user.isPresent());
assertNull(user.get().getEmail());
assertNull(user.get().getAge());
assertNull(user.get().getBirthDate());
assertNull(user.get().getWorkStartTime());
}
@Test
@DisplayName("边界:查询不存在的表应抛出 SQLException")
void testQueryInvalidTable() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(SQLException.class, () ->
template.queryList("SELECT * FROM non_existent_table",
new Object[0], String.class));
}
@Test
@DisplayName("边界:查询语法错误应抛出 SQLException")
void testQueryInvalidSql() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(SQLException.class, () ->
template.queryList("SELEC * FROM users",
new Object[0], String.class));
}
@Test
@DisplayName("边界HASH_MAP_MAPPER 同名列静默覆盖")
void testHashMapMapperColumnOverride() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
// 查询同名列(如自连接或别名相同的情况)
List<Map<String, Object>> results = template.queryList(
"SELECT id, id AS duplicated_id FROM users WHERE id = 1",
new Object[0]);
assertEquals(1, results.size());
// HASH_MAP_MAPPER 注:同名后者覆盖前者,所以 key 为 duplicated_id 的值存在
assertNotNull(results.get(0).get("duplicated_id"));
}
@Test
@DisplayName("边界:单行查询结果")
void testQuerySingleRow() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<User> user = template.queryFirst(
"SELECT * FROM users WHERE id = ?",
new Object[]{1}, new UserRowMapper());
assertTrue(user.isPresent());
assertEquals(Long.valueOf(1), user.get().getId());
}
@Test
@DisplayName("边界:使用 ParamBuilder.EMPTY_OBJECT_ARRAY")
void testQueryWithEmptyObjectArray() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<User> users = template.queryList(
"SELECT * FROM users ORDER BY id",
ParamBuilder.EMPTY_OBJECT_ARRAY, new UserRowMapper());
assertEquals(5, users.size());
}
}

View File

@@ -0,0 +1,256 @@
package xyz.zhouxy.jdbc.test;
import static org.junit.jupiter.api.Assertions.*;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import xyz.zhouxy.jdbc.DefaultBeanRowMapper;
import xyz.zhouxy.jdbc.RowMapper;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
/**
* RowMapper 测试DefaultBeanRowMapper、HASH_MAP_MAPPER、自定义 RowMapper。
*
* <p>验证 DefaultBeanRowMapper 的默认映射和自定义列映射,以及 RowMapper 接口的静态工厂方法。</p>
*/
@DisplayName("RowMapper 映射测试")
class RowMapperTest extends BaseH2Test {
private static final Logger logger = LoggerFactory.getLogger(RowMapperTest.class);
// ==================== DefaultBeanRowMapper 默认映射 ====================
@Test
@DisplayName("DefaultBeanRowMapper默认小驼峰→小写下划线映射")
void testDefaultBeanRowMapperDefaultMapping() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
RowMapper<User> rowMapper = RowMapper.beanRowMapper(User.class);
Optional<User> user = template.queryFirst(
"SELECT * FROM users WHERE username = ?",
new Object[]{"alice"}, rowMapper);
assertTrue(user.isPresent());
User u = user.get();
assertEquals(Long.valueOf(1), u.getId());
assertEquals("alice", u.getUsername());
assertEquals("alice@example.com", u.getEmail());
assertEquals(Integer.valueOf(28), u.getAge());
assertEquals(Long.valueOf(15000), u.getBalance());
assertTrue(u.getActive());
logger.info("DefaultBeanRowMapper 默认映射: {}", u);
}
@Test
@DisplayName("DefaultBeanRowMappernull 值字段映射为 null")
void testDefaultBeanRowMapperNullFields() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
RowMapper<User> rowMapper = RowMapper.beanRowMapper(User.class);
Optional<User> user = template.queryFirst(
"SELECT * FROM users WHERE username = ?",
new Object[]{"charlie"}, rowMapper);
assertTrue(user.isPresent());
User u = user.get();
assertNull(u.getEmail());
assertNull(u.getAge());
assertNull(u.getBirthDate());
assertNull(u.getWorkStartTime());
// 非 null 字段应有值
assertEquals("charlie", u.getUsername());
}
// ==================== DefaultBeanRowMapper 自定义列映射 ====================
@Test
@DisplayName("DefaultBeanRowMapperpropertyColMap 自定义列名映射")
void testDefaultBeanRowMapperWithPropertyColMap() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
// 自定义映射:属性名 -> 列名
Map<String, String> propertyColMap = new HashMap<>();
propertyColMap.put("username", "user_name_1"); // 使用别名
RowMapper<User> rowMapper = RowMapper.beanRowMapper(User.class, propertyColMap);
// 查询时使用别名匹配自定义映射
Optional<User> user = template.queryFirst(
"SELECT id, username AS user_name_1, email, age, balance, active FROM users WHERE username = ?",
new Object[]{"bob"}, rowMapper);
assertTrue(user.isPresent());
assertEquals("bob", user.get().getUsername());
logger.info("自定义列映射: {}", user.get());
}
@Test
@DisplayName("DefaultBeanRowMapperpropertyColMap 未覆盖的属性走默认映射")
void testDefaultBeanRowMapperPartialPropertyColMap() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
// 只映射 username其他走默认小驼峰→下划线
Map<String, String> propertyColMap = new HashMap<>();
propertyColMap.put("username", "user_label");
RowMapper<User> rowMapper = RowMapper.beanRowMapper(User.class, propertyColMap);
Optional<User> user = template.queryFirst(
"SELECT id, username AS user_label, email, age FROM users WHERE username = ?",
new Object[]{"eve"}, rowMapper);
assertTrue(user.isPresent());
assertEquals("eve", user.get().getUsername());
assertEquals("eve@example.com", user.get().getEmail());
assertEquals(Integer.valueOf(31), user.get().getAge());
}
// ==================== DefaultBeanRowMapper 边界 ====================
@Test
@DisplayName("DefaultBeanRowMapperBean 包含不匹配列时正常忽略")
void testDefaultBeanRowMapperUnmatchedColumns() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
RowMapper<User> rowMapper = RowMapper.beanRowMapper(User.class);
// 查询返回的列比 Bean 定义的少
Optional<User> user = template.queryFirst(
"SELECT id, username FROM users WHERE username = ?",
new Object[]{"alice"}, rowMapper);
assertTrue(user.isPresent());
assertEquals("alice", user.get().getUsername());
// 未映射的属性应为 null
assertNull(user.get().getEmail());
assertNull(user.get().getAge());
}
@Test
@DisplayName("DefaultBeanRowMapper无无参构造器的 Bean 抛出 SQLException")
void testDefaultBeanRowMapperNoNoArgConstructor() {
assertThrows(SQLException.class, () ->
DefaultBeanRowMapper.of(BeanWithoutNoArgConstructor.class));
}
@Test
@DisplayName("DefaultBeanRowMapperRowMapper.beanRowMapper 静态工厂方法")
void testRowMapperStaticBeanRowMapper() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
RowMapper<User> rowMapper = RowMapper.beanRowMapper(User.class);
Optional<User> user = template.queryFirst(
"SELECT * FROM users WHERE username = ?",
new Object[]{"alice"}, rowMapper);
assertTrue(user.isPresent());
assertEquals("alice", user.get().getUsername());
}
// ==================== HASH_MAP_MAPPER ====================
@Test
@DisplayName("HASH_MAP_MAPPER所有列映射为 Map")
void testHashMapMapper() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<Map<String, Object>> user = template.queryFirst(
"SELECT id, username, email, age FROM users WHERE username = ?",
new Object[]{"bob"});
assertTrue(user.isPresent());
Map<String, Object> map = user.get();
assertEquals(2L, map.get("id"));
assertEquals("bob", map.get("username"));
assertEquals("bob@example.com", map.get("email"));
assertEquals(35, map.get("age"));
logger.info("HASH_MAP_MAPPER 映射结果: {}", map);
}
@Test
@DisplayName("HASH_MAP_MAPPER空结果返回 Optional.empty()")
void testHashMapMapperEmpty() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<Map<String, Object>> user = template.queryFirst(
"SELECT * FROM users WHERE id = ?",
new Object[]{999});
assertFalse(user.isPresent());
}
@Test
@DisplayName("HASH_MAP_MAPPER查询列表返回 List<Map>")
void testHashMapMapperList() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Map<String, Object>> users = template.queryList(
"SELECT id, username FROM users ORDER BY id");
assertEquals(5, users.size());
// 第一行
assertEquals("alice", users.get(0).get("username"));
assertEquals(1L, users.get(0).get("id"));
}
// ==================== 自定义 RowMapper 对比 ====================
@Test
@DisplayName("自定义 RowMapper 与 DefaultBeanRowMapper 结果一致")
void testCustomVsDefaultRowMapper() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
UserRowMapper customMapper = new UserRowMapper();
DefaultBeanRowMapper<User> defaultMapper = DefaultBeanRowMapper.of(User.class);
Optional<User> userByCustom = template.queryFirst(
"SELECT * FROM users WHERE username = ?",
new Object[]{"alice"}, customMapper);
Optional<User> userByDefault = template.queryFirst(
"SELECT * FROM users WHERE username = ?",
new Object[]{"alice"}, defaultMapper);
assertTrue(userByCustom.isPresent());
assertTrue(userByDefault.isPresent());
assertEquals(userByCustom.get().getId(), userByDefault.get().getId());
assertEquals(userByCustom.get().getUsername(), userByDefault.get().getUsername());
assertEquals(userByCustom.get().getEmail(), userByDefault.get().getEmail());
logger.info("自定义 RowMapper: {}", userByCustom.get());
logger.info("DefaultBeanRowMapper: {}", userByDefault.get());
}
// ==================== 辅助类 ====================
/**
* 无无参构造器的 Bean用于验证 DefaultBeanRowMapper 的异常处理。
*/
public static class BeanWithoutNoArgConstructor {
private String name;
public BeanWithoutNoArgConstructor(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}

View File

@@ -1,255 +0,0 @@
/*
* Copyright 2026-present 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 static org.junit.jupiter.api.Assertions.*;
import static xyz.zhouxy.jdbc.ParamBuilder.buildParams;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Resources;
import xyz.zhouxy.jdbc.JdbcOperations;
import xyz.zhouxy.jdbc.RowMapper;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
import xyz.zhouxy.jdbc.TransactionException;
import xyz.zhouxy.plusone.commons.util.IdGenerator;
import xyz.zhouxy.plusone.commons.util.IdWorker;
class SimpleJdbcTemplateTests {
private static final Logger log = LoggerFactory.getLogger(SimpleJdbcTemplateTests.class);
private static SimpleJdbcTemplate jdbcTemplate;
final IdWorker idGenerator = IdGenerator.getSnowflakeIdGenerator(0);
@BeforeAll
static void initH2() throws IOException, SQLException {
JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE;MODE=MySQL");
dataSource.setUser("sa");
dataSource.setPassword("");
jdbcTemplate = new SimpleJdbcTemplate(dataSource);
// 建表
executeSqlFile("schema.sql");
}
@BeforeEach
void initData() throws IOException, SQLException {
// 初始化数据
executeSqlFile("data.sql");
}
static void executeSqlFile(String filePath) throws IOException, SQLException {
String[] sqls = Resources
.toString(Resources.getResource(filePath), StandardCharsets.UTF_8)
.split(";");
for (String sql : sqls) {
jdbcTemplate.update(sql);
}
}
@Test
void testQuery() throws SQLException {
Object[] ids = buildParams(5, 9, 13, 14, 17, 20, 108);
String sql = "SELECT id, username, account_status FROM sys_account WHERE id IN (?, ?, ?, ?, ?, ?, ?)";
log.info(sql);
List<Map<String, Object>> rs = jdbcTemplate.queryList(sql, ids);
for (Map<String, Object> dbRecord : rs) {
log.info("{}", dbRecord);
}
List<Map<String, Object>> expected = ImmutableList.of(
ImmutableMap.of("id", 5L, "account_status", "0", "username", "zhouxy5"),
ImmutableMap.of("id", 9L, "account_status", "0", "username", "zhouxy9"),
ImmutableMap.of("id", 13L, "account_status", "1", "username", "zhouxy13"),
ImmutableMap.of("id", 14L, "account_status", "1", "username", "zhouxy14"),
ImmutableMap.of("id", 17L, "account_status", "1", "username", "zhouxy17"),
ImmutableMap.of("id", 20L, "account_status", "2", "username", "zhouxy20")
);
assertEquals(expected, rs);
}
@Test
void testQueryExists() throws SQLException {
boolean isExists = jdbcTemplate.queryBoolean(
"SELECT EXISTS(SELECT 1 FROM sys_account WHERE id = ? LIMIT 1)",
buildParams(998));
assertFalse(isExists);
}
@Test
void testInsert() throws SQLException {
List<Map<String, Object>> keys = jdbcTemplate.updateAndReturnKeys(
"INSERT INTO sys_account(username, account_status, created_by) VALUES (?, ?, ?), (?, ?, ?)",
buildParams("zhouxy21", "2", 123L, "code22", '2', 456L),
RowMapper.HASH_MAP_MAPPER);
log.info("keys: {}", keys);
assertEquals(2, keys.size());
for (Map<String,Object> key : keys) {
assertTrue(key.containsKey("id"));
assertInstanceOf(Long.class, key.get("id"));
assertTrue(key.containsKey("create_time"));
assertInstanceOf(Date.class, key.get("create_time"));
}
List<Long> ids = jdbcTemplate.updateAndReturnKeys(
"INSERT INTO sys_account(username, account_status, created_by) VALUES (?, ?, ?), (?, ?, ?)",
buildParams("zhouxy21", "2", 123L, "code22", '2', 456L),
(rs, rowNumber) -> rs.getObject("id", Long.class));
log.info("ids: {}", ids);
assertEquals(2, ids.size());
}
@Test
void testUpdate() throws SQLException {
List<Map<String, Object>> keys = jdbcTemplate.updateAndReturnKeys(
"UPDATE sys_account SET account_status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ? AND version = ?",
buildParams("7", 886L, 20L, 88L),
RowMapper.HASH_MAP_MAPPER);
assertEquals(1, keys.size());
log.info("keys: {}", keys);
keys = jdbcTemplate.updateAndReturnKeys(
"UPDATE sys_account SET account_status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ? AND version = ?",
buildParams("-1", 886L, 20L, 88L),
RowMapper.HASH_MAP_MAPPER);
assertEquals(0, keys.size());
}
@Test
void testTransaction() throws TransactionException, SQLException {
// 抛异常,回滚
{
long id = this.idGenerator.nextId();
TransactionException e = assertThrows(TransactionException.class, () -> {
jdbcTemplate.executeTransaction((JdbcOperations jdbc) -> {
jdbc.update("INSERT INTO sys_account (id, username, created_by, create_time, account_status) VALUES (?, ?, ?, ?, ?)",
buildParams(id, "testTransaction1", 100, LocalDateTime.now(), "55"));
throw new NullPointerException();
});
});
assertEquals(NullPointerException.class, e.getCause().getClass());
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertFalse(first.isPresent());
}
// 没有异常,提交事务
{
long id = this.idGenerator.nextId();
jdbcTemplate.executeTransaction(jdbc -> {
jdbc.update("INSERT INTO sys_account (id, username, created_by, create_time, account_status) VALUES (?, ?, ?, ?, ?)",
buildParams(id, "testTransaction2", 101, LocalDateTime.now(), "55"));
});
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(first.isPresent());
}
// 抛异常,回滚
{
long id = this.idGenerator.nextId();
TransactionException e = assertThrows(TransactionException.class, () -> {
jdbcTemplate.commitIfTrue(jdbc -> {
jdbc.update("INSERT INTO sys_account (id, username, created_by, create_time, account_status) VALUES (?, ?, ?, ?, ?)",
buildParams(id, "testTransaction3", 102, LocalDateTime.now(), "55"));
throw new NullPointerException();
});
});
assertEquals(NullPointerException.class, e.getCause().getClass());
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertFalse(first.isPresent());
}
// 返回 false回滚
{
long id = this.idGenerator.nextId();
jdbcTemplate.commitIfTrue(jdbc -> {
jdbc.update("INSERT INTO sys_account (id, username, created_by, create_time, account_status) VALUES (?, ?, ?, ?, ?)",
buildParams(id, "testTransaction4", 103, LocalDateTime.now(), "55"));
return false;
});
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertFalse(first.isPresent());
}
// 返回 true提交事务
{
long id = this.idGenerator.nextId();
jdbcTemplate.commitIfTrue(jdbc -> {
jdbc.update("INSERT INTO sys_account (id, username, created_by, create_time, account_status) VALUES (?, ?, ?, ?, ?)",
buildParams(id, "testTransaction5", 104, LocalDateTime.now(), "55"));
return true;
});
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(first.isPresent());
}
}
@Test
void testBean() throws Exception {
Optional<AccountPO> t = jdbcTemplate.queryFirst(
"SELECT * FROM sys_account WHERE id = ?",
buildParams(18L),
RowMapper.beanRowMapper(AccountPO.class));
assertTrue(t.isPresent());
assertEquals(
new AccountPO(18L, "zhouxy18", "1",
LocalDateTime.of(2000, 1, 1, 0, 0), 118L,
LocalDateTime.of(2000, 1, 29, 0, 0), null, 7L),
t.get());
log.info("{}", t);
}
@Test
void testQueryBoolean() throws SQLException {
// 建议写法
assertTrue(jdbcTemplate.queryBoolean("SELECT EXISTS(SELECT 1 FROM sys_account WHERE id = 10)"));
assertFalse(jdbcTemplate.queryBoolean("SELECT EXISTS(SELECT 1 FROM sys_account WHERE id = 999)"));
// 不建议写法
assertTrue(jdbcTemplate.queryBoolean("SELECT 1 FROM sys_account WHERE id = 10"));
assertFalse(jdbcTemplate.queryBoolean("SELECT 1 FROM sys_account WHERE id = 999"));
}
}

View File

@@ -0,0 +1,233 @@
package xyz.zhouxy.jdbc.test;
import static org.junit.jupiter.api.Assertions.*;
import static xyz.zhouxy.jdbc.ParamBuilder.buildParams;
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 xyz.zhouxy.jdbc.JdbcOperations;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
import xyz.zhouxy.jdbc.TransactionException;
/**
* 事务 API 测试executeTransaction、commitIfTrue。
*/
@DisplayName("SimpleJdbcTemplate 事务操作")
class TransactionTest extends BaseH2Test {
private static final Logger logger = LoggerFactory.getLogger(TransactionTest.class);
@BeforeEach
void setUp() throws Exception {
resetDatabase();
}
// ==================== executeTransaction 正常提交 ====================
@Test
@DisplayName("executeTransaction正常提交数据持久化")
void testExecuteTransactionCommit() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.executeTransaction((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email, age, balance, active) VALUES (?, ?, ?, ?, ?)",
buildParams("txUser1", "tx1@test.com", 25, 1000L, true));
ops.update("UPDATE users SET balance = ? WHERE username = ?",
buildParams(99999L, "alice"));
});
// 验证事务已提交
Optional<String> newUser = template.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("txUser1"), String.class);
assertTrue(newUser.isPresent());
Optional<Long> balance = template.queryFirst(
"SELECT balance FROM users WHERE username = ?",
buildParams("alice"), Long.class);
assertEquals(Long.valueOf(99999L), balance.orElse(null));
logger.info("事务提交验证通过");
}
// ==================== executeTransaction 异常回滚 ====================
@Test
@DisplayName("executeTransaction异常回滚数据恢复原状")
void testExecuteTransactionRollback() throws Exception {
SimpleJdbcTemplate template = createTemplate();
// 记录原始 balance
Optional<Long> originalBalance = template.queryFirst(
"SELECT balance FROM users WHERE username = ?",
buildParams("alice"), Long.class);
TransactionException ex = assertThrows(TransactionException.class, () ->
template.executeTransaction((JdbcOperations ops) -> {
ops.update("UPDATE users SET balance = ? WHERE username = ?",
buildParams(0L, "alice"));
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("txUser2", "tx2@test.com"));
// 故意抛出异常触发回滚
throw new RuntimeException("模拟业务异常");
}));
logger.info("捕获到 TransactionException: {}", ex.getMessage());
assertNotNull(ex.getCause());
assertEquals(RuntimeException.class, ex.getCause().getClass());
assertEquals("模拟业务异常", ex.getCause().getMessage());
// 验证更新已回滚
Optional<Long> currentBalance = template.queryFirst(
"SELECT balance FROM users WHERE username = ?",
buildParams("alice"), Long.class);
assertEquals(originalBalance.orElse(null), currentBalance.orElse(null));
// 验证插入已回滚
Optional<String> rolledBackUser = template.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("txUser2"), String.class);
assertFalse(rolledBackUser.isPresent());
logger.info("事务回滚验证通过");
}
@Test
@DisplayName("executeTransactionSQL 异常触发回滚")
void testExecuteTransactionSqlExceptionRollback() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(TransactionException.class, () ->
template.executeTransaction((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username) VALUES (?)",
buildParams("validUser"));
// 错误的 SQL
ops.update("INVALID SQL STATEMENT");
}));
// 验证插入已回滚
assertDoesNotThrow(() -> {
Optional<String> user = template.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("validUser"), String.class);
assertFalse(user.isPresent());
});
}
// ==================== commitIfTrue 返回 true 提交 ====================
@Test
@DisplayName("commitIfTrue返回 true 提交事务")
void testCommitIfTrueCommit() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.commitIfTrue((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("cftUser", "cft@test.com"));
return true;
});
// 验证数据已持久化
Optional<String> user = template.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("cftUser"), String.class);
assertTrue(user.isPresent());
logger.info("commitIfTrue(true) 提交验证通过");
}
@Test
@DisplayName("commitIfTrue返回 false 回滚事务")
void testCommitIfFalseRollback() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.commitIfTrue((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("cffUser", "cff@test.com"));
return false;
});
// 验证数据已回滚
Optional<String> user = template.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("cffUser"), String.class);
assertFalse(user.isPresent());
logger.info("commitIfTrue(false) 回滚验证通过");
}
@Test
@DisplayName("commitIfTrue异常触发回滚")
void testCommitIfTrueExceptionRollback() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(TransactionException.class, () ->
template.commitIfTrue((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username) VALUES (?)",
buildParams("exUser"));
throw new IllegalStateException("条件不满足");
}));
// 验证回滚
assertDoesNotThrow(() -> {
Optional<String> user = template.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("exUser"), String.class);
assertFalse(user.isPresent());
});
}
// ==================== 事务内查询可见性 ====================
@Test
@DisplayName("executeTransaction事务内可查询到未提交的数据")
void testTransactionVisibility() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.executeTransaction((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("visible", "visible@test.com"));
// 在同一事务内可以查询到刚插入的数据
Optional<String> user = ops.queryFirst(
"SELECT username FROM users WHERE username = ?",
buildParams("visible"), String.class);
assertTrue(user.isPresent());
logger.info("事务内查询可见性验证通过");
});
}
// ==================== 边界情况 ====================
@Test
@DisplayName("executeTransaction空操作无异常正常提交")
void testExecuteTransactionEmpty() throws Exception {
SimpleJdbcTemplate template = createTemplate();
// 空操作不应抛异常
assertDoesNotThrow(() ->
template.executeTransaction(ops -> { /* no-op */ }));
// 数据应保持不变
int count = template.query("SELECT COUNT(*) FROM users",
rs -> { rs.next(); return rs.getInt(1); });
assertEquals(5, count);
}
@Test
@DisplayName("executeTransactionnull 操作抛异常")
@SuppressWarnings("null")
void testExecuteTransactionNullOps() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(Exception.class, () ->
template.executeTransaction(null));
}
}

View File

@@ -0,0 +1,219 @@
package xyz.zhouxy.jdbc.test;
import static org.junit.jupiter.api.Assertions.*;
import static xyz.zhouxy.jdbc.ParamBuilder.buildParams;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
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 xyz.zhouxy.jdbc.RowMapper;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
/**
* 更新 API 测试update、updateAndReturnKeys。
*/
@DisplayName("SimpleJdbcTemplate 更新操作")
class UpdateTest extends BaseH2Test {
private static final Logger logger = LoggerFactory.getLogger(UpdateTest.class);
@BeforeEach
void setUp() throws Exception {
resetDatabase();
}
// ==================== update ====================
@Test
@DisplayName("updateINSERT 操作返回影响行数 1")
void testUpdateInsert() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
User user = new User(
"frank",
"frank@example.com",
25, 10000L,
true,
LocalDateTime.now(),
LocalDate.now(),
LocalTime.now());
String sql = "INSERT INTO users (" //
+ " username," //
+ " email," //
+ " age," //
+ " balance," //
+ " active," //
+ " created_at," //
+ " birth_date," //
+ " work_start_time" //
+ ")" //
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
int rows = template.update(
sql,
buildParams(
user.getUsername(),
user.getEmail(),
user.getAge(),
user.getBalance(),
user.getActive(),
user.getCreatedAt(),
user.getBirthDate(),
user.getWorkStartTime()
));
logger.info("INSERT 影响行数: {}", rows);
assertEquals(1, rows);
}
@Test
@DisplayName("updateUPDATE 操作返回影响行数")
void testUpdateModify() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int rows = template.update(
"UPDATE users SET email = ? WHERE username = ?",
buildParams("newalice@example.com", "alice"));
logger.info("UPDATE 影响行数: {}", rows);
assertEquals(1, rows);
// 验证数据确实被更新
int count = template.update(
"UPDATE users SET email = ? WHERE username = ? AND email = ?",
buildParams("alice@example.com", "alice", "newalice@example.com"));
assertEquals(1, count);
}
@Test
@DisplayName("updateDELETE 操作返回影响行数")
void testUpdateDelete() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int rows = template.update(
"DELETE FROM users WHERE username = ?",
buildParams("charlie"));
logger.info("DELETE 影响行数: {}", rows);
assertEquals(1, rows);
}
@Test
@DisplayName("update影响 0 行")
void testUpdateZeroRows() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int rows = template.update(
"UPDATE users SET email = ? WHERE username = ?",
buildParams("x@x.com", "nobody"));
logger.info("影响 0 行: {}", rows);
assertEquals(0, rows);
}
@Test
@DisplayName("updateDELETE 全表")
void testUpdateDeleteAll() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int rows = template.update("DELETE FROM users");
logger.info("DELETE 全表影响行数: {}", rows);
assertEquals(5, rows);
// 验证表为空
int count = template.query("SELECT COUNT(*) FROM users",
rs -> { rs.next(); return rs.getInt(1); });
assertEquals(0, count);
}
@Test
@DisplayName("updatenull 参数数组")
void testUpdateWithNullParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int rows = template.update(
"DELETE FROM users WHERE username = ?",
new Object[]{ null });
// 因为 DELETE ? 中参数 null 不会匹配任何行
assertEquals(0, rows);
}
@Test
@DisplayName("update无参数重载")
void testUpdateNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int rows = template.update(
"UPDATE users SET balance = 9999 WHERE username = 'alice'");
assertEquals(1, rows);
}
@Test
@DisplayName("update语法错误抛出 SQLException")
void testUpdateInvalidSql() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(SQLException.class, () ->
template.update("UPDAT users SET x = 1"));
}
// ==================== updateAndReturnKeys ====================
@Test
@DisplayName("updateAndReturnKeysINSERT 返回自增主键")
void testUpdateAndReturnKeys() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Long> keys = template.updateAndReturnKeys(
"INSERT INTO users (username, email, age, balance, active) VALUES (?, ?, ?, ?, ?)",
new Object[]{"grace", "grace@example.com", 29, 12000L, true},
(rs, rowNumber) -> rs.getLong(1));
logger.info("updateAndReturnKeys 返回: {}", keys);
assertEquals(1, keys.size());
assertTrue(keys.get(0) > 0);
}
@Test
@DisplayName("updateAndReturnKeys批量插入返回所有自增主键")
void testUpdateAndReturnKeysMultiple() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
// 使用多条 INSERTH2 支持)
List<Long> keys = template.updateAndReturnKeys(
"INSERT INTO users (username, email, age, balance, active) VALUES " +
"(?, ?, ?, ?, ?), (?, ?, ?, ?, ?)",
new Object[]{
"henry", "henry@example.com", 33, 18000L, true,
"iris", "iris@example.com", 27, 9000L, false
},
(RowMapper<Long>) (rs, rowNumber) -> rs.getLong(1));
logger.info("updateAndReturnKeys 返回 {} 个主键", keys.size());
assertEquals(2, keys.size());
}
@Test
@DisplayName("updateAndReturnKeys无参数重载")
void testUpdateAndReturnKeysNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<Long> keys = template.updateAndReturnKeys(
"INSERT INTO users (username) VALUES ('jack')",
(rs, rowNumber) -> rs.getLong(1));
assertEquals(1, keys.size());
assertTrue(keys.get(0) > 0);
}
}

View File

@@ -0,0 +1,144 @@
package xyz.zhouxy.jdbc.test;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Objects;
/**
* 测试用的 Java Bean用于验证 RowMapper 映射。
*
* <p>注意:属性全部使用引用类型,以匹配 DefaultBeanRowMapper 的设计约束。</p>
*/
public class User {
private Long id;
private String username;
private String email;
private Integer age;
private Long balance;
private Boolean active;
private LocalDateTime createdAt;
private LocalDate birthDate;
private LocalTime workStartTime;
public User() {
}
public User(String username, String email, Integer age, Long balance, Boolean active) {
this.username = username;
this.email = email;
this.age = age;
this.balance = balance;
this.active = active;
}
public User(String username, String email, Integer age, Long balance, Boolean active,
LocalDateTime createdAt, LocalDate birthDate, LocalTime workStartTime) {
this.username = username;
this.email = email;
this.age = age;
this.balance = balance;
this.active = active;
this.createdAt = createdAt;
this.birthDate = birthDate;
this.workStartTime = workStartTime;
}
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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Long getBalance() {
return balance;
}
public void setBalance(Long balance) {
this.balance = balance;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public LocalTime getWorkStartTime() {
return workStartTime;
}
public void setWorkStartTime(LocalTime workStartTime) {
this.workStartTime = workStartTime;
}
@Override
public String toString() {
return "User{id=" + id + ", username='" + username + "', email='" + email
+ "', age=" + age + ", balance=" + balance + ", active=" + active + '}';
}
@Override
public int hashCode() {
return Objects.hash(id, username, email, age, balance, active, createdAt, birthDate, workStartTime);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof User))
return false;
User other = (User) obj;
return Objects.equals(id, other.id) && Objects.equals(username, other.username)
&& Objects.equals(email, other.email) && Objects.equals(age, other.age)
&& Objects.equals(balance, other.balance) && Objects.equals(active, other.active)
&& Objects.equals(createdAt, other.createdAt) && Objects.equals(birthDate, other.birthDate)
&& Objects.equals(workStartTime, other.workStartTime);
}
}

View File

@@ -0,0 +1,29 @@
package xyz.zhouxy.jdbc.test;
import java.sql.ResultSet;
import java.sql.SQLException;
import xyz.zhouxy.jdbc.RowMapper;
/**
* 自定义 User RowMapper直接操作 ResultSet 进行映射。
*
* <p>相较于 DefaultBeanRowMapper自定义 RowMapper 避免了反射开销,性能更优。</p>
*/
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNumber) throws SQLException {
User user = new User();
user.setId(rs.getObject("id", Long.class));
user.setUsername(rs.getString("username"));
user.setEmail(rs.getString("email"));
user.setAge(rs.getObject("age", Integer.class));
user.setBalance(rs.getObject("balance", Long.class));
user.setActive(rs.getObject("active", Boolean.class));
user.setCreatedAt(rs.getObject("created_at", java.time.LocalDateTime.class));
user.setBirthDate(rs.getObject("birth_date", java.time.LocalDate.class));
user.setWorkStartTime(rs.getObject("work_start_time", java.time.LocalTime.class));
return user;
}
}

View File

@@ -1,23 +0,0 @@
truncate table sys_account;
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (2, 'zhouxy2', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (3, 'zhouxy3', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (4, 'zhouxy4', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (5, 'zhouxy5', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (6, 'zhouxy6', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (7, 'zhouxy7', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (8, 'zhouxy8', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by) VALUES (9, 'zhouxy9', '0', 108);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (10, 'zhouxy10', '1', 118, '2000-01-01', '2000-01-29', 31);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (11, 'zhouxy11', '1', 118, '2000-01-01', '2000-01-29', 28);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (12, 'zhouxy12', '1', 118, '2000-01-01', '2000-01-29', 25);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (13, 'zhouxy13', '1', 118, '2000-01-01', '2000-01-29', 22);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (14, 'zhouxy14', '1', 118, '2000-01-01', '2000-01-29', 19);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (15, 'zhouxy15', '1', 118, '2000-01-01', '2000-01-29', 16);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (16, 'zhouxy16', '1', 118, '2000-01-01', '2000-01-29', 13);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (17, 'zhouxy17', '1', 118, '2000-01-01', '2000-01-29', 10);
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (18, 'zhouxy18', '1', 118, '2000-01-01', '2000-01-29', 7 );
INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, `version`) VALUES (19, 'zhouxy19', '1', 118, '2000-01-01', '2000-01-29', 0 );
INSERT INTO sys_account(id, username, account_status, created_by, create_time, updated_by, update_time, `version`) VALUES (20, 'zhouxy20', '2', 118, '2008-08-08 20:08:00', 31, now(), 88);

View File

@@ -0,0 +1,30 @@
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age INT,
balance BIGINT,
active BOOLEAN,
created_at TIMESTAMP,
birth_date DATE,
work_start_time TIME
);
ALTER TABLE users ADD CONSTRAINT uk_username UNIQUE (username);
INSERT INTO users (username, email, age, balance, active, created_at, birth_date, work_start_time)
VALUES ('alice', 'alice@example.com', 28, 15000, TRUE, '2024-01-15 09:30:00', '1996-05-20', '08:00:00');
INSERT INTO users (username, email, age, balance, active, created_at, birth_date, work_start_time)
VALUES ('bob', 'bob@example.com', 35, 25000, TRUE, '2023-11-01 14:00:00', '1989-03-12', '09:30:00');
INSERT INTO users (username, email, age, balance, active, created_at, birth_date, work_start_time)
VALUES ('charlie', NULL, NULL, 5000, FALSE, '2025-03-10 11:15:00', NULL, NULL);
INSERT INTO users (username, email, age, balance, active, created_at, birth_date, work_start_time)
VALUES ('diana', 'diana@example.com', 42, NULL, TRUE, NULL, '1982-11-08', '07:45:00');
INSERT INTO users (username, email, age, balance, active, created_at, birth_date, work_start_time)
VALUES ('eve', 'eve@example.com', 31, 8000, TRUE, '2024-07-22 16:45:00', '1993-01-30', '10:00:00');

View File

@@ -1,12 +0,0 @@
DROP TABLE IF EXISTS sys_account;
CREATE TABLE sys_account (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
, `username` VARCHAR(255) NOT NULL
, `account_status` VARCHAR(2) NOT NULL
, `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
, `created_by` BIGINT NOT NULL
, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
, `updated_by` BIGINT DEFAULT NULL
, `version` BIGINT NOT NULL DEFAULT 0
);