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

Spring Data Projection返回TupleBackedMap而非预期对象问题排查

问题

我定义了如下实体类:

@Entity
@Data
public class User {

    @Id
    private Long id;

    private String num;

    @OneToOne
    @JoinColumn(name = "group_id", referencedColumnName = "id")
    private Group group;
}

@Entity
@Data
public class Group {

    @Id
    private Long id;

    private String zone;
}

我想通过Spring Data JPA的投影查询,获取所有用户的num和所属的zone,于是定义了这个投影接口:

public interface UserDtoProjection {

    String getNum();

    String getZone();
}

查询方法写的是这样:

@Query(value = "SELECT u.num as num, g.zone as zone FROM \"user\" u join \"group\" g on u.group_id = g.id", nativeQuery = true)
List<UserDtoProjection> findAllUserZones();

但调用这个方法后,返回的不是预期的UserDtoProjection对象,而是TupleBackedMap,这是怎么回事?

原因及解决办法

问题出在原生SQL查询(nativeQuery=true)默认不支持直接返回接口投影,Spring Data JPA处理原生查询时,默认会把结果封装成TupleBackedMap这种键值对容器,不会自动实例化你定义的投影接口。

给你两种解决思路:

  • 思路一:改用JPQL查询配合接口投影
    把原生SQL换成JPQL,Spring Data JPA就能自动识别投影接口并完成属性映射,还能利用实体类的关联关系省掉手动join:

    @Query("SELECT u.num as num, u.group.zone as zone FROM User u")
    List<UserDtoProjection> findAllUserZones();
    

    这种方式更贴合JPA的使用逻辑,代码也更简洁。

  • 思路二:用类投影(DTO)替代接口投影(支持原生SQL)
    如果必须用原生SQL,接口投影不好使,换成带构造函数的DTO类就行:
    先写DTO类:

    @Data
    public class UserDto {
        private String num;
        private String zone;
    
        public UserDto(String num, String zone) {
            this.num = num;
            this.zone = zone;
        }
    }
    

    然后修改查询方法(注意要把nativeQuery设为false,因为new DTO()是JPQL的语法,原生SQL不支持):

    @Query(value = "SELECT new com.yourpackage.UserDto(u.num, u.group.zone) FROM User u")
    List<UserDto> findAllUserZones();
    

    要是非得用原生SQL,就得通过@SqlResultSetMapping来配置结果映射:
    先在User实体类上添加映射配置:

    @Entity
    @SqlResultSetMapping(
        name = "UserDtoMapping",
        classes = @ConstructorResult(
            targetClass = UserDto.class,
            columns = {
                @ColumnResult(name = "num", type = String.class),
                @ColumnResult(name = "zone", type = String.class)
            }
        )
    )
    public class User {
        // ... 原有代码不变
    }
    

    再修改查询方法指定映射:

    @Query(value = "SELECT u.num as num, g.zone as zone FROM \"user\" u join \"group\" g on u.group_id = g.id", 
           nativeQuery = true,
           resultSetMapping = "UserDtoMapping")
    List<UserDto> findAllUserZones();
    

内容的提问来源于stack exchange,提问作者Mr.Blev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 10:20:28