StackOverflowError utilizando JPA

Criei um projetinho utilizando o JPA mais Spring, com as entidades Categoria, Curso, Disciplina, onde uma Categoria terá vários Cursos e um Curso irá pertencer a apenas uma Categoria, o Curso terá 0 ou N Disciplinas e a Disciplina pode pertencer a 0 ou N Cursos. Fiz as entidades, repositórios e services e está funcionando o create, update e delete, porém quando tento realizar alguma busca, seja por todos os elementos de uma entidade ou pelo Id o corre o erro: java.lang.StackOverflowError: null

Imagino que seja pq uma entidade está chamando a outra de forma recursiva e infinita e estoura a exception, mas não faço ideia de como arrumar, se alguem puder me ajudar ficarei muito agradecido.

Entidade Categoria:

@Entity
@Table(name = "categorias")
public class Categoria {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String nomeCategoria;
    private String descricao;

    @OneToMany(mappedBy = "categoria", fetch = FetchType.EAGER)
    private List<Curso> cursos;



    @Deprecated
    public Categoria(){}
    public Categoria(String nomeCategoria, String descricao) {
        this.nomeCategoria = nomeCategoria;
        this.descricao = descricao;
    }

    public Long getId() {
        return id;
    }

    public String getNomeCategoria() {
        return nomeCategoria;
    }

    public void setNomeCategoria(String nomeCategoria) {
        this.nomeCategoria = nomeCategoria;
    }

    public String getDescricao() {
        return descricao;
    }

    public void setDescricao(String descricao) {
        this.descricao = descricao;
    }

    public List<Curso> getCursos() {
        return cursos;
    }

    public void setCursos(List<Curso> cursos) {
        this.cursos = cursos;
    }

    @Override
    public String toString() {
        return "Categoria{" +
                "id=" + id +
                ", nomeCategoria='" + nomeCategoria + '\'' +
                ", descricao='" + descricao + '\'' +
                ", cursos=" + cursos +
                '}';
    }
}

Entidade Curso:

@Entity
@Table(name = "cursos")
public class Curso {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String nomeCurso;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "categoria_id", nullable = false)
    @JsonIgnore
    private Categoria categoria;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "cursos_disciplinas", joinColumns = @JoinColumn(name = "curso_fk"),
                inverseJoinColumns = @JoinColumn(name = "disciplina_fk")
    )
    private List<Disciplina> disciplinas;



    @Deprecated
    public Curso(){}
    public Curso(String nomeCurso, Categoria categoria) {
        this.nomeCurso = nomeCurso;
        this.categoria = categoria;
    }


    public Long getId() {
        return id;
    }

    public String getNomeCurso() {
        return nomeCurso;
    }

    public void setNomeCurso(String nomeCurso) {
        this.nomeCurso = nomeCurso;
    }

    public Categoria getCategoria() {
        return categoria;
    }

    public void setCategoria(Categoria categoria) {
        this.categoria = categoria;
    }

    public List<Disciplina> getDisciplinas() {
        return disciplinas;
    }

    public void setDisciplinas(List<Disciplina> disciplinas) {
        this.disciplinas = disciplinas;
    }


    @Override
    public String toString() {
        return "Curso{" +
                "id=" + id +
                ", nomeCurso='" + nomeCurso + '\'' +
                ", categoria=" + categoria +
                ", disciplinas=" + disciplinas +
                '}';
    }
}

Entidade Disciplina:

@Entity
@Table(name = "disciplinas")
public class Disciplina {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String nomeDisciplina;
    private TipoAula tipoAula;
    @Column(nullable = false)
    private Double cargaHoraria;

    @ManyToMany(mappedBy = "disciplinas", fetch = FetchType.LAZY)
    @JsonIgnore
    private List<Curso> cursos;

    @Deprecated
    public Disciplina(){}
    public Disciplina(String nomeDisciplina, TipoAula tipoAula, Double cargaHoraria) {
        this.nomeDisciplina = nomeDisciplina;
        this.tipoAula = tipoAula;
        this.cargaHoraria = cargaHoraria;
    }


    public Long getId() {
        return id;
    }

    public String getNomeDisciplina() {
        return nomeDisciplina;
    }

    public void setNomeDisciplina(String nomeDisciplina) {
        this.nomeDisciplina = nomeDisciplina;
    }

    public TipoAula getTipoAula() {
        return tipoAula;
    }

    public void setTipoAula(TipoAula tipoAula) {
        this.tipoAula = tipoAula;
    }

    public Double getCargaHoraria() {
        return cargaHoraria;
    }

    public void setCargaHoraria(Double cargaHoraria) {
        this.cargaHoraria = cargaHoraria;
    }

    public List<Curso> getCursos() {
        return cursos;
    }

    public void setCursos(List<Curso> cursos) {
        this.cursos = cursos;
    }

    @Override
    public String toString() {
        return "Disciplina{" +
                "id=" + id +
                ", nomeDisciplina='" + nomeDisciplina + '\'' +
                ", tipoAula=" + tipoAula +
                ", cargaHoraria=" + cargaHoraria +
                ", cursos=" + cursos +
                '}';
    }
}

Não irei colocar o código dos repositorys mas estão herdando de CrudRepository

Categoria Service:

@Service
public class CategoriaService {

    private CategoriaRepository categoriaRepository;

    public CategoriaService(CategoriaRepository categoriaRepository){
        this.categoriaRepository = categoriaRepository;
    }


    public void visualizar(){
        Iterable<Categoria> categorias = categoriaRepository.findAll();
        System.out.println(categorias);
    }

    public void visualizarPorIdConsole(Long id){
        Optional<Categoria> cateogoriaPorId = categoriaRepository.findById(id);
        if(cateogoriaPorId.isPresent())
            System.out.println(cateogoriaPorId.get());
        else
            System.out.println("Categoria procurada pelo Id: " + id + " Não foi encontrada");
    }

    public Categoria visualizarPorId(Long id){
        Optional<Categoria> cateogoriaPorId = categoriaRepository.findById(id);
        if(cateogoriaPorId.isPresent())
            return cateogoriaPorId.get();
        else
            return null;
    }

    public Categoria cadastrar(String nome, String descricao){
        Categoria categoria = new Categoria(nome, descricao);
        this.categoriaRepository.save(categoria);
        return  categoria;
    }

    public void atualizar(Long id, String nome, String descricao){
        Optional<Categoria> categoriaById = this.categoriaRepository.findById(id);

        if(categoriaById.isPresent()){
            Categoria categoria = categoriaById.get();

            categoria.setNomeCategoria(nome);
            categoria.setDescricao(descricao);
            this.categoriaRepository.save(categoria);
        }
    }


    public void deletar(Long id){
        this.categoriaRepository.deleteById(id);
    }
}

Curso Service:

@Service
public class CursoService {

    private CursoRepository cursoRepository;
    private DisciplinaRepository disciplinaRepository;

    public CursoService(CursoRepository cursoRepository, DisciplinaRepository disciplinaRepository){
        this.cursoRepository = cursoRepository;
        this.disciplinaRepository = disciplinaRepository;
    }


    public void visualizar(){
        Iterable<Curso> cursos = cursoRepository.findAll();
        System.out.println(cursos.toString());
    }

    public void visualizarPorIdTerminal(Long id){
        Optional<Curso> cursoPorId = cursoRepository.findById(id);
        if(cursoPorId.isPresent())
            System.out.println(cursoPorId.get());
        else
            System.out.println(("Não Encontrado o Curso pelo Id: " + id));
    }

    public Curso visualizarPorId(Long id){
        Optional<Curso> cursoPorId = cursoRepository.findById(id);
        if(cursoPorId.isPresent()){
            Curso curso = cursoPorId.get();
            return curso;
        }
        else
            return null;
    }

    public void cadastrar(String nomeCurso, Categoria categoria){
        Curso curso = new Curso(nomeCurso, categoria);
        cursoRepository.save(curso);
    }

    public void atualizar(Long id, String nomeCurso, Categoria categoria){
        Optional<Curso> cursoPorId = cursoRepository.findById(id);
        if(cursoPorId.isPresent()){
            Curso curso = cursoPorId.get();

            curso.setNomeCurso(nomeCurso);
            curso.setCategoria(categoria);

            cursoRepository.save(curso);
        }
        else
            System.out.println("O Curso procurado pelo Id: " + id + "Não foi encontrado");
    }

    public void deletar(Long id){
        cursoRepository.deleteById(id);
    }


    private List<Disciplina> sicronizarDisciplinas(List<Long> ids){
        List<Disciplina> disciplinas = new ArrayList<>();;
        for(var x = 0; x < ids.size(); x++){
            Optional<Disciplina> disciplinaPorId = disciplinaRepository.findById(ids.get(x));
            if(disciplinaPorId.isPresent()){
                Disciplina disciplina = disciplinaPorId.get();
                disciplinas.add(disciplina);
            }
            else
                return null;
        }
        return disciplinas;
    }

    public void cadastrarDisciplinasNoCurso(Long idCurso, List<Long> idDisciplina){
        Optional<Curso> cursoPorId = cursoRepository.findById(idCurso);

        if(cursoPorId.isPresent()){
            Curso curso = cursoPorId.get();
            List<Disciplina> disciplinas = this.sicronizarDisciplinas(idDisciplina);
            curso.getDisciplinas().addAll(disciplinas);
            this.cursoRepository.save(curso);
        }
    }
}

Disciplina Service:

@Service
public class DisciplinaService {

    private DisciplinaRepository disciplinaRepository;

    public DisciplinaService(DisciplinaRepository disciplinaRepository) {
        this.disciplinaRepository = disciplinaRepository;
    }


    public void visualizar(){
        Iterable<Disciplina> disciplinas = disciplinaRepository.findAll();
        System.out.println(disciplinas);
    }

    public void visualizarPorId(Long id){
        Optional<Disciplina> disciplinaPorId = disciplinaRepository.findById(id);
        if(disciplinaPorId.isPresent()){
            Disciplina disciplina = disciplinaPorId.get();

            System.out.println(disciplina);
        }
        else
            System.out.println("Disciplina procurada pelo Id: " + id + "Não foi encontrada");
    }

    public void cadastrar(String nomeDisciplina, TipoAula tipoAula, Double cargaHoraria){
        Disciplina disciplina = new Disciplina(nomeDisciplina, tipoAula, cargaHoraria);
        disciplinaRepository.save(disciplina);
    }

    public void atualizar(Long id, String nomeDisciplina, TipoAula tipoAula, Double cargaHoraria){
        Optional<Disciplina> disciplinaPorId = disciplinaRepository.findById(id);
        if(disciplinaPorId.isPresent()){
            Disciplina disciplina = disciplinaPorId.get();
            disciplina.setNomeDisciplina(nomeDisciplina);
            disciplina.setTipoAula(tipoAula);
            disciplina.setCargaHoraria(cargaHoraria);

            disciplinaRepository.save(disciplina);
        }
        else
            System.out.println("Disciplina buscada pelo Id: " + id + "Não foi encontrada");
    }

    public void deletar(Long id){
        disciplinaRepository.deleteById(id);
    }
}

Posta:

  1. O stack trace do erro
  2. A função contendo a linha onde o erro acusa (preferencialmente indicando para nós que linha é)

Msg do erro:

java.lang.StackOverflowError: null
	at org.hibernate.collection.spi.AbstractPersistentCollection.read(AbstractPersistentCollection.java:136) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:589) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:459) ~[na:na]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:590) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:459) ~[na:na]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:590) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:459) ~[na:na]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:590) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:459) ~[na:na]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:590) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:459) ~[na:na]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:590) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:173) ~[na:na]
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:459) ~[na:na]
	at org.hibernate.collection.spi.PersistentBag.toString(PersistentBag.java:590) ~[hibernate-core-6.2.13.Final.jar:6.2.13.Final]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Categoria.toString(Categoria.java:61) ~[classes/:na]
	at java.base/java.lang.String.valueOf(String.java:4461) ~[na:na]
	at br.com.iseedfaved.SistemaAcademico.orm.Curso.toString(Curso.java:71) ~[classes/:na]

O método toString da tua classe Curso invoca o toString da classe Categoria, que por sua vez invoca o toStringda classe Curso`, que por sua vez invoca…

Não relacionado com o erro, vi por ai alguns fetch = FetchType.EAGER. Normalmente isto não é boa idea e vais estar na maioria das vezes a obter coisas a mais do que o que necessitas da BD, tornando o teu sistema lento.

2 curtidas

Além do que o pmlm falou, pense com carinho se você vai incluir listas de objetos dentro do toString. Além de aumentar as chances de recursão, como ocorreu com você, isso pode forçar cargas bem pesadas no seu banco (mesmo que você defina as listas como lazy).