mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add filter
This commit is contained in:
parent
ebf1632f36
commit
8205a8b03a
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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<>();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
// 无变量对应值,原样输出
|
||||
|
@ -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")) {
|
||||
// 插入默认返回主键
|
||||
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
Loading…
x
Reference in New Issue
Block a user