Erro JPA no SpringBoot

4 respostas Resolvido
S

Olá pessoal, estou tendo um problema com persistência e salvamento de transiente no JPA do SpringBoot.
Tenho duas classes, Time e Jogador, sendo que além de um atributo que é a lista de jogadores do time, tenho também na classe Time um atributo Jogador que é o capitão.
Não conheço muito ainda sobre anotações mas pelo pouco entendi pode ser feito da seguinte forma:

package com.samuelfranck.campeonatohandebol.domain;

@Entity

public class Jogador implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String nome;
private Date dataNascimento;
private String genero;
private Double altura;
private Double peso;


@ManyToOne
@JoinColumn(name="time_id")
private Time timeEmQueJoga;

public Jogador() {
}

public Jogador(Integer id, String nome, Date dataNascimento, String genero, Double altura, Double peso) {
	super();
	this.id = id;
	this.nome = nome;
	this.dataNascimento = dataNascimento;
	this.genero = genero;
	this.altura = altura;
	this.peso = peso;
	
}

“+ getters, setters e hashcode equals”
}

package com.samuelfranck.campeonatohandebol.domain;

@Entity

public class Time implements Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String nome;

@OneToOne
@JoinColumn(name="capitao_id")
private Jogador capitao;

@OneToMany(mappedBy="timeEmQueJoga")
private List<Jogador> jogadores = new ArrayList<>();

public Time() {
}

public Time(Integer id, String nome) {
	super();
	this.id = id;
	this.nome = nome;
}

" + getters, setters e hashcode equals"

Os erros que ocorrem sãos estes:

Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.samuelfranck.campeonatohandebol.domain.Jogador.timeEmQueJoga -> com.samuelfranck.campeonatohandebol.domain.Time; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.samuelfranck.campeonatohandebol.domain.Jogador.timeEmQueJoga -> com.samuelfranck.campeonatohandebol.domain.Time

Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.samuelfranck.campeonatohandebol.domain.Jogador.timeEmQueJoga -> com.samuelfranck.campeonatohandebol.domain.Time

Agradeço desde já.

4 Respostas

thimor
Solucao aceita

Quando voce vai salvar o jogador, o time ainda nao esta salvo. Pelo erro, voce precisaria primeiro salvar o time, para depois salvar o jogador. voce esta tentando salvar quem? O Jogador ou o time?

O certo é salvar o jogador e na associacao do OneToMany em time voce coloca um cascade.

@OneToOne
@JoinColumn(name="capitao_id", cascade = CascadeType.ALL)
private Jogador capitao;

@OneToMany(mappedBy="timeEmQueJoga", cascade = CascadeType.ALL)
private List<Jogador> jogadores = new ArrayList<>();
S

Valeu @thimor !
Conforme vc disse ao alterar a ordem de salvamento dos repositories e adicionar o cascade na lista de jogadores deu certo.
Não consegui adicionar o cascade no capitao, mas não fez falta por enquanto.

@thimor como eu faço pra saber qual repository salvar primeiro? Eu pensei que deveria ser na mesma ordem de instanciação dos objetos mas parece que não é bem assim.

Por exemplo, no código abaixo (está ocorrendo a exceção java.lang.NullPointerException: nulll)

package com.samuelfranck.companhiaaerea;

import java.text.SimpleDateFormat;
import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.CommandLineRunner;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.samuelfranck.companhiaaerea.domain.Passageiro;

import com.samuelfranck.companhiaaerea.domain.Pessoa;

import com.samuelfranck.companhiaaerea.domain.Piloto;

import com.samuelfranck.companhiaaerea.domain.Reserva;

import com.samuelfranck.companhiaaerea.domain.Voo;

import com.samuelfranck.companhiaaerea.domain.enums.Sexo;

import com.samuelfranck.companhiaaerea.repositories.PassageiroRepository;

import com.samuelfranck.companhiaaerea.repositories.PessoaRepository;

import com.samuelfranck.companhiaaerea.repositories.PilotoRepository;

import com.samuelfranck.companhiaaerea.repositories.ReservaRepository;

import com.samuelfranck.companhiaaerea.repositories.VooRepository;

@SpringBootApplication
public class CompanhiaAereaApplication implements CommandLineRunner {

@Autowired
private PassageiroRepository passageiroRepository;
@Autowired
private PessoaRepository pessoaRepository;
@Autowired
private ReservaRepository reservaRepository;
@Autowired
private VooRepository vooRepository;
@Autowired
private PilotoRepository pilotoRepository;

public static void main(String[] args) {
	SpringApplication.run(CompanhiaAereaApplication.class, args);

}

@Override
public void run(String... args) throws Exception {

	Pessoa p1 = new Pessoa(null, "[CPF removido]", "Júlio Carvalho", Sexo.MASCULINO);
	Pessoa p2 = new Pessoa(null, "[CPF removido]", "Camila Baroni", Sexo.FEMININO);
	Pessoa p3 = new Pessoa(null, "[CPF removido]", "José Sardenhas", Sexo.MASCULINO);

	SimpleDateFormat sdf1 = new SimpleDateFormat("dd/MM/yyyy");
	SimpleDateFormat sdf2 = new SimpleDateFormat("dd/MM/yyyy hh:mm");

	Passageiro pass1 = new Passageiro(null, sdf1.parse("16/07/1993"), p1);
	Passageiro pass2 = new Passageiro(null, sdf1.parse("30/08/2000"), p2);

	p1.setPassageiro(pass1);
	p2.setPassageiro(pass2);

	pass1.getTelefones().addAll(Arrays.asList("[telefone removido]", "[telefone removido]"));
	pass2.getTelefones().addAll(Arrays.asList("[telefone removido]", "[telefone removido]"));

	pass1.setPessoa(p1);
	pass2.setPessoa(p2);

	Voo v1 = new Voo(null, "8852", sdf2.parse("06/05/2020 13:20"));

	Reserva r1 = new Reserva(v1, pass1, "10E");
	Reserva r2 = new Reserva(v1, pass2, "10A");

	r1.setVoo(v1);
	r2.setVoo(v1);

	v1.getReservas().addAll(Arrays.asList(r1, r2));

	pass1.getReservas().addAll(Arrays.asList(r1));
	pass2.getReservas().addAll(Arrays.asList(r2));

	Piloto pil1 = new Piloto(p3, v1, "A-5480");

	pil1.setPessoa(p3);
	
	p3.setPiloto(pil1);
	
	v1.setPiloto(pil1);
	
	pil1.setVoo(v1);

	pessoaRepository.saveAll(Arrays.asList(p1, p2));
	passageiroRepository.saveAll(Arrays.asList(pass1, pass2));
	vooRepository.saveAll(Arrays.asList(v1));
	reservaRepository.saveAll(Arrays.asList(r1, r2));
	pilotoRepository.saveAll(Arrays.asList(pil1));
    
}

}

Muito obrigado!

thimor

Como o banco de dados precisa do ID para decidir quem é filho de quem, o primeiro objeto a ser salvo deve ser o que seu id ira passar para os seus filhos. Mas pelas anotações da JPA, se voce salvar o objeto pai, com o cascade definido ele ja faz isso para voce. Por exemplo, se fosse um carrinho de compras, vc tem o carrinho e os itens comprados. se voce mandar salvar o carrinho ele salva o carrinho e em seguida os itens, mas para isso o array de itens dentro do carrinho, precisa ter o cascade.

S

Ok @thimor
Então eu poderia salvar em qualquer ordem desde que estivesse aplicando o cascade em todos os objetos?

Abraço

Criado 5 de maio de 2020
Ultima resposta 8 de mai. de 2020
Respostas 4
Participantes 2