ruoyi-vue-pro 若依芋道 java springboot +mybatis 生日查询相关

ruoyi-vue-pro 若依芋道 java springboot +mybatis 生日查询相关

      • [方案一:使用 `DATE_ADD` 动态计算"下一个生日"(推荐,逻辑闭环)](#方案一:使用 DATE_ADD 动态计算“下一个生日”(推荐,逻辑闭环))
        • [1. MyBatis Mapper XML 代码](#1. MyBatis Mapper XML 代码)
        • [2. Java Mapper 接口](#2. Java Mapper 接口)
        • [3. 调用示例(Service 层)](#3. 调用示例(Service 层))
      • [方案二:使用虚拟列 + 索引(针对大数据量优化)](#方案二:使用虚拟列 + 索引(针对大数据量优化))
        • [1. 修改表结构(MySQL 5.7+)](#1. 修改表结构(MySQL 5.7+))
        • [2. 改造 MyBatis SQL(针对指定日期)](#2. 改造 MyBatis SQL(针对指定日期))
        • [3. 相对日期(今天/3天/30天)的跨年处理(使用 `birthday_mmdd`)](#3. 相对日期(今天/3天/30天)的跨年处理(使用 birthday_mmdd))
      • [关于 2月29日的特别说明](#关于 2月29日的特别说明)
      • 总结建议
  • [========================================== 优化:](#========================================== 优化:)
      • [一、数据库变更(MySQL 5.7+)](#一、数据库变更(MySQL 5.7+))
      • [二、VO 增加接收参数](#二、VO 增加接收参数)
      • [三、Service 层构建动态条件(核心)](#三、Service 层构建动态条件(核心))
        • [辅助方法:构建生日 SQL 条件](#辅助方法:构建生日 SQL 条件)
      • [四、关于 2月29日的特殊说明](#四、关于 2月29日的特殊说明)
      • 五、性能分析
      • 六、前端调用示例
      • [七、完整代码整合(你的 Service 方法改造后)](#七、完整代码整合(你的 Service 方法改造后))
  • [========================================== vue3代码:](#========================================== vue3代码:)

mysql user表中 生日字段birthday(datetime)(示例:2025-10-21 00:00:00),在springboot项目的mybatis中查询符合指定日期的数据 (前端查询条件为 示例1:今天、3天内、30天内生日的数据;示例2:指定日期 2026-9-8 00:00:00 符合该日期生日的数据)

在 Spring Boot + MyBatis 中处理 birthday(datetime) 的生日查询,核心难点是 "忽略年份,只比较月/日" ,同时处理 "跨年(如12月31日到1月2日)" 的场景。

针对你的需求(今天、3天内、30天内、指定日期),我推荐两种实现方案,方案一逻辑清晰通用方案二性能更优


方案一:使用 DATE_ADD 动态计算"下一个生日"(推荐,逻辑闭环)

核心思路:将数据库里的出生年份替换为当前年份,计算出"今年的生日 "和"明年的生日",然后判断这两个日期是否落在目标区间内。此方案能完美处理跨年(12月生日的用户,在1月查询3天内也能被查到)。

1. MyBatis Mapper XML 代码
xml 复制代码
<select id="selectUsersByBirthdayCondition" resultType="com.xxx.User">
    SELECT *
    FROM member_user
    <where>
        <choose>
            <!-- 1. 指定日期查询(如 2026-09-08) -->
            <when test="type == 'SPECIFIC' and specificDate != null">
                AND MONTH(birthday) = MONTH(#{specificDate})
                AND DAY(birthday) = DAY(#{specificDate})
            </when>

            <!-- 2. 今天 -->
            <when test="type == 'TODAY'">
                AND (
                    DATE_ADD(birthday, INTERVAL (YEAR(CURDATE()) - YEAR(birthday)) YEAR) = CURDATE()
                    OR
                    DATE_ADD(birthday, INTERVAL (YEAR(CURDATE()) - YEAR(birthday) + 1) YEAR) = CURDATE()
                )
            </when>

            <!-- 3. 3天内 -->
            <when test="type == 'WITHIN_3'">
                AND (
                    DATE_ADD(birthday, INTERVAL (YEAR(CURDATE()) - YEAR(birthday)) YEAR) 
                        BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY)
                    OR
                    DATE_ADD(birthday, INTERVAL (YEAR(CURDATE()) - YEAR(birthday) + 1) YEAR) 
                        BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 3 DAY)
                )
            </when>

            <!-- 4. 30天内 -->
            <when test="type == 'WITHIN_30'">
                AND (
                    DATE_ADD(birthday, INTERVAL (YEAR(CURDATE()) - YEAR(birthday)) YEAR) 
                        BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
                    OR
                    DATE_ADD(birthday, INTERVAL (YEAR(CURDATE()) - YEAR(birthday) + 1) YEAR) 
                        BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
                )
            </when>
        </choose>
    </where>
</select>
2. Java Mapper 接口
java 复制代码
List<User> selectUsersByBirthdayCondition(@Param("type") String type, 
                                          @Param("specificDate") LocalDateTime specificDate);
3. 调用示例(Service 层)
java 复制代码
// 场景1:查询今天过生日的人
List<User> todayUsers = mapper.selectUsersByBirthdayCondition("TODAY", null);

// 场景2:查询3天内过生日的人
List<User> within3Users = mapper.selectUsersByBirthdayCondition("WITHIN_3", null);

// 场景3:查询指定日期 2026-09-08 过生日的人(注意:年份会被忽略)
LocalDateTime date = LocalDateTime.of(2026, 9, 8, 0, 0, 0);
List<User> specificUsers = mapper.selectUsersByBirthdayCondition("SPECIFIC", date);

方案二:使用虚拟列 + 索引(针对大数据量优化)

由于方案一对 birthday 字段使用了 YEAR()MONTH()DATE_ADD() 等函数,MySQL 索引会失效 ,导致全表扫描。如果用户表数据量很大(几十万以上),建议增加一个虚拟列存储"月-日",并建立索引。

1. 修改表结构(MySQL 5.7+)
sql 复制代码
ALTER TABLE `member_user` 
ADD COLUMN `birthday_mmdd` CHAR(5) 
GENERATED ALWAYS AS (DATE_FORMAT(birthday, '%m-%d')) STORED;

CREATE INDEX `idx_birthday_mmdd` ON `member_user` (`birthday_mmdd`);
2. 改造 MyBatis SQL(针对指定日期)

指定日期查询直接命中索引:

xml 复制代码
<when test="type == 'SPECIFIC' and specificDate != null">
    AND birthday_mmdd = DATE_FORMAT(#{specificDate}, '%m-%d')
</when>
3. 相对日期(今天/3天/30天)的跨年处理(使用 birthday_mmdd

对于范围查询,需处理跨年(例如 12月30日到1月2日)。可以利用 CONCAT 构造日期字符串进行比较:

xml 复制代码
<when test="type == 'WITHIN_3'">
    AND (
        -- 情况1:不跨年(当前日期 > 生日MMDD)
        (DATE_FORMAT(CURDATE(), '%m-%d') <= '12-31' AND birthday_mmdd BETWEEN DATE_FORMAT(CURDATE(), '%m-%d') AND DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 3 DAY), '%m-%d'))
        OR
        -- 情况2:跨年(当前日期在12月,结束日期在次年1月)
        (DATE_FORMAT(CURDATE(), '%m-%d') > DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 3 DAY), '%m-%d') 
         AND (birthday_mmdd >= DATE_FORMAT(CURDATE(), '%m-%d') OR birthday_mmdd <= DATE_FORMAT(DATE_ADD(CURDATE(), INTERVAL 3 DAY), '%m-%d')))
    )
</when>

(此逻辑较绕,需严格测试;若业务允许,推荐方案一,或者使用 Java 代码计算好起始 MMDD 和结束 MMDD 传入 SQL)


关于 2月29日的特别说明

  • 方案一 :在非闰年,DATE_ADD('2020-02-29', INTERVAL 3 YEAR) 会被 MySQL 自动转为 2023-02-28。这意味着 2月29日出生的人在平年会"过生日"在 2月28日,这符合大多数业务默认规则。
  • 方案二birthday_mmdd 固定为 '02-29',在非闰年查询"今天"时不会被匹配,除非业务特殊处理。

总结建议

  1. 优先使用方案一,代码可读性强,维护简单,适合绝大多数中小型项目。
  2. 如果用户量超过 10 万 且查询频繁,采用方案二(虚拟列+索引),并配合 Java 代码计算 MMDD 区间传入,性能最佳。
  3. 前端传参时,type 建议枚举值:TODAYWITHIN_3WITHIN_30SPECIFICspecificDate 仅在 SPECIFIC 时传值即可。

==========================================

优化:

==========================================

基于你的需求,采用虚拟列 + 索引方案,对现有的 MyBatis-Plus 分页查询进行增强。核心改动点:

  1. 数据库增加虚拟列 birthday_mmdd(格式 MM-dd)并建立索引。
  2. 在原有查询条件基础上,根据前端传入的生日类型(今天/3天/30天/指定日期)动态拼接 apply 条件,利用索引高效过滤。

一、数据库变更(MySQL 5.7+)

sql 复制代码
-- 增加虚拟列(存储 'MM-dd'):在插入或编辑用户数据时,数据库会根据birthday值自动处理并对birthday_mmdd赋值,不需要java代码处理
ALTER TABLE `member_user` 
ADD COLUMN `birthday_mmdd` CHAR(5) 
GENERATED ALWAYS AS (DATE_FORMAT(birthday, '%m-%d')) STORED;

-- 创建索引(覆盖该列)
CREATE INDEX `idx_birthday_mmdd` ON `member_user` (`birthday_mmdd`);

注意STORED 会占用磁盘空间(例如:50w条数据 40M左右),但查询时无需回表;VIRTUAL 不占空间,但每次查询计算。建议使用 STORED 以获得最佳查询性能。


二、VO 增加接收参数

在分页请求 VO(例如 MemberUserPageReqVO)中添加:

java 复制代码
@Data
public class MemberUserPageReqVO extends PageParam {
    // 原有字段...
    private String mobile;
    private LocalDateTime[] loginDate;
    private String nickname;
    private LocalDateTime[] createTime;
    // 新增生日查询参数
    private String birthdayType;      // TODAY, WITHIN_3, WITHIN_30, SPECIFIC
    private LocalDateTime specificDate; // 当 birthdayType=SPECIFIC 时使用
}

三、Service 层构建动态条件(核心)

在原有的 selectPage 方法中,增加生日条件的动态拼接。保持原有 LambdaQueryWrapperX 不变,通过 apply 方法注入 SQL 片段。

java 复制代码
public IPage<MemberUserDO> getMemberUserPage(MemberUserPageReqVO reqVO) {
    // 原有的基础条件(手机、登录日期、昵称、创建时间、标签)
    LambdaQueryWrapperX<MemberUserDO> wrapper = new LambdaQueryWrapperX<MemberUserDO>()
            .likeIfPresent(MemberUserDO::getMobile, reqVO.getMobile())
            .betweenIfPresent(MemberUserDO::getLoginDate, reqVO.getLoginDate())
            .likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname())
            .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime())
            .apply(StrUtil.isNotEmpty(tagIdSql), tagIdSql);

    // ---------- 新增生日条件 ----------
    String birthdayCondition = buildBirthdayCondition(reqVO.getBirthdayType(), reqVO.getSpecificDate());
    if (StrUtil.isNotBlank(birthdayCondition)) {
        wrapper.apply(birthdayCondition);
    }

    wrapper.orderByDesc(MemberUserDO::getId);
    return baseMapper.selectPage(reqVO, wrapper);
}
辅助方法:构建生日 SQL 条件
java 复制代码
/**
 * 根据生日类型构建 SQL 条件(利用虚拟列 birthday_mmdd)
 */
private String buildBirthdayCondition(String type, LocalDateTime specificDate) {
    if (StrUtil.isBlank(type)) {
        return null;
    }
    LocalDate today = LocalDate.now();
    DateTimeFormatter mmddFormatter = DateTimeFormatter.ofPattern("MM-dd");

    switch (type) {
        case "TODAY":
            return "birthday_mmdd = '" + today.format(mmddFormatter) + "'";
        case "WITHIN_3":
            return buildRangeCondition(today, 3, mmddFormatter);
        case "WITHIN_30":
            return buildRangeCondition(today, 30, mmddFormatter);
        case "SPECIFIC":
            if (specificDate == null) {
                return null;
            }
            return "birthday_mmdd = '" + specificDate.format(mmddFormatter) + "'";
        default:
            return null;
    }
}

/**
 * 构建范围条件(处理跨年)
 */
private String buildRangeCondition(LocalDate today, int days, DateTimeFormatter formatter) {
    LocalDate endDate = today.plusDays(days);
    String startMmdd = today.format(formatter);
    String endMmdd = endDate.format(formatter);
    
    // 跨年判断:比较字符串 'MM-dd',如果 start > end 说明跨年
    if (startMmdd.compareTo(endMmdd) > 0) {
        // 跨年条件:birthday_mmdd >= startMmdd OR birthday_mmdd <= endMmdd
        return "(birthday_mmdd >= '" + startMmdd + "' OR birthday_mmdd <= '" + endMmdd + "')";
    } else {
        // 非跨年:BETWEEN
        return "birthday_mmdd BETWEEN '" + startMmdd + "' AND '" + endMmdd + "'";
    }
}

四、关于 2月29日的特殊说明

  • 虚拟列值birthday_mmdd 存储为 '02-29'
  • 平年查询 :例如 2026 年 2 月 28 日查询"今天",条件为 birthday_mmdd = '02-28',2 月 29 日出生的人不会被命中(与业务预期一致,平年无 2/29)。
  • 跨年范围 :若查询"3天内"且包含 2 月 28 日到 3 月 2 日,则会正常比较字符串,'02-28''03-02' 之间不包含 '02-29'(因为 '02-29' < '03-02' 且 > '02-28' 吗?实际上字符串比较是按字典序,'02-29''02-28''03-02' 之间,所以会包含。但注意:如果今天是 2 月 28 日,3 天内包含 2/28, 2/29(平年不存在), 3/1, 3/2?实际上 today.plusDays(3) 会得到 3月3日?需明确业务:3天内通常指今天、明天、后天,即包括今天+未来3天?我们的 days 参数含义要明确:例如 WITHIN_3 表示"3天内",通常理解为从今天开始的未来3天(含今天)。我们的代码 today.plusDays(days) 是截止到未来第 days 天,区间为 [today, today+days],与业务一致。

五、性能分析

  • 索引利用 :所有生日条件都基于 birthday_mmdd 列,查询时命中索引 idx_birthday_mmdd,无论 =BETWEEN 还是 OR(跨年)都能有效使用索引范围扫描。
  • 分页友好 :虚拟列是数据库原生列,BETWEEN= 均可正常分页(LIMIT offset, size 会通过索引快速定位),性能远优于对 birthday 字段使用函数。
  • 跨年优化 :跨年时拆分为 OR,MySQL 优化器通常会分别使用索引,然后合并结果(Index Merge),效率依然可控(如果用户量极大,可考虑将跨年查询拆为两次查询或使用 UNION,但此处 OR 已足够)。

六、前端调用示例

http 复制代码
GET /api/member/user/page?birthdayType=WITHIN_3&pageNo=1&pageSize=10
GET /api/member/user/page?birthdayType=SPECIFIC&specificDate=2026-09-08T00:00:00

七、完整代码整合(你的 Service 方法改造后)

假设你的 Service 类为 MemberUserServiceImpl,原方法为 getMemberUserPage,直接替换内部实现为上述代码即可,其他逻辑不变。

注意事项

  • 确保 MemberUserDO 实体中不需要额外字段,虚拟列只存在于数据库,MyBatis-Plus 查询时自动映射(不会插入/更新)。
  • 如果原有代码中使用 LambdaQueryWrapperXapply 方法(通常支持),可直接使用;若不确定,改用 QueryWrapperwrapper.apply 均可。

这样,你既保留了原有复杂查询条件的灵活性,又通过虚拟列+索引完美解决了生日查询的性能问题,且代码侵入性小,维护简单。如果后续需要按生日排序,只需在 wrapper.orderByAsc("birthday_mmdd") 即可,同样走索引。


==========================================

vue3代码:

==========================================

前端代码
vue 复制代码
  <ContentWrap>
    <!-- 搜索工作栏 -->
    <el-form ref="queryFormRef" :inline="true" :model="queryParams" class="-mb-15px" label-width="68px" >
	<el-form-item label="生日" prop="specificDate" class="!w-220px" ><el-date-picker v-model="queryParams.specificDate"  @change="onSpecificDate" type="date" placeholder="选择日期" value-format="YYYY-MM-DD HH:mm:ss" class="!w-240px" /></el-form-item>
      <el-form-item prop="birthdayType" :style="{marginLeft:20}">
        <el-radio-group v-model="queryParams.birthdayType" @change="onBirthdayType">
          <el-radio-button value="TODAY">今天</el-radio-button>
          <el-radio-button value="WITHIN_3">近3天</el-radio-button>
          <el-radio-button value="WITHIN_7">近1周</el-radio-button>
          <el-radio-button value="WITHIN_15">近半月</el-radio-button>
          <el-radio-button value="WITHIN_30">近1月</el-radio-button>
        </el-radio-group>
      </el-form-item>
    </el-form>
  </ContentWrap>

<script lang="ts" setup>

const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  nickname: null,
  mobile: null,
  loginDate: [],
  createTime: [],
  tagIds: [],
  levelId: null,
  groupId: null,
  birthdayType: 'SPECIFIC', // 类型 默认按日期控件值查询
  specificDate: null // 选中的生日具体日期
})

/** 查询列表 */
const getList = async () => {
  loading.value = true
  try {
    const data = await UserApi.getUserPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}

/** 生日周期选项选中 */
const onBirthdayType = () => {
  queryParams.specificDate = null // 生日日期置空
  handleQuery()
}

/** 生日日期控件选中 */
const onSpecificDate = () => {
  queryParams.birthdayType = 'SPECIFIC' //默认使用选中日期查询
  handleQuery()
}

/** 搜索按钮操作 */
const handleQuery = () => {
  queryParams.pageNo = 1
  getList()
}

/** 初始化 **/
onMounted(() => {
  getList()
})
</script>
相关推荐
AC赳赳老秦1 小时前
文旅市场公开数据分析:基于 OpenClaw 采集景区客流与门票公示数据,生成区域文旅热度监测报告
java·c语言·python·php·symfony·deepseek·openclaw
我命由我123452 小时前
Windows 操作系统 - 启用 D 盘虚拟内存
java·运维·服务器·windows·学习·java-ee·运维开发
xcl09252 小时前
智慧场馆解决方案系统源码:从架构设计到部署实战
java·spring boot
liangbo72 小时前
06-JVM垃圾回收器之CMS
java·jvm
黑马程序员毕设2 小时前
基于Java的仪器管理系统设计与实现
java·开发语言·spring boot·后端·微信小程序
骇客野人3 小时前
SpringBoot电商购物车、结算、下单、库存方案设计与落地实施步骤
java·spring boot·后端
凤山老林3 小时前
高可用分布式任务调度架构:Spring Boot 集成 PowerJob 实战指南
spring boot·分布式·架构
哪 吒3 小时前
华为OD机试 - 云服务安全策略最优选择 - 深度优先搜索DFS(Java 新系统 200分)
java·华为od·深度优先
拒绝内耗。3 小时前
程序员学架构(一):一张图看懂 Java 后端架构:一个请求怎样从手机到数据库?
java·智能手机·架构