add filter

This commit is contained in:
Looly 2024-01-10 00:48:21 +08:00
parent ebf1632f36
commit 8205a8b03a
9 changed files with 208 additions and 84 deletions

View File

@ -12,14 +12,15 @@
package org.dromara.hutool.db;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.iter.ArrayIter;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.db.handler.ResultSetUtil;
import org.dromara.hutool.db.handler.RsHandler;
import org.dromara.hutool.db.sql.SqlBuilder;
import org.dromara.hutool.db.sql.SqlLog;
import org.dromara.hutool.db.sql.StatementBuilder;
import org.dromara.hutool.db.sql.StatementWrapper;
import org.dromara.hutool.db.sql.filter.SqlLogFilter;
import java.sql.*;
import java.util.Collection;
@ -87,7 +88,7 @@ public class StatementUtil {
return StatementBuilder.of()
.setConnection(conn)
.setReturnGeneratedKey(returnGeneratedKey)
.setSqlLog(SqlLog.INSTANCE)
.setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql)
.setParams(params)
.build();
@ -120,29 +121,10 @@ public class StatementUtil {
return StatementBuilder.of()
.setConnection(conn)
.setReturnGeneratedKey(false)
.setSqlLog(SqlLog.INSTANCE)
.setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql)
.buildForBatch(paramsBatch);
}
/**
* 创建批量操作的{@link PreparedStatement}
*
* @param conn 数据库连接
* @param sql SQL语句使用"?"做为占位符
* @param fields 字段列表用于获取对应值
* @param entities "?"对应参数批次列表
* @return {@link PreparedStatement}
* @since 4.6.7
*/
public static PreparedStatement prepareStatementForBatch(final Connection conn, final String sql,
final Iterable<String> fields, final Entity... entities) {
return StatementBuilder.of()
.setConnection(conn)
.setReturnGeneratedKey(false)
.setSqlLog(SqlLog.INSTANCE)
.setSql(sql)
.buildForBatch(fields, entities);
.setParams(ArrayUtil.ofArray(paramsBatch, Object.class))
.buildForBatch();
}
/**
@ -158,7 +140,7 @@ public class StatementUtil {
public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException {
return StatementBuilder.of()
.setConnection(conn)
.setSqlLog(SqlLog.INSTANCE)
.setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql)
.setParams(params)
.buildForCall();

View File

@ -53,21 +53,21 @@ public class AnsiSqlDialect implements Dialect {
}
@Override
public PreparedStatement psForInsert(final Connection conn, final Entity entity) throws SQLException {
public PreparedStatement psForInsert(final Connection conn, final Entity entity) {
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName());
return StatementUtil.prepareStatement(conn, insert);
}
@Override
public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) throws SQLException {
public PreparedStatement psForInsertBatch(final Connection conn, final Entity... entities) {
if (ArrayUtil.isEmpty(entities)) {
throw new DbRuntimeException("Entities for batch insert is empty !");
}
// 批量根据第一行数据结构生成SQL占位符
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName());
final Set<String> fields = CollUtil.remove(entities[0].keySet(), StrUtil::isBlank);
return StatementUtil.prepareStatementForBatch(conn, insert.build(), fields, entities);
return StatementUtil.prepareStatementForBatch(conn, insert.build(), entities);
}
@Override
@ -116,7 +116,7 @@ public class AnsiSqlDialect implements Dialect {
}
@Override
public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) throws SQLException {
public PreparedStatement psForPage(final Connection conn, SqlBuilder sqlBuilder, final Page page) {
// 根据不同数据库在查询SQL语句基础上包装其分页的语句
if (null != page) {
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);

View File

@ -19,6 +19,7 @@ import org.dromara.hutool.core.map.SafeConcurrentHashMap;
import org.dromara.hutool.core.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.db.DbRuntimeException;
import org.dromara.hutool.db.DbUtil;
import org.dromara.hutool.db.GlobalDbConfig;
import org.dromara.hutool.db.driver.DriverUtil;
import org.dromara.hutool.log.LogUtil;
@ -89,6 +90,7 @@ public class DSPool implements Closeable {
*/
public DSPool(final Setting setting, final DSFactory factory) {
this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting();
DbUtil.setShowSqlGlobal(this.setting);
this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class);
this.pool = new SafeConcurrentHashMap<>();
}

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.db.sql;
import java.util.ArrayList;
import java.util.List;
/**
@ -22,8 +23,13 @@ import java.util.List;
*/
public class BoundSql {
protected String sql;
protected final List<Object> params;
private String sql;
private List<Object> params;
/**
* 构造
*/
public BoundSql() {}
/**
* 构造
@ -45,6 +51,17 @@ public class BoundSql {
return this.sql;
}
/**
* 设置SQL语句
*
* @param sql SQL语句
* @return this
*/
public BoundSql setSql(final String sql) {
this.sql = sql;
return this;
}
/**
* 获取参数列表按照占位符顺序
*
@ -62,4 +79,29 @@ public class BoundSql {
public Object[] getParamArray() {
return this.params.toArray(new Object[0]);
}
/**
* 设置参数列表
*
* @param params 参数列表
* @return this
*/
public BoundSql setParams(final List<Object> params) {
this.params = params;
return this;
}
/**
* 增加参数
*
* @param paramValue 参数值
* @return this
*/
public BoundSql addParam(final Object paramValue){
if(null == this.params){
this.params = new ArrayList<>();
}
this.params.add(paramValue);
return this;
}
}

View File

@ -17,7 +17,6 @@ import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.StrUtil;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Map;
/**
@ -46,7 +45,6 @@ public class NamedSql extends BoundSql {
* @param paramMap 名和参数的对应Map
*/
public NamedSql(final String namedSql, final Map<String, Object> paramMap) {
super(null, new LinkedList<>());
this.namedSql = namedSql;
this.paramMap = paramMap;
parse(namedSql, paramMap);
@ -78,7 +76,7 @@ public class NamedSql extends BoundSql {
*/
private void parse(final String namedSql, final Map<String, Object> paramMap) {
if (MapUtil.isEmpty(paramMap)) {
this.sql = namedSql;
setSql(namedSql);
return;
}
@ -116,7 +114,7 @@ public class NamedSql extends BoundSql {
replaceVar(nameStartChar, name, sqlBuilder, paramMap);
}
this.sql = sqlBuilder.toString();
setSql(sqlBuilder.toString());
}
/**
@ -155,11 +153,11 @@ public class NamedSql extends BoundSql {
sqlBuilder.append(',');
}
sqlBuilder.append('?');
this.params.add(ArrayUtil.get(paramValue, i));
addParam(ArrayUtil.get(paramValue, i));
}
} else {
sqlBuilder.append('?');
this.params.add(paramValue);
addParam(paramValue);
}
} else {
// 无变量对应值原样输出

View File

@ -13,17 +13,19 @@
package org.dromara.hutool.db.sql;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.collection.ListUtil;
import org.dromara.hutool.core.collection.iter.ArrayIter;
import org.dromara.hutool.core.convert.Convert;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.builder.Builder;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.db.DbRuntimeException;
import org.dromara.hutool.db.Entity;
import org.dromara.hutool.db.sql.filter.SqlFilter;
import java.sql.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -44,20 +46,19 @@ public class StatementBuilder implements Builder<StatementWrapper> {
return new StatementBuilder();
}
private SqlLog sqlLog;
private final BoundSql boundSql = new BoundSql();
private Connection connection;
private String sql;
private Object[] params;
private boolean returnGeneratedKey = true;
private SqlFilter sqlFilter;
/**
* 设置SQL日志
*
* @param sqlLog {@link SqlLog}
* @param sqlFilter {@link SqlFilter}
* @return this
*/
public StatementBuilder setSqlLog(final SqlLog sqlLog) {
this.sqlLog = sqlLog;
public StatementBuilder setSqlFilter(final SqlFilter sqlFilter) {
this.sqlFilter = sqlFilter;
return this;
}
@ -79,7 +80,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
* @return this
*/
public StatementBuilder setSql(final String sql) {
this.sql = StrUtil.trim(sql);
this.boundSql.setSql(sql);
return this;
}
@ -90,7 +91,7 @@ public class StatementBuilder implements Builder<StatementWrapper> {
* @return this
*/
public StatementBuilder setParams(final Object... params) {
this.params = params;
this.boundSql.setParams(ListUtil.of(params));
return this;
}
@ -105,6 +106,11 @@ public class StatementBuilder implements Builder<StatementWrapper> {
return this;
}
/**
* 构建{@link StatementWrapper}
*
* @return {@link StatementWrapper}{@code null}表示不执行
*/
@Override
public StatementWrapper build() {
try {
@ -117,48 +123,29 @@ public class StatementBuilder implements Builder<StatementWrapper> {
/**
* 创建批量操作的{@link StatementWrapper}
*
* @param paramsBatch "?"对应参数批次列表
* @return {@link StatementWrapper}
* @return {@link StatementWrapper}{@code null}表示不执行
* @throws DbRuntimeException SQL异常
*/
public StatementWrapper buildForBatch(final Iterable<Object[]> paramsBatch) throws DbRuntimeException {
public StatementWrapper buildForBatch() throws DbRuntimeException {
final String sql = this.boundSql.getSql();
Assert.notBlank(sql, "Sql String must be not blank!");
final List<Object> paramsBatch = this.boundSql.getParams();
sqlLog.log(sql, paramsBatch);
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
final StatementWrapper ps;
try {
ps = StatementWrapper.of(connection.prepareStatement(sql));
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
for (final Object[] params : paramsBatch) {
ps.fillParams(new ArrayIter<>(params), nullTypeMap);
ps.addBatch();
}
} catch (final SQLException e) {
throw new DbRuntimeException(e);
}
return ps;
}
/**
* 创建批量操作的{@link StatementWrapper}
*
* @param fields 字段列表用于获取对应值
* @param entities "?"对应参数批次列表
* @return {@link StatementWrapper}
* @throws DbRuntimeException SQL异常
*/
public StatementWrapper buildForBatch(final Iterable<String> fields, final Entity... entities) throws DbRuntimeException {
Assert.notBlank(sql, "Sql String must be not blank!");
sqlLog.logForBatch(sql);
final StatementWrapper ps;
try {
ps = StatementWrapper.of(connection.prepareStatement(sql));
final Map<Integer, Integer> nullTypeMap = new HashMap<>();
for (final Entity entity : entities) {
ps.fillParams(MapUtil.valuesOfKeys(entity, fields), nullTypeMap);
for (final Object params : paramsBatch) {
if (null == params) {
continue;
}
if (ArrayUtil.isArray(params)) {
ps.fillParams(new ArrayIter<>(params), nullTypeMap);
} else if (params instanceof Entity) {
ps.fillParams(((Entity) params).values(), nullTypeMap);
}
ps.addBatch();
}
} catch (final SQLException e) {
@ -170,12 +157,15 @@ public class StatementBuilder implements Builder<StatementWrapper> {
/**
* 创建存储过程或函数调用的{@link StatementWrapper}
*
* @return StatementWrapper
* @return StatementWrapper{@code null}表示不执行
* @since 6.0.0
*/
public CallableStatement buildForCall() {
final String sql = this.boundSql.getSql();
final Object[] params = this.boundSql.getParamArray();
Assert.notBlank(sql, "Sql String must be not blank!");
sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params);
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
try {
return (CallableStatement) StatementWrapper
@ -190,20 +180,23 @@ public class StatementBuilder implements Builder<StatementWrapper> {
/**
* 构建{@link StatementWrapper}
*
* @return {@link StatementWrapper}
* @return {@link StatementWrapper}{@code null}表示不执行
* @throws SQLException SQL异常
*/
private StatementWrapper _build() throws SQLException {
String sql = this.boundSql.getSql();
Object[] params = this.boundSql.getParamArray();
Assert.notBlank(sql, "Sql String must be not blank!");
if (ArrayUtil.isNotEmpty(params) && 1 == params.length && params[0] instanceof Map) {
// 检查参数是否为命名方式的参数
final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0]));
final NamedSql namedSql = new NamedSql(sql, Convert.toMap(String.class, Object.class, params[0]));
sql = namedSql.getSql();
params = namedSql.getParamArray();
}
sqlLog.log(sql, ArrayUtil.isEmpty(params) ? null : params);
sqlFilter.filter(this.connection, this.boundSql, this.returnGeneratedKey);
final PreparedStatement ps;
if (returnGeneratedKey && StrUtil.startWithIgnoreCase(sql, "insert")) {
// 插入默认返回主键

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.db.sql.filter;
import org.dromara.hutool.db.sql.BoundSql;
import java.sql.Connection;
import java.util.List;
/**
* SQL拦截器
*/
public interface SqlFilter {
/**
* 过滤
*
* @param conn {@link Connection}
* @param boundSql {@link BoundSql}包含SQL语句和参数
* 可通过{@link BoundSql#setSql(String)}{@link BoundSql#setParams(List)} 自定义SQL和参数
* @param returnGeneratedKey 是否自动生成主键
*/
void filter(Connection conn, BoundSql boundSql, boolean returnGeneratedKey);
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.db.sql.filter;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.db.sql.BoundSql;
import org.dromara.hutool.db.sql.SqlLog;
import java.sql.Connection;
/**
* SQL打印拦截器
*
* @author Looly
*/
public class SqlLogFilter implements SqlFilter {
/**
* 单例
*/
public static final SqlLogFilter INSTANCE = new SqlLogFilter();
private final SqlLog sqlLog;
/**
* 构造使用默认SqlLog
*/
public SqlLogFilter() {
this(SqlLog.INSTANCE);
}
/**
* 构造
*
* @param sqlLog {@link SqlLog}
*/
public SqlLogFilter(final SqlLog sqlLog) {
this.sqlLog = sqlLog;
}
@Override
public void filter(final Connection conn, final BoundSql boundSql, final boolean returnGeneratedKey) {
sqlLog.log(boundSql.getSql(), boundSql.getParams());
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2024. looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* https://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
/**
* 提供SQL过滤器封装
*
* @author Looly
*/
package org.dromara.hutool.db.sql.filter;