Hi,
The following is a scenario to illustrate a weird situation (at least for me) I have encountered.
Entity classes:
Code:
@Entity
@Table (name = "ACCOUNT")
@Inheritance (strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn (name = "ACCOUNT_TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class Account {
@Id
@GeneratedValue
@Column (name = "ACCOUNT_ID")
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
@Entity
@DiscriminatorValue (value = "SavingsAccount")
public class SavingsAccount extends Account {
@OneToOne (mappedBy = "savingsAccount", fetch = FetchType.EAGER)
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
@Entity
@DiscriminatorValue (value = "CheckingAccount")
public class CheckingAccount extends Account {
@OneToOne (mappedBy = "checkingAccount", fetch = FetchType.EAGER)
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
@Entity
@Table (name = "CUSTOMER")
public class Customer {
@Id
@GeneratedValue
@Column (name = "CUSTOMER_ID")
private Long id;
@JoinColumn (name = "SAVINGS_ACCOUNT_ID", referencedColumnName = "ACCOUNT_ID")
@OneToOne (optional = true, fetch = FetchType.LAZY)
private SavingsAccount savingsAccount;
@JoinColumn (name = "CHECKING_ACCOUNT_ID", referencedColumnName = "ACCOUNT_ID")
@OneToOne (optional = true, fetch = FetchType.LAZY)
private CheckingAccount checkingAccount;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public SavingsAccount getSavingsAccount() {
return savingsAccount;
}
public void setSavingsAccount(SavingsAccount savingsAccount) {
this.savingsAccount = savingsAccount;
}
public CheckingAccount getCheckingAccount() {
return checkingAccount;
}
public void setCheckingAccount(CheckingAccount checkingAccount) {
this.checkingAccount = checkingAccount;
}
}
SQL statements:
Code:
CREATE TABLE ACCOUNT (ACCOUNT_ID NUMBER NOT NULL, CONSTRAINT ACCOUNT_PK PRIMARY KEY (ACCOUNT_ID) ENABLE);
CREATE TABLE CUSTOMER (CUSTOMER_ID NUMBER NOT NULL, CHECKING_ACCOUNT_ID NUMBER, SAVINGS_ACCOUNT_ID NUMBER, CONSTRAINT CUSTOMER_PK PRIMARY KEY (CUSTOMER_ID) ENABLE);
ALTER TABLE CUSTOMER ADD CONSTRAINT FK_CHECKING_ACC FOREIGN KEY(CHECKING_ACCOUNT_ID) REFERENCES ACCOUNT (ACCOUNT_ID) ENABLE;
ALTER TABLE CUSTOMER ADD CONSTRAINT FK_SAVING_ACC FOREIGN KEY (SAVINGS_ACCOUNT_ID) REFERENCES ACCOUNT(ACCOUNT_ID) ENABLE;
ALTER TABLE ACCOUNT ADD (ACCOUNT_TYPE VARCHAR2(20));
INSERT INTO ACCOUNT (ACCOUNT_ID, ACCOUNT_TYPE) VALUES ('1', 'SavingsAccount');
INSERT INTO ACCOUNT (ACCOUNT_ID, ACCOUNT_TYPE) VALUES ('2', 'CheckingAccount');
INSERT INTO CUSTOMER (CUSTOMER_ID, CHECKING_ACCOUNT_ID, SAVING_ACCOUNT_ID) VALUES ('1', '2', '1');
When I run the following code to find the account with id 1, the customer field of the account is null.
Code:
Account savingsAccount = entityManager.find(Account.class, 1L);
The query created by hibernate is
Code:
SELECT account0_.ACCOUNT_ID AS ACCOUNT2_18_2_,
account0_.ACCOUNT_TYPE AS ACCOUNT1_18_2_,
customer1_.CUSTOMER_ID AS CUSTOMER1_85_0_,
customer1_.CHECKING_ACCOUNT_ID AS CHECKING2_85_0_,
customer1_.SAVING_ACCOUNT_ID AS SAVING3_85_0_,
customer2_.CUSTOMER_ID AS CUSTOMER1_85_1_,
customer2_.CHECKING_ACCOUNT_ID AS CHECKING2_85_1_,
customer2_.SAVING_ACCOUNT_ID AS SAVING3_85_1_
FROM ACCOUNT account0_
LEFT OUTER JOIN CUSTOMER customer1_
ON account0_.ACCOUNT_ID=customer1_.SAVING_ACCOUNT_ID
LEFT OUTER JOIN CUSTOMER customer2_
ON account0_.ACCOUNT_ID =customer2_.CHECKING_ACCOUNT_ID
WHERE account0_.ACCOUNT_ID=?
Although the account with id 1 is associated with the only customer in the database and hibernate performs a query to retrieve the relation; the account entity's customer field is not initialized properly. However, if I change the retrieval code to the following (note that I am retrieving a SavingsAccount now):
Code:
Account savingsAccount = entityManager.find(SavingsAccount.class, 1L);
The account's relation with the customer is initialized as expected with the following query generated:
Code:
SELECT savingsacc0_.ACCOUNT_ID AS ACCOUNT2_18_1_,
customer1_.CUSTOMER_ID AS CUSTOMER1_85_0_,
customer1_.CHECKING_ACCOUNT_ID AS CHECKING2_85_0_,
customer1_.SAVING_ACCOUNT_ID AS SAVING3_85_0_
FROM ACCOUNT savingsacc0_
LEFT OUTER JOIN CUSTOMER customer1_
ON savingsacc0_.ACCOUNT_ID =customer1_.SAVING_ACCOUNT_ID
WHERE savingsacc0_.ACCOUNT_ID=?
AND savingsacc0_.ACCOUNT_TYPE='SavingsAccount'
My question is why hibernate is unable to construct the account object with the customer field initialized properly in the first case I mentioned? This has to be related with the fact that account subclasses CheckingAccount and SavingsAccount both have a one-to-one relation with the customer and hibernate is somehow unable to detect the correct customer associated with the account in case the retrieval is performed over the base class Account. Is this a bug or am I missing sth? I would be glad if someone explains hibernate's behavior. By the way hibernate version is 3.4.