Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting Duplicate Entries using Hibernate

I am experiencing a weird issue when using JPA/Hibernate. I am seeing duplicate entries during testing.

Here is my BaseEntity class:

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity {
    
    @Id
    @Column(name = "ID", updatable = false, nullable = false)
    @GenericGenerator(name = "uniqueIDGenerator", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", parameters = {
            @org.hibernate.annotations.Parameter(name = "sequence_name", value = "ID_SEQUENCE"),
            @org.hibernate.annotations.Parameter(name = "increment_size", value = "100"),
            @org.hibernate.annotations.Parameter(name = "optimizer ", value = "pooled") })
    @GeneratedValue(generator = "uniqueIDGenerator")
    @NotNull
    protected int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    
}

Here is my main partner class:

@Entity
@Table(name = "partner")
public class Partner extends BaseEntity{
    
    @Column(name = "name")
    private String name;
    
    @OneToMany(mappedBy="partner", fetch=FetchType.EAGER)
    @Cascade(CascadeType.DELETE)
    private List<Receipt> receipts;
    
    @OneToMany(mappedBy="partner", fetch=FetchType.EAGER)
    @Cascade(CascadeType.DELETE)
    private List<Delivery> deliveries;
    
    public Partner() {
        setReceipts(new ArrayList<Receipt>());
        setDeliveries(new ArrayList<Delivery>());
    }
    
    public Partner(String name){
        this();
        
        this.setName(name);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Receipt> getReceipts() {
        return receipts;
    }

    public void setReceipts(List<Receipt> receipts) {
        this.receipts = receipts;
    }

    public List<Delivery> getDeliveries() {
        return deliveries;
    }

    public void setDeliveries(List<Delivery> deliveries) {
        this.deliveries = deliveries;
    }
}

Partner has two subclasses, Receipt and Delivery. Here is the Receipt class:

@Entity
@Table(name = "receipt")
public class Receipt extends BaseEntity{
    
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="partnerId")
    private Partner partner;
    
    @Column(name = "name")
    private String name;
    @Column(name = "json")
    private String json;

    @Transient
    private ReceiptContainer receiptContainer;
    
    
    public Receipt() {
    }
    public Receipt(String name){
        this();
        
        this.setName(name);
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJson() {
        return json;
    }

    public void setJson(String json) {
        this.json = json;
    }

    public ReceiptContainer getReceiptContainer() {
        return receiptContainer;
    }

    public void setReceiptContainer(ReceiptContainer receiptContainer) {
        this.receiptContainer = receiptContainer;
    }

    public Partner getPartner() {
        return partner;
    }

    public void setPartner(Partner partner) {
        this.partner = partner;
    }
    
}

Here is the Delivery class:

@Entity
@Table(name = "delivery")
public class Delivery extends BaseEntity{
    
    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="partnerId")
    private Partner partner;
    
    @Column(name = "name")
    private String name;
    @Column(name = "json")
    private String json;

    @Transient
    private DeliveryContainer deliveryContainer;
    
    
    public Delivery() {
    }
    public Delivery(String name){
        this();
        
        this.setName(name);
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getJson() {
        return json;
    }

    public void setJson(String json) {
        this.json = json;
    }

    public DeliveryContainer getDeliveryContainer() {
        return deliveryContainer;
    }

    public void setDeliveryContainer(DeliveryContainer deliveryContainer) {
        this.deliveryContainer = deliveryContainer;
    }

    public Partner getPartner() {
        return partner;
    }

    public void setPartner(Partner partner) {
        this.partner = partner;
    }
    
}

I am getting my objects like this:

@Transactional
public Partner get(int id){
    Partner partner = entityManager.find(Partner.class, id);
    return partner;
}

As you may see, the partner class includes a List of objects and a List of objects, which are stored in their own tables, receipt and delivery, respectively.

---Before I go any further, I would like to note that the correct number of entries are displayed in the database. This issue only occurs when getting the objects in my Java app.---

When I add one object and no objects are present, one object is displayed and no objects are displayed, which is expected. If one object and one objects exist, one object and one object are displayed, again, which is expected. The problem occurs when more than one or object are present. For example, if two objects are added but only one object is added, two objects AND two objects are displayed.

To better illustrate this, see my test case table below. Unexpected results are surrounded with dashes.

<Receipt> Obj Added | <Delivery> Obj Added | <Receipt> Obj Displayed | <Delivery> Obj Displayed
         0          |          0           |           0             |            0
         0          |          1           |           0             |            1
         1          |          0           |           1             |            0
         1          |          1           |           1             |            1
         1          |          2           |        ---2---          |         ---2---
         2          |          1           |        ---2---          |         ---2---
         2          |          2           |        ---4---          |         ---4---

My question is, why am I seeing these unexpected results?

like image 526
user1472409 Avatar asked Nov 05 '14 17:11

user1472409


People also ask

Why does JPA return duplicate rows?

Issue with @Id column, If we check closely, @Id column value is same for all the rows. Hence hibernate/JPA not able to get different records, it just get 1st record with this @Id and return duplicate records of it. Solution - Use @IdClass with columns which result in unique row instead of duplicate row.

How can we get distinct values in hibernate criteria?

Criteria crit = session. createCriteria(Test. class); final ResultTransformer trans = new DistinctRootEntityResultTransformer(); crit. setResultTransformer(trans); List rsList = trans.


2 Answers

The reason you're seeing duplicates is because you're eagerly fetching the children, which is creating an outer join.

(You could use @Fetch( FetchMode.SELECT), but this will result in additional queries to retrieve your items)

Hibernate API reference indicating outer join takes place.

Here is an in depth answer to your question: Hibernate Criteria returns children multiple times with FetchType.EAGER

You can switch from List to Set (be sure you override equals/compareTo/hashCode if you do so). Or you can add a result transformer to your query results if you're extracting these items from the database via criteria.

For what its worth, hibernate recommends using sets over lists

like image 119
zmf Avatar answered Sep 28 '22 15:09

zmf


Maybe you can try doing your query using DISTINCT property.

Simply add this to your criteria:

criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

If you are not using criteria:

SELECT DISTINCT <QUERY>

If it doesn't solve your problem, show your query code.

like image 27
Gernan Avatar answered Sep 28 '22 14:09

Gernan