Cant persist a Hashset in hibernate

2020-07-30 10:27发布

问题:

I am having trouble in saving a Hashset in hibernate. I am trying to save more than one entries but it is only saving the first item it gets. I am looping through and adding new element to set in every iteration but in the end its size is always 1. After execution, in database table, a new priceList is created but only one entry is created in price table (It should create 5). More info in images.

PriceList.java

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    public void setId(int id){
        this.id = id;
    }
    public Integer getId(){
        return this.id;
    }

    @Column(name = "name")
    @NotBlank( message = "Name is required" )
    private String name;
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }

    @OneToMany(mappedBy = "priceList", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JsonIgnore
    private Set<Price> prices = new HashSet<Price>();
    public void setPrices(Set<Price> prices)
    {
        this.prices.addAll(prices);
    }
    public Set<Price> getPrices()
    {   return this.prices; }

Price.java

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    public void setId(int id){
        this.id = id;
    }
    public Integer getId(){
        return this.id;
    }

    @ManyToOne
    @JoinColumn(name = "product_id",referencedColumnName = "id")
    @JsonSerialize(using = ProductSerializer.class)
    private Product product;
    public void setProduct(Product product){
        this.product = product;
    }
    public Product getProduct(){
        return this.product;
    }

    @ManyToOne
    @JoinColumn(name = "price_list_id",referencedColumnName = "id",updatable = true,insertable = true)
    private PriceList priceList;
    public void setPriceList(PriceList priceList){
        this.priceList = priceList;
    }
    public PriceList getPriceList(){
        return this.priceList;
    }

    @Column(name = "price")
    private float price;
    public void setPrice(float price){
        this.price = price;
    }
    public float getPrice(){
        return this.price;
    }

    @Column(name = "discount")
    private float discount;
    public void setDiscount(float discount){
        this.discount = discount;
    }
    public float getDiscount(){
        return this.discount;
    }

In My Controller i am doing this

Controller

PriceList defaultPriceList = priceListService.findDefaultPriceList();
                    List<Price> defaultPriceListPrices = pricesService.findPricesByPriceList(defaultPriceList);
                    Set<Price> newPrices = new HashSet<Price>();

                    System.out.println(defaultPriceListPrices.size());
                    for (Price p : defaultPriceListPrices)
                    {
                        Price myPrice = new Price();
                        myPrice.setPrice(p.getPrice());
                        myPrice.setDiscount(p.getDiscount());
                        myPrice.setProduct(p.getProduct());
                        myPrice.setPriceList(p.getPriceList());

                        newPrices.add(myPrice);

                        System.out.println("product id" + myPrice.getProduct().getId());
                        System.out.println("price list id" + myPrice.getPriceList().getId());
                        System.out.println("price" + myPrice.getPrice());
                        System.out.println("discount" + myPrice.getDiscount());
                        System.out.println();

                    }
                    System.out.println("new prices size" + newPrices.size());

                    priceList.setPrices(newPrices);
                  priceListService.save(priceList);

I am currently getting this output on console

Before Execution

After Execution

UPDATE

Price.java

@Override
public boolean equals(Object object) {
    if (object instanceof Price) {
        Price price = (Price) object;
        return
                price.getId() == this.getId() && price.getPriceList().getId() == this.getPriceList().getId();
    }
    return true;
}

@Override
public int hashCode() {
    return new HashCodeBuilder(17,37)
            .append(id)
            .toHashCode();
}

回答1:

The problem is with your hashCode implementation on Price.

Implementations of both equals and hashCode often wrong because they base their equality and hash calculation solely off the value of the entity's ID only. In cases of newly created instances where the ID is a @GeneratedValue result, this won't work.

In your case, everytime you add a new Price instance to your Set<>, the same hashCode value is calculated because each new instance has a null ID, so they keep getting replaced.

Adjust your equals and hashCode implementations:

@Override
public boolean equals(Object object) {
  if ( object == this ) {
    return true; // instance equality
  }
  if ( object == null || object.getClass() != getClass() ) {
    return false; 
  }
  final Price other = Price.class.cast( object );
  if ( getId() == null && other.getId() == null ) {
    // perform equality check against all non-id attributes
  }
  else {
    // perform equality check only on id
  }
}

@Override
public int hashCode() {
  final HashCodeBuilder hcb = new HashCodeBuilder( 17, 37 );
  if ( id == null ) {
     hcb.append( price );
     hcb.append( discount );
     // other fields
  }
  else {
    // only identity basis
    hcb.append( id );
  }
  return hcb.toHashCode();
}

This makes sure that when comparing two non-persisted objects of a Price, their comparison/hash is based on the non-identity attributes. Once persisted, the methods will base their comparison/hash against only the identity value, allowing for two instances where one have been modified and the other hasn't to equate to the same.



回答2:

Your code seem to make a copy in a some way a set of existing Price instances in a new PriceList instance and as last step it persists these new data (the new PriceList and the new Price instances).

If I am right, look closely at the price_list_id column value recorded in your Price table after inserting.
You don't use as fk the new price_list_id (36) but you still use the price_list_id (25) used by the previous set of prices.

The problem is in the loop where you write myPrice.setPriceList(p.getPriceList()); instead of referencing priceList, the variable referencing the new PriceList instance you are creating and you are using to set the prices Set before persisting the PriceList instance :

priceList.setPrices(newPrices);
priceListService.save(priceList);

I think that replacing myPrice.setPriceList(p.getPriceList()); by myPrice.setPriceList(priceList); should solve your problem.