1.0.0-alpha

Reviewed-on: #1
This commit is contained in:
zhouxy108 2024-11-02 11:41:50 +08:00
commit 42af67b651
11 changed files with 1424 additions and 572 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

29
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>xyz.zhouxy.jdbc</groupId>
<artifactId>simple-jdbc</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0.0-alpha</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
@ -18,7 +18,7 @@
<dependency>
<groupId>xyz.zhouxy.plusone</groupId>
<artifactId>plusone-commons</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0.0-alpha</version>
</dependency>
<dependency>
@ -35,28 +35,11 @@
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.h2database/h2 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.8</version>
<exclusions>
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -16,49 +16,78 @@
package xyz.zhouxy.jdbc;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import javax.annotation.Nonnull;
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import xyz.zhouxy.plusone.commons.collection.AbstractMapWrapper;
import xyz.zhouxy.plusone.commons.util.AssertTools;
import xyz.zhouxy.plusone.commons.util.OptionalTools;
import xyz.zhouxy.plusone.commons.util.StringTools;
import java.util.*;
/**
* DbRecord
*
* <p>
* 封装 Map<String, Object>表示一条 DB 记录
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
@Beta
public class DbRecord extends AbstractMapWrapper<String, Object, DbRecord> {
public DbRecord() {
super(new HashMap<>(), k -> Preconditions.checkArgument(StringTools.isNotBlank(k), "Key must has text."), null);
super(new HashMap<>(),
k -> AssertTools.checkArgument(StringTools.isNotBlank(k), "Key must has text."),
null);
}
public DbRecord(Map<String, Object> map) {
super(map, k -> Preconditions.checkArgument(StringTools.isNotBlank(k), "Key must has text."), null);
super(map,
k -> AssertTools.checkArgument(StringTools.isNotBlank(k), "Key must has text."),
null);
}
/**
* 将值强转为 {@link String}并放在 {@link Optional}
* 如果 {@code key} 存在而值不存在则返回 {@link Optional#empty()}
*/
public Optional<String> getValueAsString(String key) {
return this.getAndConvert(key);
}
public <T> List<T> getValueAsList(String key) {
return this.<Collection<T>>getAndConvert(key)
.map(l -> (l instanceof List) ? (List<T>) l : new ArrayList<>(l))
.orElse(Collections.emptyList());
}
public <T> Set<T> getValueAsSet(String key) {
return this.<Collection<T>>getAndConvert(key)
.map(l -> (l instanceof Set) ? (Set<T>) l : new HashSet<>(l))
.orElse(Collections.emptySet());
}
/**
* 将值强转为 {@code int}并放在 {@link OptionalInt}
* 如果 {@code key} 存在而值不存在则返回 {@link OptionalInt#empty()}
*/
@Nonnull
public OptionalInt getValueAsInt(String key) {
return OptionalTools.toOptionalInt(this.getAndConvert(key));
}
/**
* 将值强转为 {@code long}并放在 {@link OptionalLong}
* 如果 {@code key} 存在而值不存在则返回 {@link OptionalLong#empty()}
*/
@Nonnull
public OptionalLong getValueAsLong(String key) {
return OptionalTools.toOptionalLong(this.getAndConvert(key));
}
/**
* 将值强转为 {@code double}并放在 {@link OptionalDouble}
* 如果 {@code key} 存在而值不存在则返回 {@link OptionalDouble#empty()}
*/
@Nonnull
public OptionalDouble getValueAsDouble(String key) {
return OptionalTools.toOptionalDouble(this.getAndConvert(key));
}

View File

@ -35,9 +35,28 @@ import javax.annotation.Nullable;
import com.google.common.base.CaseFormat;
import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod;
/**
* DefaultBeanRowMapper
*
* <p>
* 默认实现的将 {@link ResultSet} 转换为 Java Bean {@link RowMapper}
* </p>
*
* <p>
* <b>NOTE: 使用反射获取类型信息也是使用反射调用无参构造器和 {@code setter} 方法
* 实际使用中还是建议针对目标类型自定义 {@link RowMapper}</b>
* </p>
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
public class DefaultBeanRowMapper<T> implements RowMapper<T> {
/** Bean 的无参构造器 */
private final Constructor<T> constructor;
/** 列名到属性的映射 */
private final Map<String, PropertyDescriptor> colPropertyMap;
private DefaultBeanRowMapper(Constructor<T> constructor, Map<String, PropertyDescriptor> colPropertyMap) {
@ -45,10 +64,29 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
this.colPropertyMap = colPropertyMap;
}
/**
* 创建一个 DefaultBeanRowMapper
*
* @param <T> Bean 类型
* @param beanType Bean 类型
* @return DefaultBeanRowMapper 对象
* @throws SQLException 创建 DefaultBeanRowMapper 出现错误的异常时抛出
*/
@StaticFactoryMethod(DefaultBeanRowMapper.class)
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType) throws SQLException {
return of(beanType, null);
}
/**
* 创建一个 DefaultBeanRowMapper
*
* @param <T> Bean 类型
* @param beanType Bean 类型
* @param propertyColMap Bean 字段与列名的映射关系key 是字段value 是列名
* @return DefaultBeanRowMapper 对象
* @throws SQLException 创建 DefaultBeanRowMapper 出现错误的异常时抛出
*/
@StaticFactoryMethod(DefaultBeanRowMapper.class)
public static <T> DefaultBeanRowMapper<T> of(Class<T> beanType, @Nullable Map<String, String> propertyColMap)
throws SQLException {
try {
@ -60,6 +98,7 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
BeanInfo beanInfo = Introspector.getBeanInfo(beanType);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
// Bean 的属性名为小驼峰对应的列名为下划线
Function<? super PropertyDescriptor, String> keyMapper;
if (propertyColMap == null || propertyColMap.isEmpty()) {
keyMapper = p -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, p.getName());
@ -84,13 +123,17 @@ public class DefaultBeanRowMapper<T> implements RowMapper<T> {
}
}
/** {@inheritDoc} */
@Override
public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
try {
// 调用无参构造器创建实例
T newInstance = this.constructor.newInstance();
ResultSetMetaData metaData = rs.getMetaData();
// 遍历结果的每一列
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String colName = metaData.getColumnName(i);
// 获取查询结果列名对应的属性调用 setter
PropertyDescriptor propertyDescriptor = this.colPropertyMap.get(colName);
if (propertyDescriptor != null) {
Method setter = propertyDescriptor.getWriteMethod();

View File

@ -0,0 +1,471 @@
/*
* Copyright 2024 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;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.google.common.collect.Lists;
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
import xyz.zhouxy.plusone.commons.util.AssertTools;
import xyz.zhouxy.plusone.commons.util.OptionalTools;
/**
* JdbcOperationSupport
*
* <p>
* 提供静态方法封装 JDBC 基础操作
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
class JdbcOperationSupport {
// #region - query
/**
* 执行查询并按照自定义处理逻辑对结果进行处理将结果转换为指定类型并返回
*
* @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
/**
* 执行查询将查询结果的第一行数据按照指定逻辑进行处理返回 {@link Optional}
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
static <T> Optional<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> Optional<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));
}
/**
* 查询第一行第一列并转换为字符串
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
*/
static Optional<String> queryFirstString(Connection conn, String sql, Object[] params)
throws SQLException {
return queryFirst(conn, sql, params, (rs, rowNumber) -> rs.getString(1));
}
/**
* 查询第一行第一列并转换为整数值
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
*/
static OptionalInt queryFirstInt(Connection conn, String sql, Object[] params)
throws SQLException {
Optional<Integer> result = queryFirst(conn, sql, params, (rs, rowNumber) -> rs.getInt(1));
return OptionalTools.toOptionalInt(result);
}
/**
* 查询第一行第一列并转换为长整型
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
*/
static OptionalLong queryFirstLong(Connection conn, String sql, Object[] params)
throws SQLException {
Optional<Long> result = queryFirst(conn, sql, params, (rs, rowNumber) -> rs.getLong(1));
return OptionalTools.toOptionalLong(result);
}
/**
* 查询第一行第一列并转换为双精度浮点型
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
*/
static OptionalDouble queryFirstDouble(Connection conn, String sql, Object[] params)
throws SQLException {
Optional<Double> result = queryFirst(conn, sql, params, (rs, rowNumber) -> rs.getDouble(1));
return OptionalTools.toOptionalDouble(result);
}
/**
* 查询第一行第一列并转换为 {@link BigDecimal}
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
*/
static Optional<BigDecimal> queryFirstBigDecimal(Connection conn, String sql, Object[] params)
throws SQLException {
return queryFirst(conn, sql, params, (rs, rowNumber) -> rs.getBigDecimal(1));
}
// #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);
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
fillStatement(stmt, params);
return stmt.executeUpdate();
}
}
/**
* 执行 SQL 并返回生成的 keys
*
* @param conn 数据库连接
* @param sql 要执行的 SQL
* @param params 参数
* @param rowMapper 行数据映射逻辑
*
* @return generated keys
* @throws SQLException 执行 SQL 遇到异常情况将抛出
*/
static <T> List<T> update(Connection conn, String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
assertRowMapperNotNull(rowMapper);
final List<T> result = new ArrayList<>();
try (PreparedStatement stmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
fillStatement(stmt, params);
stmt.executeUpdate();
try (ResultSet generatedKeys = stmt.getGeneratedKeys();) {
int rowNumber = 0;
while (generatedKeys.next()) {
T e = rowMapper.mapRow(generatedKeys, rowNumber++);
result.add(e);
}
}
return result;
}
}
/**
* 执行批量更新批量更新数据返回每条记录更新的行数
*
* @param conn 数据库连接
* @param sql SQL 语句
* @param params 参数列表
* @param batchSize 每次批量更新的数据量
*/
static List<int[]> batchUpdate(Connection conn, String sql, Collection<Object[]> params, int batchSize)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
if (params == null || params.isEmpty()) {
return Collections.emptyList();
}
int executeCount = params.size() / batchSize;
executeCount = (params.size() % batchSize == 0) ? executeCount : (executeCount + 1);
List<int[]> result = Lists.newArrayListWithCapacity(executeCount);
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
int i = 0;
for (Object[] ps : params) {
i++;
fillStatement(stmt, ps);
stmt.addBatch();
if (i % batchSize == 0 || i >= params.size()) {
int[] n = stmt.executeBatch();
result.add(n);
stmt.clearBatch();
}
}
return result;
}
}
/**
* 批量更新返回更新成功的记录行数发生异常时不中断操作将异常存入 {@code exceptions}
*
* @param conn 数据库连接
* @param sql sql语句
* @param params 参数列表
* @param batchSize 每次批量更新的数据量
* @param exceptions 异常列表用于记录异常信息
*/
static List<int[]> batchUpdateAndIgnoreException(Connection conn,
String sql, @Nullable Collection<Object[]> params, int batchSize,
List<Exception> exceptions)
throws SQLException {
assertConnectionNotNull(conn);
assertSqlNotNull(sql);
AssertTools.checkArgument(CollectionTools.isNotEmpty(exceptions),
"The list used to store exceptions should be non-null and empty.");
if (params == null || params.isEmpty()) {
return Collections.emptyList();
}
int executeCount = params.size() / batchSize;
executeCount = (params.size() % batchSize == 0) ? executeCount : (executeCount + 1);
List<int[]> result = Lists.newArrayListWithCapacity(executeCount);
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
int i = 0;
for (Object[] ps : params) {
i++;
fillStatement(stmt, ps);
stmt.addBatch();
final int batchIndex = i % batchSize;
if (batchIndex == 0 || i >= params.size()) {
try {
int[] n = stmt.executeBatch();
result.add(n);
stmt.clearBatch();
}
catch (Exception e) {
int n = (i >= params.size() && batchIndex != 0) ? batchIndex : batchSize;
result.add(new int[n]);
stmt.clearBatch();
// 收集异常信息
exceptions.add(e);
}
}
}
return result;
}
}
// #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 {
try (PreparedStatement stmt = conn.prepareStatement(sql)) {
fillStatement(stmt, params);
try (ResultSet rs = stmt.executeQuery()) {
return resultHandler.handle(rs);
}
}
}
/**
* 执行查询将查询结果的每一行数据按照指定逻辑进行处理返回结果列表
*
* @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, rs -> {
List<T> result = new ArrayList<>();
int rowNumber = 0;
while (rs.next()) {
T e = rowMapper.mapRow(rs, rowNumber++);
result.add(e);
}
return result;
});
}
/**
* 执行查询将查询结果的第一行数据按照指定逻辑进行处理返回 {@link Optional}
*
* @param conn 数据库连接
* @param sql SQL
* @param params 参数
* @param rowMapper 行数据映射逻辑
*/
private static <T> Optional<T> queryFirstInternal(@Nonnull Connection conn,
@Nonnull String sql,
@Nullable Object[] params,
@Nonnull RowMapper<T> rowMapper)
throws SQLException {
return queryInternal(conn, sql, params, rs -> {
if (rs.next()) {
return Optional.ofNullable(rowMapper.mapRow(rs, 0));
}
return Optional.empty();
});
}
// #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 instanceof java.sql.Date) {
stmt.setDate(i + 1, (java.sql.Date) param);
}
else if (param instanceof java.sql.Time) {
stmt.setTime(i + 1, (java.sql.Time) param);
}
else if (param instanceof java.sql.Timestamp) {
stmt.setTimestamp(i + 1, (java.sql.Timestamp) param);
}
else {
stmt.setObject(i + 1, param);
}
}
}
}
// #region - 参数校验
private static void assertConnectionNotNull(Connection conn) {
AssertTools.checkArgumentNotNull(conn, "The argument \"conn\" could not be null.");
}
private static void assertSqlNotNull(String sql) {
AssertTools.checkArgumentNotNull(sql, "The argument \"sql\" could not be null.");
}
private static void assertRowMapperNotNull(RowMapper<?> rowMapper) {
AssertTools.checkArgumentNotNull(rowMapper, "The argument \"rowMapper\" could not be null.");
}
private static void assertResultHandlerNotNull(ResultHandler<?> resultHandler) {
AssertTools.checkArgumentNotNull(resultHandler, "The argument \"resultHandler\" could not be null.");
}
private static void assertClazzNotNull(Class<?> clazz) {
AssertTools.checkArgumentNotNull(clazz, "The argument \"clazz\" could not be null.");
}
// #endregion
private JdbcOperationSupport() {
throw new IllegalStateException("Utility class");
}
}

View File

@ -0,0 +1,359 @@
package xyz.zhouxy.jdbc;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.OptionalLong;
import javax.annotation.Nullable;
/**
* JdbcOperations
*
* <p>
* 定义 JdbcTemplate API
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
interface JdbcOperations {
// #region - query
/**
* 执行查询并按照自定义处理逻辑对结果进行处理将结果转换为指定类型并返回
*
* @param sql SQL
* @param params 参数
* @param resultHandler 结果处理器用于处理 {@link ResultSet}
*/
<T> T query(String sql, Object[] params, ResultHandler<T> resultHandler)
throws SQLException;
/**
* 执行查询并按照自定义处理逻辑对结果进行处理将结果转换为指定类型并返回
*
* @param sql SQL
* @param resultHandler 结果处理器用于处理 {@link ResultSet}
*/
<T> T query(String sql, ResultHandler<T> resultHandler)
throws SQLException;
// #endregion
// #region - queryList
/**
* 执行查询将查询结果的每一行数据按照指定逻辑进行处理返回结果列表
*
* @param sql SQL
* @param params 参数
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
<T> List<T> queryList(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException;
/**
* 执行查询返回结果映射为指定的类型当结果为单列时使用
*
* @param sql SQL
* @param params 参数
* @param clazz 将结果映射为指定的类型
*/
<T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
throws SQLException;
/**
* 执行查询每一行数据映射为 {@code Map<String, Object>}返回结果列表
*
* @param sql SQL
* @param params 参数列表
*/
List<Map<String, Object>> queryList(String sql, Object[] params)
throws SQLException;
/**
* 执行查询每一行数据映射为 {@link DbRecord}返回结果列表
*
* @param sql SQL
* @param params 参数列表
*/
List<DbRecord> queryRecordList(String sql, Object[] params)
throws SQLException;
/**
* 执行查询将查询结果的每一行数据按照指定逻辑进行处理返回结果列表
*
* @param sql SQL
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
<T> List<T> queryList(String sql, RowMapper<T> rowMapper)
throws SQLException;
/**
* 执行查询返回结果映射为指定的类型当结果为单列时使用
*
* @param sql SQL
* @param clazz 将结果映射为指定的类型
*/
<T> List<T> queryList(String sql, Class<T> clazz)
throws SQLException;
/**
* 执行查询每一行数据映射为 {@code Map<String, Object>}返回结果列表
*
* @param sql SQL
*/
List<Map<String, Object>> queryList(String sql)
throws SQLException;
/**
* 执行查询每一行数据映射为 {@link DbRecord}返回结果列表
*
* @param sql SQL
*/
List<DbRecord> queryRecordList(String sql)
throws SQLException;
// #endregion
// #region - queryFirst
/**
* 执行查询将查询结果的第一行数据按照指定逻辑进行处理返回 {@link Optional}
*
* @param sql SQL
* @param params 参数
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
<T> Optional<T> queryFirst(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException;
/**
* 查询第一行第一列并转换为指定类型
*
* @param <T> 目标类型
* @param sql SQL
* @param params 参数
* @param clazz 目标类型
*/
<T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
throws SQLException;
/**
* 执行查询将第一行数据转为 Map<String, Object>
*
* @param sql SQL
* @param params 参数
*/
Optional<Map<String, Object>> queryFirst(String sql, Object[] params)
throws SQLException;
/**
* 执行查询将第一行数据转为 DbRecord
*
* @param sql SQL
* @param params 参数
*/
Optional<DbRecord> queryFirstRecord(String sql, Object[] params)
throws SQLException;
/**
* 查询第一行第一列并转换为字符串
*
* @param sql SQL
* @param params 参数
*/
Optional<String> queryFirstString(String sql, Object[] params)
throws SQLException;
/**
* 查询第一行第一列并转换为整数值
*
* @param sql SQL
* @param params 参数
*/
OptionalInt queryFirstInt(String sql, Object[] params)
throws SQLException;
/**
* 查询第一行第一列并转换为长整型
*
* @param sql SQL
* @param params 参数
*/
OptionalLong queryFirstLong(String sql, Object[] params)
throws SQLException;
/**
* 查询第一行第一列并转换为双精度浮点型
*
* @param sql SQL
* @param params 参数
*/
OptionalDouble queryFirstDouble(String sql, Object[] params)
throws SQLException;
/**
* 查询第一行第一列并转换为 {@link BigDecimal}
*
* @param sql SQL
* @param params 参数
*/
Optional<BigDecimal> queryFirstBigDecimal(String sql, Object[] params)
throws SQLException;
/**
* 执行查询将查询结果的第一行数据按照指定逻辑进行处理返回 {@link Optional}
*
* @param sql SQL
* @param rowMapper {@link ResultSet} 中每一行的数据的处理逻辑
*/
<T> Optional<T> queryFirst(String sql, RowMapper<T> rowMapper)
throws SQLException;
/**
* 查询第一行第一列并转换为指定类型
*
* @param <T> 目标类型
* @param sql SQL
* @param clazz 目标类型
*/
<T> Optional<T> queryFirst(String sql, Class<T> clazz)
throws SQLException;
/**
* 执行查询将第一行数据转为 Map<String, Object>
*
* @param sql SQL
*/
Optional<Map<String, Object>> queryFirst(String sql)
throws SQLException;
/**
* 执行查询将第一行数据转为 DbRecord
*
* @param sql SQL
*/
Optional<DbRecord> queryFirstRecord(String sql)
throws SQLException;
/**
* 查询第一行第一列并转换为字符串
*
* @param sql SQL
*/
Optional<String> queryFirstString(String sql)
throws SQLException;
/**
* 查询第一行第一列并转换为整数值
*
* @param sql SQL
*/
OptionalInt queryFirstInt(String sql)
throws SQLException;
/**
* 查询第一行第一列并转换为长整型
*
* @param sql SQL
*/
OptionalLong queryFirstLong(String sql)
throws SQLException;
/**
* 查询第一行第一列并转换为双精度浮点型
*
* @param sql SQL
*/
OptionalDouble queryFirstDouble(String sql)
throws SQLException;
/**
* 查询第一行第一列并转换为 {@link BigDecimal}
*
* @param sql SQL
*/
Optional<BigDecimal> queryFirstBigDecimal(String sql)
throws SQLException;
// #endregion
// #region - update & batchUpdate
/**
* 执行更新操作
*
* @param sql 要执行的 SQL
* @param params 参数
* @return 更新记录数
*/
int update(String sql, Object[] params)
throws SQLException;
/**
* 执行更新操作
*
* @param sql 要执行的 SQL
* @return 更新记录数
*/
int update(String sql)
throws SQLException;
/**
* 执行 SQL 并返回生成的 keys
*
* @param sql 要执行的 SQL
* @param params 参数
* @param rowMapper 行数据映射逻辑
*
* @return generated keys
* @throws SQLException 执行 SQL 遇到异常情况将抛出
*/
<T> List<T> update(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException;
/**
* 执行 SQL 并返回生成的 keys
*
* @param sql 要执行的 SQL
* @param rowMapper 行数据映射逻辑
*
* @return generated keys
* @throws SQLException 执行 SQL 遇到异常情况将抛出
*/
<T> List<T> update(String sql, RowMapper<T> rowMapper)
throws SQLException;
/**
* 执行批量更新批量更新数据返回每条记录更新的行数
*
* @param sql SQL 语句
* @param params 参数列表
* @param batchSize 每次批量更新的数据量
*/
List<int[]> batchUpdate(String sql, @Nullable Collection<Object[]> params, int batchSize)
throws SQLException;
/**
* 批量更新返回更新成功的记录行数发生异常时不中断操作将异常存入 {@code exceptions}
*
* @param sql sql语句
* @param params 参数列表
* @param batchSize 每次批量更新的数据量
* @param exceptions 异常列表用于记录异常信息
*/
List<int[]> batchUpdateAndIgnoreException(String sql, @Nullable Collection<Object[]> params,
int batchSize, List<Exception> exceptions)
throws SQLException;
// #endregion
}

View File

@ -27,12 +27,21 @@ import java.util.OptionalLong;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import xyz.zhouxy.plusone.commons.collection.CollectionTools;
import xyz.zhouxy.plusone.commons.util.ArrayTools;
import xyz.zhouxy.plusone.commons.util.AssertTools;
import xyz.zhouxy.plusone.commons.util.OptionalTools;
/**
* ParamBuilder
*
* <p>
* JDBC 参数构造器将数据转换为 {@code Object[]} 类型以传给 {@link PreparedStatement}
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
public class ParamBuilder {
public static final Object[] EMPTY_OBJECT_ARRAY = {};
@ -60,8 +69,8 @@ public class ParamBuilder {
}
public static <T> List<Object[]> buildBatchParams(final Collection<T> c, final Function<T, Object[]> func) {
Preconditions.checkNotNull(c, "The collection can not be null.");
Preconditions.checkNotNull(func, "The func can not be null.");
AssertTools.checkNotNull(c, "The collection can not be null.");
AssertTools.checkNotNull(func, "The func can not be null.");
if (CollectionTools.isEmpty(c)) {
return Collections.emptyList();
}

View File

@ -19,7 +19,21 @@ package xyz.zhouxy.jdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* ResultHandler
*
* <p>
* 处理 {@link ResultSet}
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
@FunctionalInterface
public interface ResultHandler<T> {
/**
* {@link ResultSet} 转换为指定类型的对象
*/
T handle(ResultSet resultSet) throws SQLException;
}

View File

@ -22,10 +22,21 @@ import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* RowMapper
*
* <p>
* {@link ResultSet} 中每一行数据的处理逻辑
* </p>
*
* @author <a href="http://zhouxy.xyz:3000/ZhouXY108">ZhouXY</a>
* @since 1.0.0
*/
@FunctionalInterface
public interface RowMapper<T> {
T mapRow(ResultSet rs, int rowNumber) throws SQLException;
/** 每一行数据转换为 {@link HashMap} */
public static final RowMapper<Map<String, Object>> HASH_MAP_MAPPER = (rs, rowNumber) -> {
Map<String, Object> result = new HashMap<>();
ResultSetMetaData metaData = rs.getMetaData();
@ -37,13 +48,16 @@ public interface RowMapper<T> {
return result;
};
/** 每一行数据转换为 {@link DbRecord} */
public static final RowMapper<DbRecord> RECORD_MAPPER =
(rs, rowNumber) -> new DbRecord(HASH_MAP_MAPPER.mapRow(rs, rowNumber));
/** 默认实现的将 {@link ResultSet} 转换为 Java Bean 的 {@link RowMapper}。 */
public static <T> RowMapper<T> beanRowMapper(Class<T> beanType) throws SQLException {
return DefaultBeanRowMapper.of(beanType);
}
/** 默认实现的将 {@link ResultSet} 转换为 Java Bean 的 {@link RowMapper}。 */
public static <T> RowMapper<T> beanRowMapper(Class<T> beanType, Map<String, String> propertyColMap)
throws SQLException {
return DefaultBeanRowMapper.of(beanType, propertyColMap);

File diff suppressed because it is too large Load Diff

View File

@ -1,98 +1,148 @@
package xyz.zhouxy.jdbc.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
import static xyz.zhouxy.jdbc.ParamBuilder.*;
import static xyz.zhouxy.plusone.commons.sql.JdbcSql.IN;
import static xyz.zhouxy.plusone.commons.sql.JdbcSql.*;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import javax.sql.DataSource;
import org.h2.jdbcx.JdbcDataSource;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import xyz.zhouxy.jdbc.DbRecord;
import xyz.zhouxy.jdbc.RowMapper;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
import xyz.zhouxy.jdbc.SimpleJdbcTemplate.JdbcExecutor;
import xyz.zhouxy.plusone.commons.sql.SQL;
import xyz.zhouxy.plusone.commons.util.ArrayTools;
import xyz.zhouxy.plusone.commons.util.IdGenerator;
import xyz.zhouxy.plusone.commons.util.IdWorker;
import xyz.zhouxy.plusone.commons.util.Numbers;
class SimpleJdbcTemplateTests {
private static final Logger log = LoggerFactory.getLogger(SimpleJdbcTemplateTests.class);
private static final DataSource dataSource;
private static final SimpleJdbcTemplate jdbcTemplate;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:postgresql://localhost:5432/plusone");
config.setUsername("postgres");
config.setPassword("zhouxy108");
config.setMaximumPoolSize(8);
config.setConnectionTimeout(1000000);
dataSource = new HikariDataSource(config);
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);
}
@BeforeAll
static void setUp() throws SQLException {
jdbcTemplate.update("CREATE TABLE sys_account ("
+ "\n" + " id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY"
+ "\n" + " ,username VARCHAR(255) NOT NULL"
+ "\n" + " ,account_status VARCHAR(2) NOT NULL"
+ "\n" + " ,create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"
+ "\n" + " ,created_by BIGINT NOT NULL"
+ "\n" + " ,update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP"
+ "\n" + " ,updated_by BIGINT DEFAULT NULL"
+ "\n" + " ,version BIGINT NOT NULL DEFAULT 0"
+ "\n" + ")");
jdbcTemplate.batchUpdate("INSERT INTO sys_account(id, username, account_status, created_by) VALUES (?, ?, ?, ?)", Lists.newArrayList(
buildParams(2L, "zhouxy2", "0", 108L),
buildParams(3L, "zhouxy3", "0", 108L),
buildParams(4L, "zhouxy4", "0", 108L),
buildParams(5L, "zhouxy5", "0", 108L),
buildParams(6L, "zhouxy6", "0", 108L),
buildParams(7L, "zhouxy7", "0", 108L),
buildParams(8L, "zhouxy8", "0", 108L),
buildParams(9L, "zhouxy9", "0", 108L)
), 10);
jdbcTemplate.batchUpdate("INSERT INTO sys_account(id, username, account_status, created_by, create_time, update_time, version) VALUES (?, ?, ?, ?, ?, ?, ?)", Lists.newArrayList(
buildParams(10L, "zhouxy10", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 31),
buildParams(11L, "zhouxy11", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 28),
buildParams(12L, "zhouxy12", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 25),
buildParams(13L, "zhouxy13", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 22),
buildParams(14L, "zhouxy14", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 19),
buildParams(15L, "zhouxy15", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 16),
buildParams(16L, "zhouxy16", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 13),
buildParams(17L, "zhouxy17", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 10),
buildParams(18L, "zhouxy18", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 7),
buildParams(19L, "zhouxy19", "1", 118L, LocalDate.of(2000, 1, 1), LocalDate.of(2000, 1, 29), 0)
), 10);
jdbcTemplate.update("INSERT INTO sys_account(id, username, account_status, created_by, create_time, updated_by, update_time, version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
buildParams(20L, "zhouxy20", "2", 118L, LocalDateTime.of(2008, 8, 8, 20, 8), 31L, LocalDateTime.now(), 88L));
}
@Test
void testQuery() throws SQLException {
Object[] ids = buildParams(22915, 22916, 22917, 22918, 22919, 22920, 22921);
Object[] ids = buildParams(5, 9, 13, 14, 17, 20, 108);
String sql = SQL.newJdbcSql()
.SELECT("*")
.FROM("test_table")
.SELECT("id", "username", "account_status")
.FROM("sys_account")
.WHERE(IN("id", ids))
.toString();
log.info(sql);
List<DbRecord> rs = jdbcTemplate.queryRecordList(sql, ids);
assertNotNull(rs);
for (DbRecord baseEntity : rs) {
// log.info("id: {}", baseEntity.getValueAsString("id")); // NOSONAR
log.info(baseEntity.toString());
assertTrue(baseEntity.getValueAsString("username").isPresent());
for (DbRecord dbRecord : rs) {
log.info("{}", dbRecord);
}
assertEquals(
Lists.newArrayList(
new DbRecord(ImmutableMap.of("id", 5L, "account_status", "0", "username", "zhouxy5")),
new DbRecord(ImmutableMap.of("id", 9L, "account_status", "0", "username", "zhouxy9")),
new DbRecord(ImmutableMap.of("id", 13L, "account_status", "1", "username", "zhouxy13")),
new DbRecord(ImmutableMap.of("id", 14L, "account_status", "1", "username", "zhouxy14")),
new DbRecord(ImmutableMap.of("id", 17L, "account_status", "1", "username", "zhouxy17")),
new DbRecord(ImmutableMap.of("id", 20L, "account_status", "2", "username", "zhouxy20"))
),
rs
);
}
@Test
void testInsert() throws SQLException {
List<DbRecord> keys = jdbcTemplate.update(
"INSERT INTO base_table(status, created_by) VALUES (?, ?)",
buildParams(1, 886L),
RowMapper.RECORD_MAPPER);
List<Map<String, Object>> keys = jdbcTemplate.update(
"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(1, keys.size());
DbRecord result = keys.get(0);
assertEquals(1, result.getValueAsInt("status").getAsInt());
assertEquals(886L, result.getValueAsLong("created_by").getAsLong());
assertTrue(result.get("id").isPresent());
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.update(
"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<DbRecord> keys = jdbcTemplate.update(
"UPDATE base_table SET status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ? AND version = ?",
buildParams(2, 886, 571328822575109L, 0),
"UPDATE sys_account SET account_status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ? AND version = ?",
buildParams("7", 886L, 20L, 88L),
RowMapper.RECORD_MAPPER);
assertEquals(1, keys.size());
log.info("keys: {}", keys);
keys = jdbcTemplate.update(
"UPDATE sys_account SET account_status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ? AND version = ?",
buildParams("-1", 886L, 20L, 88L),
RowMapper.RECORD_MAPPER);
assertEquals(0, keys.size());
}
final IdWorker idGenerator = IdGenerator.getSnowflakeIdGenerator(0);
@ -104,8 +154,8 @@ class SimpleJdbcTemplateTests {
long id = this.idGenerator.nextId();
try {
jdbcTemplate.executeTransaction((JdbcExecutor jdbc) -> {
jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)",
buildParams(id, 100, LocalDateTime.now(), 0));
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();
});
}
@ -113,7 +163,7 @@ class SimpleJdbcTemplateTests {
// ignore
}
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id));
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(!first.isPresent());
}
@ -122,12 +172,12 @@ class SimpleJdbcTemplateTests {
{
long id = this.idGenerator.nextId();
jdbcTemplate.executeTransaction(jdbc -> {
jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)",
buildParams(id, 101, LocalDateTime.now(), 0));
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 base_table WHERE id = ?", buildParams(id));
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(first.isPresent());
}
@ -137,8 +187,8 @@ class SimpleJdbcTemplateTests {
long id = this.idGenerator.nextId();
try {
jdbcTemplate.commitIfTrue(jdbc -> {
jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)",
buildParams(id, 102, LocalDateTime.now(), 0));
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();
});
}
@ -146,7 +196,7 @@ class SimpleJdbcTemplateTests {
// ignore
}
Optional<Map<String, Object>> first = jdbcTemplate
.queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id));
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(!first.isPresent());
}
@ -155,13 +205,13 @@ class SimpleJdbcTemplateTests {
{
long id = this.idGenerator.nextId();
jdbcTemplate.commitIfTrue(jdbc -> {
jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)",
buildParams(id, 103, LocalDateTime.now(), 0));
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 base_table WHERE id = ?", buildParams(id));
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(!first.isPresent());
}
@ -170,72 +220,57 @@ class SimpleJdbcTemplateTests {
{
long id = this.idGenerator.nextId();
jdbcTemplate.commitIfTrue(jdbc -> {
jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)",
buildParams(id, 104, LocalDateTime.now(), 0));
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 base_table WHERE id = ?", buildParams(id));
.queryFirst("SELECT * FROM sys_account WHERE id = ?", buildParams(id));
log.info("first: {}", first);
assertTrue(first.isPresent());
}
}
@Test
void testBatch() throws Exception {
Random random = ThreadLocalRandom.current();
LocalDate handleDate = LocalDate.of(1949, 10, 1);
List<DbRecord> datas = Lists.newArrayList();
while (handleDate.isBefore(LocalDate.of(2008, 8, 8))) {
DbRecord r1 = new DbRecord();
r1.put("username", "张三2");
r1.put("usage_date", handleDate);
r1.put("usage_duration", random.nextInt(500));
datas.add(r1);
DbRecord r2 = new DbRecord();
r2.put("username", "李四2");
r2.put("usage_date", handleDate);
r2.put("usage_duration", random.nextInt(500));
datas.add(r2);
handleDate = handleDate.plusDays(1L);
}
try {
List<int[]> result = jdbcTemplate.batchUpdate(
"insert into test_table (username, usage_date, usage_duration) values (?,?,?)",
buildBatchParams(datas, item -> buildParams(
item.getValueAsString("username"),
item.getValueAsString("usage_date"),
item.getValueAsString("usage_duration"))),
400);
long sum = Numbers.sum(ArrayTools.concatIntArray(result));
assertEquals(datas.size(), sum);
log.info("sum: {}", sum);
}
catch (Exception e) {
e.printStackTrace();
throw e;
}
}
@Test
void testBean() throws Exception {
Optional<TestBean> t = jdbcTemplate.queryFirst(
"SELECT * FROM test_table WHERE id = ?",
buildParams(22915),
RowMapper.beanRowMapper(TestBean.class));
log.info("t: {}", t);
Optional<AccountPO> t = jdbcTemplate.queryFirst(
"SELECT * FROM sys_account WHERE id = ?",
buildParams(18L),
RowMapper.beanRowMapper(AccountPO.class));
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);
}
}
class TestBean {
class AccountPO {
Long id;
String username;
LocalDate usageDate;
Long usageDuration;
String accountStatus;
LocalDateTime createTime;
Long createdBy;
LocalDateTime updateTime;
Long updatedBy;
Long version;
public AccountPO() {
}
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;
@ -253,25 +288,78 @@ class TestBean {
this.username = username;
}
public LocalDate getUsageDate() {
return usageDate;
public String getAccountStatus() {
return accountStatus;
}
public void setUsageDate(LocalDate usageDate) {
this.usageDate = usageDate;
public void setAccountStatus(String accountStatus) {
this.accountStatus = accountStatus;
}
public Long getUsageDuration() {
return usageDuration;
public LocalDateTime getCreateTime() {
return createTime;
}
public void setUsageDuration(Long usageDuration) {
this.usageDuration = usageDuration;
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 "TestBean [id=" + id + ", username=" + username + ", usageDate=" + usageDate + ", usageDuration="
+ usageDuration + "]";
return "AccountPO [id=" + id + ", username=" + username + ", accountStatus=" + accountStatus + ", createTime="
+ createTime + ", createdBy=" + createdBy + ", updateTime=" + updateTime + ", updatedBy=" + updatedBy
+ ", version=" + version + "]";
}
}