You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JdbcTemplate中复用参数执行含多占位符的复杂SQL?

问题描述

我有如下SQL语句:

SELECT fm.*, 
       u.username AS friend_username
FROM friend_messages fm
JOIN users u ON 
    (u.id = fm.sender_id AND fm.sender_id = :friendId) 
    OR 
    (u.id = fm.receiver_id AND fm.receiver_id = :friendId)
WHERE 
    (fm.sender_id = :userId AND fm.receiver_id = :friendId) 
    OR 
    (fm.sender_id = :friendId AND fm.receiver_id = :userId)
ORDER BY fm.created_at ASC

由于需要使用JdbcTemplate执行,我得把:userId和:friendId替换成?。通常简单场景我会这么写:

String serverQuery = "SELECT sm.id AS message_id, sm.server_id, sm.sender_id, u.username AS sender_username, sm.content, sm.created_at FROM server_messages sm JOIN users u ON sm.sender_id = u.id WHERE sm.server_id = ? ORDER BY sm.created_at ASC";
List<ServerMessageDTO> collection = this.db.query(serverQuery, new ServerMessageRowMapper(), serverId);

但当前SQL更复杂,userId要填2个位置,friendId要填4个位置,我试过重复传参的方式:
修改后的SQL:

SELECT fm.*, 
       u.username AS friend_username
FROM friend_messages fm
JOIN users u ON 
    (u.id = fm.sender_id AND fm.sender_id = ?) 
    OR 
    (u.id = fm.receiver_id AND fm.receiver_id = ?)
WHERE 
    (fm.sender_id = ? AND fm.receiver_id = ?) 
    OR 
    (fm.sender_id = ? AND fm.receiver_id = ?)
ORDER BY fm.created_at ASC

Java代码:

List<FriendMessageDTO> collection = this.db.query(serverQuery, new FriendMessageRowMapper(), friendId, friendId , userId, friendId, friendId, userId);

请问有没有更直观的实现方式?


解决方案

1. 使用NamedParameterJdbcTemplate(最推荐)

Spring提供的NamedParameterJdbcTemplate支持命名参数(即原SQL中的:userId、:friendId),无需手动替换为?,参数对应关系清晰,不会混淆顺序。

示例代码:

// 保留原SQL的命名参数,无需修改
String query = """
    SELECT fm.*, u.username AS friend_username 
    FROM friend_messages fm 
    JOIN users u ON 
        (u.id = fm.sender_id AND fm.sender_id = :friendId) 
        OR 
        (u.id = fm.receiver_id AND fm.receiver_id = :friendId)
    WHERE 
        (fm.sender_id = :userId AND fm.receiver_id = :friendId) 
        OR 
        (fm.sender_id = :friendId AND fm.receiver_id = :userId)
    ORDER BY fm.created_at ASC
""";

// 构造参数Map,每个参数只需设置一次
Map<String, Object> params = new HashMap<>();
params.put("userId", userId);
params.put("friendId", friendId);

// 初始化NamedParameterJdbcTemplate(建议注入到类中,无需每次新建)
NamedParameterJdbcTemplate namedJdbcTemplate = new NamedParameterJdbcTemplate(db);
List<FriendMessageDTO> collection = namedJdbcTemplate.query(query, params, new FriendMessageRowMapper());

2. 优化SQL减少占位符数量

通过简化逻辑,减少同一参数的占位符出现次数,让SQL更简洁,传参更轻松:

优化后的SQL(支持命名参数):

SELECT fm.*, 
       u.username AS friend_username
FROM friend_messages fm
JOIN users u ON u.id = :friendId AND u.id IN (fm.sender_id, fm.receiver_id)
WHERE (fm.sender_id, fm.receiver_id) IN ((:userId, :friendId), (:friendId, :userId))
ORDER BY fm.created_at ASC

如果坚持使用普通JdbcTemplate,替换为?后:

SELECT fm.*, 
       u.username AS friend_username
FROM friend_messages fm
JOIN users u ON u.id = ? AND u.id IN (fm.sender_id, fm.receiver_id)
WHERE (fm.sender_id, fm.receiver_id) IN ((?, ?), (?, ?))
ORDER BY fm.created_at ASC

传参时按顺序传入friendId, userId, friendId, friendId, userId即可,参数重复次数明显减少。

3. 封装参数数组(兼容普通JdbcTemplate)

若不想切换模板类,可将参数封装为数组并添加注释,明确每个占位符对应的参数:

// 按SQL中?的顺序封装参数,添加注释明确含义
Object[] params = new Object[]{
    friendId,  // JOIN条件:fm.sender_id = ?
    friendId,  // JOIN条件:fm.receiver_id = ?
    userId,    // WHERE条件1:fm.sender_id = ?
    friendId,  // WHERE条件1:fm.receiver_id = ?
    friendId,  // WHERE条件2:fm.sender_id = ?
    userId     // WHERE条件2:fm.receiver_id = ?
};
List<FriendMessageDTO> collection = this.db.query(serverQuery, new FriendMessageRowMapper(), params);

内容的提问来源于stack exchange,提问作者Onyx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 04:09:54