13

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?

Community
  • 1
  • 1
user1472409
  • 397
  • 1
  • 7
  • 20
  • Need a few more pieces of info to help: show how you're generating the ID for these classes (post your baseentity class), show how you're persisting and retrieving the data – zmf Nov 05 '14 at 17:23

5 Answers5

17

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

Community
  • 1
  • 1
zmf
  • 9,095
  • 2
  • 26
  • 28
  • 5
    someone should use List or Set based on whether they want List or Set semantics, not based on whether some persistence solution "recommends" one. The persistence solution should be transparent to everything – Neil Stockton Nov 05 '14 at 18:08
  • Is that a criticism of my recommendation to use sets, or of hibernate? – zmf Nov 05 '14 at 19:25
  • 1
    It's a simple statement of why any developer should select a particular Collection type - the persistence solution choice should not come into the argument, only the model. If people choose to take it as "criticism" is up to them – Neil Stockton Nov 06 '14 at 06:36
6

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.

Gernan
  • 169
  • 6
0

You could store the result of your query in a Set, it will remove duplicate for you.

ToYonos
  • 16,469
  • 2
  • 54
  • 70
0

If you are using JPA:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Receipt> query = cb.createQuery(Receipt.class);
query.distinct(true);

otherwise see Gernan's answer.

alex
  • 8,904
  • 6
  • 49
  • 75
0

Hibernate does not return distinct results for a query with outer join fetching enabled for a collection (even if I use the distinct keyword)? read more, it may help you..

Srikanth Malyala
  • 941
  • 15
  • 24