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; 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.collection.iter.ArrayIter;
import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.db.handler.ResultSetUtil; import org.dromara.hutool.db.handler.ResultSetUtil;
import org.dromara.hutool.db.handler.RsHandler; import org.dromara.hutool.db.handler.RsHandler;
import org.dromara.hutool.db.sql.SqlBuilder; 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.StatementBuilder;
import org.dromara.hutool.db.sql.StatementWrapper; import org.dromara.hutool.db.sql.StatementWrapper;
import org.dromara.hutool.db.sql.filter.SqlLogFilter;
import java.sql.*; import java.sql.*;
import java.util.Collection; import java.util.Collection;
@ -87,7 +88,7 @@ public class StatementUtil {
return StatementBuilder.of() return StatementBuilder.of()
.setConnection(conn) .setConnection(conn)
.setReturnGeneratedKey(returnGeneratedKey) .setReturnGeneratedKey(returnGeneratedKey)
.setSqlLog(SqlLog.INSTANCE) .setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql) .setSql(sql)
.setParams(params) .setParams(params)
.build(); .build();
@ -120,29 +121,10 @@ public class StatementUtil {
return StatementBuilder.of() return StatementBuilder.of()
.setConnection(conn) .setConnection(conn)
.setReturnGeneratedKey(false) .setReturnGeneratedKey(false)
.setSqlLog(SqlLog.INSTANCE) .setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql) .setSql(sql)
.buildForBatch(paramsBatch); .setParams(ArrayUtil.ofArray(paramsBatch, Object.class))
} .buildForBatch();
/**
* 创建批量操作的{@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);
} }
/** /**
@ -158,7 +140,7 @@ public class StatementUtil {
public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException { public static CallableStatement prepareCall(final Connection conn, final String sql, final Object... params) throws SQLException {
return StatementBuilder.of() return StatementBuilder.of()
.setConnection(conn) .setConnection(conn)
.setSqlLog(SqlLog.INSTANCE) .setSqlFilter(SqlLogFilter.INSTANCE)
.setSql(sql) .setSql(sql)
.setParams(params) .setParams(params)
.buildForCall(); .buildForCall();

View File

@ -53,21 +53,21 @@ public class AnsiSqlDialect implements Dialect {
} }
@Override @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()); final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entity, this.dialectName());
return StatementUtil.prepareStatement(conn, insert); return StatementUtil.prepareStatement(conn, insert);
} }
@Override @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)) { if (ArrayUtil.isEmpty(entities)) {
throw new DbRuntimeException("Entities for batch insert is empty !"); throw new DbRuntimeException("Entities for batch insert is empty !");
} }
// 批量根据第一行数据结构生成SQL占位符 // 批量根据第一行数据结构生成SQL占位符
final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName()); final SqlBuilder insert = SqlBuilder.of(quoteWrapper).insert(entities[0], this.dialectName());
final Set<String> fields = CollUtil.remove(entities[0].keySet(), StrUtil::isBlank); 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 @Override
@ -116,7 +116,7 @@ public class AnsiSqlDialect implements Dialect {
} }
@Override @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语句基础上包装其分页的语句 // 根据不同数据库在查询SQL语句基础上包装其分页的语句
if (null != page) { if (null != page) {
sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), 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.spi.SpiUtil;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.db.DbRuntimeException; import org.dromara.hutool.db.DbRuntimeException;
import org.dromara.hutool.db.DbUtil;
import org.dromara.hutool.db.GlobalDbConfig; import org.dromara.hutool.db.GlobalDbConfig;
import org.dromara.hutool.db.driver.DriverUtil; import org.dromara.hutool.db.driver.DriverUtil;
import org.dromara.hutool.log.LogUtil; import org.dromara.hutool.log.LogUtil;
@ -89,6 +90,7 @@ public class DSPool implements Closeable {
*/ */
public DSPool(final Setting setting, final DSFactory factory) { public DSPool(final Setting setting, final DSFactory factory) {
this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting(); this.setting = null != setting ? setting : GlobalDbConfig.createDbSetting();
DbUtil.setShowSqlGlobal(this.setting);
this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class); this.factory = null != factory ? factory : SpiUtil.loadFirstAvailable(DSFactory.class);
this.pool = new SafeConcurrentHashMap<>(); this.pool = new SafeConcurrentHashMap<>();
} }

View File

@ -12,6 +12,7 @@
package org.dromara.hutool.db.sql; package org.dromara.hutool.db.sql;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -22,8 +23,13 @@ import java.util.List;
*/ */
public class BoundSql { public class BoundSql {
protected String sql; private String sql;
protected final List<Object> params; private List<Object> params;
/**
* 构造
*/
public BoundSql() {}
/** /**
* 构造 * 构造
@ -45,6 +51,17 @@ public class BoundSql {
return this.sql; 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() { public Object[] getParamArray() {
return this.params.toArray(new Object[0]); 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 org.dromara.hutool.core.text.StrUtil;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList;
import java.util.Map; import java.util.Map;
/** /**
@ -46,7 +45,6 @@ public class NamedSql extends BoundSql {
* @param paramMap 名和参数的对应Map * @param paramMap 名和参数的对应Map
*/ */
public NamedSql(final String namedSql, final Map<String, Object> paramMap) { public NamedSql(final String namedSql, final Map<String, Object> paramMap) {
super(null, new LinkedList<>());
this.namedSql = namedSql; this.namedSql = namedSql;
this.paramMap = paramMap; this.paramMap = paramMap;
parse(namedSql, paramMap); parse(namedSql, paramMap);
@ -78,7 +76,7 @@ public class NamedSql extends BoundSql {
*/ */
private void parse(final String namedSql, final Map<String, Object> paramMap) { private void parse(final String namedSql, final Map<String, Object> paramMap) {
if (MapUtil.isEmpty(paramMap)) { if (MapUtil.isEmpty(paramMap)) {
this.sql = namedSql; setSql(namedSql);
return; return;
} }
@ -116,7 +114,7 @@ public class NamedSql extends BoundSql {
replaceVar(nameStartChar, name, sqlBuilder, paramMap); 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(',');
} }
sqlBuilder.append('?'); sqlBuilder.append('?');
this.params.add(ArrayUtil.get(paramValue, i)); addParam(ArrayUtil.get(paramValue, i));
} }
} else { } else {
sqlBuilder.append('?'); sqlBuilder.append('?');
this.params.add(paramValue); addParam(paramValue);
} }
} else { } else {
// 无变量对应值原样输出 // 无变量对应值原样输出

View File

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