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

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
错误原因分析
  1. 泛型擦除导致类型丢失:Java泛型在编译后会被擦除,AbstractLogDetail中的E entity会被处理为Object类型,Hibernate无法识别Object上的user属性,因此Spring Data JPA解析entity.user.displayName路径时失败。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 21:39:52