在 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 加条件。只要记住"分页 + 计数 + 统计一起改",基本就不会踩坑。

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

相关推荐
2401_895521348 小时前
SpringBoot Maven快速上手
spring boot·后端·maven
disgare8 小时前
关于 spring 工程中添加 traceID 实践
java·后端·spring
ictI CABL8 小时前
Spring Boot与MyBatis
spring boot·后端·mybatis
小江的记录本10 小时前
【Linux】《Linux常用命令汇总表》
linux·运维·服务器·前端·windows·后端·macos
yhole13 小时前
springboot三层架构详细讲解
spring boot·后端·架构
香香甜甜的辣椒炒肉13 小时前
Spring(1)基本概念+开发的基本步骤
java·后端·spring
白毛大侠14 小时前
Go Goroutine 与用户态是进程级
开发语言·后端·golang
ForteScarlet14 小时前
从 Kotlin 编译器 API 的变化开始: 2.3.20
android·开发语言·后端·ios·开源·kotlin
大阿明15 小时前
SpringBoot - Cookie & Session 用户登录及登录状态保持功能实现
java·spring boot·后端
Binary-Jeff15 小时前
Spring 创建 Bean 的关键流程
java·开发语言·前端·spring boot·后端·spring·学习方法