Spring-boot EntityListener, The dependencies of some of the beans in the application context form a cycle
Asked Answered
B

1

7

I am facing dependency cycle in my following design (taken from here).

I have 2 entities Post and PostLog. When Post is created, I want to persist it in PostLog as well. So created listener and applied it to "Post" entity. Both entities Post and PostLog are also using spring-boot "AuditingEntityListener", but for simplicity purpose I do not add that code here.

My Entities and Listener structure -

@Data
@EqualsAndHashCode(callSuper = false)
@Entity
@Table(name = "post")
@EntityListeners({AuditingEntityListener.class, PostLogListener.class})
public class Post extends Auditable<String> {
...
}

@Data
@EqualsAndHashCode(callSuper = false)
@Entity
@Table(name = "post_log")
@EntityListeners(AuditingEntityListener.class)
public class PostLog extends Auditable<String> {
...
}

@Component
@RequiredArgsConstructor
public class PostLogListener {
  
  private final PostLogRepository repo;

  @PostPersist
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void logEvent(final Post post) {
    PostLog log = createLog(post); // implementation is omitted here for keeping short
    repo.save(log);
  }
}

@Repository
public interface PostLogRepository extends CrudRepository<PostLog, Long> {}

Error I am getting -

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

Description:

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

┌─────┐
|  entityManagerFactory defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]
↑     ↓
|  com.example.listener.PostLogListener
↑     ↓
|  postLogRepository defined in com.example.repository.PostLogRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration
↑     ↓
|  (inner bean)#53c2dd3a
└─────┘

I have done some research but could not find the right solution.

Beverlybevers answered 21/2, 2021 at 16:41 Comment(0)
P
17

Use lazy initialization to solve cyclic dependencies. For that, you need to create the constructor yourself to inject spring bean and use @Lazy (org.springframework.context.annotation.Lazy)

@Component
public class PostLogListener {
  
  private final PostLogRepository repo;

  public PostLogListener(@Lazy PostLogRepository repo) {
    this.repo = repo;
  }

  @PostPersist
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void logEvent(final Post post) {
    PostLog log = createLog(post); // implementation is omitted here for keeping short
    repo.save(log);
  }
}

Note - This is required if any of the injected beans depends on the EntityManager. Spring Data repositories depend on EntityManager, so any bean having a repository or a direct entityManager will make a circle. Spring Dependency Injection into JPA entity listener

Perspire answered 24/2, 2021 at 6:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.