[hibernate-issues] [Hibernate-JIRA] Issue Comment Edited: (HHH-5809) Mapping composite foreign key to composite primary key fails

David (JIRA) noreply at atlassian.com
Mon Dec 27 12:45:05 EST 2010


    [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-5809?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=39433#action_39433 ] 

David edited comment on HHH-5809 at 12/27/10 11:44 AM:
-------------------------------------------------------

Ok, so having played with this a bit more, this is what I have determined...

I created two test tables:

*Table Person*
||Name||Type||Notes|| 
|id|number(30)|primary key| 
|full_name|varchar(16)| |
|state|varchar (2)|FK Billing(state, country)| 
|country|varchar (3)| |

*Table Billing*
||Name||Type||Notes|| 
|state|varchar (2)|primary composite key (state, country)| 
|country|varchar (3)| |
|price|number(16)| |

Then I ran Hibernate Tool 3.3.0, and it generated:

{code:title=Person.java|borderStyle=solid}
@Entity
@Table(name="PERSON")
public class Person implements java.io.Serializable {

    private BigDecimal id;
    private Billing billing;

    // Constructors...

    // getters / setters

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns ( { 
    @JoinColumn(name = "STATE", referencedColumnName = "STATE", nullable = false),
    @JoinColumn(name = "COUNTRY", referencedColumnName = "COUNTRY", nullable = false) } )
    @NotFound(action=NotFoundAction.IGNORE)
    public Billing getBilling()
    {
       return this.billing;
    }
}
{code}

{code:title=Billing.java|borderStyle=solid}
@Entity
@Table(name="BILLING")
public class Person implements java.io.Serializable {

    private BillingId id;
    private long price;
    private Set<Person> persons = new HashSet<Person> (0);

   // constructors...

   // getters / setters...

   @EmbeddedId
   @AttributeOverrides( {
      @AttributeOverride(name = "state", column = @Column(name = "STATE", nullable = false, length=2)),
      @AttributeOverride(name = "country", column = @Column(name = "COUNTRY", nullable = false, length=3)) })
   public BillingId getId()
   {
       return this.id;
   }

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "billing")
   public Set<Person> getPersons()
   {
     return this.persons;
   }
{code}

{code:title=BillingId.java|borderStyle=solid}
@Embeddable
public class BillingId implements java.io.Serializable {

    private String state;
    private String country;

   // constructors...

   @Column(name = "STATE", nullable = false, length = 2)
   public String getState()
   {
       return this.state;
   }

   // other getters / setters...
{code}

All classes are read-only and they all implement hashcode and equals based on their primary keys.  There is not billing information for each state/country combination, so we don't want to throw an exception if that occurs, just return null.

The problem I am encountering is that when I do an HQL left fetch join on person.billing, the correct SQL is deployed and the Billing objects are loaded, but they are not put into the session/level2 cache.  So immediately after launching the query, it iterates through all the people objects and does a single query to load each person's billing information (N + 1).  I could potentially live with that, but when I set batchSize = 4 on the Billing object, it will correctly do the left join in SQL, then iterate through the results, and load 4 Billing objects at a time, but I see the same number of queries being sent.  So in the batchSize = 1, I see (N + X) queries, and with batchSize = 4, I see (N + X) instead of the expected (N + (X/4)) queries, but for each X, it generates SQL that loads 4 at a time.  Very peculiar.  So I cannot seem to get around (N + 1) and my N can be in the thousands.  Interestingly enough, if I execute the same HQL statement twice, it will correctly pull the Billing objects from the 2nd LvL Cache the second time, without the need to do (N + 1) again.

It's also odd, because the N SQL statements to load the N Billing objects occur within Hibernate somewhere, and are not invoked by my calling person.getBilling() (even though it's marked as lazy).

Right-joins work correctly (for those times I want to only see persons with billing information).  One SQL statement will be executed and it will load the Billing objects at that point.

Thanks.

      was (Author: davidemm):
    Ok, so having played with this a bit more, this is what I have determined...

I created two test tables:

*Table Person*
||Name||Type||Notes|| 
|id|number(30)|primary key| 
|full_name|varchar(16)| |
|state|varchar (2)|FK Billing(state, country)| 
|country|varchar (3)| |

*Table Billing*
||Name||Type||Notes|| 
|state|varchar (2)|primary composite key (state, country)| 
|country|varchar (3)| |
|price|number(16)| |

Then I ran Hibernate Tool 3.3.0, and it generated:

{code:title=Person.java|borderStyle=solid}
@Entity
@Table(name="PERSON")
public class Person implements java.io.Serializable {

    private BigDecimal id;
    private Billing billing;

    // Constructors...

    // getters / setters

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns ( { 
    @JoinColumn(name = "STATE", referencedColumnName = "STATE", nullable = false),
    @JoinColumn(name = "COUNTRY", referencedColumnName = "COUNTRY", nullable = false) } )
    @NotFound(action=NotFoundAction.IGNORE)
    public Billing getBilling()
    {
       return this.billing;
    }
}
{code}

{code:title=Billing.java|borderStyle=solid}
@Entity
@Table(name="BILLING")
public class Person implements java.io.Serializable {

    private BillingId id;
    private long price;
    private Set<Person> persons = new HashSet<Person> (0);

   // constructors...

   // getters / setters...

   @EmbeddedId
   @AttributeOverrides( {
      @AttributeOverride(name = "state", column = @Column(name = "STATE", nullable = false, length=2)),
      @AttributeOverride(name = "country", column = @Column(name = "COUNTRY", nullable = false, length=3)) })
   public BillingId getId()
   {
       return this.id;
   }

   @OneToMany(fetch = FetchType.LAZY, mappedBy = "billing")
   public Set<Person> getPersons()
   {
     return this.persons;
   }
{code}

{code:title=BillingId.java|borderStyle=solid}
@Embeddable
public class BillingId implements java.io.Serializable {

    private String state;
    private String country;

   // constructors...

   @Column(name = "STATE", nullable = false, length = 2)
   public String getState()
   {
       return this.state;
   }

   // other getters / setters...
{code}

All classes are read-only and they all implement hashcode and equals based on their primary keys.  There is not billing information for each state/country combination, so we don't want to throw an exception if that occurs, just return null.

The problem I am encountering is that when I do an HQL left fetch join on person.billing, the correct SQL is deployed and the Billing objects are loaded, but they are not put into the session/level2 cache.  So immediately after launching the query, it iterates through all the people objects and does a single query to load each person's billing information (N + 1).  I could potentially live with that, but when I set batchSize = 4 on the Billing object, it will correctly do the left join in SQL, then iterate through the results, and load 4 Billing objects at a time, but I see the same number of queries being sent.  So in the batchSize = 1, I see (N + X) queries, and with batchSize = 4, I see (N + X) instead of the expected (N + (X/4)) queries, but for each X, it generates SQL that loads 4 at a time.  Very peculiar.  So I cannot seem to get around (N + 1) and my N can be in the thousands.

Thanks.
  
> Mapping composite foreign key to composite primary key fails
> ------------------------------------------------------------
>
>                 Key: HHH-5809
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-5809
>             Project: Hibernate Core
>          Issue Type: Bug
>          Components: core
>    Affects Versions: 3.5.6
>         Environment: Java Hibernate 3.5.6_FINAL, Oracle 11g
>            Reporter: David
>
> I am seeing a problem similiar to the problems described at:
> https://forum.hibernate.org/viewtopic.php?f=25&t=996445
> I have mapped a composite foreign key to a primary composite key and it does not cache the results properly. It's a bi-directional, many-to-one association mapped essentially a cut+paste (with variable name substitution) from:
> http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html/entity.html#d0e2177
> but with a @NotFound set to IGNORE.
> When I include the relationship in an HQL left fetch join, I can see that the Child's Parent entity is placed in the session-cache.  But when the Child class's getParent() method is invoked, the result is loaded from the database instead of the session.  It completely ignores the session and the second-level cache, so I'm left with the N + 1 problem.  If I enable batch fetch (size = 8), the entities loaded from the batch are also not placed into either session cache (or level 2), so I get 1 + N (8-batch) fetches.
> If the query runs again, everything correctly loads from the second-level cache.
> Thanks.

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

       



More information about the hibernate-issues mailing list