Sorry for the poor title, but I couldn't think of a better succint description.
Basicaly, I have a "complex" inheritance scheme, and when running a simple query (or criteria query), the final query is built with an inner join (instead of an outer), while a similar query is generated via "load" (by PK), but this time with outer. The result is that I'm able to find the record that I'm looking for when using "load", but not when using the query. This becomes a problem, as I actually need another restriction on the query. In my case, an Account is owned by an User and an Account has Transactions, so, it goes without saying that I want an user to be able to manipulate only transactions pertaining to accounts owned by said user.
My scenario is as follows, with the code reduced to a minimum that is required to reproduce the problem. It's probably not relevant, but the classes have an "@Id private String id = UUID.randomUUID().toString()", ommitted from the code below. The full code can be found here: https://github.com/jpkrohling/hibernate-orm/tree/MultiInheritanceTest/hibernate-core/src/test/java/org/hibernate/test/multiinheritance
Code:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Account {
@OneToMany(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected List<Transaction> transactions = new ArrayList<Transaction>();
}
@Entity
public class FinancialInstitution {}
@Entity
public class MutualFund {}
@Entity
public class MutualFundAccount extends Account {
@ManyToOne(optional = false)
private MutualFund mutualFund;
}
@Entity
public class SavingsAccount extends Account {
@ManyToOne(optional = false)
public FinancialInstitution financialInstitution;
}
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Transaction {
@ManyToOne
private Account account;
}
Session session = openSession();
session.getTransaction().begin();
FinancialInstitution financialInstitution = new FinancialInstitution();
SavingsAccount savingsAccount = new SavingsAccount();
savingsAccount.setFinancialInstitution(financialInstitution);
Transaction transaction = new Transaction();
transaction.setAccount(savingsAccount);
session.persist(financialInstitution);
session.persist(savingsAccount);
session.persist(transaction);
String id = transaction.getId();
session.getTransaction().commit();
session.clear();
session.getTransaction().begin();
String hql = "from Transaction t where t.id = :id";
Transaction t1 = (Transaction) session.createQuery(hql)
.setParameter("id", id)
.uniqueResult();
assertEquals(id, t1.getId());
session.getTransaction().commit();
On the final transaction, if I replace the code by this one, it works:
Code:
Transaction t0 = (Transaction) session.load(Transaction.class, id);
Also, if I run a load and run the query, I get the desired record (as the transaction is already on the session cache), so, a workaround is:
Code:
session.load(Transaction.class, id);
String hql = "from Transaction t where t.id = :id";
Transaction t1 = (Transaction) session.createQuery(hql)
.setParameter("id", id)
.uniqueResult();
This is the query that is generated with the inner join. As you can see, it would be impossible to find any record, as an account is either MutualFundAccount (thus, having a MutualFund) *or* a SavingsAccount (thus, with a FinancialInstitution), but never both.
Code:
select
account0_.id as id0_2_0_,
account0_1_.financialInstitution_id as financ1_4_0_,
account0_2_.mutualFund_id as mutual1_5_0_,
case
when account0_1_.id is not null then 1
when account0_2_.id is not null then 2
when account0_.id is not null then 0
end as clazz_0_,
financiali1_.id as id0_0_1_,
financiali1_.bic as bic1_0_1_,
mutualfund2_.id as id0_3_2_,
mutualfund2_.isin as isin1_3_2_
from
Account account0_
left outer join
SavingsAccount account0_1_
on account0_.id=account0_1_.id
left outer join
MutualFundAccount account0_2_
on account0_.id=account0_2_.id
inner join
FinancialInstitution financiali1_
on account0_1_.financialInstitution_id=financiali1_.id
inner join
MutualFund mutualfund2_
on account0_2_.mutualFund_id=mutualfund2_.id
where
account0_.id=?
And this is the query generated for the "load":
Code:
select
transactio0_.id as id0_1_0_,
transactio0_.amount as amount1_1_0_,
transactio0_.account_id as accoun2_1_0_,
account1_.id as id0_2_1_,
account1_1_.mutualFund_id as mutual1_5_1_,
account1_2_.financialInstitution_id as financ1_3_1_,
case
when account1_1_.id is not null then 1
when account1_2_.id is not null then 2
when account1_.id is not null then 0
end as clazz_1_,
financiali2_.id as id0_0_2_,
financiali2_.bic as bic1_0_2_,
mutualfund3_.id as id0_4_3_,
mutualfund3_.isin as isin1_4_3_
from
Transaction transactio0_
left outer join
Account account1_
on transactio0_.account_id=account1_.id
left outer join
MutualFundAccount account1_1_
on account1_.id=account1_1_.id
left outer join
SavingsAccount account1_2_
on account1_.id=account1_2_.id
left outer join
FinancialInstitution financiali2_
on account1_2_.financialInstitution_id=financiali2_.id
left outer join
MutualFund mutualfund3_
on account1_1_.mutualFund_id=mutualfund3_.id
where
transactio0_.id=?
Am I missing something? Is this expected, or would this be considered a bug?