{
return betweenIfPresent(column, val1, val2);
}
+ /**
+ * 拼接两个闭区间的重叠条件
+ *
+ * 记录区间与查询区间重叠的条件为:记录开始值小于等于查询结束值,
+ * 且记录结束值大于等于查询开始值。不能使用单字段 {@code BETWEEN} 代替,
+ * 否则会漏掉完整覆盖查询区间的记录。
+ *
+ * @param beginColumn 记录开始值字段
+ * @param endColumn 记录结束值字段
+ * @param times 查询闭区间
+ * @return 查询条件
+ */
+ public LambdaQueryWrapperX betweenIfPresent(
+ SFunction beginColumn, SFunction endColumn, LocalDateTime[] times) {
+ return leIfPresent(beginColumn, ArrayUtils.get(times, 1))
+ .geIfPresent(endColumn, ArrayUtils.get(times, 0));
+ }
+
// ========== 重写父类方法,方便链式调用 ==========
@Override
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java
index bff0122..525d575 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperX.java
@@ -102,16 +102,9 @@ public class QueryWrapperX extends QueryWrapper {
}
public QueryWrapperX betweenIfPresent(String column, Object[] values) {
- if (values != null && values.length != 0 && values[0] != null && values[1] != null) {
- return (QueryWrapperX) super.between(column, values[0], values[1]);
- }
- if (values != null && values.length != 0 && values[0] != null) {
- return (QueryWrapperX) ge(column, values[0]);
- }
- if (values != null && values.length != 0 && values[1] != null) {
- return (QueryWrapperX) le(column, values[1]);
- }
- return this;
+ Object val1 = values != null && values.length > 0 ? values[0] : null;
+ Object val2 = values != null && values.length > 1 ? values[1] : null;
+ return betweenIfPresent(column, val1, val2);
}
// ========== 重写父类方法,方便链式调用 ==========
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
index ab4612c..1cdf7f1 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/main/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtils.java
@@ -24,6 +24,8 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
/**
* MyBatis 工具类
@@ -179,6 +181,28 @@ public class MyBatisUtils {
return findInSet(columnName, paramIndex);
}
+ /**
+ * 跨数据库的 find_in_set 实现,适用于同一字段匹配多个参数的场景
+ *
+ * 每个参数生成一个 find_in_set 条件,并使用 OR 连接。
+ *
+ * @param columnName 字段名称
+ * @param values 参数集合
+ * @return sql
+ */
+ public static String findInSet(String columnName, Collection> values) {
+ return findInSet(JdbcUtils.getDbType(), columnName, values);
+ }
+
+ static String findInSet(DbType dbType, String columnName, Collection> values) {
+ if (CollUtil.isEmpty(values)) {
+ throw new IllegalArgumentException("Values cannot be empty");
+ }
+ return IntStream.range(0, values.size())
+ .mapToObj(index -> findInSet(dbType, columnName, index))
+ .collect(Collectors.joining(" OR "));
+ }
+
private static String findInSet(String columnName, int paramIndex) {
DbType dbType = JdbcUtils.getDbType();
return findInSet(dbType, columnName, paramIndex);
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperXTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperXTest.java
new file mode 100644
index 0000000..e708e96
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/query/LambdaQueryWrapperXTest.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.framework.mybatis.core.query;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.core.MybatisConfiguration;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import lombok.Data;
+import org.apache.ibatis.builder.MapperBuilderAssistant;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link LambdaQueryWrapperX} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class LambdaQueryWrapperXTest {
+
+ @BeforeAll
+ public static void initTableInfo() {
+ TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), TestDO.class);
+ }
+
+ @Test
+ public void testBetweenIfPresent_overlap() {
+ // 调用,并断言:空范围不追加条件
+ assertEquals("", new LambdaQueryWrapperX()
+ .betweenIfPresent(TestDO::getStartTime, TestDO::getEndTime, null)
+ .getSqlSegment());
+
+ // 调用,并断言:双边范围使用闭区间重叠条件
+ LocalDateTime beginTime = LocalDateTime.of(2026, 8, 1, 0, 0);
+ LocalDateTime endTime = LocalDateTime.of(2026, 8, 31, 23, 59);
+ String sqlSegment = new LambdaQueryWrapperX()
+ .betweenIfPresent(TestDO::getStartTime, TestDO::getEndTime,
+ new LocalDateTime[]{beginTime, endTime})
+ .getSqlSegment();
+ assertTrue(sqlSegment.contains("start_time <="));
+ assertTrue(sqlSegment.contains("end_time >="));
+
+ // 调用,并断言:单边范围只追加对应的重叠边界
+ sqlSegment = new LambdaQueryWrapperX()
+ .betweenIfPresent(TestDO::getStartTime, TestDO::getEndTime,
+ new LocalDateTime[]{beginTime})
+ .getSqlSegment();
+ assertTrue(sqlSegment.contains("end_time >="));
+ sqlSegment = new LambdaQueryWrapperX()
+ .betweenIfPresent(TestDO::getStartTime, TestDO::getEndTime,
+ new LocalDateTime[]{null, endTime})
+ .getSqlSegment();
+ assertTrue(sqlSegment.contains("start_time <="));
+ }
+
+ @Data
+ @TableName("test_data")
+ private static class TestDO {
+
+ private LocalDateTime startTime;
+ private LocalDateTime endTime;
+
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperXTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperXTest.java
new file mode 100644
index 0000000..09a2674
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/query/QueryWrapperXTest.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.framework.mybatis.core.query;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * {@link QueryWrapperX} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class QueryWrapperXTest {
+
+ @Test
+ public void testBetweenIfPresent_array() {
+ // 调用,并断言:空范围不追加条件
+ assertEquals("", new QueryWrapperX<>().betweenIfPresent("create_time", (Object[]) null).getSqlSegment());
+ assertEquals("", new QueryWrapperX<>().betweenIfPresent("create_time", new Object[0]).getSqlSegment());
+
+ // 调用,并断言:单边范围安全降级为大于等于或小于等于
+ assertTrue(new QueryWrapperX<>().betweenIfPresent("create_time", new Object[]{1})
+ .getSqlSegment().contains("create_time >="));
+ assertTrue(new QueryWrapperX<>().betweenIfPresent("create_time", new Object[]{null, 2})
+ .getSqlSegment().contains("create_time <="));
+
+ // 调用,并断言:双边范围使用左右闭合的 BETWEEN
+ assertTrue(new QueryWrapperX<>().betweenIfPresent("create_time", new Object[]{1, 2})
+ .getSqlSegment().contains("create_time BETWEEN"));
+ }
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java
index abe8891..b74558c 100644
--- a/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java
+++ b/yudao-framework/yudao-spring-boot-starter-mybatis/src/test/java/cn/iocoder/yudao/framework/mybatis/core/util/MyBatisUtilsTest.java
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -129,6 +130,14 @@ public class MyBatisUtilsTest {
}
@Test
+ public void testFindInSet_multipleValues() {
+ assertEquals("FIND_IN_SET({0}, tag_ids) <> 0 OR FIND_IN_SET({1}, tag_ids) <> 0",
+ MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", Arrays.asList(1L, 2L)));
+ assertThrows(IllegalArgumentException.class,
+ () -> MyBatisUtils.findInSet(DbType.MYSQL, "tag_ids", Collections.emptyList()));
+ }
+
+ @Test
public void testFindInSet_applyBindsValue() {
// 准备参数
QueryWrapper