Hibernate Соедините две таблицы, когда оба имеют составной первичный ключ
Я пишу Java-приложение, используя hibernate 5.2
но без HQL
есть две таблицы, Transactions
и ResponseCode
Логика оператора 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
содержит несколько языков для одного кода ответа
первый оператор выбора содержит ограничение на языке, но второй оператор выбора не имеет это ограничение, и это не метр, на каком языке представлен в первом заявлении, конечный результат операций объекта содержит для некоторых операций 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" и т.д.).