Hibernate Соедините две несвязанные таблицы, когда оба имеют составной первичный ключ - PullRequest
0 голосов
/ 24 декабря 2018

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

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

enter image description here

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

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, второй оператор выбора будет выполнен три раза, для 000,116 и 400 кода rc.

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

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

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

Сгенерированный в спящем режиме выберите 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 дополнительных запросов для получения кода ответаописание для каждой операции

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

Ответы [ 5 ]

0 голосов
/ 09 января 2019

Наконец я обнаружил, что

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();
0 голосов
/ 07 января 2019

Вы отобразили одну 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» и т. Д.).

0 голосов
/ 27 декабря 2018

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

Начнем с сущности 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 вы можете сделать это с помощью 2 @JoinColumn аннотаций Hibernate.Более старые версии Hibernate и стандарт JPA в версии 2.1 должны заключать эти аннотации в дополнительную аннотацию @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 голосов
/ 07 января 2019

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

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

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

0 голосов
/ 26 декабря 2018

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

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

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

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

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

Или, может быть, вам не нужно hibernate-отношение между Transaction и ResponseCode.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...