JPA Object References an Unsaved Transient instance - save the Transient instance before Flushing :

Asked

Viewed 11,232 times

9

When trying to save the "Addresses" of "Client" JPA/Spring returns me the following error in "main"

Caused by: org.hibernate.Transientpropertyvalueexception: Object References an Unsaved Transient instance - save the Transient instance before Flushing : com.nelioalves.cursomc.domain.Endereco.cidade -> com.nelioalves.curswto.domain.City

The code is like this:

    Cliente cli1 = new Cliente(null, "Maria Silva", "[email protected]", "36378912377", TipoCliente.PESSOAFISICA);

    cli1.getTelefones().addAll(Arrays.asList("27363323", "93838393"));

    Endereco e1 = new Endereco(null, "Rua Flores", "300", "Apto 303", "Jardim", "38220834", cli1, c1);
    Endereco e2 = new Endereco(null, "Avenida Matos", "105", "Sala 800", "Centro", "38777012", cli1, c2);

    cli1.getEnderecos().addAll(Arrays.asList(e1, e2));

    enderecoRepository.saveAll(Arrays.asList(e1, e2));
    clienteRepository.saveAll(Arrays.asList(cli1));

When removing the address and saving only the client it saves normally.

Here is the class Endereco:

@Entity
public class Endereco {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String logradouro;
    private String numero;
    private String complemento;
    private String bairro;
    private String cep;

    @ManyToOne
    @JoinColumn(name="cliente_id")
    private Cliente cliente;

    @ManyToOne
    @JoinColumn(name="cidade_id")
    private Cidade cidade;

    public Endereco() {
    }

Here is the class Cliente:

@Entity
public class Cliente implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String nome;
    private String email;
    private String cpfOuCnpj;
    private Integer tipo;

    @OneToMany(mappedBy="cliente")
    private List<Endereco> enderecos = new ArrayList<>();

    @ElementCollection
    @CollectionTable(name="TELEFONE")
    private Set<String> telefones = new HashSet<>();

    public Cliente() {
    }
  • 2

    Hello, put the code in the question and not the links.

4 answers

8


The Transientpropertyvalueexception error occurs when you are persisting an object that references another that is not persisted. In case when saving the Address your City object has not yet been saved.

To fix save the city reference or modify the @Manytoone annotation by adding the parameterCascade=Cascadetype.PERSIST so that the city is saved the moment the address is saved.

@ManyToOne(cascade=CascadeType.PERSIST)
@JoinColumn(name="cidade_id")
private Cidade cidade;

You can see the types of Scade in this link What are the types of Cache in JPA?

  • 1

    Thank you, I went to check the city of the second address had not been saved, so the mistake

4

Try trading this in your class Endereco:

@OneToMany(mappedBy="cliente")
private List<Endereco> enderecos = new ArrayList<>();

That’s why:

@OneToMany(mappedBy = "cliente", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Endereco> enderecos = new ArrayList<>();

And then, you can eliminate that line:

enderecoRepository.saveAll(Arrays.asList(e1, e2));

The idea is that persistence operations executed in the Cliente should be cascateadas for all Enderecos associated. The CascadeType.ALL is a wildcard value representing the merging of all operations of the CascadeType (PERSIST, REMOVE, REFRESH, MERGE and DETACH).

When a Cliente is persisted (both inclusion and change), deleted, re-read from the database or removed from the management of EntityManager, the same should happen to all Enderecois linked to it. Therefore, the CascadeType.ALL.

As to the orphanRemoval = true, is to eliminate Enderecowhich do not belong to any Cliente. See more about this in that question.

Without making use of the Cascade, saving the entities one by one as you were trying to do would be complicated since there are (and is correct to be) cyclical references between them as the relationship is bidirectional.

Also put the CascadeType.PERSIST here:

@ManyToOne
@JoinColumn(name="cidade_id", cascade = CascadeType.PERSIST)
private Cidade cidade;

This is because when the address is saved, the city to which it refers should also be saved. Or, let the CascadeType.PERSIST side and make sure that the instance of Cidade chosen already exists before saving the address to avoid duplicates.

2

TransientPropertyValueException. This is because one or more instances of Cidade are not being administered at the time of persistence (i.e., the city is Detached).

The solution to the problem depends on the relationship situation:

  1. If you wish to persist new cities, you can do it manually before persisting the address:

    // Assumindo um repositório que salve as cidades
    cidadeRepository.saveAll(Arrays.asList(c1, c2));
    
  2. If cities already exist in the bank - and you have the respective ids - the ideal solution would be to obtain managed references and associate them to new addresses:

    // Assumindo que você está usando JPA
    Cidade mc1 = entityManager.getReference(Cidade.class, c1.getId()); 
    Cidade mc2 = entityManager.getReference(Cidade.class, c2.getId());
    e1.setCidade(mc1);
    e2.setCidade(mc2);
    
  3. If you always want linked cities to be persisted next to an address (i.e., you want to persist cascading), there is also a proper annotation to do this.

    // No corpo de endereço
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name="cidade_id")
    private Cidade cidade;
    

Thinking of a properly normalized database, where there are no repeated entries in the City table, option number 2 seems to me to be the most correct.

If the city is coming directly from a input user, and cities are not fixed / previously fed on database (not very good modeling for the problem in my opinion), you will need to write a logic to verify if the city in question already exists in the bank (e.g., a SELECT by the name of the city) and only then decide whether you should upload the existing reference or persist the new city.

-1

Just reverse and save the customer first and then the address: addressRepository.saveAll(Arrays.asList(E1, E2)); clientRepository.saveAll(Arrays.asList(cli1));

switch to:

clienteRepository.saveAll(Arrays.asList(cli1));

addressRepository.saveAll(Arrays.asList(E1, E2));

Browser other questions tagged

You are not signed in. Login or sign up in order to post.