web-dev-qa-db-fra.com

Hibernate Joindre deux tables non liées lorsque les deux ont une clé primaire composée

J'écris une application Java en utilisant hibernate 5.2 mais sans HQL

il y a deux tables, Transactions et ResponseCode

 enter image description here

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  enter image description here

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?

12
Jibo

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();
2
Jibo

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.

2
Khalid Shah

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 ResponseCodeavec 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.

0
Anatoly Samoylenko

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
0
Thorben Janssen

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.).

0
gaborsch