EclipseLink ManyToOne-CriteriaBuilder生成的查询错误

时间:2019-07-17 09:22:09

标签: jpa eclipselink

我有一个与另一个实体的主键具有ManyToOne关系的实体。当我创建引用此外键的查询时,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));

或者,如果进行了连接,则应考虑到ManyToOne关系是可为空的,因此请进行LEFT OUTER JOIN联接。

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中获得所需的行为)

2 个答案:

答案 0 :(得分:1)

  

我不明白为什么

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

我认为这是特定于实现的;在任何情况下,它都不会影响结果。

  
      
  • 首先创建联接。
  •   

通过说Path<Long> garageId = houseRoot.get(House_.garage).get(Garage_.id),您基本上是在告诉EclipseLink:“将GarageHouse连接起来,我们会需要它”。然后访问Garage_.id(而不是Garage_.space)是无关紧要的。

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

  

或者,如果进行了连接,则应考虑到ManyToOne关系是可为空的,因此请进行LEFT OUTER JOIN联接。

Path.get(...)始终默认为INNER JOIN。如果要使用其他联接类型,请使用Root.join(..., JoinType.LEFT),i。 e。 houseRoot.join(House_.garage, JoinType.LEFT).get(Garage_.id)

答案 1 :(得分:0)

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

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