0

I have a Spring Boot application using JPA/Hibernate in its persistence layer. The application has read-only access to a database and basically has three entities Article, Category, and Field, which have the following relationships.

Article (*) -> (1) Category (*) <-> (1) Field

That is, an Article has a Category, and a Category always belongs to a single Field, however, multiple Category instances can belong to the same Field.

The application provides two REST endpoints, which give a single Article and a single Field by their IDs, respectively. Of course, this cannot work when using Jackson for serialization due to the cyclic dependency Category <-> Field.

What I want is when I retrieve an Article, it should give me its Category including the category's Field, but not all the other Category instances that belong to the this same Field. On the other hand, when I retrieve a Field, it should give me the Field including all Category instances that belong to this Field.

How can I achieve this?

Edit: I basically have a similar question as Jackson infinite loops many-to-one one-to-many relation

Said Savci
  • 818
  • 4
  • 14
  • 28
  • This question https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue does not help with my issue. – Said Savci Nov 15 '22 at 07:11
  • That question addresses pretty much all posibilities there are to address this cyclic dependency (`@JsonIgnore`, `@JsonIgnoreProperties`, `@JsonManagedReference`, `@JsonView`, DTO projections, ...). So I think this is still a duplicate. – g00glen00b Nov 15 '22 at 08:24
  • The only duplicate I could found is https://stackoverflow.com/questions/25581678/jackson-infinite-loops-many-to-one-one-to-many-relation?noredirect=1&lq=1, which does not have any answer. – Said Savci Nov 15 '22 at 08:56

1 Answers1

0

You can use interface-based projections, to only retrieve needed properties, since Spring Data allows modeling dedicated return types, to more selectively retrieve partial views of the managed aggregates.

Let's assume the entities are declared as shown below. For simplicity, only the id attribute is defined alongside association-mapping attributes.

@Entity
public class Article {
    @Id
    private Long id;
    @ManyToOne
    private Category category;
}
@Entity
public class Category {
    @Id
    private Long id;
    @OneToMany
    private Set<Article> articles;
    @ManyToOne
    private Field field;
}
@Entity
public class Field {
    @Id
    private Long id;
    @OneToMany
    private Set<Category> categories;
}

For the first endpoint where the Article is fetched by id, the projections should be declared as follows:

public interface ArticleDto {
    Long getId();
    CategoryDto1 getCategory();

    interface CategoryDto1 {
      Long getId();
      FieldDto1 getField();
    }

    interface FieldDto1 {
      Long getId();
    }
 }

The important bit here is that the properties defined here exactly match properties in the aggregate root.

Then, the additional query method should be defined in ArticleRepository:

interface ArticleRepository extends JpaRepository<Article, Long> {
    Optional<ArticleDto> findDtoById(Long id);
}

The query execution engine creates proxy instances of that interface at runtime for each element returned and forwards calls to the exposed methods to the target object.

Declare additional projections to retrieve properties needed for the second case:

public interface FieldDto2 {
    Long getId();
    Set<CategoryDto2> getCategories();

    interface CategoryDto2 {
      Long getId();
    }
}

Lastly, define the following query method in FieldRepository:

interface FieldRepository extends JpaRepository<Field, Long> {
    Optional<FieldDto2> findDtoById(Long id);
}

With this approach, the infinite recursion exception would never appear, as long as projections don't contain attributes causing recursion.

Toni
  • 3,296
  • 2
  • 13
  • 34