Mapping composite foreign key to composite primary key fails
Description
Activity
Brett MeyerJuly 8, 2014 at 3:11 PM
Bulk rejecting stale issues. If this is still a legitimate issue on ORM 4, feel free to comment and attach a test case. I'll address responses case-by-case. Thanks!
Brett MeyerApril 7, 2014 at 5:48 PM
In an effort to clean up, in bulk, tickets that are most likely out of date, we're transitioning all ORM 3 tickets to an "Awaiting Test Case" state. Please see http://in.relation.to/Bloggers/HibernateORMJIRAPoliciesAndCleanUpTactics for more information.
If this is still a legitimate bug in ORM 4, please provide either a test case that reproduces it or enough detail (entities, mappings, snippets, etc.) to show that it still fails on 4. If nothing is received within 3 months or so, we'll be automatically closing them.
Thank you!
BDecember 27, 2010 at 10:53 PM
So, from stepping through the code, I can see that Hibernate finds the association and sees that it's associated by a BillingId object. It recognizes it as a primary key and loads the key values (State,Country), creates a BillingId Object, and puts it into the context cache. The problem seems to be that it never instantiates the Billing object so it is never put into the context cache. When all the loading from the original query happens, it iterates through each and every object in the context, looking for unitialized values. Every time it finds a Person address, it (correctly) sees that the Billing object has not been loaded yet, so it does the look-up with the BillingId object. So what looks like needs to happen is that, in the ManyToOne Entity loader, the Billing objects needs to be created, when a BillingId object is created, and the Billing objects needs to get put into the context cache.
BDecember 27, 2010 at 7:20 PMEdited
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:
Person.java
@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;
}
}
Billing.java
@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;
}
BillingId.java
@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...
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.
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.