Spring Boot3.1.5中Spring Sort访问实体泛型字段异常求助
问题背景
使用Spring Boot 3.1.5、Hibernate 6.2.3时,无法通过Spring Sort功能访问泛型实体中的嵌套字段。相关实体结构如下:
@Data @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class AbstractLog<I extends AbstractLogDetail<?, ?>> { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; @Column(name = "action") @Enumerated(EnumType.STRING) private Action action; @OneToMany(mappedBy = "log", cascade = CascadeType.ALL) private List<I> changeLog; } @Data @MappedSuperclass public abstract class AbstractLogDetail<A extends AbstractLog<?>, E> { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; @ManyToOne @JoinColumn(name = "log_id", nullable = false) private A log; @ManyToOne @JoinColumn(name = "entity_id", nullable = false) private E entity; //detail fields } @Data @Entity @Table(name = "supporter_log") public class SupporterLog extends AbstractLog<SupporterLogDetail> { } @Data @Entity @Table(name = "supporter_log_detail") public class SupporterLogDetail extends AbstractLogDetail<SupporterLog, Supporter> { } @Getter @Setter @Entity @Builder @ToString @NoArgsConstructor @AllArgsConstructor @Table(name = "supporter") public class Supporter { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private Integer id; @Column(name = "level", nullable = false) private int level; @Column(name = "status", nullable = false) @Convert(converter = StatusConverter.class) private Status status; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") @ToString.Exclude private User user; }
尝试通过字符串路径映射Sort字段时,出现错误:
List<Sort.Order> updatedOrders = pageable.getSort().stream() .map(order -> { String property = order.getProperty(); return switch (property) { case "entityField" -> new Sort.Order(order.getDirection(), "entity.user.displayName"); default -> order; }; }) .toList(); pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(updatedOrders));
错误信息:
org.springframework.dao.InvalidDataAccessApiUsageException: Unable to locate Attribute with the given name [user] on this ManagedType [java.lang.Object] Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the given name [user] on this ManagedType [java.lang.Object]
尝试使用TypedSort时,又出现代理生成错误:
Sort sort = Sort.sort(FundingApproverActivityChangeLog.class) .by((FundingApproverActivityChangeLog e) -> e.getEntity().getUser().getDisplayName()).ascending(); pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);
错误信息:
org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class java.lang.Object: Common causes of this problem include using a final class or a non-visible class Caused by: org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @6b419da
错误原因分析
- 泛型擦除导致类型丢失:Java泛型在编译后会被擦除,
AbstractLogDetail中的E entity会被处理为Object类型,Hibernate无法识别Object上的user属性,因此Spring Data JPA解析entity.user.displayName路径时失败。 - TypedSort的代理生成限制:TypedSort通过方法引用记录属性路径,但由于泛型擦除,
getEntity()返回的类型被视为Object,而CGLIB无法生成Object的子类代理(同时JDK模块系统限制了对java.lang包的反射访问),导致代理生成失败。
解决方案
方法1:在具体实体中覆盖泛型字段,指定具体类型
在SupporterLogDetail中重新定义entity字段,明确指定其类型为Supporter,让Hibernate能识别该字段的实际类型,从而正确解析嵌套属性路径:
@Data @Entity @Table(name = "supporter_log_detail") public class SupporterLogDetail extends AbstractLogDetail<SupporterLog, Supporter> { // 覆盖父类的entity字段,指定具体类型 @Override @ManyToOne @JoinColumn(name = "entity_id", nullable = false) private Supporter entity; }
修改后,原有的字符串路径排序写法"entity.user.displayName"即可正常工作,因为Hibernate现在能识别entity是Supporter类型,可找到对应的user属性。
方法2:使用Criteria API手动构建排序逻辑
如果不想修改实体结构,可通过JPA的Criteria API手动构建查询和排序,直接操作实体关联路径,避开泛型擦除的影响:
@Repository public interface SupporterLogRepository extends JpaRepository<SupporterLog, Integer>, JpaSpecificationExecutor<SupporterLog> { default Page<SupporterLog> findAllWithEntitySort(Pageable pageable) { Specification<SupporterLog> spec = (root, query, cb) -> { // 关联changeLog、entity、user Join<SupporterLog, SupporterLogDetail> changeLogJoin = root.join("changeLog"); Join<SupporterLogDetail, Supporter> entityJoin = changeLogJoin.join("entity"); Join<Supporter, User> userJoin = entityJoin.join("user"); // 转换Sort为JPA Order对象 List<javax.persistence.criteria.Order> jpaOrders = pageable.getSort().stream() .map(order -> { if ("entityField".equals(order.getProperty())) { return order.getDirection().isAscending() ? cb.asc(userJoin.get("displayName")) : cb.desc(userJoin.get("displayName")); } // 处理其他排序字段 return order.getDirection().isAscending() ? cb.asc(root.get(order.getProperty())) : cb.desc(root.get(order.getProperty())); }) .collect(Collectors.toList()); query.orderBy(jpaOrders); return cb.conjunction(); }; return findAll(spec, pageable); } }
调用该自定义方法即可实现基于泛型嵌套字段的排序。
方法3:解决TypedSort的模块权限问题(需配合方法1使用)
若坚持使用TypedSort,需先解决JDK模块的反射访问限制,在应用启动参数中添加:
--add-opens java.lang/java.lang=ALL-UNNAMED
但此方法仅解决代理生成错误,泛型擦除导致的类型丢失问题仍存在,因此必须配合方法1(在具体实体中指定entity的具体类型)才能让TypedSort正确识别属性路径。
内容的提问来源于stack exchange,提问作者thang dang
相关产品推荐
相关产品推荐

