【问题标题】:Hibernate Join two unrelated table when both has Composite Primary Key当两个表都具有复合主键时,休眠连接两个不相关的表
【发布时间】:2019-05-24 04:03:36
【问题描述】:

我正在使用hibernate 5.2 编写java 应用程序,但没有HQL

有两个表,TransactionsResponseCode

我想用 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 )

但是我的代码有问题,这里是我的实现 sn-p

交易实体

@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 生成两个 select 语句,第一个语句获取事务列表,第二个语句获取包含在事务列表中的响应代码详细信息。

例子: 如果有30000个交易,15000个交易有000响应码,5000个交易有116个响应码,10000个交易有400个响应码,会运行第二个select语句 3 次,分别为 000,116 和 400 rcCode。

但问题是ResponseCode 表包含一种响应代码的多种语言

第一个select语句包含对语言的限制,但是第二个select语句没有这个限制,并且它不测量第一个语句中提供的语言,事务对象的最终结果包含一些事务en语言rc描述以及一些交易ge语言rc描述。

我认为这取决于oracle最后选择了哪种语言描述

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 = ?'

P.s 如果我建立 OneToMany 关系,它将获得 30000 笔交易和 执行 30000 次附加查询以获取每个响应的代码描述 运算,称为N+1问题

你知道怎么解决吗?

【问题讨论】:

  • 这里没有错误。 where 子句不会更改实体包含的内容。它会更改查询检索到的实体。因此,如果您的 where 子句允许选择事务 t,当您请求 t 的响应代码时,您将始终得到 t 的响应代码。而且,如果有多行相同的响应码,那么响应码显然不能是实体的ID:ID应该是唯一标识一个实体,就像一个主键一样。
  • @JB Nizet 我已经编辑了问题,请检查您是否有任何想法如何解决它

标签: java sql oracle hibernate jpa


【解决方案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实现代码sn-p(实体类中没有改变)

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

【讨论】:

  • 您也可以尝试LEFT OUTER JOIN t.rc r WITH r.rcLang = :rcLang ...,它将额外的条件直接包含在生成的 SQL 语句的连接子句中。
【解决方案2】:

将关系从 @OneToOne 更改为 @OneToMany 并使用 fetch 而不是 join ,它将只执行一个查询,希望它可以工作。

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

你也可以用@OneToOne试试。

【讨论】:

  • 无法从 Fetch 转换为 Join
  • @Jibo 你试过上面给出的确切陈述吗?
  • @Jibo 很奇怪。我实际上正在使用它,它工作得很好。
  • @Jibo 看到这个我的问题。可能有帮助。有人告诉解决方案,这对我有用。 stackoverflow.com/questions/48061564/…
【解决方案3】:

您在Transaction 中映射了单个ResponseCode 实体,这是错误的。响应代码不是 PK,它不会唯一标识给定 Transaction 实体的 ResponseCode 实体。例如。对于响应代码为 000 的事务,有 2 个 ResponseCode 实体(带有 'en' 和 'ge' 语言)。

我建议您尝试映射一个集合。

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

由于您的查询 WHERE 条件仅适用于事务,因此您可以简单地查询事务实体。 Hibernate 缓存将优化每个响应代码所需的最终子查询(一个查询“000”,一个查询“116”,等等)。

【讨论】:

    【解决方案4】:

    您对这两个实体的映射是错误的。

    让我们从ResponseCode 实体开始。您的表模型显示由RcCodeLang 列组成的复合主键。但是您的实体映射仅将 rcCode 属性声明为主键。您需要在ResponseCode 实体的rcLang 属性中添加额外的@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 的两个 @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
    

    【讨论】:

    • 如果不将其包含在“@JoinColumns”注释中,则不能添加两个“@JoinColumn”注释。 “事务”实体中没有“rc_lang”列,因此无法将“rc_lang”列引用到引用实体中。
    • 需要使用Hibernate的@JoinColumn注解。 JPA 2.1 标准定义的不是@Repeatable,因此需要@JoinColumns 包装器。如果没有 rc_lang 列,则需要使用 @OneToMany 关联而不是 @OneToOne。根据您的表模型,ResonseCode 表中可能有多个具有相同 id 的记录
    • @ThorbenJanssen 在问题中提到,“P.s 如果我建立 OneToMany 关系,它将获得 30000 个事务并执行 30000 个额外查询以获取每个操作的响应代码描述”
    【解决方案5】:

    您需要更改映射。 rcCode 不能作为标识符,因为它不能唯一标识记录。我认为这会带来很多问题。 ResponseCode 必须有不同的标识符。

    @OneToOne 表示一对一。您有一项事务、一个响应代码,但有多种语言。

    您可以使用特定语言映射 (@OneToOne) TransactionResponseCode 之间的连接(通过 composite key)。

    您可以使用@OneToMany,但通常在这种情况下,引用应该是从ResponseCode 表到Transaction 表。

    但也许您需要 3 个表:事务、响应代码(包含代码本身及其一般信息)、响应代码本地化(包含不同语言的消息)。事务一对一 response_codes,response_codes 一对多 rc_localizations。

    或者您可能不需要 TransactionResponseCode 之间的休眠关系。

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

    您可以通过代码和语言选择必要的ResponseCode。两个选择: 1 - 选择 Transaction(使用字符串 rc-code); 2 - 通过Transaction中的rc-code选择ResponseCode(使用必要的语言)。

    【讨论】:

      猜你喜欢
      • 2019-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-12-04
      • 1970-01-01
      • 1970-01-01
      • 2018-11-16
      • 1970-01-01
      相关资源
      最近更新 更多