I'm writing java application using hibernate 5.2
but without HQL
there is two table, Transactions
and ResponseCode
The logic of select statement which I want to be generated by Hibernate should look like this select bellow
SELECT t.tranType
,t.tranId
,t.requestDate
,t.rcCode
,t.tranAmount
,r.description
,r.status
FROM transactions t
LEFT OUTER JOIN responseCode r
ON t.rcCode = r.rcCode
AND (r.lang = 'en')
WHERE (t.merchant_id =5 )
But something is wrong in my code, here is my implementation snippet
Transaction Entity
@Entity
@Table(name = "transactions")
public class Transaction implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Column(name = "merchant_id", nullable = true)
private String merchantID;
@Column(name = "tran_amount", nullable = true)
private String tranAmount;
@Id
@Column(name = "tran_type", nullable = true)
private String tranType;
@Column(name = "auth_request_date", nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date authRequestDate;
@Id
@Column(name = "tran_id", nullable = true)
private String tranID;
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="rc")
private ResponseCode rc;
// Contructos and getters/setters
ResponseCode Entity
@Entity
@Table(name = "response_codes")
public class ResponseCode implements java.io.Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name = "response_code")
private String rcCode;
@Column(name = "rc_status")
private String rcStatus;
@Column(name = "rc_description")
private String rcDesc;
@Column(name = "rc_lang")
private String rcLang;
// Contructos and getters/setters
Implementation code
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Transaction> criteria = builder.createQuery(Transaction.class);
Root<Transaction> transaction = criteria.from(Transaction.class);
Join<Transaction, ResponseCode> bJoin = transaction.join("rc",JoinType.LEFT);
bJoin.on(builder.equal(bJoin.get("rcLang"), tRequest.getLang()));
Predicate predicate = builder.and(transaction.get("merchantID").in(tRequest.getMerchantList()));
predicate = builder.and(predicate, builder.between(transaction.get("authRequestDate"), dateFrom, dateTo));
criteria.where(predicate);
Hibernate Generates two select statement, first statement gets transactions list, and second statement gets the response code details which is included in transactions list.
example: if there is 30000 transaction, and 15000 transaction has 000 response code, 5000 transaction has 116 response code and 10000 transaction has 400 response code, it will run second select statement three times, for 000,116 and 400 rcCode.
but the problem is that ResponseCode
table contains several language for one response code
first select statement contains the restriction on language but second select statement does not has this restriction, and it does not meter which language is provided in first statement, the final result of transactions object contains for some transactions en
language rc description and for some transactions ge
language rc descriptions.
I think it depends on which language description was selected by oracle at last
Hibernate generated select I
SELECT t.tran_type
,t.tran_id
,t.auth_request_date
,t.merchant_id
,t.rc
,t.tran_amount
FROM transactions t
LEFT OUTER JOIN response_codes r
ON t.rc = r.response_code
AND (r.rc_lang = ?)
WHERE (t.merchant_id IN (?))
AND (t.AUTH_REQUEST_DATE BETWEEN ? AND ?)
ORDER BY t.AUTH_REQUEST_DATE ASC
Hibernate generated select II
SELECT r.response_code
,r.rc_description
,r.rc_lang
,r.rc_status
FROM response_codes r
WHERE r.response_code = ?
//this select statement should have 'AND r.rc_lang = ?'
P.s If I make
OneToMany
relation it gets 30000 transaction and performs 30000 additional query to get response Code description for each operation, known as N + 1 problem
Do you know how to fix it?
Change the relation from
@OneToOne
to@OneToMany
and usefetch
instead ofjoin
, it will execute only one query and hopefully it works.and you can try it with
@OneToOne
too.Finally I found out that
Maybe there is some workarounds, but in this case, I think the better way is to use HQL instead of Criteria API.
Here is HQL implementation code snippet (nothing was changed in entity classes)
You need to change your mapping.
rcCode
cannot be an identifier because it does not identify records uniquely. I think that it will bring a lot of problems.ResponseCode
must have a different identifier.@OneToOne
means one to one. You have one transaction, one response code but many languages.You can mapping (
@OneToOne
) a connection between aTransaction
and aResponseCode
with a specific language (through a composite key).You can use
@OneToMany
, but usually in that case the reference should be from theResponseCode
table to theTransaction
table.But maybe you need 3 tables: transactions, response codes (with the code itself and its general information), response codes localizations (with messages on different languages). transactions one-to-one response_codes, response_codes one-to-many rc_localizations.
Or maybe you do not need hibernate-relation between
Transaction
andResponseCode
.You can select the necessary
ResponseCode
by code and language. Two selects: 1 - selectTransaction
(with String rc-code); 2 - selectResponseCode
(with the necessary language) by rc-code fromTransaction
.Your mapping of both entities is wrong.
Let's start with the
ResponseCode
entity. Your table model shows a composite primary key that consists of theRcCode
andLang
columns. But your entity mapping only declares thercCode
attribute as the primary key. You need to add an additional@Id
annotation to thercLang
attribute of theResponseCode
entity.This should be the fixed mapping of the
ResponseCode
entity:After fixing the primary key of your
ReponseCode
entity, you need to reference both attributes/columns in the association mapping of yourTransaction
entity. With Hibernate 5.2, you can do that with 2 of Hibernate's@JoinColumn
annotations. Older Hibernate versions and the JPA standard in version 2.1 need to wrap these annotations in an additional@JoinColumns
annotation.Here is the fixed mapping of your
Transaction
entity:You have mapped a single
ResponseCode
entity inTransaction
, which is wrong. The response code is not a PK, it does not uniquely identifies a ResponseCode entity for a given Transaction entity. E.g. for a transaction with response code000
there are 2 ResponseCode entities (with 'en' and 'ge' langs).I recommend you to try to map a collection instead.
Since your query WHERE conditions apply to the Transaction only, you can simply query the Transaction entities. Hibernate cache will optimize the eventual subqueries you need for each response code (one query for '000', one for '116', etc).