Files
simple-jdbc/src/main/java/xyz/zhouxy/jdbc/JdbcOperationSupport.java
ZhouXY108 a0df4136f4 refactor!: 移除 plusone-commons 及 Guava 依赖,内化工具方法
完全移除对外部工具库 plusone-commons 和 Guava 的编译期依赖,
将所需功能内化实现,使项目成为真正的零依赖轻量级 JDBC 封装库。

- 新增 AssertTools: 断言工具(checkArgument / checkNotNull / checkState / checkCondition)
- 新增 NamingTools: 命名转换(camelToSnake)
- 新增 ThrowingConsumer / ThrowingPredicate: 可抛受检异常的函数式接口
- 新增 AssertToolsTests: 完整覆盖断言工具所有方法及边界场景
- pom.xml 移除 plusone-dependencies BOM,直接声明 jsr305 / test 依赖版本
- DefaultBeanRowMapper 使用 NamingTools.camelToSnake 替代 Guava CaseFormat
- ParamBuilder 内联 Optional 展开逻辑,去除 OptionalTools / CollectionTools
- JdbcOperationSupport 用 null/长度判断替代 ArrayTools.isEmpty/isNotEmpty
- NOTICE 移除第三方依赖声明;README 移除无依赖分支说明
- 测试:新增缩写映射(URL/XML/ID/HTML/HTTP)覆盖、TransactionException 构造器测试

BREAKING CHANGE: BatchUpdateStatus 不再实现 IWithIntCode 接口;
plusone-commons 和 Guava 不再作为传递依赖提供。
2026-06-17 17:21:31 +08:00

428 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright 2026-present ZhouXY
*
* 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;
import static xyz.zhouxy.jdbc.util.AssertTools.checkArgument;
import static xyz.zhouxy.jdbc.util.AssertTools.checkArgumentNotNull;
import java.sql.BatchUpdateException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* JdbcOperationSupport
*
* <p>
* 提供静态方法,封装 JDBC 基础操作
* </p>
*
* @author ZhouXY
* @since 1.0.0
*/
class JdbcOperationSupport {
// #region - query
/**
* 表示无法获取所更新的行数
*/
public static final int UNKNOWN_COUNT = -999;
/**
* 执行查询,并按照自定义处理逻辑对结果进行处理,将结果转换为指定类型并返回
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param resultHandler 结果处理器,用于处理 {@link ResultSet}
*/
static <T> T query(Connection conn, String sql, Object[] params, ResultHandler<T> resultHandler)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertResultHandlerNotNull(resultHandler);
return queryInternal(conn, sql, params, resultHandler);
}
// #endregion
// #region - queryList
/**
* 执行查询,将查询结果的每一行数据按照指定逻辑进行处理,返回结果列表
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
static <T> List<T> queryList(Connection conn, String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertRowMapperNotNull(rowMapper);
return queryListInternal(conn, sql, params, rowMapper);
}
/**
* 执行查询,返回结果映射为指定的类型。当结果为单列时使用
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param clazz 将结果映射为指定的类型
*/
static <T> List<T> queryList(Connection conn, String sql, Object[] params, Class<T> clazz)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertClazzNotNull(clazz);
return queryListInternal(conn, sql, params, (rs, rowNumber) -> rs.getObject(1, clazz));
}
// #endregion
// #region - queryFirst
/**
* 执行查询,将查询结果的第一行数据按照指定逻辑进行映射
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
static <T> T queryFirst(Connection conn, String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertRowMapperNotNull(rowMapper);
return queryFirstInternal(conn, sql, params, rowMapper);
}
/**
* 查询第一行第一列,并转换为指定类型
*
* @param <T> 目标类型
* @param sql SQL
* @param params 参数
* @param clazz 目标类型
*/
static <T> T queryFirst(Connection conn, String sql, Object[] params, Class<T> clazz)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertClazzNotNull(clazz);
return queryFirstInternal(conn, sql, params, (rs, rowNumber) -> rs.getObject(1, clazz));
}
// #endregion
// #region - update & batchUpdate
/**
* 执行更新操作
*
* @param conn 数据库连接
* @param sql 要执行的 SQL
* @param params 参数
* @return 更新记录数
*/
static int update(Connection conn, String sql, Object[] params)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
if (params != null && params.length > 0) {
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
fillStatement(stmt, params);
return stmt.executeUpdate();
}
}
else {
try (Statement stmt = conn.createStatement()) {
return stmt.executeUpdate(sql);
}
}
}
/**
* 执行 SQL 并返回生成的 keys
*
* @param conn 数据库连接
* @param sql 要执行的 SQL
* @param params 参数
* @param rowMapper 行数据映射逻辑
*
* @return generated keys
* @throws SQLException 数据库执行异常
*/
static <T> List<T> updateAndReturnKeys(Connection conn, String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertRowMapperNotNull(rowMapper);
if (params != null && params.length > 0) {
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
fillStatement(stmt, params);
stmt.executeUpdate();
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
final ResultHandler<List<T>> resultHandler = ResultHandler.mapToList(rowMapper);
return resultHandler.handle(generatedKeys);
}
}
}
else {
try (Statement stmt = conn.createStatement()) {
stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
try (ResultSet generatedKeys = stmt.getGeneratedKeys()) {
final ResultHandler<List<T>> resultHandler = ResultHandler.mapToList(rowMapper);
return resultHandler.handle(generatedKeys);
}
}
}
}
/**
* 批量更新
*
* <p>
* 当无法获取所更新的行数时,对应位置的更新行数将被设置为 {@link #UNKNOWN_COUNT}。
*
* @param conn 数据库连接
* @param sql sql语句
* @param params 参数列表
* @param batchSize 每次批量更新的数据量
* @param quietly 静默分批更新。
* 如果 {@code quietly} 为 {@code true},分批更新过程中发生异常不中断操作;
* 如果 {@code quietly} 为 {@code false},分批更新过程中发生异常即中断操作,并返回结果。
*/
static BatchUpdateResult batchUpdate(Connection conn,
String sql, @Nullable Collection<Object[]> params, int batchSize,
boolean quietly)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
checkArgument(batchSize > 0, "The batch size must be greater than 0.");
if (params == null || params.isEmpty()) {
return new BatchUpdateResult(0, 0, batchSize);
}
final int paramsSize = params.size();
final int batchCount = (paramsSize + batchSize - 1) / batchSize;
final BatchUpdateResult result = new BatchUpdateResult(paramsSize, batchCount, batchSize);
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
// 表示第几条数据1, 2, 3, ..., paramsSize
int itemIndex = 0;
// 表示第几个批次0, 1, ..., batchCount-1
int batchIndex = 0;
for (Object[] ps : params) {
itemIndex++;
fillStatement(stmt, ps);
stmt.addBatch();
// 表示当前数据在批次中的索引1, 2, 3, ..., batchSize-1, batchSize
final int indexInBatch = (itemIndex - 1) % batchSize + 1;
if (indexInBatch == batchSize || itemIndex == paramsSize) {
try {
int[] updateCounts = stmt.executeBatch();
result.recordSuccessBatch(batchIndex, updateCounts);
}
catch (Exception e) {
final int[] updateCounts = getUpdateCountsOnError(indexInBatch, e);
result.recordErrorBatch(batchIndex, updateCounts, e);
if (!quietly) {
result.interrupt();
break;
}
}
finally {
stmt.clearBatch();
batchIndex++;
}
}
}
return result;
}
}
private static int[] getUpdateCountsOnError(final int indexInBatch, final Exception e) {
final int[] updateCounts;
if (e instanceof BatchUpdateException) {
updateCounts = ((BatchUpdateException) e).getUpdateCounts();
}
else {
updateCounts = new int[indexInBatch];
Arrays.fill(updateCounts, UNKNOWN_COUNT);
}
return updateCounts;
}
// #endregion
// #region - internal
/**
* 执行查询,将查询结果按照指定逻辑进行处理并返回
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param resultHandler 结果处理器,用于处理 {@link ResultSet}
*/
private static <T> T queryInternal(@Nonnull Connection conn,
@Nonnull String sql,
@Nullable Object[] params,
@Nonnull ResultHandler<T> resultHandler)
throws SQLException {
if (params != null && params.length > 0) {
try (PreparedStatement stmt = createPreparedStatementInternal(conn, sql, params);
ResultSet rs = stmt.executeQuery()) {
return resultHandler.handle(rs);
}
}
else {
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
return resultHandler.handle(rs);
}
}
}
private static PreparedStatement createPreparedStatementInternal(
@Nonnull Connection conn,
@Nonnull String sql,
@Nullable Object[] params)
throws SQLException {
PreparedStatement stmt = conn.prepareStatement(sql);
fillStatement(stmt, params);
return stmt;
}
/**
* 执行查询,将查询结果的每一行数据按照指定逻辑进行处理,返回结果列表
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
private static <T> List<T> queryListInternal(@Nonnull Connection conn,
@Nonnull String sql,
@Nullable Object[] params,
@Nonnull RowMapper<T> rowMapper)
throws SQLException {
return queryInternal(conn, sql, params, ResultHandler.mapToList(rowMapper));
}
/**
* 执行查询,将查询结果的第一行数据按照指定逻辑进行处理,返回映射结果
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param rowMapper 行数据映射逻辑
* @return 映射结果。如果查询结果为空,则返回 null
*/
private static <T> T queryFirstInternal(@Nonnull Connection conn,
@Nonnull String sql,
@Nullable Object[] params,
@Nonnull RowMapper<T> rowMapper)
throws SQLException {
return queryInternal(conn, sql, params, rs ->
rs.next() ? rowMapper.mapRow(rs, 0) : null);
}
// #endregion
/**
* 填充参数
*/
private static void fillStatement(@Nonnull PreparedStatement stmt, @Nullable Object[] params)
throws SQLException {
if (params != null && params.length > 0) {
Object param;
for (int i = 0; i < params.length; i++) {
param = params[i];
if (param == null) {
stmt.setObject(i + 1, null, Types.NULL);
}
else if (param instanceof LocalDate) {
stmt.setDate(i + 1, java.sql.Date.valueOf((LocalDate) param));
}
else if (param instanceof LocalTime) {
stmt.setTime(i + 1, java.sql.Time.valueOf((LocalTime) param));
}
else if (param instanceof LocalDateTime) {
stmt.setTimestamp(i + 1, java.sql.Timestamp.valueOf((LocalDateTime) param));
}
else if (param instanceof Instant) {
stmt.setTimestamp(i + 1, java.sql.Timestamp.from((Instant) param));
}
else {
stmt.setObject(i + 1, param);
}
}
}
}
// #region - 参数校验
private static void assertConnectionNotNull(Connection conn) {
checkArgumentNotNull(conn, "The argument \"conn\" could not be null.");
}
private static void assertSqlNotNull(String sql) {
checkArgumentNotNull(sql, "The argument \"sql\" could not be null.");
}
private static void assertRowMapperNotNull(RowMapper<?> rowMapper) {
checkArgumentNotNull(rowMapper, "The argument \"rowMapper\" could not be null.");
}
private static void assertResultHandlerNotNull(ResultHandler<?> resultHandler) {
checkArgumentNotNull(resultHandler, "The argument \"resultHandler\" could not be null.");
}
private static void assertClazzNotNull(Class<?> clazz) {
checkArgumentNotNull(clazz, "The argument \"clazz\" could not be null.");
}
// #endregion
private JdbcOperationSupport() {
throw new IllegalStateException("Utility class");
}
}