1

The Contact entity defines relationships to two collections of entity of type email and nickname which exist in two MySQL tables.

My issue is that the result set this returns has duplicated email and nicknames.

{
  "contactId": 1,
  "givenName": "toast",
  "middleName": "brown",
  "familyName": "jam",
  "dob": "2014-11-19",
  "contactEmailAddress": [
    {
      "emailAddressId": 1,
      "emailAddress": "donald.duck@disney.com",
      "contactId": 1
    },
    {
      "emailAddressId": 1,
      "emailAddress": "donald.duck@disney.com",
      "contactId": 1
    },
    {
      "emailAddressId": 2,
      "emailAddress": "mickey.mouse@disney.com",
      "contactId": 1
    },
    {
      "emailAddressId": 2,
      "emailAddress": "mickey.mouse@disney.com",
      "contactId": 1
    }
  ],
  "contactNickname": [
    {
      "contactNicknameId": 1,
      "nickname": "mm",
      "contactId": 1
    },
    {
      "contactNicknameId": 2,
      "nickname": "mouse",
      "contactId": 1
    },
    {
      "contactNicknameId": 1,
      "nickname": "mm",
      "contactId": 1
    },
    {
      "contactNicknameId": 2,
      "nickname": "mouse",
      "contactId": 1
    }
  ]
}

If I remove the ContactNickname collection from the Contact entity the result set is as follows.

{
  "contactId": 1,
  "givenName": "toast",
  "middleName": "brown",
  "familyName": "jam",
  "dob": "2014-11-19",
  "contactEmailAddress": [
    {
      "emailAddressId": 1,
      "emailAddress": "donald.duck@disney.com",
      "contactId": 1
    },
    {
      "emailAddressId": 2,
      "emailAddress": "mickey.mouse@disney.com",
      "contactId": 1
    }
  ]
}

I expected a distinct collection of email address and a distinct collection of nicknames but this is not the case. What am I doing incorrectly.

Using JPA I have mapped the the class as follows.

@Entity
@Table(name="contact")
public class Contact implements Serializable {    
    @OneToMany(fetch=FetchType.EAGER)
    @JoinColumn(name="contact_id")
    private Collection<ContactEmailAddress> contactEmailAddress;

    @OneToMany(fetch=FetchType.EAGER)
    @JoinColumn(name="contact_id")
    private Collection<ContactNickname> contactNickname;

    public Collection<ContactEmailAddress> getContactEmailAddress(){
        return this.contactEmailAddress;
    }

    public void setContactEmailAddress(Collection<ContactEmailAddress> contactEmailAddress){        
        this.contactEmailAddress=contactEmailAddress;
    }

    public Collection<ContactNickname> getContactNickname(){
        return this.contactNickname;
    }

    public void setContactNickname(final Collection<ContactNickname> contactNickname){
        this.contactNickname=contactNickname;
    }    
}
@Entity
@Table(name="contact_email_address")
public class ContactEmailAddress implements Serializable {   
    @Id
    @GeneratedValue
    @Column(name="email_address_id")
    private int emailAddressId;

    @Column(name="email_address")
    private String emailAddress;

    @Column(name="contact_id")
    private int contactId;

  public void setContactId(int contactId){
        this.contactId=contactId;
    }

    public int getContactId(){
        return this.contactId;
    }.

   public int getEmailAddressId(){
        return emailAddressId;
    }

    public void setEmailAddressId(final int emailAddressId){
        this.emailAddressId=emailAddressId;
    }

    public String getEmailAddress(){
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress){
        this.emailAddress=emailAddress;
    }
}
@Entity
@Table(name="contact_nickname")
public class ContactNickname implements Serializable {
    @Id
    @GeneratedValue
    @Column(name="contact_nickname_id")
    private int contactNicknameId;

    @Column(name="nickname")
    private String nickname;

    @Column(name="contact_id")
    private int contactId; 

    public int getContactNicknameId(){
        return contactNicknameId;
    }

    public void setContactNicknameId(final int contactNicknameId){
        this.contactNicknameId=contactNicknameId;
    }

    public String getNickname(){
        return this.nickname;
    }

    public void setNickname(String nickname){
        this.nickname=nickname;
    }    

    public void setContactId(int contactId){
        this.contactId=contactId;
    }

    public int getContactId(){
        return this.contactId;
    }
}

Hibernate is running the following when the duplication happens.

SELECT contact0_.contact_id             AS contact_1_0_0_, 
       contact0_.dob                    AS dob2_0_0_, 
       contact0_.family_name            AS family_n3_0_0_, 
       contact0_.given_name             AS given_na4_0_0_, 
       contact0_.middle_name            AS middle_n5_0_0_, 
       contactema1_.contact_id          AS contact_2_0_1_, 
       contactema1_.email_address_id    AS email_ad1_1_1_, 
       contactema1_.email_address_id    AS email_ad1_1_2_, 
       contactema1_.contact_id          AS contact_2_1_2_, 
       contactema1_.email_address       AS email_ad3_1_2_, 
       contactnic2_.contact_id          AS contact_2_0_3_, 
       contactnic2_.contact_nickname_id AS contact_1_3_3_, 
       contactnic2_.contact_nickname_id AS contact_1_3_4_, 
       contactnic2_.contact_id          AS contact_2_3_4_, 
       contactnic2_.nickname            AS nickname3_3_4_ 
FROM   contact contact0_ 
       LEFT OUTER JOIN contact_email_address contactema1_ 
                    ON contact0_.contact_id = contactema1_.contact_id 
       LEFT OUTER JOIN contact_nickname contactnic2_ 
                    ON contact0_.contact_id = contactnic2_.contact_id 
WHERE  contact0_.contact_id =? 

Kind regards, Ian.

Tiny
  • 27,221
  • 105
  • 339
  • 599
Ian Hudson
  • 21
  • 3
  • Basically you are trying to do an unidirectional oneToMany mapping; you're missing a 'referencedColumnName' attribute as explained in this very similar question: http://stackoverflow.com/questions/12038380/how-to-define-unidirectional-onetomany-relationship-in-jpa – Gimby Nov 20 '14 at 15:07
  • @Gimby I still get the same result set when setting referencedColumnName="contact_id" – Ian Hudson Nov 20 '14 at 15:26
  • Hm. Just to exclude possibilities: does anything change when you remove the EAGER fetching from the collections? – Gimby Nov 20 '14 at 15:29
  • 1
    @Gimby I currently get org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role... Contact.contactEmailAddress, could not initialize proxy - no Session – Ian Hudson Nov 20 '14 at 15:57
  • Yeah. That might indicate that there is something off with when and how your sessions are created and cleaned up too, but at this point in time it is simply going too far to dive into this new problem when I'm only operating on a wild guess as to what the original problem may be. Better revert to EAGER and ignore my comments - and also me, because now I'm out of options. – Gimby Nov 20 '14 at 16:10
  • 1
    @Gimby after setting both collections to lazy then calling size() on both of them before the transaction is closed Jackson generates the correct JSON without the entity dups. So all is good. Why does eager loading the collections generate the dups though. – Ian Hudson Nov 20 '14 at 19:32
  • I can't really explain it other than "the resulting SQL is the culprit", I operated on a hunch because I ran into this issue myself; only mine was a performance issue because Hibernate had to fetch way more data in one gigantic join-fest to get to the correct result. This related question might explain it for you: http://stackoverflow.com/questions/1995080/hibernate-criteria-returns-children-multiple-times-with-fetchtype-eager – Gimby Nov 21 '14 at 10:00

1 Answers1

0

If you never want to have duplicates in your collection, you should use Set instead of Collection in your entity.

Matt
  • 3,422
  • 1
  • 23
  • 28