简单的JPA CriteriaQuery的条件

时间:2014-02-22 22:21:08

标签: java hibernate jpa

所以这是我第一次尝试使用JPA和CriteriaQuery

我有以下(简化)实体:

@Entity
@Table(name = "hours")
@XmlRootElement
public class Hours implements Serializable
{
    @EmbeddedId
    protected HoursPK hoursPK;

    @Column(name = "total_hours")
    private Integer totalHours;

    @JoinColumn(name = "trainer_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    private Trainer trainer;

    public Hours()
    {
    }

    ... getter and setter for the attributes
}

@Embeddable
public class HoursPK implements Serializable
{
    @Basic(optional = false)
    @Column(name = "date_held", nullable = false)
    @Temporal(TemporalType.DATE)
    private Date dateHeld;

    @Basic(optional = false)
    @Column(name = "trainer_id", nullable = false, length = 20)
    private String trainerId;

    @Column(name = "total_hours")
    private Integer totalHours;


    public HoursPK()
    {
    }

    ... getter and setter ...
}

@Entity
@Table(name = "trainer")
public class Trainer implements Serializable
{
    @Id
    @Basic(optional = false)
    @Column(name = "id", nullable = false, length = 20)
    private String id;

    @Basic(optional = false)
    @Column(name = "firstname", nullable = false, length = 200)
    private String firstname;

    @Basic(optional = false)
    @Column(name = "lastname", nullable = false, length = 200)
    private String lastname;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "trainer", fetch = FetchType.LAZY)
    private List<Hours> hoursList;

    ... more attributes, getters and setters

    @XmlTransient
    public List<Hours> getHoursList() {
       return hoursList;
    }

    public void setHoursList(List<Hours> hoursList) {
      this.hoursList = hoursList;
    }
}

基本上Trainer举行培训,培训所花费的时间存储在Hours实体中。 hours表的PK是(trainer_id, date_held),因为每位培训师每天只进行一次培训。

我正在尝试创建CriteriaQuery以获取特定月份的培训师的所有时间。这是我的尝试:

EntityManagerFactory emf = ...
EntityManager em = emf.createEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();

CriteriaQuery<Hours> c = builder.createQuery(Hours.class);

Root<Hours> root = c.from(Hours.class);

Calendar cal = Calendar.getInstance();
cal.set(2014, 0, 1);
Expression<Date> from = builder.literal(cal.getTime());

cal.set(2014, 1, 1);
Expression<Date> to = builder.literal(cal.getTime());

Predicate who = builder.equal(root.get(Hours_.trainer), "foobar"); // it fails here

Predicate gt = builder.greaterThanOrEqualTo(root.get(Hours_.hoursPK).get(HoursPK_.dateHeld), from);
Predicate lt = builder.lessThan(root.get(Hours_.hoursPK).get(HoursPK_.dateHeld), to);

c.where(gt,lt,who);
c.orderBy(builder.asc( root.get(Hours_.hoursPK).get(HoursPK_.dateHeld)  ));

TypedQuery<Hours> q = em.createQuery(c);

List<Hours> resultList = q.getResultList();

我正在使用Hibernate 4.3.1作为JPA提供程序,上面的代码失败,但有例外:

  

线程“main”中的异常java.lang.IllegalArgumentException:参数值[foobar]与预期类型[persistence.Trainer(n / a)]不匹配       在org.hibernate.jpa.spi.BaseQueryImpl.validateBinding(BaseQueryImpl.java:885)

除了这个看起来非常复杂的查询,即使是SQL新手也可以在几分钟内编写,我不知道如何为trainer_id列中的hours列提供正确的值上面查询中的{1}}表。

我也尝试过:

Predicate who = builder.equal(root.get("trainer_id"), "foobar");

但是失败的例外是:

  

java.lang.IllegalArgumentException:无法在此ManagedType上找到具有给定名称[trainer_id]的Attribute [persistence.Hours]

当我获得映射到"foobar" id:

的实际实体实例时,它可以工作
CriteriaQuery<Trainer> cq = builder.createQuery(Trainer.class);
Root<Trainer> trainerRoot = cq.from(Trainer.class);
cq.where(builder.equal(trainerRoot.get(Trainer_.id), "foobar"));
TypedQuery<Trainer> trainerQuery = em.createQuery(cq);  
Trainer foobarTrainer = trainerQuery.getSingleResult();
....
Predicate who = builder.equal(root.get(Hours_.trainer), foobarTrainer);

但这似乎是一种非常愚蠢(和缓慢)的方式。

我确信我错过了一些非常明显的东西,但我找不到它。

1 个答案:

答案 0 :(得分:23)

首先,JPA查询始终使用类和字段名称。从不列名。因此,尝试使用trainer_id将无效。

builder.equal(root.get(Hours_.trainer), "foobar");

您正在尝试将Hours实体的training字段与字符串“foobar”进行比较。培训师属于培训师。培训师不能等于字符串。它的ID,firstName或lastName,所有类型String,都可以与String进行比较。所以你可能想要

builder.equal(root.get(Hours_.trainer).get(Trainer_.id), "foobar");

也就是说,正如您所注意到的那样,Criteria API非常复杂,导致代码难以理解,难以维护。当你必须从几个可选标准(因此名称)动态编写查询时,这很有用,但对于静态查询,你肯定应该使用JPQL,它比SQL更简单,更短:

select h from Hours h 
where h.trainer.id = :trainerId
and h.hoursPK.dateHeld >= :from
and h.hoursPK.dateHeld < :to
order by h.hoursPK.dateHeld

我强烈建议不要使用复合键,特别是当其中一个组件是一个可能需要更改的功能数据(dateHeld)时。使用数字,单列,自动生成的主键,一切都会更简单,更高效。