J'écris une application Java en utilisant hibernate 5.2
mais sans HQL
il y a deux tables, Transactions
et ResponseCode
La logique de l'instruction select que je souhaite générer par Hibernate devrait ressembler à ceci ci-dessous
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 )
Mais quelque chose ne va pas dans mon code, voici mon extrait de mise en œuvre
Entité de transaction
@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
Entité ResponseCode
@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
Code d'implémentation
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 Génère deux instructions select, la première instruction obtient la liste des transactions et la seconde instruction obtient les détails du code de réponse inclus dans la liste des transactions.
exemple: _ s'il y a 30000 transactions et que 15000 transactions ont un code de réponse 000, 5000 transactions ont un code de réponse 116 et 10000 transactions ont un code de réponse 400, il exécutera la deuxième instruction select trois fois, par 000,116 et 400 rcCode.
mais le problème est que la table ResponseCode
contient plusieurs langues pour un code de réponse
la première instruction select contient la restriction sur la langue, mais la seconde instruction select n'a pas cette restriction et ne mesure pas la langue fournie dans la première instruction. Le résultat final de l'objet transactions contient pour certaines transactions en
langue rc description et pour certaines transactions ge
langue rc descriptions.
Je pense que cela dépend de la description de la langue sélectionnée par Oracle
Hibernate généré 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 a généré le choix 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 _ Si je crée une relation
OneToMany
, elle obtient 30000 transactions et Effectue 30000 requêtes supplémentaires pour obtenir une réponse Description du code pour chaque opération
Savez-vous comment résoudre ce problème?
Finalement j'ai découvert que
L'API de critères ne prend pas en charge la jonction d'entités non liées. JPQL fait pas soutenir cela non plus. Cependant, Hibernate le supporte en HQL depuis 5.1. https://discourse.hibernate.org/t/join-two-table-when-both-has-composite-primary-key/1966
Il existe peut-être des solutions de contournement, mais dans ce cas, je pense que le meilleur moyen consiste à utiliser HQL au lieu de l'API Criteria.
Voici l'extrait de code d'implémentation HQL (rien n'a été modifié dans les classes d'entité)
String hql = "FROM Transaction t \r\n" +
" LEFT OUTER JOIN FETCH t.rc r \r\n" +
" WHERE (t.merchantID IN (:merchant_id))\r\n" +
" AND (t.authRequestDate BETWEEN :from AND :to)\r\n" +
" AND (r.rcLang = :rcLang or r.rcLang is null)\r\n";
Query query = session.createQuery(hql,Transaction.class);
query.setParameter("merchant_id", tRequest.getMerchantList());
query.setParameter("rcLang", tRequest.getLang());
query.setParameter("from", dateFrom);
query.setParameter("to", dateTo);
List<Transaction> dbTransaction = query.getResultList();
Changez la relation de @OneToOne
à @OneToMany
et utilisez fetch
au lieu de join
, il n'exécutera qu'une seule requête et, espérons-le, cela fonctionnera.
Join<Transaction, ResponseCode> join =
(Join<Transaction,ResponseCode>)transaction.fetch("rc",JoinType.LEFT);
et vous pouvez aussi l'essayer avec @OneToOne
.
Vous devez changer votre cartographie. rcCode
ne peut pas être un identifiant car il n'identifie pas les enregistrements de manière unique. Je pense que cela apportera beaucoup de problèmes. ResponseCode
doit avoir un identifiant différent.
@OneToOne
signifie un à un. Vous avez une transaction, un code de réponse mais plusieurs langues.
Vous pouvez mapper (@OneToOne
) une connexion entre une Transaction
et une ResponseCode
avec une langue spécifique (via une clé composite ).
Vous pouvez utiliser @OneToMany
, mais dans ce cas, la référence doit généralement aller de la table ResponseCode
à la table Transaction
.
Mais vous avez peut-être besoin de 3 tables: transactions, codes de réponse (avec le code lui-même et ses informations générales), localisation des codes de réponse (avec des messages dans différentes langues). transactions one-to-one code_réponses, code_réponses un-à-plusieurs rc_localizations.
Ou peut-être n'avez-vous pas besoin d'une relation d'hibernation entre Transaction
et ResponseCode
.
public class Transaction implements Java.io.Serializable {
...
@Column(name="rc")
private String rc;
...
}
Vous pouvez sélectionner la ResponseCode
nécessaire par code et par langue. Deux sélections: 1 - sélectionnez Transaction
(avec String rc-code); 2 - Sélectionnez ResponseCode
(avec la langue requise) par code-rc de Transaction
.
Votre mappage des deux entités est incorrect.
Commençons par l'entité ResponseCode
. Votre modèle de table affiche une clé primaire composite composée des colonnes RcCode
et Lang
. Mais votre mappage d'entité déclare uniquement l'attribut rcCode
en tant que clé primaire. Vous devez ajouter une annotation @Id
supplémentaire à l'attribut rcLang
de l'entité ResponseCode
.
Cela devrait être le mappage fixe de l'entité ResponseCode
:
@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;
@Id
@Column(name = "rc_lang")
private String rcLang;
// Contructors and getters/setters
}
Après avoir corrigé la clé primaire de votre entité ReponseCode
, vous devez référencer les deux attributs/colonnes dans le mappage d'association de votre entité Transaction
. Avec Hibernate 5.2, vous pouvez le faire avec 2 des annotations @JoinColumn
d'Hibernate. Les versions plus anciennes d'Hibernate et le standard JPA de la version 2.1 doivent envelopper ces annotations dans une annotation @JoinColumns
supplémentaire.
Voici le mappage fixe de votre entité Transaction
:
@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_id", referencedColumnName = "id")
@JoinColumn(name="rc_lang", referencedColumnName = "lang")
private ResponseCode rc;
// Contructos and getters/setters
Vous avez mappé une seule entité ResponseCode
dans Transaction
, ce qui est faux. Le code de réponse est pas / une PK, il n'identifie pas de manière unique une entité ResponseCode pour une entité de transaction donnée. Par exemple. pour une transaction avec le code de réponse 000
, il existe 2 entités ResponseCode (avec 'en' et 'ge' langs).
Je vous recommande d'essayer plutôt de mapper une collection.
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="rc")
private List<ResponseCode> rcList;
Étant donné que les conditions WHERE de votre requête s'appliquent uniquement à la transaction, vous pouvez simplement interroger les entités de la transaction. Le cache Hibernate optimisera les éventuelles sous-requêtes dont vous avez besoin pour chaque code de réponse (une requête pour "000", une pour "116", etc.).