Error inserting into the Hibernate database

4

How do I insert the ProductIngredient 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 the cascade and put in the Ingredient the primary key auto increment of the object, it gives an error. The correct would not Hibernate not save the object? Is it at the moment of insertion that it belittles the ID because it is 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;
    }
}

    
asked by anonymous 08.03.2014 / 22:20

1 answer

4

How would I model the problem.

Database

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

    alter table produto_ingrediente drop column id_produtoIngrediente;
    
  • Instead of artificial PK would use a natural compound 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

  • Of course, your id_produtoIngrediente entity should reflect the compound 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
    }
    
  • Have you noticed the ProdutoIngrediente annotation to map a compound key? You can actually choose this syntax for composite keys or a slightly different syntax using built-in keys . Personally I find the first variation cleaner. Thus, an implementation for @IdClass follows:

    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;
        }
    }
    
  • On the product side you have a collection of associations. Since the associations are unique and the order of the ingredients does not seem to be important, I believe that a ProdutoIngredienteId is more appropriate than a Set :

    @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
    
  • Finally, as it is the responsibility of your application to keep the two tips of a bidirectional relationship, the code to associate an ingredient with 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);
    

    In this way, since you will add persistent ingredients to the product, you can hide the complexity of the association in a List method;

    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 handle associations directly, so you can 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 large, 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: Sorry for the gigantic answer, but I'd rather run the risk of being pedantic at the risk of taking on previous knowledge and skipping some important information for solving the problem.

        
    09.03.2014 / 04:13