2014-02-22 29 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不會改變,在這種情況下,他們確實不會。如果墨菲出現這種情況,那麼刪除錯誤信息並在 –