test: 重新编写测试用例完成必要单元测试
This commit is contained in:
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
91
src/test/java/xyz/zhouxy/jdbc/test/BaseH2Test.java
Normal file
91
src/test/java/xyz/zhouxy/jdbc/test/BaseH2Test.java
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
292
src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
Normal file
292
src/test/java/xyz/zhouxy/jdbc/test/BatchUpdateTest.java
Normal 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("batchUpdate:batchSize 不整除")
|
||||
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("batchUpdate:null 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("batchUpdate:quietly=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("batchUpdate:quietly=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("batchUpdate:batchSize < 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
|
||||
// ================================
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
441
src/test/java/xyz/zhouxy/jdbc/test/QueryTest.java
Normal file
441
src/test/java/xyz/zhouxy/jdbc/test/QueryTest.java
Normal 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 + RowMapper:null 参数")
|
||||
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());
|
||||
}
|
||||
}
|
||||
256
src/test/java/xyz/zhouxy/jdbc/test/RowMapperTest.java
Normal file
256
src/test/java/xyz/zhouxy/jdbc/test/RowMapperTest.java
Normal 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("DefaultBeanRowMapper:null 值字段映射为 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("DefaultBeanRowMapper:propertyColMap 自定义列名映射")
|
||||
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("DefaultBeanRowMapper:propertyColMap 未覆盖的属性走默认映射")
|
||||
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("DefaultBeanRowMapper:Bean 包含不匹配列时正常忽略")
|
||||
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("DefaultBeanRowMapper:RowMapper.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
233
src/test/java/xyz/zhouxy/jdbc/test/TransactionTest.java
Normal file
233
src/test/java/xyz/zhouxy/jdbc/test/TransactionTest.java
Normal 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("executeTransaction:SQL 异常触发回滚")
|
||||
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("executeTransaction:null 操作抛异常")
|
||||
@SuppressWarnings("null")
|
||||
void testExecuteTransactionNullOps() {
|
||||
SimpleJdbcTemplate template = createTemplate();
|
||||
|
||||
assertThrows(Exception.class, () ->
|
||||
template.executeTransaction(null));
|
||||
}
|
||||
}
|
||||
219
src/test/java/xyz/zhouxy/jdbc/test/UpdateTest.java
Normal file
219
src/test/java/xyz/zhouxy/jdbc/test/UpdateTest.java
Normal 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("update:INSERT 操作返回影响行数 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("update:UPDATE 操作返回影响行数")
|
||||
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("update:DELETE 操作返回影响行数")
|
||||
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("update:DELETE 全表")
|
||||
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("update:null 参数数组")
|
||||
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("updateAndReturnKeys:INSERT 返回自增主键")
|
||||
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();
|
||||
|
||||
// 使用多条 INSERT(H2 支持)
|
||||
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);
|
||||
}
|
||||
}
|
||||
144
src/test/java/xyz/zhouxy/jdbc/test/User.java
Normal file
144
src/test/java/xyz/zhouxy/jdbc/test/User.java
Normal 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);
|
||||
}
|
||||
}
|
||||
29
src/test/java/xyz/zhouxy/jdbc/test/UserRowMapper.java
Normal file
29
src/test/java/xyz/zhouxy/jdbc/test/UserRowMapper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
30
src/test/resources/init_tables.sql
Normal file
30
src/test/resources/init_tables.sql
Normal 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');
|
||||
@@ -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
|
||||
);
|
||||
Reference in New Issue
Block a user