2014-02-22 30 views
5

所以这是我第一次尝试使用JPA和CriteriaQuery简单的条件为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提供者和上面的代码失败例外:

在线程异常“主” java.lang.IllegalArgumentException异常:参数值foobar的]没有匹配预期类型[persistence.Trainer (N/A)] 在org.hibernate.jpa.spi.BaseQueryImpl.validateBinding(BaseQueryImpl.java:885)

除了事实,这似乎是一个非常查询复杂,即使是新手SQL可能在几分钟内写完,我不知道如何为上述查询中的hours表中的trainer_id列提供正确的值。

我也试过:

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

但失败,出现异常:

java.lang.IllegalArgumentException异常:无法找到这个ManagedType与给定名称[trainer_id]属性[持久性。小时]

它的工作,当我获得一个实际的实体实例,映射到"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); 

但是,这似乎是一个非常愚蠢(和缓慢)的方式来做到这一点。

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

回答

22

首先,JPA查询总是使用类和字段名称。从不列名称。所以试图使用trainer_id将无法​​正常工作。

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

你试图比较字符串“foobar的”时时刻刻实体的教练场。教练员是训练师的类型。培训师不能等于一个字符串。它的ID,它的firstName或者它的lastName,都是String类型,可以与一个String进行比较。所以,你可能想

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

这就是说,当你注意到,该标准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)时。使用数字,单列,自动生成的主键,一切都会更简单,更高效。

+0

感谢,'root.get(Hours_.trainer).get(Trainer_.id)'做了窍门。关于复合PK:这是我对ORM最大的批评之一:他们强迫你使用“真实”的PK。将一个自动生成的列添加到小时表中确实可以从关系角度改进模型。它只会将另一个索引的开销添加到表中,而不会给予任何好处(在SQL中)。在我玩弄这个之后,我同意:API给我们的类型安全带来的好处并不能保证它的复杂性。 –

+1

JPA如何强制您使用复合键?相反:使用单列键更容易。或者我误解了你?一个自动生成的键极大地改进了模型:它避免了在所有引用hours列的表中有两列的外键,它使联接更快,并且它不会强制你修改PK和所有FK,当你意识到必须更改dateHeld值。 –

+0

对不起,这是一个错字。我的意思是写**,不是**“真实的东西”*。如果真正的PK *是一个复合键,我认为添加额外的列和索引并没有任何好处,只是为了让ORM的生活更轻松。 PK不会改变,在这种情况下,他们确实不会。如果墨菲出现这种情况,那么删除错误信息并在 –