2

I was studying Spring Data JPA recently, and I faced a weird circular dependency error.

Previously, I often used MyBatis as shown below.

// I often declare mapper first, and inject it to my persistence.

@Mapper
public interface MyBatisMapper {/* ... */}

/* ------------ */

@Repository
public class MyBatisRepo {
  private final MyBatisMapper mapper;

  public MyBatisRepo(MyBatisMapper mapper) {
    this.mapper = mapper;
  }
  
  /* ... */

}

So I tried JPA on this way, and faced a circular dependency.

Here's the code on JPA


  1. TestEntity
// entity just for test
@Entity
public class TestEntity {

    @Id
    private Long id;
}
  1. Repository interface (contracts)
// Persistence contracts
public interface TestRepo {

}
  1. JPA DAO
// 
public interface JPATestRepo extends JpaRepository<TestEntity, Long> {

}
  1. Repository implements
@Repository
public class JPATestRepoImpl implements TestRepo {

    private final JPATestRepo jpaRepo;

    public JPATestRepoImpl(JPATestRepo jpaRepo) {
        this.jpaRepo = jpaRepo;
    }
}
  1. Errors
2024-10-19T19:46:00.760+09:00  WARN 66384 --- [testing] [           main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'JPATestRepoImpl' defined in file [/~~~/Desktop/Coding/testing/build/classes/java/main/core/testing/JPATestRepoImpl.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'JPATestRepoImpl': Requested bean is currently in creation: Is there an unresolvable circular reference?
2024-10-19T19:46:00.761+09:00  INFO 66384 --- [testing] [           main] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2024-10-19T19:46:00.762+09:00  INFO 66384 --- [testing] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2024-10-19T19:46:00.807+09:00  INFO 66384 --- [testing] [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
2024-10-19T19:46:00.811+09:00  INFO 66384 --- [testing] [           main] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-10-19T19:46:00.825+09:00 ERROR 66384 --- [testing] [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌──->──┐
|  JPATestRepoImpl defined in file [/~~~/Desktop/Coding/testing/build/classes/java/main/core/testing/JPATestRepoImpl.class]
└──<-──┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.


Process finished with exit code 1

In console, it says JPATestRepoImpl has a cycle to itself.

But in JPATestRepoImpl on above, it doesn't. (at least on my thought)

I've treid restart, claen & rebuild application, but it's all the same.

HOWEVER, if I rename JPATestRepoImpl to another (like TestRepoJPAImpl), the circular dependency disapper like magic...

It seems like Spring or JPA conflicts its' bean names to each other, but I have no idea why it has.

So I wanted to ask whether there is really a circular dependency in my source code or where it comes from if there isn't. If there is any policy coming from JPA, is there any documentation on it?

You can see full source code on here

5
  • When you used private final JPATestRepo jpaRepo; in your JPATestRepoImpl, are you sure you aren't referring to the interface you are implementing? Commented Oct 19, 2024 at 14:07
  • Yes I’m pretty sure about it. All the beans I made is all above on source code. I haven’t made any more beans. Commented Oct 19, 2024 at 14:20
  • I've made github repository of it. Github link Commented Oct 19, 2024 at 14:26
  • @Andy Wilkinson, I think the project in Github as reported from OP above should be investigated as a possible bug in spring boot framework. I have verified it is reproduceable and it seems like not code related in OP example project. Commented Oct 19, 2024 at 20:35
  • It might be related to this document. Custom Repository Implementations Commented Oct 20, 2024 at 6:41

2 Answers 2

3

The default repositoryImplementationPostfix is Impl so Spring is assuming that your JPATestRepoImpl is a custom implementation of JPATestRepo.

If you want to use Impl for another purpose, you can change the repositoryImplementationPostfix with the following setting:

@EnableJpaRepositories(repositoryImplementationPostfix = "CustomImpl")

link to the repositoryImplementationPostfix documentation

Sign up to request clarification or add additional context in comments.

Comments

0

I recently encountered the same issue, and the problem was as follows:

Currently, the file name for the JPA DAO is JPATestRepo.

JPA looks for the implementation class of the JpaRepository interface by appending Impl to the class name by default. Impl is a convention used for naming implementation classes, but this behavior can be changed using the repositoryImplementationPostfix property.

However, the file name for our custom repository implementation class was also named JPATestRepoImpl. As a result, the implementation class for JPATestRepo was recognized as the JPATestRepoImpl we created. Since JPATestRepoImpl references JPATestRepo, a circular reference issue occurred.

2 Comments

I've reported this behavior to Spring GitHub Issue, and I think it's a bug.
They changed Custom Repository Implementations document. According to its' WARNING section, Spring Data custom repository implementation was built on Impl postfix-pattern, and a type located in the same package as the repository interface is considered a custom implementation. And further, they say A class following that name can lead to undesired behavior. We consider the single-custom implementation naming deprecated and recommend not using this.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.