【问题标题】:EclipseLink ManyToOne - CriteriaBuilder Generated Query is WrongEclipseLink ManyToOne - CriteriaBuilder 生成的查询错误
【发布时间】:2019-07-17 09:22:09
【问题描述】:

我有一个与另一个实体的主键具有多对一关系的实体。当我创建一个引用此外键的查询时,eclipseLink 总是创建一个连接,而不是简单地访问外键。

我创建了一个高度简化的示例来说明我的问题:

@Entity
public class House {
  @Id
  @Column(name = "H_ID")
  private long id;

  @Column(name = "NAME")
  private String name;

  @ManyToOne
  @JoinColumn(name = "G_ID")
  private Garage garage;
}

@Entity
public class Garage{
  @Id
  @Column(name = "G_ID")
  private long id;

  @Column(name = "SPACE")
  private Integer space;
}

我创建了一个查询,它应该使用 CriteriaBuilder 返回所有没有车库或车库 G_ID = 0 的房屋。

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<House> query = cb.createQuery(House.class);
Root<House> houseRoot = query.from(House.class);
Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);
query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garageId)));
TypedQuery<House> typedQuery = entityManager.createQuery(query);
List<House> houses = typedQuery.getResultList();

生成的查询是:

SELECT h.NAME, h.G_ID FROM HOUSE h, GARAGE g WHERE (((h.G_ID= 0) OR (g.G_ID IS NULL)) AND (g.G_ID = h.G_ID));

我不明白为什么

  • or 条件首先引用表 HOUSE,然后是 GARAGE(而不是 HOUSE)
  • 首先创建连接。

按照我的理解,正确的查询应该是这样的:

SELECT h.NAME, h.G_ID FROM HOUSE h WHERE (((h.G_ID= 0) OR (h.G_ID IS NULL));

或者,如果进行连接,则应考虑到多对一关系可以为空,因此执行左外连接。

SELECT h.NAME, h.G_ID FROM HOUSE h LEFT OUTER JOIN GARAGE g ON (h.G_ID = g.G_ID ) WHERE (h.G_ID = 0) OR (g.G_ID IS NULL);

(请注意,这两个查询在我更复杂的设置中都可以正常工作。当我只想检索所有没有车库的房子时,我也会遇到同样的错误。)

我怎样才能做到这一点(同时仍使用 CriteriaBuilder,最好不必更改数据库模型)?

(请让我知道可能需要的任何其他信息,我对这个主题非常陌生,并且在迁移现有应用程序时遇到了这个问题。)

-- 编辑-- 我找到了一个解决我的问题的方法,它会导致行为略有不同(但在我的应用程序中,我必须迁移的那部分代码一开始并没有多大意义)。而不是使用

Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id);

我用

Path<Garage> garage = houseRoot.get(House_.garage);

然后正如预期的那样,表 Garage 不再加入。 (我假设之前的代码一定是某种 hack 才能从 openJPA 获得所需的行为)

【问题讨论】:

    标签: jpa eclipselink


    【解决方案1】:

    我不明白为什么

    • or 条件首先引用表 HOUSE,然后是 GARAGE(而不是 HOUSE)

    我相信这是特定于实现的;无论如何,它不应该对结果产生任何影响。

    • 首先创建连接。

    说Path&lt;Long&gt; garageId = houseRoot.get(House_.garage).get(Garage_.id) 你基本上是在告诉EclipseLink:'加入Garage 到House,我们需要它'。然后访问Garage_.id(而不是,例如Garage_.space)是无关紧要的。

    如果您不想加入,只需将G_ID 列再次映射为一个简单属性:@Column(name = "G_ID", insertable = false, updatable = false) private Long garageId。然后在您的查询中引用House_.garageId。

    或者,如果进行连接,则应考虑到多对一关系可以为空,因此执行左外连接。

    Path.get(...) 始终默认为 INNER JOIN。如果您想要不同的连接类型,请使用Root.join(..., JoinType.LEFT),即。 e. houseRoot.join(House_.garage, JoinType.LEFT).get(Garage_.id).

    【讨论】:

    • 非常感谢您的回答。它让我更清楚了很多事情,我找到了一个现在有效的解决方案,它的行为与最初提出的问题略有不同。我会将我的解决方案作为问题的补充发布。
    【解决方案2】:

    导致相同行为的一种解决方案是:

    CriteriaBuilder cb = entityManager.getCriteriaBuilder();
    CriteriaQuery<House> query = cb.createQuery(House.class);
    Root<House> houseRoot = query.from(House.class);
    Path<Garage> garage = houseRoot.get(House_.garage);
    Path<Long> garageId = garage.get(Garage_.id);
    query.where(cb.or(cb.equal(garageId , 0), cb.isNull(garage)));
    TypedQuery<House> typedQuery = entityManager.createQuery(query);
    List<House> houses = typedQuery.getResultList();
    

    这会产生以下 SQL:

    SELECT H_ID, NAME, G_ID FROM HOUSE WHERE ((G_ID = 0) OR (G_ID IS NULL));
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-08
      • 2018-03-26
      • 1970-01-01
      相关资源
      最近更新 更多