Erro ao recuperar dados do backend

Boa noite pessoal,

Eu to convertendo meu projeto Java que usava Vraptor( que está funcionando) para usar Spring Boot(Banco de dados mysql).
Copiei as entidades que tinha no Vraptor e joguei pro novo projeto no springboot, porém quando fiz a chamada no postman da minha api para testar, tá dando erro de recursividade.

(through reference chain: br.com.backupsafe.api.model.configuracao.ConfiguracaoBackup["agendaBackups"]->org.hibernate.collection.internal.PersistentBag[0]->br.com.backupsafe.api.model.configuracao.AgendaBackup["configuracaoBackup"]->br.com.backupsafe.api.model.configuracao.ConfiguracaoBackup["agendaBackups"]->org.hibernate.collection.internal.PersistentBag[0]->br.com.backupsafe.api.model.configuracao.AgendaBackup["configuracaoBackup"]->br.com.backupsafe.api.model.configuracao.ConfiguracaoBackup["agendaBackups"]->org.hibernate.collection.internal.PersistentBag[0]-
java.lang.StackOverflowError: null
	at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_231]
	at java.lang.ClassLoader.defineClass(Unknown Source) ~[na:1.8.0_231]
	at java.security.SecureClassLoader.defineClass(Unknown Source) ~[na:1.8.0_231]
	at java.net.URLClassLoader.defineClass(Unknown Source) ~[na:1.8.0_231]
	at java.net.URLClassLoader.access$100(Unknown Source) ~[na:1.8.0_231]
	at java.net.URLClassLoader$1.run(Unknown Source) ~[na:1.8.0_231]
	at java.net.URLClassLoader$1.run(Unknown Source) ~[na:1.8.0_231]
	at java.security.AccessController.doPrivileged(Native Method) ~[na:1.8.0_231]
	at java.net.URLClassLoader.findClass(Unknown Source) ~[na:1.8.0_231]
	at java.lang.ClassLoader.loadClass(Unknown Source) ~[na:1.8.0_231]
	at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) ~[na:1.8.0_231]

Eu to achando que o problema está no relacionamento do JPA que criei.

Abaixo segue minhas classes:
@Entity
@Table(name=“configuracaobackup”)
public class ConfiguracaoBackup implements Serializable{

private static final long serialVersionUID = -6947762387122485012L;

@Id
@Column(name="codigo_cbk")
private Integer id;
	
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="codemp_cbk")
private Empresa empresa;

@Column(name="ativo_cbk")
private Boolean ativo;

@Column(name="descri_cbk")
private String descricao;

@Column(name="diasbkp_cbk")
private Integer diasBackup;

@Column(name="cmd_cbk")
private String comando;

@ManyToOne
@JoinColumn(name="usuins_cbk")
private Usuario usuarioInsercao;

@Column(name="dahins_cbk")
private Date dataHoraInsercao;

@ManyToOne
@JoinColumn(name="usualt_cbk")
private Usuario usuarioAlteracao;

@Column(name="dahalt_cbk")
private Date dataHoraAlteracao;
	
@OneToMany(mappedBy="configuracaoBackup",cascade=CascadeType.ALL)	
private List<AgendaBackup> agendaBackups;

@Column(name="dirbkp1_cbk")
private String diretorioCopia1;

@Column(name="dirbkp2_cbk")
private String diretorioCopia2;

@Column(name="dirbkp3_cbk")
private String diretorioCopia3;

public ConfiguracaoBackup() {
}
// Getters and setters
}

@Entity
@Table(name = “agendabackup”)
public class AgendaBackup implements Serializable {

private static final long serialVersionUID = -5853146851210765003L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "codigo_abk")
private Integer id;

@ManyToOne
@JoinColumn(name = "codcbk_abk", nullable = false)
private ConfiguracaoBackup configuracaoBackup;

@ManyToOne
@JoinColumn(name = "codabt_abk", nullable = false)
private AgendaBackupTipo agendaBackupTipo;

@Column(name = "dahini_abk", nullable = false)
private Date dataHoraInicio;

@Column(name = "temrep_abk")
private Integer tempoRepeticao;

@Column(name = "diasem_abk")
private Integer diasSemana;

@Column(name = "diames_abk")
private Long diasMes;

public AgendaBackup() {
}
// getters and setters
}

Tabela configuracaobackup

CREATE TABLE  configuracaobackup (
  codigo_cbk int(11) NOT NULL AUTO_INCREMENT,
  usualt_cbk int(11) DEFAULT NULL,
  usuins_cbk int(11) NOT NULL,
  codemp_cbk int(11) NOT NULL,
  codftp_cbk int(11) NOT NULL,
  ativo_cbk tinyint(1) NOT NULL,
  diasbkp_cbk int(11) DEFAULT NULL,
  cmd_cbk varchar(200) DEFAULT NULL,
  dahins_cbk datetime NOT NULL,
  dahalt_cbk datetime DEFAULT NULL,
  descri_cbk varchar(200) DEFAULT NULL,
  dirbkp1_cbk text COMMENT 'Diretorio para salvar o backup 1',
  dirbkp2_cbk text COMMENT 'Diretorio para salvar o backup 2',
  dirbkp3_cbk text COMMENT 'Diretorio para salvar o backup 3',
  PRIMARY KEY (codigo_cbk),
  KEY backupconfiguracao_FK_empresa (codemp_cbk),
  KEY backupconfiguracao_FK_ftp (codftp_cbk),
  KEY configuracaobackup_FK_usuario_cadastro (usuins_cbk),
  KEY configuracaobackup_FK_usuario_alteracao (usualt_cbk),
  CONSTRAINT configuracaobackup_ibfk_1 FOREIGN KEY (codemp_cbk) REFERENCES empresa (codigo_emp) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT configuracaobackup_ibfk_2 FOREIGN KEY (codftp_cbk) REFERENCES ftp (codigo_ftp) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT configuracaobackup_ibfk_3 FOREIGN KEY (usuins_cbk) REFERENCES usuario (codigo_usu) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT configuracaobackup_ibfk_4 FOREIGN KEY (usualt_cbk) REFERENCES usuario (codigo_usu) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=295 DEFAULT CHARSET=latin1;    

Tabela agendabackup

CREATE TABLE  agendabackup (
  codigo_abk int(11) NOT NULL AUTO_INCREMENT,
  codabt_abk int(11) NOT NULL,
  codcbk_abk int(11) NOT NULL,
  dahini_abk datetime NOT NULL,
  temrep_abk int(11) DEFAULT NULL,
  diasem_abk int(11) DEFAULT NULL,
  diames_abk int(11) DEFAULT NULL,
  PRIMARY KEY (codigo_abk),
  KEY agendabackup_FK_configuracaobackup (codcbk_abk),
  KEY agendabackup_FK_agendabackuptipo (codabt_abk),
  CONSTRAINT agendabackup_ibfk_1 FOREIGN KEY (codcbk_abk) REFERENCES configuracaobackup (codigo_cbk) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT agendabackup_ibfk_2 FOREIGN KEY (codabt_abk) REFERENCES agendabackuptipo (codigo_abt) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1108 DEFAULT CHARSET=latin1;

A classe ConfiguracaoBackup está com uma referência cíclica com AgendaBackup (uma configuração possui várias agendas, onde cada agenda possui uma configuração). Com isso, quando o spring vai serializar para JSON, ele vai entrar num looooop por conta desse relacionamento.

O ideal msm e sempre retornar objetos DTO para a tela, para deixar desacoplado, mas, vc também pode usar a anotação @JsonIgnore do jackson para evitar o problema (coloque essa anotação no atributo configuracaoBackup da classe AgendaBackup, ou no agendaBackups da classe ConfiguracaoBackup).

Deu certo colocando o ‘@JsonIgnore’.
Com relação a retornar um DTO, eu teria que na classe configuracaoBackupServiceImpl que é onde ele faz a chamada ao repositorio, ter um método que popula esse DTO? Ou tem uma outra forma de fazer sem ter que setar o dto pegando o get da classe?
Ex: dto.setDescricao(entidade.getdescricao())

Eu gosto da abordagem de converter para DTO dentro do controller, para não ficar misturando as coisas. Não eh legal a camada de serviço conhecer os DTOs.

Ahhhh entendi!
A ideia de criar um DTO então é para que retorne exatamente o que precisa. Não traga um monte de informação “desnecessária nesse momento”.

1 curtida

Isso, evitando alto acoplamento.

Legal @Lucas_Camara. Muito obrigado pela ajuda!

Eu to utilizando a paginação do SpringBoot. Como eu faria para paginar os dados de DTO?

Esse é o metodo que chamdo via api.

@GetMapping(value = "{page}/{count}")
public ResponseEntity<Response<Page<ConfiguracaoBackup>>> findAll(HttpServletRequest request, @PathVariable("page") int page,@PathVariable("count") int count) {
	
	Response<Page<ConfiguracaoBackup>> response = new Response<Page<ConfiguracaoBackup>>();
	Page<ConfiguracaoBackup> configuracoes = service.findAll(page,count);
	
	response.setData(configuracoes);
	return ResponseEntity.ok(response);
	
}

Repare que ele pagina a classe configuraçãobackup e não meu dto.

Classe de DTO.

public class ConfiguracaoBackupDto {

private Integer id;
private Integer empresaId;
private Boolean ativo;
private String descricao;
private Integer diasbackup;	
private Integer usuarioInsercaoId;
private Integer usuarioAlteracaoId;
private Date dataHoraInsercao;
private Date dataHoraAlteracao;
private String diretorioCopia1;
private String diretorioCopia2;
private String diretorioCopia3;

public ConfiguracaoBackupDto(Integer id, Integer empresaId, boolean ativo,String descricao,Integer diasBackup,String dirCopia1,
		String dirCopia2,String dirCopia3,Integer usuarioInsercaoId,Integer usuarioAlteracaoId,Date dataHoraInsercao, Date dataHoraAlteracao) {
	
	this.id = id;
	this.empresaId = empresaId;
	this.ativo = ativo;
	this.descricao = descricao;
	this.diasbackup = diasBackup;		
	this.diretorioCopia1 = dirCopia1;
	this.diretorioCopia2 = dirCopia2;
	this.diretorioCopia3 = dirCopia3;
	this.usuarioInsercaoId = usuarioInsercaoId;
	this.usuarioAlteracaoId = usuarioAlteracaoId;
	this.dataHoraInsercao = dataHoraInsercao;
	this.dataHoraAlteracao = dataHoraAlteracao;
	
}