Error in @Onetomany mapping with JPA and Hibernate

Asked

Viewed 598 times

3

At the moment, I’m having a problem with mapping @OneToMany/@ManyToOne in my application. I would like it when I persist my entity Sale automatically save the sale items in single shot.

@Entity
@Table(name = "sale")
public class Sale implements Serializable {

    private static final long serialVersionUID = -3857608619547245492L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Column(name = "company", nullable = false)
    private Long company;


    @JsonIgnore
    @OneToMany(mappedBy = "sale", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<SaleItem> saleItems = new ArrayList<>();

    public Sale() {
    }

    public void addSaleItem(SaleItem saleItem) {
       getSaleItems().add(saleItem);
       saleItem.setSale(this);
    }

    //****************************Getters and Setters****************************

}



@Entity
@Table(name = "sale_item")
public class SaleItem implements Serializable {

    private static final long serialVersionUID = 1016354254590870341L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Column(name = "company", nullable = false)
    private Long company;    

    @Column(name = "description", nullable = false)
    private String description;

    @Column(name = "amount", nullable = false)
    private Double amount;

    @Column(name = "price", nullable = false)
    private Double price;

    @ManyToOne(fetch = FetchType.LAZY)
    private Sale sale;

    public SaleItem() {

    }

    //**************************** SEM SALE Getters e Setters ****************************





    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (!(o instanceof SaleItem))
            return false;
        return id != null && id.equals(((SaleItem) o).getId());
    }

    @Override
    public int hashCode() {
        return 31;
    }   

    @Override
    public String toString() {
        return "SaleItem [id=" + id + ", company=" + company + ", product=" + product + ", created=" + created
                + ", updated=" + updated + ", description=" + description + ", amount=" + amount + ", price=" + price
                + ", discount=" + discount + ", user=" + user + ", unit=" + unit + ", sale=" + sale + "]";
    }


}

    //*********************** My Controller ***********************

    /**
     * Create Sale
     * 
     * @param sale
     * @param result
     * @return ResponseEntity<Response<SaleDto>>
     * @throws ParseException
     */
    @PostMapping
    public ResponseEntity<Response<SaleDto>> create(@Valid @RequestBody SaleDto saleDto, BindingResult result)
            throws ParseException {

        log.info("Creating sale: {}", saleDto.toString());
        Response<SaleDto> response = new Response<SaleDto>();

        validateSale(saleDto, result);

        if (result.hasErrors()) {
            log.error("Error - invalid information for Sale: {}", result.getAllErrors());
            result.getAllErrors().forEach(error -> response.getErrors().add(error.getDefaultMessage()));
            return ResponseEntity.badRequest().body(response);
        }

        response.setData(this.convertSaleDto(this.nova(saleDto, result)));
        return ResponseEntity.ok(response);
    }

    private Sale nova(SaleDto saleDto, BindingResult result) throws ParseException {

       Sale sale = new Sale();
       sale.setCompany(saleDto.getCompany());
       sale.setUser(saleDto.getUser());
       sale.setType(TypeSales.valueOf(saleDto.getType()));
       sale.setDescription(saleDto.getDescription());
       sale.setValue(saleDto.getValue());
       sale.setSubValue(saleDto.getSubValue());
       sale.setDiscount(saleDto.getDiscount());


       for (int i = 0; i < saleDto.getItems().size(); i++) {
           SaleItem saleItem = new SaleItem();
           saleItem.setCompany(saleDto.getCompany());
           saleItem.setUser(saleDto.getUser());
           saleItem.setProduct(saleDto.getItems().get(i).getProduct());
           saleItem.setAmount(saleDto.getItems().get(i).getAmount());
           saleItem.setPrice(saleDto.getItems().get(i).getPrice());
           saleItem.setDescription(saleDto.getItems().get(i).getDescription());
           saleItem.setDiscount(saleDto.getItems().get(i).getDiscount());
           saleItem.setUnit(saleDto.getItems().get(i).getUnit());
           sale.addSaleItem(saleItem);
       }

       return this.saleService.persist(sale);
    }



    //**************************My Service**************

    /**
     * Persist a sale
     * 
     * @param Sale
     * @return Sale
     */
    Sale persist(Sale sale);



//******************My impl***********************
@Transactional
public Sale persist(Sale sale) {
    log.info("Persisting sale {}", sale);
    return this.saleRepository.save(sale);
}

The problem that is only saving the entity Sale leaving items blank at all times.

2 answers

2

In this case you can use a one-way relationship:

@Entity
@Table(name = "sale")
public class Sale implements Serializable {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
   @JoinColumn(name = "sale_id")
   private List<SaleItem> saleItems = new ArrayList<>();

   public List<SaleItem> getSaleItems() {
        return saleItems;
   }


}
@Entity
@Table(name = "sale_item")
public class SaleItem implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

   // deve remover a classe Sale desse lado
} 

To insert an item you can use, for example:

public void addSaleItem(SaleItem saleItem) {
   getSaleItems().add(item);
}

http://blog.triadworks.com.br/jpa-por-que-voce-deveria-evitar-relacionamento-bidirecional

  • So using this suggestion I end up having the error that was not filled sale_id. 2020-02-09 00:50:55.734 ERROR 17036 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.Sqlexceptionhelper : Field 'sale_id' doesn’t have a default value

  • You kept the id attribute in the classes ? The ids are being auto-incremented ?

  • I kept the ID’s but I took the Sale class of entity item.

  • The property Mapped by tbm has been removed?

  • Yes, I removed the only doubt I had if I should declare in the entity Saleitem the "sale_id" ?

  • 1

    No need. It may be that your changes are not being reflected in the bank and may require re-creation of the bank. If it still makes sense for you to take a look at this link, it has a very similar problem to yours and that’s how it solved https://stackoverflow.com/questions/804514/hibernate-field-id-doesnt-have-a-default-value

  • It makes sense the unidirectional mapping which makes no sense that at any time I identify for the items who is the father. I just read the article you referred me and was held the father identification in the items.

  • 1

    In the article indicated in the answer, it exemplifies on top of a bidirectional, promoting some improvements. In the case of unidirectional something like this should work for you: https://reinaldoc.wordpress.com/2011/11/25/jpa-onetomany-unidirectionl-join-comjoin-columncolumn/

Show 3 more comments

2


When the relationship is bidirectional, you should connect the two sides of the relationship explicitly:

private Sale nova(SaleDto saleDto, BindingResult result) throws ParseException {
    SaleItem saleItem = new SaleItem();
    saleItem.setCompany(saleDto.getCompany());
    saleItem.setPrice(saleDto.getItems().get(0).getPrice());
    saleItem.setDescription(saleDto.getItems().get(0).getDescription());
    saleItem.setUnit(saleDto.getItems().get(0).getUnit());

    Sale sale = new Sale();
    sale.setCompany(saleDto.getCompany());
    sale.setUser(saleDto.getUser());
    sale.addSaleItem(saleItem);
    saleItem.setSale(sale);   // <-- Isso é muito importante de se fazer.

    return this.saleService.persist(sale);
}
  • So your suggestion worked after I used @Jsonignore in the Sale entity which I have no idea because it only worked if I used this annotation. I updated my code if you want to analyze it.

  • 1

    You have solved with @jsonignorewhat is called a circular Reference that happens when one object refers to another and that other object refers to the first. A very common error in two-way relationships. The @Jsonmanagedreference and @ Jsonbackreference annotations are an option to resolve this as well.

  • @mPissolato Using the same class to do object-relational mapping and generating JSON is a bad idea. I used to work in a system like this, and it was a pain in the ass. The reason is that there are many cases where the JSON structure should not be the same as that of the bank. Examples: add calculated fields in JSON that are not in the database; structures in JSON that reflect the result of reports generated from several tables with a complex query; do not put passwords in JSON; not putting data from related entities that are unnecessary for some particular functionality; etc.

  • @mPissolato And then the solution is you have a set of classes to do the object-relational mapping, and let Hibernate/JPA handle it and another set of different classes just to generate JSON and some methods in both classes to convert from one to another, remembering that this conversion is not always a 1-to-1 ratio. By the way, you’re already doing it in some places by having a SaleDto.

  • @Victorstafusa! Yes I agree with you I will use DTO to treat input as output making conversions. I’ve always done the persistence process using JEE however I’m trying to improve my Skils with spring and using the crud’s Repository to persist master/Detail is being complicated.

Browser other questions tagged

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