【问题标题】:JPA Discriminator using Spring-Data Repositories and JPQL Query使用 Spring-Data Repositories 和 JPQL Query 的 JPA 鉴别器
【发布时间】:2016-08-08 11:17:46
【问题描述】:

我在 MySQL 中有两个实体,如下所示。 nnm_tran 的主键是 id 和 source 的组合。讨价还价的主键其实是nnm_tran表的外键链接

我正在尝试使用 JPA 继承来表示这些。

nnm_tran 实体

@Entity
@Table(name = "nnm_tran")
@IdClass(CommonTransactionKey.class)
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "bargain_flag", discriminatorType = DiscriminatorType.CHAR)
@DiscriminatorValue("N")
public class CommonTransaction {

    @Id
    @Column(name = "id", nullable = false)
    private String transactionId;

    @Column(name = "plan_number", nullable = false)
    private String planNumber;

    @Column(name = "tran_date")
    private LocalDateTime transactionDatetime;

    @Column(name = "bargain_flag")
    private String bargainFlag;
    ...
}

讨价还价实体

@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "bargains")
@DiscriminatorValue("B")
@PrimaryKeyJoinColumns({ @PrimaryKeyJoinColumn(name = "nnm_tran_id", referencedColumnName = "id"), @PrimaryKeyJoinColumn(name = "nnm_tran_source", referencedColumnName = "source") })
public class Bargain extends CommonTransaction implements Serializable {

    @Column(name = "unit_price")
    private BigDecimal unitPrice;

    @Column(name = "client_price")
    private BigDecimal clientPrice;
    ...
}

我认为到目前为止这一切都正确连接。当我附加一个带有自定义查询的 spring-data 存储库时,我的问题就出现了。

存储库

public interface CommonTransactionRepository extends CrudRepository<CommonTransaction, CommonTransactionKey> {

    @Query("select t from CommonTransaction t left join IoPlan p ON t.planNumber = p.planNumber "
        + "where (p.planNumber is NULL or p.planNumber = '') "
        + "and t.transactionDatetime between ?1 and ?2 "
        + "and t.cancelled = false")
    public Iterable<CommonTransaction> findOrphanedTransactionsByTranDate(LocalDateTime fromDate, LocalDateTime toDate);
   ...
}

当它被代理并执行该方法时,它会生成 SQL 语句
SELECT DISTINCT nnm_tran.bargain_flag FROM nnm_tran t1 LEFT OUTER JOIN io_plan t0 ON (t1.plan_number = t0.plan_number) WHERE ((((t0.plan_number IS NULL) OR (t0.plan_number = ?)) AND (t1.tran_date BETWEEN ? AND ?)) AND (t1.CANCELLED = ?))

问题在于 nnm_tran 表别名为 t1 但鉴别器列引用了完整的表名 nnm_tran.bargain_flag 结果很可爱

UnitOfWork(17171249)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'nnm_tran.bargain_flag' in 'field list'

这里的问题是,我做错了什么还是这是 spring-data 和/或 eclipselink 中的错误?

版本:spring-data 1.7.2、Eclipselink 2.5.2、MySQL 5.6.28

【问题讨论】:

  • 我怀疑发布的JPA会导致发布的SQL查询(JPA查询以SELECT t FROM CommonTransaction t开头,而SQL查询是SELECT DISTINCT nnm_tran.bargain_flag FROM nnm_tran t1。您发布实际代码吗?我也无法使用您提供的版本重现您所指出的行为。您可以运行 my sample app 中包含的测试以查看生成的正确 SQL。您可以复制示例并将其调整到您的代码中,以查看您为什么不正确SQL。
  • 首先,感谢您花时间为此设置测试。它有助于我看到树林中的木头。 :-) 要回答您的问题,是的,这是实际代码,是的,SQL 是 JPA 查询的结果。我已经设法弄清楚了这个怪癖,虽然不是特别优雅,但它现在对我有用。我已经在我的回答中详细说明了。
  • EclipseLink 可以使用“选择不同的 discriminatorColumn”查询来获取它需要构建的子类,然后为每个类发出单独的查询。这是可配置的,如下所述:wiki.eclipse.org/EclipseLink/UserGuide/JPA/… 尝试使用定制器将其设置为使用外连接来读取单个查询中的子类,如果查询中有排序或分页,则应使用它。
  • @Chris Holy s**t 零食。这让我自己的解决方案大吃一惊。添加了定制器并关闭并运行。它确实只需要切换到外部连接。 :-) 谢谢

标签: java mysql jpa eclipselink spring-data


【解决方案1】:

使用@manish 的示例应用程序作为起点,我开始重新审视缺失的复杂性,并很快偶然发现导致流氓 SQL 的事情。这取决于我在 JPQL 中执行的连接

注意:如果您从未来来到这里,请忽略此答案的其余部分,而改用 @Chris 的评论。

大部分时间我都不需要看,甚至不需要去想@Query中可以看到的IoPlan表

@Query("select t from CommonTransaction t left join IoPlan p ON t.planNumber = p.planNumber "
    + "where (p.planNumber is NULL or p.planNumber = '') "
    + "and t.transactionDatetime between ?1 and ?2 "
    + "and t.cancelled = false")

所以这个表不是作为字段的CommonTransaction 实体的一部分。甚至这个查询的结果也不是很在意,因为它只是将CommonTransaction 视为与IoPlan 表中的no 关联联接。

当我从@manish 将联接添加回示例应用程序时,它以与我的应用程序在 EclipseLink 中相同的方式中断,但在 Hibernate 中以不同的方式中断。 Hibernate 需要一个字段供您加入,如果您问我,这会违背在@Query 中编写加入的目的。事实上,在 Hibernate 中,您必须纯粹在 JPA 中定义连接,因此您不妨使用点符号在 JPQL 中访问它。

无论如何,按照这个想法,我尝试在我的CommonTransaction 实体中添加一个虚拟字段来保存一个IoPlan,它几乎可以工作。它默认了一些连接逻辑,但更接近

SELECT DISTINCT t1.bargain_flag FROM nnm_tran t1 LEFT OUTER JOIN io_plan t0 ON ((t0.ID = t1.IOPLAN_ID) AND (t1.plan_number = t0.plan_number)) WHERE ((((t0.plan_number IS NULL) OR (t0.plan_number = ?)) AND (t1.tran_date BETWEEN ? AND ?)) AND (t1.CANCELLED = ?))

在这种情况下,t1.IOPLAN_IDt0.ID 不存在。所以我最终在我的CommonTransaction 实体中定义了整个连接

    @OneToOne
    @JoinColumn(insertable = false, updatable = false, name = "plan_number", referencedColumnName = "plan_number")
    private IoPlan ioPlan;

瞧,它开始工作了。它不漂亮,现在我有一个多余的连接条件

LEFT OUTER JOIN io_plan t1 
ON ((t1.plan_number = t0.plan_number) AND (t0.plan_number = t1.plan_number)) 

但我可以解决这个问题。我必须为它定义一个字段仍然很烦人,我实际上并不想要或不需要它,更不用说这个查询的结果是返回没有 IoPlanCommonTransaction 实体,所以该字段将永久为空。

【讨论】:

    猜你喜欢
    • 2014-04-07
    • 2019-08-08
    • 1970-01-01
    • 1970-01-01
    • 2017-11-29
    • 2017-12-07
    • 2023-03-17
    • 1970-01-01
    • 2017-05-06
    相关资源
    最近更新 更多