使用@Transient注解后无法获取原生查询生成的theDate字段值
解决JPA原生SQL查询中@Transient字段返回NULL的问题
问题根源
@Transient注解的作用是告诉JPA该字段不属于数据库表的列,不会参与实体与数据库的映射过程——所以即使你的原生SQL返回了theDate列,JPA也会自动忽略这个字段的赋值,导致调用getTheDate()时返回NULL。
三种可行解决方案
1. 用@Column(insertable=false, updatable=false)替代@Transient
这是最简单的方案:既保留“不写入数据库”的需求,又让JPA识别该字段为查询结果的映射列。
修改实体类的theDate字段:
@Getter @Setter @ToString @Entity @Table(name = "amortization_schedules") public class AmortizationSchedule { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; // 替换@Transient,设置insertable和updatable为false,避免写入数据库 @Column(name = "theDate", insertable = false, updatable = false) private Date theDate; // 其他字段省略 }
- 原理:
insertable=false和updatable=false会让JPA在执行INSERT/UPDATE操作时忽略该字段,同时保留查询结果的映射逻辑。 - 注意:
@Column的name属性必须和原生SQL返回的列名完全一致(这里是theDate)。
2. 使用DTO/接口投影分离查询结果
如果不想修改原实体类,可以创建专门的DTO或投影接口来接收查询结果:
方式A:DTO构造函数投影
先定义包含所有查询字段的DTO:
@Getter @Setter public class AmortizationScheduleDTO { private Date theDate; private Long id; // 复制原实体类中需要的其他字段,顺序要和SQL查询列的顺序一致 private BigDecimal interestPayment; private BigDecimal interestRate; // ... 其他字段 }
然后修改Repository方法(注意nativeQuery要设为false,改用JPQL):
@Query(value = "SELECT new com.yourpackage.AmortizationScheduleDTO(" + "tab.theDate, asl.id, asl.interest_payment, asl.interest_rate, ...) " + // 按DTO构造函数顺序罗列所有列 "FROM GENERATE_SERIES(" + " (SELECT MIN(ams.date) FROM AmortizationSchedule ams)," + " (SELECT MAX(ams.date) + INTERVAL '1' MONTH FROM AmortizationSchedule ams)," + " '1 MONTH'" + ") AS tab(theDate) " + "FULL JOIN AmortizationSchedule asl ON FUNCTION('to_char', tab.theDate, 'yyyy-mm') = FUNCTION('to_char', asl.date, 'yyyy-mm')") List<AmortizationScheduleDTO> findAllByDate();
方式B:接口投影(更适合原生SQL)
定义一个投影接口,方法名与查询返回的列名对应:
public interface AmortizationScheduleProjection { Date getTheDate(); Long getId(); BigDecimal getInterestPayment(); // 对应SQL中的interest_payment,JPA会自动处理下划线转驼峰 BigDecimal getInterestRate(); // ... 其他字段对应的get方法 }
修改Repository方法:
@Query(value = "SELECT " + " theDate, asl.id, asl.interest_payment, asl.interest_rate, ... " + // 原SQL不变 "FROM ...", nativeQuery = true) List<AmortizationScheduleProjection> findAllByDate();
- 原理:JPA会动态生成代理类实现该接口,自动将查询结果映射到接口方法的返回值。
3. 用@SqlResultSetMapping自定义结果映射
如果必须使用原实体类且不想修改字段注解,可以通过结果集映射告诉JPA如何处理theDate字段:
在实体类上添加映射定义:
@SqlResultSetMapping( name = "AmortizationScheduleWithTheDate", entities = @EntityResult( entityClass = AmortizationSchedule.class, fields = { @FieldResult(name = "id", column = "id"), @FieldResult(name = "theDate", column = "theDate"), // 必须逐一映射所有需要的字段,name是实体属性名,column是SQL返回的列名 @FieldResult(name = "interestPayment", column = "interest_payment"), @FieldResult(name = "interestRate", column = "interest_rate"), // ... 其他所有字段 } ) ) @Getter @Setter @ToString @Entity @Table(name = "amortization_schedules") public class AmortizationSchedule { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Long id; @Transient private Date theDate; // 其他字段省略 }
然后在Repository的@Query中指定该映射:
@Query(value = "SELECT ...", nativeQuery = true, resultSetMapping = "AmortizationScheduleWithTheDate") List<AmortizationSchedule> findAllByDate();
- 原理:通过自定义映射规则,强制JPA将SQL返回的
theDate列赋值给实体的theDate属性,即使它被标记为@Transient。
内容的提问来源于stack exchange,提问作者Alexander
相关产品推荐
相关产品推荐

