原生的 MyBatis-Plus QueryWrapper 不支持 LEFT JOIN

QueryWrapper 的设计初衷是用于单表的 CRUD 操作。它生成的 SQL 结构固定为 SELECT ... FROM table WHERE ...,无法直接生成 JOIN 子句。

但是,如果你需要在 MyBatis-Plus 中实现联查(特别是结合你上一个问题中的 PostgreSQL LATERAL 语法),有以下 3 种解决方案


方案一:使用 XML 映射文件(最推荐,功能最强)

如果你需要使用 PostgreSQL 特有的 LEFT JOIN LATERAL 语法,或者联查逻辑比较复杂,XML 是最佳选择。它保留了 MyBatis-Plus 的分页和基础功能,同时允许你编写任意复杂的 SQL。

1. Mapper 接口


@Mapper
public interface ApplicationMapper extends BaseMapper<Application> {
    // 自定义方法,返回 DTO 而不是 Entity
    List<AppDetailDTO> selectAppWithAttachment(IPage page, @Param("ew") Wrapper<Application> wrapper);
}

2. XML 文件 (ApplicationMapper.xml) 在这里你可以直接使用之前讨论的 LATERAL 语法,并动态拼接 QueryWrapper 生成的 WHERE 条件。


<mapper namespace="com.example.mapper.ApplicationMapper">
    <select id="selectAppWithAttachment" resultType="com.example.dto.AppDetailDTO">
        SELECT 
            a.id, a.title, 
            att.file_url AS attachmentUrl
        FROM applications a
        <!-- 这里可以自由写复杂的 PG 语法 -->
        LEFT JOIN LATERAL (
            SELECT file_url 
            FROM attachments 
            WHERE app_id = a.id 
            ORDER BY created_at DESC 
            LIMIT 1
        ) att ON true
        <!-- 动态拼接 QueryWrapper 的 WHERE 条件 -->
        ${ew.customSqlSegment}
    </select>
</mapper>

3. 调用


Page<AppDetailDTO> page = new Page<>(1, 10);
QueryWrapper<Application> wrapper = new QueryWrapper<>();
wrapper.eq("a.user_id", 123); // 注意:XML 中表别名要对应

// 调用自定义方法
applicationMapper.selectAppWithAttachment(page, wrapper);
  • 优点:完全控制 SQL,支持 PG 特性,性能最好。
  • 缺点:需要写 XML 文件。

方案二:使用增强插件 mybatis-plus-join (最像 Wrapper)

社区有一个非常流行的插件叫 mybatis-plus-join (MpJ),它在 QueryWrapper 的基础上扩展了联查功能。

1. 引入依赖


<dependency>
    <groupId>com.github.yulichang</groupId>
    <artifactId>mybatis-plus-join</artifactId>
    <version>1.4.0</version> <!-- 请检查最新版本 -->
</dependency>

2. 代码使用


// 使用 MpJoinWrapper 代替 QueryWrapper
MpJoinWrapper<Application> wrapper = new MpJoinWrapper<>();
wrapper.select(Application::getId, Application::getTitle)
       .select(Attachment::getFileUrl)
       .leftJoin(Attachment.class, Attachment::getAppId, Application::getId)
       // 注意:MpJ 默认可能不支持 LIMIT 1 的子查询逻辑,
       // 复杂的一对一取最新记录,可能还是需要配合 apply 或 XML
       .orderByDesc(Attachment::getCreatedAt); 

// 查询
List<AppDetailDTO> list = applicationMapper.selectJoinList(page, wrapper, AppDetailDTO.class);
  • 优点:链式调用,无需 XML,开发快。
  • 缺点:对于“一对多取一条”(如取最新附件)这种逻辑,原生 MpJ 处理起来可能不如 LATERAL 直观,可能需要配合 apply

方案三:使用 apply() 方法硬拼 SQL (不推荐,仅限简单场景)

原生 QueryWrapper 提供了 apply() 方法,可以注入原生 SQL 片段。


QueryWrapper<Application> wrapper = new QueryWrapper<>();
wrapper.select("a.*", "att.file_url")
       // 硬编码 JOIN 语句
       .apply("LEFT JOIN attachments att ON a.id = att.app_id") 
       .eq("a.user_id", 123);
  • 严重缺点
    1. 无法处理一对多取一条:直接 LEFT JOIN 会导致申请记录膨胀(一条申请对应多条附件,结果集会有多行)。
    2. SQL 注入风险apply 中的参数如果拼接不当,容易出问题。
    3. 维护性差:SQL 散落在 Java 代码中。
    4. 不支持 PG LATERAL:很难在 apply 里优雅地写子查询逻辑。

关键提示:结果映射 (DTO/VO)

无论用哪种方式,联查后的结果不能直接映射到 Application 实体类上,因为实体类里没有 attachment_url 字段,且一对多会导致数据重复。

你需要创建一个 DTO (Data Transfer Object) 或 VO (View Object)


@Data
public class AppDetailDTO {
    private Long id;
    private String title;
    private String attachmentUrl; // 附件字段
}

总结建议

  1. 如果你必须用 PostgreSQL 的 LATERAL 优化(取最新附件):请使用 方案一 (XML)。这是最稳妥、性能最好且能完整利用 PG 特性的方式。
  2. 如果只是普通联查且不想写 XML:可以尝试 方案二 (mybatis-plus-join)
  3. 不要试图用原生 QueryWrapper 强行做复杂联查,那是它的设计盲区。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐