在 Spring Boot + MyBatis 项目中为查询接口添加入参查询字段支持——以房费台账 paySource 为例

记录一次日常需求开发:给房费台账分页查询接口增加 paySource(支付来源)筛选条件。


一、需求背景

项目是一个基于 Spring Boot + MyBatis 的 PMS 酒店管理系统。

业务方希望在前端页面中能够按照"支付来源"对房费台账进行筛选,因此需要在后端查询接口中新增 paySource 入参,并在 SQL 中对该字段进行过滤。

数据库表 checkin_cost_records 中早已存在 pay_source 字段,但之前的查询 BO 和 Mapper XML 都没有将其作为查询条件使用。本次改动就是要把这个能力补上。


二、整体思路

我的实现步骤非常直接:

  1. 在查询参数对象 CheckinCostRecordQueryBO 中增加 paySource 字段及其 getter/setter
  2. 在 Mapper XML 的三个相关查询中增加 paySource 的动态 SQL 条件
  3. 编译验证,确保没有语法错误

改动面很小,但能完整体现"加字段"的标准流程。


三、具体改动

1)修改查询 BO:增加 paySource 字段

文件:src/main/java/com/crt/pms/bean/bo/checkincostrecord/CheckinCostRecordQueryBO.java

java 复制代码
public class CheckinCostRecordQueryBO {
    private String checkInNo;
    private Integer type;
    private Integer payMethod;
    private Integer paySource;        // 新增
    private Integer costRecordType;
    private String roomNo;
    private String handlerName;
    private String startTime;
    private String endTime;

    // getters & setters ...

    public Integer getPaySource() {
        return paySource;
    }

    public CheckinCostRecordQueryBO setPaySource(Integer paySource) {
        this.paySource = paySource;
        return this;
    }
}

说明:

  • 类型选择 Integer,与数据库 pay_source 的定义保持一致
  • 采用链式 setter,方便后续扩展(虽然查询场景并不强制)

2)修改 Mapper XML:在查询中增加条件

文件:src/main/resources/mapper/CheckinCostRecordMapper.xml

需要在以下三个 <select> 中都加上 paySource 的条件,保证分页查询、计数查询、统计查询行为一致。

(1) 分页查询:searchCheckinCostRecordsPaged

xml 复制代码
<select id="searchCheckinCostRecordsPaged"
        resultType="com.crt.pms.bean.vo.checkincostrecord.CheckinCostRecordPageVO">
    SELECT c.*,
           rc.check_in_no AS checkInNo,
           rc.actual_guest_name AS actualGuestName,
           rc.actual_check_in_time AS actualCheckInTime,
           rc.status AS checkInStatus,
           rc.current_room_number AS currentRoomNumber
    FROM checkin_cost_records c
    JOIN room_checkin rc ON c.check_in_id = rc.id
    <where>
        c.deleted = 0
        AND rc.deleted = 0

        <if test="query != null and query.checkInNo != null and query.checkInNo != ''">
            AND rc.check_in_no LIKE CONCAT('%', #{query.checkInNo}, '%')
        </if>

        <if test="query != null and query.type != null">
            AND c.type = #{query.type}
        </if>

        <if test="query != null and query.payMethod != null">
            AND c.pay_method = #{query.payMethod}
        </if>

        <!-- 新增:paySource -->
        <if test="query != null and query.paySource != null">
            AND c.pay_source = #{query.paySource}
        </if>

        <if test="query != null and query.costRecordType != null">
            AND c.cost_record_type = #{query.costRecordType}
        </if>

        <if test="query != null and query.roomNo != null and query.roomNo != ''">
            AND rc.current_room_number LIKE CONCAT('%', #{query.roomNo}, '%')
        </if>

        <if test="query != null and query.handlerName != null and query.handlerName != ''">
            AND c.update_by_name LIKE CONCAT('%', #{query.handlerName}, '%')
        </if>

        <if test="query != null and query.startTime != null and query.startTime != ''">
            AND DATE_FORMAT(c.update_time, '%Y-%m-%d') &gt;= #{query.startTime}
        </if>

        <if test="query != null and query.endTime != null and query.endTime != ''">
            AND DATE_FORMAT(c.update_time, '%Y-%m-%d') &lt;= #{query.endTime}
        </if>
    </where>
    ORDER BY c.create_time DESC
    LIMIT #{size} OFFSET #{start}
</select>

(2) 计数查询:searchCheckinCostRecordsCount

xml 复制代码
<select id="searchCheckinCostRecordsCount" resultType="long">
    SELECT COUNT(1)
    FROM checkin_cost_records c
    JOIN room_checkin rc ON c.check_in_id = rc.id
    <where>
        c.deleted = 0
        AND rc.deleted = 0

        <!-- 其他条件省略 -->

        <!-- 新增:paySource -->
        <if test="query != null and query.paySource != null">
            AND c.pay_source = #{query.paySource}
        </if>

        <!-- 其他条件省略 -->
    </where>
</select>

(3) 统计查询:getCheckinCostRecordsStatistics

xml 复制代码
<select id="getCheckinCostRecordsStatistics"
        resultType="com.crt.pms.bean.vo.checkincostrecord.CheckinCostRecordStatisticsVO">
    SELECT COALESCE(SUM(CASE WHEN c.type = #{incomeType} THEN c.amount ELSE #{zero} END), #{zero}) AS income,
           COALESCE(SUM(CASE WHEN c.type = #{costType} THEN c.amount ELSE #{zero} END), #{zero}) AS refunded,
           COALESCE(SUM(CASE
                            WHEN c.type = #{incomeType} THEN c.amount
                            WHEN c.type = #{costType} THEN -c.amount
                            ELSE #{zero}
                        END), #{zero}) AS total
    FROM checkin_cost_records c
    JOIN room_checkin rc ON c.check_in_id = rc.id
    <where>
        c.deleted = 0
        AND rc.deleted = 0

        <!-- 其他条件省略 -->

        <!-- 新增:paySource -->
        <if test="queryVO != null and queryVO.query != null and queryVO.query.paySource != null">
            AND c.pay_source = #{queryVO.query.paySource}
        </if>

        <!-- 其他条件省略 -->
    </where>
</select>

四、为什么是这三个地方都要改?

  • 分页查询:真正返回数据的查询,必须支持新条件
  • 计数查询:前端分页依赖总条数,若不一致会导致"查到的数据条数"和"分页总数"不匹配
  • 统计查询:台账页面的汇总金额需要与当前筛选条件一致,否则会出现"列表和总额对不上"的问题

这三者保持相同的过滤逻辑,才能保证前端体验一致、数据自洽。


五、一些实现细节与避坑指南

  1. 判空很重要
    使用 <if test="query != null and query.paySource != null"> 避免传 null 时 SQL 拼接出错。
  2. 命名一致性
    Java 用 paySource,数据库列名用 pay_source,MyBatis 自动映射更省心。
  3. 类型对齐
    BO 中字段类型与数据库一致,减少隐式转换带来的潜在问题。
  4. 同步修改所有相关查询
    凡是受筛选条件影响的查询,都要一并更新,避免"顾此失彼"。

六、调用示例

前端请求体示例(分页查询):

json 复制代码
{
  "page": 1,
  "size": 20,
  "query": {
    "paySource": 1,
    "checkInNo": "CI20260312001",
    "type": 2,
    "startTime": "2026-03-01",
    "endTime": "2026-03-12"
  }
}

生成的 SQL 片段类似:

sql 复制代码
AND c.pay_source = 1

七、小结

这次改动非常"工程日常":

  • 代码量不大
  • 思路清晰
  • 收益明确(前端可按支付来源精确筛选)

同时也再次验证了一个经验:在 MyBatis 里加查询条件,本质就是两件事------BO 加字段、XML 加条件。只要记住"分页 + 计数 + 统计一起改",基本就不会踩坑。

如果你也维护着类似的后台系统,这类"加字段"的操作完全可以当成标准模板来用。

相关推荐
-Da-2 小时前
【操作系统学习日记】操作系统核心机制深度解析:中断、DMA与进程管理
linux·后端·系统架构
ding_zhikai3 小时前
【Web应用开发笔记】Django笔记9:Django部署注意事项补充
笔记·后端·python·django
小鸡脚来咯3 小时前
RESTful API 设计与后端服务开发面试题
后端·restful
Assby3 小时前
Java开发者学习Go语言:Go开发和Java开发的一些区别
后端·go
吃吃喝喝小朋友3 小时前
Django Admin后台系统
后端·python·django
树獭叔叔3 小时前
检索增强生成(RAG):让大模型突破知识边界
后端·aigc·openai
南囝coding3 小时前
OpenClaw 到底能干什么?可以看看这 60 个真实用例
前端·后端
重庆穿山甲3 小时前
Java开发者的大模型入门:AgentScope Java组件全攻略(二)
前端·后端