Hibernate Соедините две таблицы, когда оба имеют составной первичный ключ

Я пишу Java-приложение, используя hibernate 5.2 но без HQL

есть две таблицы, Transactions и ResponseCode

enter image description here

Логика оператора select, который я хочу генерировать в Hibernate, должна выглядеть следующим образом:

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 Создает два оператора выбора, первый оператор получает список транзакций, а второй оператор получает данные кода ответа, которые включены в список транзакций.

пример: если транзакция 30000, транзакция 15000 с кодом ответа 000, транзакция 5000 с кодом ответа 116, транзакция 10000 с кодом ответа 400, второй оператор select будет выполнен три раза, для 000 116 и 400 rcCode.

но проблема в том, что таблица ResponseCode содержит несколько языков для одного кода ответа enter image description here

первый оператор выбора содержит ограничение на языке, но второй оператор выбора не имеет это ограничение, и это не метр, на каком языке представлен в первом заявлении, конечный результат операций объекта содержит для некоторых операций en языка описания гса и для некоторых операций ge Язык описания.

Я думаю, что это зависит от того, какой язык описание был выбран оракулом, наконец

Hibernate генерируется выбрать 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 = ?'

Ps Если я сделаю отношение OneToMany он получает 30000 транзакций и выполняет 30000 дополнительных запросов, чтобы получить описание кода ответа для каждой операции.

Вы знаете, как это исправить?

Ответы

Ответ 1

Наконец я узнал, что

Criteria API не поддерживает объединение не связанных между собой объектов. JPQL также не поддерживает это. Однако Hibernate поддерживает его в HQL начиная с версии 5.1. https://discourse.hibernate.org/t/join-two-table-when-both-has-composite-primary-key/1966

Может быть, есть некоторые обходные пути, но в этом случае, я думаю, лучше использовать HQL вместо Criteria API.

Вот фрагмент кода реализации 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

Измените отношение с @OneToOne на @OneToMany и используйте fetch вместо join, он выполнит только один запрос и, надеюсь, будет работать.

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

и вы можете попробовать это с @OneToOne тоже.

Ответ 3

Вы должны изменить свое отображение. rcCode не может быть идентификатором, потому что он не идентифицирует записи однозначно. Я думаю, что это принесет много проблем. ResponseCode должен иметь другой идентификатор.

@OneToOne означает один в один. У вас есть одна транзакция, один код ответа, но много языков.

Вы можете @OneToOne (@OneToOne) соединение между Transaction и ResponseCode с конкретным языком (через составной ключ).

Вы можете использовать @OneToMany, но обычно в этом случае ссылка должна быть из таблицы ResponseCode таблицу Transaction.

Но, возможно, вам понадобятся 3 таблицы: транзакции, коды ответов (с самим кодом и его общей информацией), локализация кодов ответов (с сообщениями на разных языках). транзакции один-к-одному response_codes, response_codes один-ко-многим rc_localizations.

Или, может быть, вам не нужны спящие отношения между Transaction и ResponseCode.

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

Вы можете выбрать необходимый ResponseCode по коду и языку. Два выбора: 1 - выбрать Transaction (со строковым rc-кодом); 2 - выберите ResponseCode (с нужным языком) по rc-коду из Transaction.

Ответ 4

Ваше отображение обеих сущностей неверно.

Давайте начнем с сущности ResponseCode. Ваша табличная модель показывает составной первичный ключ, который состоит из RcCode и Lang. Но ваше сопоставление сущностей только объявляет атрибут rcCode в качестве первичного ключа. Вам необходимо добавить дополнительную аннотацию @Id к rcLang сущности ResponseCode.

Это должно быть фиксированное отображение сущности 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 @JoinColumn. Более старые версии Hibernate и стандарт JPA в версии 2.1 должны @JoinColumns эти аннотации в дополнительную аннотацию @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

Ответ 5

Вы сопоставили одну сущность ResponseCode в Transaction, что неверно. Код ответа не является PK, он не уникальным образом идентифицирует объект ResponseCode для данного объекта транзакции. Например, для транзакции с кодом ответа 000 существует 2 объекта ResponseCode (с "en" и "ge" langs).

Я рекомендую вам попробовать сопоставить коллекцию вместо этого.

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

Поскольку ваш запрос, ГДЕ условия применяются только к Транзакции, вы можете просто запросить сущности Транзакции. Кэш Hibernate оптимизирует возможные подзапросы, необходимые для каждого кода ответа (один запрос для "000", один для "116" и т.д.).