web-dev-qa-db-ja.com

Hibernate両方に複合主キーがある場合、2つの無関係なテーブルを結合する

Java hibernate 5.2ですが、HQLはありません

TransactionsResponseCodeの2つのテーブルがあります

enter image description here

Hibernateで生成するselectステートメントのロジックは、次の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 )

しかし、コードに問題があります。ここに私の実装スニペットがあります

トランザクションエンティティ

@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
@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

実装コード

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 2つのselectステートメントを生成します。最初のステートメントはトランザクションリストを取得し、2番目のステートメントはトランザクションリストに含まれる応答コードの詳細を取得します。

例:30000トランザクションがあり、15000トランザクションに000応答コードがある場合、5000トランザクションには116応答コードと10000トランザクションがあります400の応答コードがあり、000,116と400のrcCodeに対して2番目の選択ステートメントを3回実行します。

しかし、問題は、ResponseCodeテーブルに1つの応答コードに複数の言語が含まれていることです enter image description here

最初の選択ステートメントには言語の制限が含まれていますが、2番目の選択ステートメントにはこの制限がありません。また、最初のステートメントで提供される言語は測定されません。トランザクションオブジェクトの最終結果には、一部のトランザクションが含まれますen言語rc説明一部のトランザクションでは、ge言語rcの説明。

それは、最後にOracleによって選択された言語の説明に依存すると思います

Hibernateが生成した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によって生成された選択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 = ?'

PsOneToManyリレーションを作成すると、30000トランザクションを取得し、30000追加クエリを実行して応答を取得しますコードの説明N + 1の問題として知られている各操作について

あなたはそれを修正する方法を知っていますか?

12
jiboOne

最後に私はそれを見つけました

Criteria APIは、無関係なエンティティの結合をサポートしていません。 JPQLもサポートしていません。ただし、5.1以降、HibernateはHQLでサポートしています。 https://discourse.hibernate.org/t/join-two-table-when-both-has-composite-primary-key/1966

いくつかの回避策があるかもしれませんが、この場合は、Criteria APIの代わりにHQLを使用する方が良いと思います。

HQL実装のコードスニペットは次のとおりです(エンティティクラスでは何も変更されていません)

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

リレーションを@OneToOneから@OneToManyに変更し、fetchの代わりにjoinを使用すると、クエリが1つだけ実行され、うまくいくと思います。

 Join<Transaction, ResponseCode> join =
        (Join<Transaction,ResponseCode>)transaction.fetch("rc",JoinType.LEFT);

@OneToOneでもお試しいただけます。

2
Khalid Shah

マッピングを変更する必要があります。 rcCodeはレコードを一意に識別しないため、識別子にすることはできません。多くの問題が発生すると思います。 ResponseCodeには別の識別子が必要です。

@OneToOneは1対1を意味します。 1つのトランザクション、1つの応答コードがありますが、多くの言語があります。

TransactionResponseCodeの間の接続を特定の言語でマッピング(@OneToOne)できます( 複合キー を使用)。

@OneToManyを使用できますが、通常、その場合、参照はResponseCodeテーブルからTransactionテーブルへの参照である必要があります。

しかし、おそらく3つのテーブルが必要です。トランザクション、応答コード(コード自体とその一般情報を含む)、応答コードのローカリゼーション(さまざまな言語のメッセージを含む)。トランザクションは1対1のresponse_codes、response_codesは1対多のrc_localizationsです。

または、TransactionResponseCodeの間にhibernate関係が必要ない場合もあります。

public class Transaction implements Java.io.Serializable {
...
    @Column(name="rc")
    private String rc;
...
}

必要なResponseCodeをコードと言語で選択できます。 2つのselect:1-select Transaction(with string rc-code); 2-ResponseCodeからrc-codeによってTransaction(必要な言語で)を選択します。

0

両方のエンティティのマッピングが間違っています。

ResponseCodeエンティティから始めましょう。テーブルモデルには、RcCode列とLang列で構成される複合主キーが表示されます。ただし、エンティティマッピングではrcCode属性を主キーとして宣言するだけです。 rcLangエンティティのResponseCode属性に追加の@Idアノテーションを追加する必要があります。

これは、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
}

ReponseCodeエンティティの主キーを修正した後、Transactionエンティティの関連付けマッピングで両方の属性/列を参照する必要があります。 Hibernate 5.2では、Hibernateの2つの@JoinColumnアノテーションを使用してそれを行うことができます。古いHibernateバージョンとバージョン2.1のJPA標準では、これらのアノテーションを追加の@JoinColumnsアノテーションでラップする必要があります。

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

ResponseCodeに単一のTransactionエンティティをマッピングしましたが、これは誤りです。応答コードはnot PKであり、特定のトランザクションエンティティのResponseCodeエンティティを一意に識別しません。例えば。応答コード000のトランザクションの場合、2つのResponseCodeエンティティがあります( 'en'および 'ge'言語を使用)。

代わりにコレクションをマップすることをお勧めします。

@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name="rc")
private List<ResponseCode> rcList;

クエリのWHERE条件はトランザクションにのみ適用されるため、トランザクションエンティティをクエリするだけで済みます。 Hibernateキャッシュは、応答コードごとに必要な最終的なサブクエリを最適化します( '000'に対して1つのクエリ、 '116'に対して1つのクエリなど)。

0
gaborsch