1

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

enter image description here

Before Execution

enter image description here

enter image description here

After Execution

enter image description here

enter image description here

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();
}
usmanwalana
  • 1,002
  • 2
  • 14
  • 31
  • This smells like a problem of an improperly implemented hashCode() method. Does the Price class have one? – Gimby Feb 03 '17 at 16:01
  • No it does not. Class code is complete minus the import statements. I think it needs some fixing in the controller code. – usmanwalana Feb 03 '17 at 16:06
  • I would still try implementing (or letting your IDE generate) a hashcode and equals method. A Set does not allow duplicate entries and that is what I see happening in the code VS the log output. You're adding 5 prices to the set, and 4 of them don't actually end up in there. – Gimby Feb 03 '17 at 16:15
  • Dear Gimby, I am really sorry. Price class had those methods. I have attached them in the update please review. Thanks – usmanwalana Feb 03 '17 at 16:22
  • So if all the new Price objects have no id, which they don't because you're not setting it, they are all going to be duplicates of each other. – Gimby Feb 03 '17 at 16:30
  • Shouldn't they be autogenerated by hibernate? – usmanwalana Feb 03 '17 at 16:33
  • That happens on save, not when you're creating the objects. – Gimby Feb 03 '17 at 16:36
  • So i should save them in the loop? – usmanwalana Feb 03 '17 at 16:37
  • Your hashcode() and equals() method are not consistent. I think that the id should be enough to identify a `Price` instance.Implementing both methods by using only the id. – davidxxx Feb 03 '17 at 16:50
  • So how should they be changed? – usmanwalana Feb 03 '17 at 17:03
  • Just use the id of Price. Don't refer to `price.getPriceList().getId()` – davidxxx Feb 03 '17 at 17:55

2 Answers2

2

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.

Naros
  • 19,928
  • 3
  • 41
  • 71
1

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.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Sorry that was a typing mistake. I adjusted the code as you suggested but still the same result (only 1 record insrted). Only the price_list_id column now has the id of newly created. – usmanwalana Feb 03 '17 at 16:16