diff --git a/CHANGELOG.md b/CHANGELOG.md index 789d79960..efaec628e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * 【core 】 IdcardUtil增加getIdcardInfo方法(issue#1092@Github) * 【core 】 改进ObjectUtil.equal,支持BigDecimal判断 * 【core 】 ArrayConverter增加可选是否忽略错误(issue#I1VNYQ@Gitee) +* 【db 】 增加ConditionBuilder ### Bug修复 * 【core 】 修复Dict.of错误(issue#I1UUO5@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java index 69ebe3fe0..fef0bfac6 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/ListUtil.java @@ -478,6 +478,9 @@ public class ListUtil { * @since 5.2.6 */ public static List unmodifiable(List list) { + if(null == list){ + return null; + } return Collections.unmodifiableList(list); } diff --git a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java index 3cf9bdadf..86a05ba3b 100644 --- a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java +++ b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java @@ -10,6 +10,7 @@ import cn.hutool.db.handler.RsHandler; import cn.hutool.db.handler.StringHandler; import cn.hutool.db.sql.Condition; import cn.hutool.db.sql.Condition.LikeType; +import cn.hutool.db.sql.LogicalOperator; import cn.hutool.db.sql.Query; import cn.hutool.db.sql.SqlExecutor; import cn.hutool.db.sql.SqlUtil; @@ -628,7 +629,7 @@ public abstract class AbstractDb implements Serializable { * 根据多个条件查询数据列表,返回所有字段 * * @param tableName 表名 - * @param wheres 字段名 + * @param wheres 条件,多个条件的连接逻辑使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 * @return 数据对象列表 * @throws SQLException SQL执行异常 * @since 4.0.0 diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java index 055120621..03b58dda1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java @@ -11,7 +11,6 @@ import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.Dialect; import cn.hutool.db.dialect.DialectName; import cn.hutool.db.sql.Condition; -import cn.hutool.db.sql.LogicalOperator; import cn.hutool.db.sql.Query; import cn.hutool.db.sql.SqlBuilder; import cn.hutool.db.sql.Wrapper; @@ -67,7 +66,7 @@ public class AnsiSqlDialect implements Dialect { // 对于无条件的删除语句直接抛出异常禁止,防止误删除 throw new SQLException("No 'WHERE' condition, we can't prepared statement for delete everything."); } - final SqlBuilder delete = SqlBuilder.create(wrapper).delete(query.getFirstTableName()).where(LogicalOperator.AND, where); + final SqlBuilder delete = SqlBuilder.create(wrapper).delete(query.getFirstTableName()).where(where); return StatementUtil.prepareStatement(conn, delete); } @@ -76,13 +75,13 @@ public class AnsiSqlDialect implements Dialect { public PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException { Assert.notNull(query, "query must not be null !"); - Condition[] where = query.getWhere(); + final Condition[] where = query.getWhere(); if (ArrayUtil.isEmpty(where)) { // 对于无条件的删除语句直接抛出异常禁止,防止误删除 throw new SQLException("No 'WHERE' condition, we can't prepare statement for update everything."); } - final SqlBuilder update = SqlBuilder.create(wrapper).update(entity).where(LogicalOperator.AND, where); + final SqlBuilder update = SqlBuilder.create(wrapper).update(entity).where(where); return StatementUtil.prepareStatement(conn, update); } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java index 017a2113a..d624f4242 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Condition.java @@ -68,6 +68,11 @@ public class Condition extends CloneSupport { */ private Object secondValue; + /** + * 与前一个Condition连接的逻辑运算符,可以是and或or + */ + private LogicalOperator linkOperator = LogicalOperator.AND; + /** * 解析为Condition * @@ -282,6 +287,26 @@ public class Condition extends CloneSupport { this.secondValue = secondValue; } + /** + * 获取与前一个Condition连接的逻辑运算符,可以是and或or + * + * @return 与前一个Condition连接的逻辑运算符,可以是and或or + * @since 5.4.3 + */ + public LogicalOperator getLinkOperator() { + return linkOperator; + } + + /** + * 设置与前一个Condition连接的逻辑运算符,可以是and或or + * + * @param linkOperator 与前一个Condition连接的逻辑运算符,可以是and或or + * @since 5.4.3 + */ + public void setLinkOperator(LogicalOperator linkOperator) { + this.linkOperator = linkOperator; + } + // --------------------------------------------------------------- Getters and Setters end @Override diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/ConditionBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionBuilder.java new file mode 100644 index 000000000..9f5df4d7d --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/sql/ConditionBuilder.java @@ -0,0 +1,116 @@ +package cn.hutool.db.sql; + +import cn.hutool.core.builder.Builder; +import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.CharUtil; +import cn.hutool.core.util.StrUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * 多条件构建封装
+ * 可以将多个条件构建为SQL语句的一部分,并将参数值转换为占位符,并提取对应位置的参数值。
+ * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @author looly + * @since 5.4.3 + */ +public class ConditionBuilder implements Builder { + private static final long serialVersionUID = 1L; + + /** + * 创建构建器 + * + * @param conditions 条件列表 + * @return {@link ConditionBuilder} + */ + public static ConditionBuilder of(Condition... conditions) { + return new ConditionBuilder(conditions); + } + + /** + * 条件数组 + */ + private final Condition[] conditions; + /** + * 占位符对应的值列表 + */ + private List paramValues; + + /** + * 构造 + * + * @param conditions 条件列表 + */ + public ConditionBuilder(Condition... conditions) { + this.conditions = conditions; + } + + /** + * 返回构建后的参数列表
+ * 此方法调用前必须调用{@link #build()} + * + * @return 参数列表 + */ + public List getParamValues() { + return ListUtil.unmodifiable(this.paramValues); + } + + /** + * 构建组合条件
+ * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @return 构建后的SQL语句条件部分 + */ + @Override + public String build() { + if(null == this.paramValues){ + this.paramValues = new ArrayList<>(); + } else { + this.paramValues.clear(); + } + return build(this.paramValues); + } + + /** + * 构建组合条件
+ * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @param paramValues 用于写出参数的List,构建时会将参数写入此List + * @return 构建后的SQL语句条件部分 + */ + public String build(List paramValues) { + if (ArrayUtil.isEmpty(conditions)) { + return StrUtil.EMPTY; + } + + final StringBuilder conditionStrBuilder = new StringBuilder(); + boolean isFirst = true; + for (Condition condition : conditions) { + // 添加逻辑运算符 + if (isFirst) { + isFirst = false; + } else { + // " AND " 或者 " OR " + conditionStrBuilder.append(CharUtil.SPACE).append(condition.getLinkOperator()).append(CharUtil.SPACE); + } + + // 构建条件部分:"name = ?"、"name IN (?,?,?)"、"name BETWEEN ?AND ?"、"name LIKE ?" + conditionStrBuilder.append(condition.toString(paramValues)); + } + return conditionStrBuilder.toString(); + } + + /** + * 构建组合条件
+ * 例如:name = ? AND type IN (?, ?) AND other LIKE ? + * + * @return 构建后的SQL语句条件部分 + */ + @Override + public String toString() { + return build(); + } +} diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java index 433c62ab9..893a6dae9 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java @@ -67,11 +67,11 @@ public class SqlBuilder implements Builder{ } // --------------------------------------------------------------- Enums end - final private StringBuilder sql = new StringBuilder(); + private final StringBuilder sql = new StringBuilder(); /** 字段列表(仅用于插入和更新) */ - final private List fields = new ArrayList<>(); + private final List fields = new ArrayList<>(); /** 占位符对应的值列表 */ - final private List paramValues = new ArrayList<>(); + private final List paramValues = new ArrayList<>(); /** 包装器 */ private Wrapper wrapper; @@ -284,14 +284,18 @@ public class SqlBuilder implements Builder{ } /** - * 添加Where语句,所有逻辑之间为AND的关系 + * 添加Where语句,所有逻辑之间关系使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 * * @param conditions 条件,当条件为空时,只添加WHERE关键字 * @return 自己 * @since 4.4.4 */ public SqlBuilder where(Condition... conditions) { - return where(LogicalOperator.AND, conditions); + if (ArrayUtil.isNotEmpty(conditions)) { + where(buildCondition(conditions)); + } + + return this; } /** @@ -301,17 +305,11 @@ public class SqlBuilder implements Builder{ * @param logicalOperator 逻辑运算符 * @param conditions 条件,当条件为空时,只添加WHERE关键字 * @return 自己 + * @deprecated logicalOperator放在Condition中了,因此请使用 {@link #where(Condition...)} */ + @Deprecated public SqlBuilder where(LogicalOperator logicalOperator, Condition... conditions) { - if (ArrayUtil.isNotEmpty(conditions)) { - if (null != wrapper) { - // 包装字段名 - conditions = wrapper.wrap(conditions); - } - where(buildCondition(logicalOperator, conditions)); - } - - return this; + return where(conditions); } /** @@ -366,14 +364,23 @@ public class SqlBuilder implements Builder{ * @param logicalOperator 逻辑运算符 * @param conditions 条件 * @return 自己 + * @deprecated logicalOperator放在Condition中了,因此请使用 {@link #having(Condition...)} */ + @Deprecated public SqlBuilder having(LogicalOperator logicalOperator, Condition... conditions) { + return having(conditions); + } + + /** + * 添加Having语句,所有逻辑之间关系使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 + * + * @param conditions 条件 + * @return this + * @since 5.4.3 + */ + public SqlBuilder having(Condition... conditions) { if (ArrayUtil.isNotEmpty(conditions)) { - if (null != wrapper) { - // 包装字段名 - conditions = wrapper.wrap(conditions); - } - having(buildCondition(logicalOperator, conditions)); + having(buildCondition(conditions)); } return this; @@ -461,14 +468,23 @@ public class SqlBuilder implements Builder{ * @param logicalOperator 逻辑运算符 * @param conditions 条件 * @return 自己 + * @deprecated logicalOperator放在Condition中了,因此请使用 {@link #on(Condition...)} */ + @Deprecated public SqlBuilder on(LogicalOperator logicalOperator, Condition... conditions) { + return on(conditions); + } + + /** + * 配合JOIN的 ON语句,多表关联的条件语句,所有逻辑之间关系使用{@link Condition#setLinkOperator(LogicalOperator)} 定义 + * + * @param conditions 条件 + * @return this + * @since 5.4.3 + */ + public SqlBuilder on(Condition... conditions) { if (ArrayUtil.isNotEmpty(conditions)) { - if (null != wrapper) { - // 包装字段名 - conditions = wrapper.wrap(conditions); - } - on(buildCondition(logicalOperator, conditions)); + on(buildCondition(conditions)); } return this; @@ -582,34 +598,20 @@ public class SqlBuilder implements Builder{ * 构建组合条件
* 例如:name = ? AND type IN (?, ?) AND other LIKE ? * - * @param logicalOperator 逻辑运算符 * @param conditions 条件对象 * @return 构建后的SQL语句条件部分 */ - private String buildCondition(LogicalOperator logicalOperator, Condition... conditions) { + private String buildCondition(Condition... conditions) { if (ArrayUtil.isEmpty(conditions)) { return StrUtil.EMPTY; } - if (null == logicalOperator) { - logicalOperator = LogicalOperator.AND; + + if (null != wrapper) { + // 包装字段名 + conditions = wrapper.wrap(conditions); } - final StringBuilder conditionStrBuilder = new StringBuilder(); - boolean isFirst = true; - for (Condition condition : conditions) { - // 添加逻辑运算符 - if (isFirst) { - isFirst = false; - } else { - // " AND " 或者 " OR " - conditionStrBuilder.append(StrUtil.SPACE).append(logicalOperator).append(StrUtil.SPACE); - } - - // 构建条件部分:"name = ?"、"name IN (?,?,?)"、"name BETWEEN ?AND ?"、"name LIKE ?" - conditionStrBuilder.append(condition.toString(this.paramValues)); - } - - return conditionStrBuilder.toString(); + return ConditionBuilder.of(conditions).build(this.paramValues); } /** diff --git a/hutool-db/src/test/java/cn/hutool/db/sql/ConditionBuilderTest.java b/hutool-db/src/test/java/cn/hutool/db/sql/ConditionBuilderTest.java new file mode 100644 index 000000000..6cfc7e6c9 --- /dev/null +++ b/hutool-db/src/test/java/cn/hutool/db/sql/ConditionBuilderTest.java @@ -0,0 +1,21 @@ +package cn.hutool.db.sql; + +import org.junit.Assert; +import org.junit.Test; + +public class ConditionBuilderTest { + + @Test + public void buildTest(){ + Condition c1 = new Condition("user", null); + Condition c2 = new Condition("name", "!= null"); + c2.setLinkOperator(LogicalOperator.OR); + Condition c3 = new Condition("group", "like %aaa"); + + final ConditionBuilder builder = ConditionBuilder.of(c1, c2, c3); + final String sql = builder.build(); + Assert.assertEquals("user IS NULL OR name IS NOT NULL AND group LIKE ?", sql); + Assert.assertEquals(1, builder.getParamValues().size()); + Assert.assertEquals("%aaa", builder.getParamValues().get(0)); + } +}