Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can not use Spring's Sort to access generic fields, using both Sort and TypedSort #3382

Open
dtthang912 opened this issue Feb 25, 2024 · 1 comment
Assignees
Labels
status: waiting-for-triage An issue we've not yet triaged

Comments

@dtthang912
Copy link

dtthang912 commented Feb 25, 2024

I can not use Spring's Sort function to access generic fields in entities (I'm using Spring Boot 3.1.5, Hibernate 6.2.3). Here is my entity code:

@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;
}

Here is how I use Spring's Sort to access the displayName field, in which entity is AbstractLogDetail 's generic field:

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));

Here is the error:

org.springframework.dao.InvalidDataAccessApiUsageException: Unable to locate Attribute with the given name [user] on this ManagedType [java.lang.Object]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:371)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:234)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the given name [user] on this ManagedType [java.lang.Object]
    at org.hibernate.metamodel.model.domain.AbstractManagedType.checkNotNull(AbstractManagedType.java:287)
    at org.hibernate.metamodel.model.domain.AbstractManagedType.getAttribute(AbstractManagedType.java:160)
    at org.hibernate.metamodel.model.domain.AbstractManagedType.getAttribute(AbstractManagedType.java:51)
    at org.springframework.data.jpa.repository.query.QueryUtils.requiresOuterJoin(QueryUtils.java:836)
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:777)
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:796)
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:756)
    at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:752)
    at org.springframework.data.jpa.repository.query.QueryUtils.toJpaOrder(QueryUtils.java:741)
    at org.springframework.data.jpa.repository.query.QueryUtils.toOrders(QueryUtils.java:693)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:753)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:710)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:447)

I think it's because the type "E entity" was transformed to Object after being compiled. I even tested TypedSort to see if it can access the generic field:

 Sort sort = Sort.sort(FundingApproverActivityChangeLog.class)
            .by((FundingApproverActivityChangeLog e) -> e.getEntity().getUser().getDisplayName()).ascending();
    pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), sort);

But seems like Spring can not create a proxy, I got this error:

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
    at org.springframework.aop.framework.CglibAopProxy.buildProxy(CglibAopProxy.java:216)
    at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:158)
    at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:110)
    at org.springframework.data.util.MethodInvocationRecorder.create(MethodInvocationRecorder.java:100)
    at org.springframework.data.util.MethodInvocationRecorder$RecordingMethodInterceptor.registerInvocation(MethodInvocationRecorder.java:159)
    at org.springframework.data.util.MethodInvocationRecorder$RecordingMethodInterceptor.invoke(MethodInvocationRecorder.java:150)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)
    at com.ntuc.lhub.custom.common.data.entity.funding.FundingApproverActivityChangeLog$$SpringCGLIB$$0.getEntity(<generated>)
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
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:545)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:367)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:575)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.lambda$new$1(AbstractClassGenerator.java:103)
    at org.springframework.cglib.core.internal.LoadingCache.lambda$createEntry$1(LoadingCache.java:52)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at org.springframework.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:57)
    at org.springframework.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 25, 2024
@mp911de
Copy link
Member

mp911de commented Feb 26, 2024

This ticket seems related to #3307 where Hibernate loses type information on parametrized type paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-triage An issue we've not yet triaged
Projects
None yet
Development

No branches or pull requests

4 participants