Spring JPA设级联仍无法查询嵌套对象,抛TransientObjectException
这个问题的核心原因很明确:你在查询时传入的Origin是一个未持久化的瞬态对象(没有被保存到数据库,没有生成id),而Hibernate在执行criteriaBuilder.equal(root.get(Wine_.ORIGIN), origin)时,默认会尝试对比实体的持久化标识(也就是id),但这个瞬态Origin没有有效id,所以触发了异常。
你配置的CascadeType.ALL只作用于保存/更新关联实体的场景(比如保存Wine时自动保存关联的Origin),和查询阶段的实体对比逻辑完全无关,所以这个配置帮不了查询时的问题。
下面是两种可行的修复方案:
方案1:对比Origin的业务唯一属性(推荐)
既然Origin的region和country是业务上的唯一标识(从你的构造器和非空约束能看出来),直接对比这两个属性,而不是整个实体对象。修改你的hasOrigin Specification:
public static Specification<Wine> hasOrigin(Origin origin) { return (root, criteriaQuery, criteriaBuilder) -> { // 关联到Origin的region和country属性进行匹配 Predicate regionMatch = criteriaBuilder.equal( root.get(Wine_.ORIGIN).get(Origin_.REGION), origin.getRegion() ); Predicate countryMatch = criteriaBuilder.equal( root.get(Wine_.ORIGIN).get(Origin_.COUNTRY), origin.getCountry() ); // 同时匹配region和country return criteriaBuilder.and(regionMatch, countryMatch); }; }
这种方式不需要依赖Origin的持久化状态,只要传入的Origin有正确的region和country值就能完成查询,非常适合前端传参或者手动构造查询条件的场景。
方案2:先获取持久化的Origin实例再查询
如果你一定要通过对比整个实体对象来查询,那需要先从数据库中获取已经持久化的Origin实例,再用这个实例构建查询条件:
首先给OriginRepository添加一个根据region和country查询的方法:
public interface OriginRepository extends JpaRepository<Origin, Long> { Optional<Origin> findByRegionAndCountry(String region, String country); }
然后在WineService的查询逻辑中,先获取持久化的Origin:
public Page<Wine> searchWines(String searchTerm, Origin origin) { Specification<Wine> spec = Specification.where(null); if (origin != null) { // 从数据库获取已持久化的Origin Origin persistedOrigin = originRepository.findByRegionAndCountry(origin.getRegion(), origin.getCountry()) .orElseThrow(() -> new IllegalArgumentException("指定的Origin不存在")); // 使用持久化的Origin构建查询条件 spec = spec.and(hasOrigin(persistedOrigin)); } // 处理searchTerm的逻辑... return wineRepository.findAll(spec, Pageable.unpaged()); // 替换成你的分页参数 }
这种方式确保了查询时使用的是Hibernate管理的持久化实体,不会触发瞬态对象异常,但需要额外的数据库查询。
额外建议
为了避免数据库中出现重复的Origin数据,建议在Origin实体的@Table注解中添加唯一约束,确保region和country的组合唯一:
@Entity @Data @NoArgsConstructor @RequiredArgsConstructor @AllArgsConstructor @Table(name = "regions", uniqueConstraints = { @UniqueConstraint(columnNames = {"region", "country"}) }) public class Origin { // 原有属性... }
这样不仅能保证数据一致性,也能让方案2中的查询逻辑更可靠。
内容的提问来源于stack exchange,提问作者Panossa

