6 Commits
1.0.0 ... dev

Author SHA1 Message Date
7de2a7eec1 refactor: 将单列查询 Class 重载标记为过时,新增语义明确的方法
- 新增 queryValues(sql, args, Class) 替代 queryList(sql, args, Class)
- 新增 queryValue(sql, args, Class) 替代 queryFirst(sql, args, Class)
- 新增 queryValueOrDefault(sql, args, Class, defaultVal) 聚合查询便捷方法
- 旧方法标记 @Deprecated,委托至新方法,后续版本移除
- 更新 README 方法列表和示例代码
- 补充 queryValueOrDefault 单元测试 6 个
2026-06-18 02:58:43 +08:00
76bfff2d90 docs: 更新设计考量章节标题并补充内容 2026-06-18 02:52:37 +08:00
6f2882cd08 refactor!: 将工厂方法异常类型从 SQLException 改为 IllegalStateException
DefaultBeanRowMapper.of() 及 RowMapper.beanRowMapper() 在反射异常时
不再抛出受检异常 SQLException,改为抛出非受检异常 IllegalStateException。
同时优化相关 Javadoc 文档并同步更新测试断言。

BREAKING CHANGE: beanRowMapper() 和 of() 方法不再抛出 SQLException。
调用方 catch (SQLException e) 将静默失效,建议移除相关 catch 块或
改为 catch (IllegalStateException)。
2026-06-18 02:41:10 +08:00
5100fa5f1d docs: 添加示例代码 2026-06-17 23:27:43 +08:00
9abbef8b86 prepare 1.1.0 2026-06-17 21:24:03 +08:00
1f566ca488 release/1.0.0 [plusone/simple-jdbc#10 (Gitea)] 2026-06-17 21:16:38 +08:00
13 changed files with 295 additions and 90 deletions

View File

@@ -1,5 +1,36 @@
# Changelog
## [1.1.0] - Unreleased
### ⚠️ 破坏性变更
- **`DefaultBeanRowMapper.of()` 不再抛出 `SQLException`**:工厂方法在反射异常时改为抛出非受检异常 `IllegalStateException`。调用方如果 `catch (SQLException e)` 包裹 `of()` 调用,该捕获将失效,需移除相关 `catch` 块或改为捕获 `IllegalStateException`
### 新增
- `queryValueOrDefault(sql, params, Class<T>, T defaultValue)`:查询单行单列,结果为空时返回指定默认值
- `queryValueOrDefault(sql, Class<T>, T defaultValue)`:无参数重载
- 补充 `QueryTest``queryValueOrDefault` 单元测试 6 个
### 重构
**将单列查询方法标记为过时,消除 Class 参数重载歧义**
- `queryList(sql, params, Class<T>)` → 已过时,请使用 `queryValues(sql, params, Class<T>)`
语义明确为"多行单列 → 值列表",不与整行 `RowMapper` 重载混淆
- `queryFirst(sql, params, Class<T>)` → 已过时,请使用 `queryValue(sql, params, Class<T>)`
语义明确为"单行单列 → 单值",不与整行 `RowMapper` 重载混淆
- 同时将对应的无参数重载标记为过时:
- `queryList(sql, Class<T>)` → 请使用 `queryValues(sql, Class<T>)`
- `queryFirst(sql, Class<T>)` → 请使用 `queryValue(sql, Class<T>)`
- 旧方法将在后续版本中移除
### 文档
- 优化 `DefaultBeanRowMapper` 类注释,明确性能限制和使用建议
---
## [1.0.0] - 2026-06-17
### 重构

View File

@@ -16,13 +16,15 @@
---
## 2. 设计考量与边界
## 2. 设计考量与取舍
`Simple JDBC` 不追求大而全,在功能设计上保持克制。以下是本项目的一些设计考量:
`Simple JDBC` 不追求大而全,在功能设计上保持克制。以下是本项目的一些设计考量与取舍
- 明确**不支持存储过程**:专注于基础 CRUD。
- **不提供分页 API**:不同数据库的分页方言差异巨大,且实际业务中的分页查询往往不是简单的 `LIMIT OFFSET`(存在如游标分页、延迟关联等深度优化空间)。为了保持轻量与灵活,分页 SQL 交由开发者根据具体数据库与业务场景自行编写。
- **不提供缓存支持**:数据缓存应当被视为一个独立的关注点,通常交由更高层的抽象模块来处理。
- **不支持直接传入 Connection**为确保数据库连接资源的规范获取与安全释放Simple JDBC 舍弃了一定的连接管理灵活性,不支持直接传入 Connection而是需要通过 `DataSource` 来获取连接(详见[8. 连接池集成](#8-连接池集成))。常规操作由 `SimpleJdbcTemplate` 自动完成连接的获取与归还;事务场景下,由 `TransactionTemplate` 实现连接绑定,保障同一事务内所有操作的连接一致性。
- **有限但灵活的结果映射**为应对多样化的数据处理需求Simple JDBC 提供了 `ResultHandler``RowMapper` 两层抽象机制。前者负责整体结果集的统筹处理,后者专注于单行数据的解析与映射(详见 [4.2 结果映射策略](#42-结果映射策略))。两者均设计为函数式接口,支持通过 Lambda 表达式快速定制映射逻辑。
---
@@ -76,7 +78,7 @@ List<Account> accounts = jdbcTemplate.query(
);
// 查询列表(单列)
List<String> usernames = jdbcTemplate.queryList(
List<String> usernames = jdbcTemplate.queryValues(
"SELECT username FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
String.class
@@ -114,6 +116,20 @@ Optional<Account> account = jdbcTemplate.queryFirst(
)
);
// 查询单个值(所有 ResultSet.getObject 支持的类型)
Long count = jdbcTemplate.queryValue(
"SELECT COUNT(*) FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
Long.class
).orElse(0L);
// 或者
Long count = jdbcTemplate.queryValueOrDefault(
"SELECT COUNT(*) FROM account WHERE deleted = 0 AND username LIKE ? AND org_no = ?",
buildParams("admin%", "0000"),
Long.class,
0L
);
// 查询 Boolean 值
boolean exists = jdbcTemplate.queryBoolean(
"SELECT EXISTS(SELECT 1 FROM account WHERE deleted = 0 AND id = ?)",
@@ -236,14 +252,15 @@ jdbcTemplate.transaction().commitIfTrue(jdbc -> {
| :--- | :--- |
| `query(sql, params, resultHandler)` | 最基础的查询,通过 `ResultHandler` 自定义完整的映射逻辑。 |
| `queryList(sql, params, rowMapper)` | 查询列表,通过 `RowMapper` 逐行映射。 |
| `queryList(sql, params, Class)` | 单列查询列表,每行提取第一列并转换为指定类型。 |
| `queryList(sql, params)` | 查询列表,每行自动转换为 `Map<String, Object>`。 |
| `queryFirst(sql, params, rowMapper)` | 查询第一行,通过 `RowMapper` 映射,返回 `Optional<T>`。 |
| `queryFirst(sql, params, Class)` | 查询第一行第一列,返回 `Optional<T>`。 |
| `queryFirst(sql, params)` | 查询第一行,返回 `Optional<Map<String, Object>>`。 |
| `queryValues(sql, params, Class)` | 单列查询列表,每行提取第一列并转换为指定类型。 |
| `queryValue(sql, params, Class)` | 查询第一行第一列,返回 `Optional<T>`。 |
| `queryValueOrDefault(sql, params, Class, default)` | 查询第一行第一列,结果为空时返回默认值。适用于 COUNT/SUM 等聚合查询。 |
| `queryBoolean(sql, params)` | 查询第一行第一列并转换为 `boolean`,若结果为空则返回 `false`。 |
*💡 提示:以上方法均有省略 `params` 的重载(如 `queryList(sql, rowMapper)`),适用于不含占位符的 SQL 语句。*
*💡 提示:以上方法均有省略 `params` 的重载(如 `queryList(sql, rowMapper)`),适用于不含占位符的 SQL 语句。`queryValues`、`queryValue`、`queryValueOrDefault` 同理。*
### 4.2 结果映射策略

View File

@@ -5,7 +5,7 @@
<groupId>xyz.zhouxy.jdbc</groupId>
<artifactId>simple-jdbc</artifactId>
<version>1.0.0</version>
<version>1.1.0-SNAPSHOT</version>
<name>Simple JDBC</name>
<description>对 JDBC 的简单封装。</description>

View File

@@ -41,16 +41,17 @@ import xyz.zhouxy.jdbc.util.NamingTools;
*
* <p>
* 将 {@link ResultSet} 转换为 Java Bean 的 {@link RowMapper} 的基础实现。
* <i>仅在对性能不敏感的场景下使用。</i>
* <p>
* <i>性能和规则上的限制都比较大,仅在对性能不敏感的场景下便捷使用,
* 一般情况下你应该自定义 {@link RowMapper}。</i>
*
* <p>
* 说明:
* <ul>
* <li>使用反射获取类型信息,也是使用反射调用无参构造器和 {@code setter} 方法。</li>
* <li>{@code propertyColMap} 未指定的列名和属性名的映射时,默认 JavaBean 的属性名为小驼峰,列名为小写蛇形命名。</li>
* <li>支持自定义列名和属性名的映射,当未指定 {@code propertyColMap} 时,默认 JavaBean 的属性名为小驼峰,列名为小写蛇形命名。</li>
* <li>使用 {@link ResultSet#getObject(String, Class)} 从 {@link ResultSet} 中获取属性值。</li>
* <li>JavaBean 属性仅支持引用类型,不支持基本数据类型。</li>
* <li><b>实际使用中更建议针对目标类型自定义 {@link RowMapper}。</b></li>
* </ul>
*
* @author ZhouXY
@@ -86,9 +87,9 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
* @param <T> Bean 类型
* @param beanType Bean 类型
* @return DefaultBeanRowMapper 对象
* @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
* @throws IllegalStateException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
*/
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType) throws SQLException {
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType) {
return of(beanType, null);
}
@@ -99,10 +100,10 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
* @param beanType Bean 类型
* @param propertyColMap Bean 字段与列名的映射关系。key 是字段value 是列名。
* @return {@code DefaultBeanRowMapper} 对象
* @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
* @throws IllegalStateException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出
*/
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType, @Nullable Map<String, String> propertyColMap)
throws SQLException {
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType,
@Nullable Map<String, String> propertyColMap) {
try {
// 获取无参构造器
Constructor<T> constructor = beanType.getDeclaredConstructor();
@@ -113,10 +114,10 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
return new DefaultBeanRowMapper<>(beanType, constructor, colPropertyMap, colSetterMap);
}
catch (IntrospectionException e) {
throw new SQLException("There is an exception occurs during introspection.", e);
throw new IllegalStateException("There is an exception occurs during introspection.", e);
}
catch (NoSuchMethodException e) {
throw new SQLException("Could not find a no-args constructor in " + beanType.getName(), e);
throw new IllegalStateException("Could not find a no-args constructor in " + beanType.getName(), e);
}
}
@@ -141,7 +142,7 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
return newInstance;
}
catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new SQLException("Could not map row to " + beanType.getName(), e);
throw new IllegalStateException("Could not map row to " + beanType.getName(), e);
}
}

View File

@@ -93,14 +93,14 @@ class JdbcOperationSupport {
}
/**
* 执行查询,返回结果映射为指定类型。当结果为单列时使用
* 执行查询,只取结果集每行第一列的值,映射为指定类型并返回列表
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param clazz 将结果映射为指定的类型
*/
static <T> List<T> queryList(Connection conn, String sql, Object[] params, Class<T> clazz)
static <T> List<T> queryValues(Connection conn, String sql, Object[] params, Class<T> clazz)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
@@ -129,14 +129,15 @@ class JdbcOperationSupport {
}
/**
* 查询第一行第一列,并转换为指定类型
* 执行查询,只取结果集第一行第一列的值,映射为指定类型并返回
*
* @param conn 数据库连接
* @param <T> 目标类型
* @param sql SQL
* @param params 参数
* @param clazz 目标类型
*/
static <T> T queryFirst(Connection conn, String sql, Object[] params, Class<T> clazz)
static <T> T queryValue(Connection conn, String sql, Object[] params, Class<T> clazz)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);

View File

@@ -87,17 +87,18 @@ public interface JdbcOperations {
throws SQLException;
/**
* 执行查询,返回结果映射为指定类型。当结果为单列时使用
* 执行查询,只取结果集每行第一列的值,映射为指定类型并返回列表
* 适用于 {@code SELECT single_column FROM ...} 单列查询场景。
*
* @param <T> 目标类型
* @param <T> 目标类型(对应结果集第一列的 Java 类型)
* @param sql SQL
* @param params 参数
* @param clazz 目标类型
*
* @return 映射结果。如果查询结果为空,则返回空列表
* @return 每一行第一列的值列表。如果查询结果为空,则返回空列表
* @throws SQLException SQL异常
*/
<T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
<T> List<T> queryValues(String sql, Object[] params, Class<T> clazz)
throws SQLException;
/**
@@ -128,18 +129,39 @@ public interface JdbcOperations {
}
/**
* 执行查询,返回结果映射为指定类型。当结果为单列时使用
* 执行查询,只取结果集每行第一列的值,映射为指定类型并返回列表
* 适用于 {@code SELECT single_column FROM ...} 单列查询场景。
*
* @param <T> 目标类型
* @param sql SQL
* @param clazz 将结果映射为指定的类型
*
* @return 查询结果
* @return 每一行第一列的值列表。如果查询结果为空,则返回空列表
* @throws SQLException SQL 异常
*/
default <T> List<T> queryValues(String sql, Class<T> clazz)
throws SQLException {
return queryValues(sql, ParamBuilder.EMPTY_OBJECT_ARRAY, clazz);
}
/**
* @deprecated 自 1.1.0 起,请使用 {@link #queryValues(String, Object[], Class)}。
* 此方法将在后续版本中移除。
*/
@Deprecated
default <T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
throws SQLException {
return queryValues(sql, params, clazz);
}
/**
* @deprecated 自 1.1.0 起,请使用 {@link #queryValues(String, Class)}。
* 此方法将在后续版本中移除。
*/
@Deprecated
default <T> List<T> queryList(String sql, Class<T> clazz)
throws SQLException {
return queryList(sql, ParamBuilder.EMPTY_OBJECT_ARRAY, clazz);
return queryValues(sql, clazz);
}
/**
@@ -174,17 +196,18 @@ public interface JdbcOperations {
throws SQLException;
/**
* 查询第一行第一列,并转换为指定类型
* 执行查询,只取结果集第一行第一列的值,映射为指定类型并返回。
* 适用于 {@code SELECT single_column FROM ... WHERE ...} 单列单行查询场景。
*
* @param <T> 目标类型
* @param sql SQL
* @param params 参数
* @param clazz 目标类型
*
* @return 查询结果
* @return 第一行第一列的值。如果查询结果为空,则返回 {@code Optional.empty()}
* @throws SQLException SQL 异常
*/
<T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
<T> Optional<T> queryValue(String sql, Object[] params, Class<T> clazz)
throws SQLException;
/**
@@ -215,18 +238,39 @@ public interface JdbcOperations {
}
/**
* 查询第一行第一列,并转换为指定类型
* 执行查询,只取结果集第一行第一列的值,映射为指定类型并返回。
* 适用于 {@code SELECT single_column FROM ... WHERE ...} 单列单行查询场景。
*
* @param <T> 目标类型
* @param sql SQL
* @param clazz 目标类型
*
* @return 第一行第一列的值,如果查询结果为空,则返回 {@code Optional#empty()}
* @return 第一行第一列的值,如果查询结果为空,则返回 {@code Optional.empty()}
* @throws SQLException SQL 异常
*/
default <T> Optional<T> queryValue(String sql, Class<T> clazz)
throws SQLException {
return queryValue(sql, ParamBuilder.EMPTY_OBJECT_ARRAY, clazz);
}
/**
* @deprecated 自 1.1.0 起,请使用 {@link #queryValue(String, Object[], Class)}。
* 此方法将在后续版本中移除。
*/
@Deprecated
default <T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
throws SQLException {
return queryValue(sql, params, clazz);
}
/**
* @deprecated 自 1.1.0 起,请使用 {@link #queryValue(String, Class)}。
* 此方法将在后续版本中移除。
*/
@Deprecated
default <T> Optional<T> queryFirst(String sql, Class<T> clazz)
throws SQLException {
return queryFirst(sql, ParamBuilder.EMPTY_OBJECT_ARRAY, clazz);
return queryValue(sql, clazz);
}
/**
@@ -242,6 +286,43 @@ public interface JdbcOperations {
return queryFirst(sql, ParamBuilder.EMPTY_OBJECT_ARRAY);
}
/**
* 执行查询,只取结果集第一行第一列的值,映射为指定类型并返回。
* 如果查询结果为空,则返回指定的默认值。
* 适用于 {@code SELECT COUNT(*)}、{@code SELECT MAX(...)} 等聚合查询场景。
*
* @param <T> 目标类型
* @param sql SQL
* @param params 参数
* @param clazz 目标类型
* @param defaultValue 查询结果为空时返回的默认值
*
* @return 第一行第一列的值,如果查询结果为空则返回 {@code defaultValue}
* @throws SQLException SQL 异常
*/
default <T> T queryValueOrDefault(String sql, Object[] params, Class<T> clazz, T defaultValue)
throws SQLException {
return queryValue(sql, params, clazz).orElse(defaultValue);
}
/**
* 执行查询,只取结果集第一行第一列的值,映射为指定类型并返回。
* 如果查询结果为空,则返回指定的默认值。
* 适用于 {@code SELECT COUNT(*)}、{@code SELECT MAX(...)} 等聚合查询场景。
*
* @param <T> 目标类型
* @param sql SQL
* @param clazz 目标类型
* @param defaultValue 查询结果为空时返回的默认值
*
* @return 第一行第一列的值,如果查询结果为空则返回 {@code defaultValue}
* @throws SQLException SQL 异常
*/
default <T> T queryValueOrDefault(String sql, Class<T> clazz, T defaultValue)
throws SQLException {
return queryValueOrDefault(sql, ParamBuilder.EMPTY_OBJECT_ARRAY, clazz, defaultValue);
}
/**
* 查询第一行第一列并转换为 boolean
*

View File

@@ -60,9 +60,9 @@ public interface RowMapper<T> {
* @param <T> Java Bean 的类型
*
* @return {@link DefaultBeanRowMapper}
* @throws SQLException 如果创建 {@link DefaultBeanRowMapper} 失败
* @throws IllegalStateException 如果创建 {@link DefaultBeanRowMapper} 失败
*/
static <T> RowMapper<T> beanRowMapper(Class<T> beanType) throws SQLException {
static <T> RowMapper<T> beanRowMapper(Class<T> beanType) {
return DefaultBeanRowMapper.of(beanType);
}
@@ -74,10 +74,9 @@ public interface RowMapper<T> {
* @param <T> Java Bean 的类型
*
* @return {@link DefaultBeanRowMapper}
* @throws SQLException 如果创建 {@link DefaultBeanRowMapper} 失败
* @throws IllegalStateException 如果创建 {@link DefaultBeanRowMapper} 失败
*/
static <T> RowMapper<T> beanRowMapper(Class<T> beanType, Map<String, String> propertyColMap)
throws SQLException {
static <T> RowMapper<T> beanRowMapper(Class<T> beanType, Map<String, String> propertyColMap) {
return DefaultBeanRowMapper.of(beanType, propertyColMap);
}
}

View File

@@ -96,10 +96,10 @@ public class SimpleJdbcTemplate implements JdbcOperations {
/** {@inheritDoc} */
@Override
public <T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
public <T> List<T> queryValues(String sql, Object[] params, Class<T> clazz)
throws SQLException {
try (Connection conn = this.dataSource.getConnection()) {
return JdbcOperationSupport.queryList(conn, sql, params, clazz);
return JdbcOperationSupport.queryValues(conn, sql, params, clazz);
}
}
@@ -128,10 +128,10 @@ public class SimpleJdbcTemplate implements JdbcOperations {
/** {@inheritDoc} */
@Override
public <T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
public <T> Optional<T> queryValue(String sql, Object[] params, Class<T> clazz)
throws SQLException {
try (Connection conn = this.dataSource.getConnection()) {
final T result = JdbcOperationSupport.queryFirst(conn, sql, params, clazz);
final T result = JdbcOperationSupport.queryValue(conn, sql, params, clazz);
return Optional.ofNullable(result);
}
}
@@ -153,7 +153,7 @@ public class SimpleJdbcTemplate implements JdbcOperations {
throws SQLException {
try (Connection conn = this.dataSource.getConnection()) {
final Boolean result = JdbcOperationSupport
.queryFirst(conn, sql, params, Boolean.class);
.queryValue(conn, sql, params, Boolean.class);
return Boolean.TRUE.equals(result);
}
}

View File

@@ -181,9 +181,9 @@ public class TransactionTemplate {
/** {@inheritDoc} */
@Override
public <T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
public <T> List<T> queryValues(String sql, Object[] params, Class<T> clazz)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, clazz);
return JdbcOperationSupport.queryValues(this.conn, sql, params, clazz);
}
/** {@inheritDoc} */
@@ -207,9 +207,9 @@ public class TransactionTemplate {
/** {@inheritDoc} */
@Override
public <T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
public <T> Optional<T> queryValue(String sql, Object[] params, Class<T> clazz)
throws SQLException {
final T result = JdbcOperationSupport.queryFirst(this.conn, sql, params, clazz);
final T result = JdbcOperationSupport.queryValue(this.conn, sql, params, clazz);
return Optional.ofNullable(result);
}
@@ -227,7 +227,7 @@ public class TransactionTemplate {
public boolean queryBoolean(String sql, Object[] params)
throws SQLException {
final Boolean result = JdbcOperationSupport
.queryFirst(this.conn, sql, params, Boolean.class);
.queryValue(this.conn, sql, params, Boolean.class);
return Boolean.TRUE.equals(result);
}

View File

@@ -199,7 +199,7 @@ class BatchUpdateTest extends BaseH2Test {
void testBatchUpdateQuietlyFalseInterrupted() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int count0 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class)
int count0 = template.queryValue("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() });
@@ -231,7 +231,7 @@ class BatchUpdateTest extends BaseH2Test {
assertNull(result.getUpdateCounts(3));
assertNull(result.getUpdateCounts(4));
Optional<Integer> count8 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class);
Optional<Integer> count8 = template.queryValue("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(count0 + 8, count8.get().intValue());
}
@@ -242,7 +242,7 @@ class BatchUpdateTest extends BaseH2Test {
void testBatchUpdateQuietlyTrue() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int count0 = template.queryFirst("SELECT COUNT(*) FROM users", Integer.class)
int count0 = template.queryValue("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() });
@@ -261,7 +261,7 @@ class BatchUpdateTest extends BaseH2Test {
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);
Optional<Integer> count11 = template.queryValue("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(count0 + 11, count11.get().intValue());
}

View File

@@ -18,7 +18,7 @@ import xyz.zhouxy.jdbc.ResultHandler;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
/**
* 查询 API 测试query、queryList、queryFirst、queryBoolean。
* 查询 API 测试query、queryList、queryFirst、queryValues、queryValue、queryBoolean。
*/
@DisplayName("SimpleJdbcTemplate 查询操作")
class QueryTest extends BaseH2Test {
@@ -119,28 +119,28 @@ class QueryTest extends BaseH2Test {
assertEquals(5, users.size());
}
// ==================== queryList(Class) ====================
// ==================== queryValues(Class) ====================
@Test
@DisplayName("queryList(Class):单列查询返回 String 列表")
void testQueryListWithClassString() throws SQLException {
@DisplayName("queryValues(Class):单列查询返回 String 列表")
void testQueryValuesWithClassString() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<String> usernames = template.queryList(
List<String> usernames = template.queryValues(
"SELECT username FROM users ORDER BY id",
String.class);
logger.info("queryList(Class) 返回用户名: {}", usernames);
logger.info("queryValues(Class) 返回用户名: {}", usernames);
assertEquals(5, usernames.size());
assertTrue(usernames.contains("alice"));
}
@Test
@DisplayName("queryList(Class):空结果集返回空列表")
void testQueryListEmptyResult() throws SQLException {
@DisplayName("queryValues(Class):空结果集返回空列表")
void testQueryValuesEmptyResult() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
List<String> result = template.queryList(
List<String> result = template.queryValues(
"SELECT username FROM users WHERE id = ?",
buildParams(999), String.class);
@@ -215,14 +215,14 @@ class QueryTest extends BaseH2Test {
assertTrue(user.isPresent());
}
// ==================== queryFirst(Class) ====================
// ==================== queryValue(Class) ====================
@Test
@DisplayName("queryFirst(Class):查询第一行第一列")
void testQueryFirstWithClass() throws SQLException {
@DisplayName("queryValue(Class):查询第一行第一列")
void testQueryValueWithClass() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<String> username = template.queryFirst(
Optional<String> username = template.queryValue(
"SELECT username FROM users ORDER BY id",
String.class);
@@ -231,11 +231,11 @@ class QueryTest extends BaseH2Test {
}
@Test
@DisplayName("queryFirst(Class):空结果返回 Optional.empty()")
void testQueryFirstClassEmpty() throws SQLException {
@DisplayName("queryValue(Class):空结果返回 Optional.empty()")
void testQueryValueClassEmpty() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Optional<String> result = template.queryFirst(
Optional<String> result = template.queryValue(
"SELECT username FROM users WHERE id = ?",
buildParams(999), String.class);
@@ -244,11 +244,11 @@ class QueryTest extends BaseH2Test {
@Test
@DisplayName("queryFirst + Class统计总行数")
void testQueryFirstWithClass_queryCount() throws SQLException {
@DisplayName("queryValue + Class统计总行数")
void testQueryValueWithClass_queryCount() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
int count = template.queryFirst(
int count = template.queryValue(
"SELECT COUNT(*) FROM users",
new Object[0],
Integer.class)
@@ -259,11 +259,11 @@ class QueryTest extends BaseH2Test {
}
@Test
@DisplayName("queryFirst + Class聚合求和")
void testQueryFirstWithClass_queryAggregation() throws SQLException {
@DisplayName("queryValue + Class聚合求和")
void testQueryValueWithClass_queryAggregation() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
Long totalBalance = template.queryFirst(
Long totalBalance = template.queryValue(
"SELECT SUM(balance) FROM users",
new Object[0],
Long.class)
@@ -273,6 +273,81 @@ class QueryTest extends BaseH2Test {
assertNotNull(totalBalance);
}
// ==================== queryValueOrDefault ====================
@Test
@DisplayName("queryValueOrDefault有结果时返回值")
void testQueryValueOrDefaultWithResult() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
String username = template.queryValueOrDefault(
"SELECT username FROM users WHERE id = ?",
new Object[]{1}, String.class, "default");
assertEquals("alice", username);
}
@Test
@DisplayName("queryValueOrDefault无结果时返回默认值")
void testQueryValueOrDefaultWithDefault() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
String username = template.queryValueOrDefault(
"SELECT username FROM users WHERE id = ?",
new Object[]{999}, String.class, "unknown");
assertEquals("unknown", username);
}
@Test
@DisplayName("queryValueOrDefaultCOUNT 聚合查询")
void testQueryValueOrDefaultCount() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
long count = template.queryValueOrDefault(
"SELECT COUNT(*) FROM users",
new Object[0], Long.class, 0L);
assertEquals(5L, count);
}
@Test
@DisplayName("queryValueOrDefaultSUM 聚合查询")
void testQueryValueOrDefaultSum() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
long totalBalance = template.queryValueOrDefault(
"SELECT SUM(balance) FROM users",
new Object[0], Long.class, 0L);
assertTrue(totalBalance > 0);
logger.info("queryValueOrDefault SUM 结果: {}", totalBalance);
}
@Test
@DisplayName("queryValueOrDefault无参数重载")
void testQueryValueOrDefaultNoParams() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
long count = template.queryValueOrDefault(
"SELECT COUNT(*) FROM users",
Long.class, 0L);
assertEquals(5L, count);
}
@Test
@DisplayName("queryValueOrDefault空表 COUNT 返回默认值 0")
void testQueryValueOrDefaultEmptyTable() throws SQLException {
SimpleJdbcTemplate template = createTemplate();
long count = template.queryValueOrDefault(
"SELECT COUNT(*) FROM users WHERE id = ?",
new Object[]{999}, Long.class, 0L);
assertEquals(0L, count);
}
// ==================== queryFirst(Map) ====================
@Test
@@ -385,7 +460,7 @@ class QueryTest extends BaseH2Test {
SimpleJdbcTemplate template = createTemplate();
assertThrows(SQLException.class, () ->
template.queryList("SELECT * FROM non_existent_table",
template.queryValues("SELECT * FROM non_existent_table",
new Object[0], String.class));
}
@@ -395,7 +470,7 @@ class QueryTest extends BaseH2Test {
SimpleJdbcTemplate template = createTemplate();
assertThrows(SQLException.class, () ->
template.queryList("SELEC * FROM users",
template.queryValues("SELEC * FROM users",
new Object[0], String.class));
}

View File

@@ -137,9 +137,9 @@ class RowMapperTest extends BaseH2Test {
}
@Test
@DisplayName("DefaultBeanRowMapper无无参构造器的 Bean 抛出 SQLException")
@DisplayName("DefaultBeanRowMapper无无参构造器的 Bean 抛出 IllegalStateException")
void testDefaultBeanRowMapperNoNoArgConstructor() {
assertThrows(SQLException.class, () ->
assertThrows(IllegalStateException.class, () ->
DefaultBeanRowMapper.of(BeanWithoutNoArgConstructor.class));
}

View File

@@ -44,12 +44,12 @@ class TransactionTest extends BaseH2Test {
});
// 验证事务已提交
Optional<String> newUser = template.queryFirst(
Optional<String> newUser = template.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("txUser1"), String.class);
assertTrue(newUser.isPresent());
Optional<Long> balance = template.queryFirst(
Optional<Long> balance = template.queryValue(
"SELECT balance FROM users WHERE username = ?",
buildParams("alice"), Long.class);
assertEquals(Long.valueOf(99999L), balance.orElse(null));
@@ -65,7 +65,7 @@ class TransactionTest extends BaseH2Test {
SimpleJdbcTemplate template = createTemplate();
// 记录原始 balance
Optional<Long> originalBalance = template.queryFirst(
Optional<Long> originalBalance = template.queryValue(
"SELECT balance FROM users WHERE username = ?",
buildParams("alice"), Long.class);
@@ -85,13 +85,13 @@ class TransactionTest extends BaseH2Test {
assertEquals("模拟业务异常", ex.getCause().getMessage());
// 验证更新已回滚
Optional<Long> currentBalance = template.queryFirst(
Optional<Long> currentBalance = template.queryValue(
"SELECT balance FROM users WHERE username = ?",
buildParams("alice"), Long.class);
assertEquals(originalBalance.orElse(null), currentBalance.orElse(null));
// 验证插入已回滚
Optional<String> rolledBackUser = template.queryFirst(
Optional<String> rolledBackUser = template.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("txUser2"), String.class);
assertFalse(rolledBackUser.isPresent());
@@ -114,7 +114,7 @@ class TransactionTest extends BaseH2Test {
// 验证插入已回滚
assertDoesNotThrow(() -> {
Optional<String> user = template.queryFirst(
Optional<String> user = template.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("validUser"), String.class);
assertFalse(user.isPresent());
@@ -135,7 +135,7 @@ class TransactionTest extends BaseH2Test {
});
// 验证数据已持久化
Optional<String> user = template.queryFirst(
Optional<String> user = template.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("cftUser"), String.class);
assertTrue(user.isPresent());
@@ -155,7 +155,7 @@ class TransactionTest extends BaseH2Test {
});
// 验证数据已回滚
Optional<String> user = template.queryFirst(
Optional<String> user = template.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("cffUser"), String.class);
assertFalse(user.isPresent());
@@ -177,7 +177,7 @@ class TransactionTest extends BaseH2Test {
// 验证回滚
assertDoesNotThrow(() -> {
Optional<String> user = template.queryFirst(
Optional<String> user = template.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("exUser"), String.class);
assertFalse(user.isPresent());
@@ -196,7 +196,7 @@ class TransactionTest extends BaseH2Test {
buildParams("visible", "visible@test.com"));
// 在同一事务内可以查询到刚插入的数据
Optional<String> user = ops.queryFirst(
Optional<String> user = ops.queryValue(
"SELECT username FROM users WHERE username = ?",
buildParams("visible"), String.class);
assertTrue(user.isPresent());