What are the ways to avoid infinite recursion without using the JsonIgnore annotation in SpringBoot

5

I have a @OneToMany relationship between Produto and TipoProduto where TipoProduto can have multiple Produto s and a Produto can only have TipoProduto .

I would like to list all Produto s of a TipoProduto when searching for it and would also like to list the TipoProduto of a Produto when searching for it. I tried to overwrite the toString() method but with no success. I had some ideas but I do not know if they are valid, so I would like to know if there is a way to avoid this or else the problem is in the design of my application.

TipoProduto :

public class TipoProduto {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String descricao;
    @OneToMany(mappedBy = "tipoProduto", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JsonIgnore
    private Set<Produto> produtos;
    ....
}

Produto :

public class Produto {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String nome;
    @ManyToOne
    @JoinColumn(name = "tipo_produto_id", nullable = true)
    private TipoProduto tipoProduto;
    ...
}
    
asked by anonymous 30.10.2017 / 00:33

2 answers

4

Why is not it a good idea to turn entities into JSON directly

The error lies in wanting to transform your JPA entities into JSON. I already participated in a project that delayed months only because of this.

Transforming JPA entities into JSON is problematic on account of:

  • Problems with ties - it is difficult to know who the father is and who the child is. Sometimes in some cases you will want the children telling you who the parents are and sometimes you will want the parents telling you who the children are.

  • The format of the JPA entity is not always the same as the format you want in JSON. For example, if you have a Usuario entity with a senha field, you will not want the senha field appearing in JSON. If you have a Vendedor entity and you want to know how many sales it has made in the month, JSON would look huge and polluted with all the sales information when the only thing you want is to know how many sales it is. li>

  • You can not contextualize JSON, so that it has a set of certain information in one context and a different set of information in another context.

  • There are cases where the data you need to export or import does not correspond directly to any of your entities.

All these problems come from the fact that JPA mapping is used to map object-relational mapping of the application, whereas object-JSON mapping has a completely different purpose. Putting the two in the same classes, you end up doing a mapping of the database tables to JSON, which is not usually what you want to do.

The solution

The solution is to create a group of classes in parallel to represent your JSON. In a different project than the one I mentioned above, when using this approach we had almost no problem with JSON and that part of the project was extremely quiet and simple to work with.

In the middle of the project, we separated these classes representing JSON into a separate library so as not to run the risk of mixing (from time to time it happened by accident). When separating them, any attempt to mix them with the entities became a compilation error, since the package with the entities depended on the package of classes that represent JSON, but the inverse does not.

For example:

public final class ProdutoJSON {
    private final String nome;
    private final Long id;
    private final String descricaoTipo;
    private final Long idTipo;

    public ProdutoJSON(String nome, Long id, String descricaoTipo, Long idTipo) {
        this.nome = nome;
        this.id = id;
        this.descricaoTipo = descricaoTipo;
        this.idTipo = idTipo;
    }

    // Acrescente os getters aqui.
}
public final class TipoProdutoJSON {
    private final String descricao;
    private final Long id;
    private final List<ProdutoPorTipoJSON> produtosListados;

    public TipoProdutoJSON(String descricao, Long id, List<ProdutoPorTipoJSON> produtosListados) {
        this.descricao = descricao;
        this.id = id;
        this.produtosListados = produtosListados;
    }

    // Acrescente os getters aqui.
}
public final class ProdutoPorTipoJSON {
    private final String nome;
    private final Long id;

    public ProdutoPorTipoJSON(String nome, Long id) {
        this.nome = nome;
        this.id = id;
    }

    // Acrescente os getters aqui.
}

Note that the classes above are just a bunch of raw data and they have an immutable structure. This is because the only purpose for them is to only be used to structure JSONs and nothing else. They should have the desired JSON structure with nothing more and nothing less, so do not worry too much about reusing them. They can be modeled to the liking of your tool, be it Jackson, GSON or whatever you are using, making these classes free of any JPA restrictions or rules.

Here's how you instantiate these classes from their entities:

public class TipoProduto {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String descricao;

    @OneToMany(mappedBy = "tipoProduto", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<Produto> produtos;

    // ...

    public TipoProdutoJSON criarJsonComProdutos() {
        List<ProdutoPorTipoJSON> p = produtos
                .stream()
                .map(Produto::criarJsonSemTipoProduto)
                .collect(Collectors.toList());

        return new TipoProdutoJSON(descricao, id, p);
    }
}
public class Produto {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String nome;

    @ManyToOne
    @JoinColumn(name = "tipo_produto_id", nullable = true)
    private TipoProduto tipoProduto;

    // ...

    public ProdutoPorTipoJSON criarJsonSemTipoProduto() {
        return new ProdutoPorTipoJSON(nome, id);
    }

    public ProdutoJSON criarJsonComTipoProduto() {
        return new ProdutoJSON(
                nome,
                id,
                tipoProduto == null ? null : tipoProduto.getDescricao(),
                tipoProduto == null ? null : tipoProduto.getId());
    }
}
    
30.10.2017 / 03:38
2

Quite possibly your application has a design problem, as it is suffering from this circular reference problem (or circular dependency, as some call it).

If it is the case to restructure the design, it would fit the old question:

  

In the case of the two entities, in a scenario of excluding one, who is still alive without depending on the other?

Now if the solution you deploy is inherent in the application business, little can be done about design.

For these cases, there are several "contour solutions" (famous workarounds).

In the context of Spring, take a look at this great stuff . Among the various options that it shows, choose one that best suits your scenario.

Of the cases that the author cites, I have already used the reference method set to do the beans injection.

    
30.10.2017 / 03:26