Getting InvalidDataAccessApiUsageException: Path '%s' from root %s must not span a cyclic property reference when using ExampleMatcher

Viewed 570

Client Model:

@Entity
public class Client {
    @Id
    private Integer id;

    @OneToOne(mappedBy = "client", fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.REMOVE})
    private ClientLegalTerm clientLegalTerm;

    ...//Getters and Setters
}

ClientLegalTerm model:

@Entity
public class ClientLegalTerm {
    @Id
    private Integer id;

    @OneToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(foreignKey = @ForeignKey(name = "FK_client_legal_term_client_id"))
    private Client client;

    ...//Getters and Setters
}

MyNewEntity model:

@Entity
public class MyNewEntity {
    @Id
    private Integer id;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Client client;

    ...//Getters and Setters

I'm using JpaRepository to manage MyNewEntity and I want to use QueryByExample to get an instance from the repo.

    ExampleMatcher matcher = ExampleMatcher.matching()
           .withMatcher("client.id", match -> match.exact());
    Example<MyNewEntity> example = Example.of(new MyNewEntity(client), matcher);

    Optional<MyNewEntity> optionalEntity = investorClosedEndTransactionRepository.findOne(example);

But I'm getting an exception on the findOne() call if the ClientLegalTerm is populated. org.springframework.dao.InvalidDataAccessApiUsageException: Path 'client.legalTerm.client' from root MyNewEntity must not span a cyclic property reference! I know it's because there is a circular reference back to the same client from within client, but because ClientLegalTerm is a One to One table, and that's the model I am dealing with. Is there a way to get around that exception?

Maybe there a different way to match the client by just its Id?

Full Stacktrace:

2020-11-04 12:28:17,588 ERROR [com.krfs.web.handler.ControllerExceptionHandler handleUncaughtException] - 127.0.0.1 8de1b613-6e6d-4d00-82b3-773934d9f09d An unhandled exception has occurred
org.springframework.dao.InvalidDataAccessApiUsageException: Path 'client.legalTerm.client' from root InvestorClosedEndTransaction must not span a cyclic property reference!
[{ com.alpsinc.live.loader.InvestorClosedEndTransaction@c0c64c52 }] -client-> [{ com.krfs.model.Client@86d }] -legalTerm-> [{ com.krfs.model.ClientLegalTerm@86d }] -client-> [{ com.krfs.model.Client@86d }]
        at org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder.getPredicates(QueryByExamplePredicateBuilder.java:163)
        at org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder.getPredicates(QueryByExamplePredicateBuilder.java:167)
        at org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder.getPredicates(QueryByExamplePredicateBuilder.java:167)
        at org.springframework.data.jpa.convert.QueryByExamplePredicateBuilder.getPredicate(QueryByExamplePredicateBuilder.java:102)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository$ExampleSpecification.toPredicate(SimpleJpaRepository.java:886)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.applySpecificationToCriteria(SimpleJpaRepository.java:762)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:693)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findOne(SimpleJpaRepository.java:466)
        at sun.reflect.GeneratedMethodAccessor6222.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45005)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:371)
        at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:204)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:657)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:621)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:367)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
        at com.sun.proxy.$Proxy2110.findOne(Unknown Source)
        at sun.reflect.GeneratedMethodAccessor6220.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:45005)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.zeroturnaround.javarebel.integration.util.ReloadingProxyFactory$ReloadingMethodHandler.invoke(SourceFile:74)
        at com.sun.proxy.$Proxy2110.findOne(Unknown Source)
        at com.alpsinc.live.loader.InvestorClosedEndTransactionService.validateEntityStatus(InvestorClosedEndTransactionService.java:78)
1 Answers

I had the same error:

org.springframework.dao.InvalidDataAccessApiUsageException: Path 'company.taxData.company' from root User must not span a cyclic property reference!

In my case was because I had these models:

@Entity
@Table(name = "users")
public class User extends Item
{
    @NotBlank
    @Email
    private String email;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(nullable = false)
    @NotNull
    private Company company;
}
@Entity
public class Company extends Item
{
    @OneToMany(
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER,
            orphanRemoval = true)
    private Set<User> users;

    @OneToMany(mappedBy = "company",
            cascade = CascadeType.ALL,
            fetch = FetchType.EAGER,
            orphanRemoval = true)
    private Set<Address> addresses;
}
@Entity
public class Address extends Item
{
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(nullable = false)
    @NotNull
    private Company company;
}

And I was trying to get the user in this way:

public Optional<User> findOneByEmailAndCompany(final String email, final Company company)
{
    final User user = new User();
    user.setCompany(company);
    user.setEmail(email);
    return userRepository.findOne(Example.of(user));
}

And the QueryByExamplePredicateBuilder JPA class tries to resolve in a reflection way the attributes and classes, but in spansCycle function checks that the identityHex of the actual value and the identityHex of the parent are the same and throws the InvalidDataAccessApiUsageException to avoid an infinite recursión:

boolean spansCycle() {

    if (value == null) {
        return false;
    }

    String identityHex = ObjectUtils.getIdentityHexString(value);
    PathNode current = parent;

    while (current != null) {

        if (current.value != null && ObjectUtils.getIdentityHexString(current.value).equals(identityHex)) {
            return true;
        }
        current = current.parent;
    }

return false;
}

I solved that by creating a custom query in the UserRepository doing the Inner join between Company and User:

@Repository
public interface UserRepository extends JpaRepository<User, Long>
{
    @Query("SELECT u FROM User u INNER JOIN u.company uc WHERE uc.id = :companyId AND u.email = :userEmail")
    public Optional<User> findByEmailAndCompanyId(String userEmail, Long companyId);
}
Related