Erro JPA no SpringBoot

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á.

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<>();

1 curtida

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, "331.453.981-23", "Júlio Carvalho", Sexo.MASCULINO);
	Pessoa p2 = new Pessoa(null, "239.444.761-45", "Camila Baroni", Sexo.FEMININO);
	Pessoa p3 = new Pessoa(null, "321.987.345-72", "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("15 3887-1523", "15 8985-1237"));
	pass2.getTelefones().addAll(Arrays.asList("16 2974-1231", "16 9786-0013"));

	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!

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.

1 curtida

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

Abraço