Error inserting into Hibernate database

Asked

Viewed 1,449 times

4

How do I insert the Product object into the database that is composed of an Ingredient object that already exists in the database, without duplicating the Ingredient object in the database. If I remove Scade and place the ID (Primary key auto increment) of the object in the Ingredient, it is an error. Wouldn’t the correct Hibernate not save the object? Does at the time of insertion it belittles the ID for being auto increment?

Error:

Exception in thread "main" org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : entity.ProdutoIngrediente.ingrediente -> entity.Ingrediente

Classes:

@Entity
@Table(name = "produto_ingrediente")
public class ProdutoIngrediente implements java.io.Serializable {
           ......
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id_ingrediente", nullable = false)
    public Ingrediente getIngrediente() {
        return this.ingrediente;
    }

    public void setIngrediente(Ingrediente ingrediente) {
        this.ingrediente = ingrediente;
    }
}

@Entity
@Table(name = "produto", catalog = "grupotenkite2")
public class Produto implements java.io.Serializable {
...
    @Id
    @GeneratedValue(strategy = IDENTITY)
    @Column(name = "id_produto", unique = true, nullable = false)
    public Integer getIdProduto() {
        return this.idProduto;
    }
}

Relação entre o produto e ingrediente

  • 1

    Project error. If there is an attribute that cannot be duplicated it should be the key and not create an auto-increment.

  • 1

    Post the ids of your entities, the modeling of relationships as well as your data scheme. From what I understood the table produto_ingrediente is a Many-to-Many relationship table (probably with extra attributes, which justifies the decision to have a separate entity).

  • 1

    If this is the case, model the entity with a composite key and relationships using @PrimaryKeyJoinColumns (and reflect this in the bank by creating a Composite PK, which avoids duplicated ingredients for the same product). You can persist the entity without problems. Example - In English. Think about the addEmployee(Employee employee, boolean teamLead) such as, for example adicionarIngrediente(Ingrediente ingrediente, BigDecimal quantidade);

  • @Anthonyaccioly take a look at the entity and relationship diagram I put together, please. What you recommend me. the intention is to let a product have one more ingredient.. In my program, I first insert all the ingredients of the system and then add the product and make the connection through the 'product' table'

1 answer

4


How I would model the problem.

Database

  1. Would remove the PK id_produtoIngrediente completely. It allows the same ingredient to be repeated for a product, which does not reflect your business (each ingredient will be associated once to the product):

    alter table produto_ingrediente drop column id_produtoIngrediente;
    
  2. In place of artificial PK would use a natural composite PK:

    alter table produto_ingrediente add primary key(id_produto, id_ingrediente);
    

    This new model does not allow the same ingredient to be associated multiple times with a product or vice versa.

JPA

  1. Of course, its entity ProdutoIngrediente shall reflect the composite key:

    @Entity
    @Table(name="produto_ingrediente")
    @IdClass(ProdutoIngredienteId.class)
    public class ProdutoIngrediente implements Serializable {
       @Id
       @Column(name="id_produto", nullable = false)
       private Integer idProduto;
       @Id
       @Column(name="id_ingrediente", nullable = false)
       private Integer idIngrediente;
       @ManyToOne
       @PrimaryKeyJoinColumn(name="id_produto")
       private Produto produto;
       @ManyToOne
       @PrimaryKeyJoinColumn(name="id_ingrediente")
       private Ingrediente ingrediente;
    
       // Demais campos...
       // Getters & Setters
       // equals e hashCode como abaixo
    }
    
  2. Did you notice the note @IdClass to map a composite key? Actually you can choose between this syntax for composite keys or a slightly different syntax using built-in keys. Personally I find the first variation cleaner. So, follows an implementation to ProdutoIngredienteId:

    public class ProdutoIngredienteId {
        private Integer idProduto;
        private Integer idIngrediente;
    
        // getters & setters
    
        // Métodos de exemplo gerados com o IDE
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ProdutoIngredienteId that = (ProdutoIngredienteId) o;
            if (!idIngrediente.equals(that.idIngrediente)) return false;
            if (!idProduto.equals(that.idProduto)) return false;
    
            return true;
        }
    
        @Override
        public int hashCode() {
            int result = idProduto.hashCode();
            result = 31 * result + idIngrediente.hashCode();
            return result;
        }
    }
    
  3. On the product side you own a collection of associations. As the associations are unique and the order of the ingredients does not seem to be important, I believe that a Set is more appropriate than a List:

    @Entity
    @Table(name = "produto", catalog = "grupotenkite2")
    public class Produto implements java.io.Serializable {    
        @Id
        @GeneratedValue(strategy = IDENTITY)
        @Column(name = "id_produto", unique = true, nullable = false)
        private Integer idProduto;
        @OneToMany(mappedBy="produto")
        private Set<ProdutoIngrediente> ingredientes;
    
        // getters & setters
    
  4. Finally, as it is your application’s responsibility to maintain the two tips of a bi-directional relationship, the code to associate an ingredient to a product is somewhat tedious:

        Produto pizza = em.find(Produto.class, idPizza);
        Ingrediente farinha = em.find(Ingrediente.class, idFarinha);
    
        // Cria a associacao (tabela many to many)
        ProdutoIngrediente associacao = new ProdutoIngrediente();
        associacao.setProduto(pizza);
        associacao.setIngrediente(farinha);
        associacao.setIdProduto(pizza.getId());
        associacao.setIdIngrediente(farinha.getId());
    
        // demais parâmetros da associação
    
        // adiciona a associacao do lado do produto
        pizza.getIngredientes().add(associacao);
        // adiciona a associacao do lado do ingrediente (se existir)
        farinha.getProdutos().add(associacao);
    

    That way, as you will add persistent ingredients to the product, you can hide the complexity of the association in a Produto;

    public boolean adicionarIngrediente(Ingrediente ingrediente, boolean opcional, 
            boolean padrao, BigDecimal valor) { // ...
    

    The advantage of doing this is that your client code will not have to deal with associations directly, being able to add new ingredients to the product in a natural way:

    Produto pizza = em.find(Produto.class, idPizza);
    Ingrediente farinha = em.find(Ingrediente.class, idFarinha);
    pizza.adicionarIngrediente(farinha, false, true, new BigDecimal("0.60"));
    

    If the number of association parameters starts to grow too much rethink your model and create an auxiliary object.

OBS1: I preferred to use the annotations in the fields, but you can adapt this code to annotate properties according to your current code.

OBS2: Excuse the giant answer, but I prefer to take the risk of being pedantic to the risk of assuming prior knowledge and skip some important information to the solution of the problem.

  • Thank you so much for the effort.. it became clear the problem and the solution, I will try to adapt it to my code :)

  • No problem. Good luck! :)

Browser other questions tagged

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