Einfache where-Bedingung für JPA CriteriaQuery
Dies ist mein Erster Versuch, JPA und ein CriteriaQuery
.
Habe ich das folgende (vereinfachte) Unternehmen:
@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;
}
}
Im wesentlichen eine Trainer
hält Schulungen und die Stunden damit verbracht, die in den Schulungen werden gespeichert in der Hours
Einheit. Die PK für die hours
Tabelle (trainer_id, date_held)
als jeder trainer enthält nur ein training pro Tag.
Ich versuche zu erstellen CriteriaQuery
zu Holen, alle Stunden eines trainers für einen bestimmten Monat. Dies ist mein Versuch:
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();
Ich bin mit Hibernate 4.3.1 als JPA-provider und der obige code bricht mit der Ausnahme:
Exception in thread "main" java.lang.IllegalArgumentException: Parameter Wert [foobar] passten nicht zu dem erwarteten Typ [Persistenz.Trainer (n/a)]
bei org.hibernate.jpa.spi.BaseQueryImpl.validateBinding(BaseQueryImpl.java:885)
Abgesehen von der Tatsache, dass dies scheint furchtbar kompliziert für eine Abfrage, die auch ein SQL newbie schreiben konnte, in ein paar Minuten, ich habe keine Ahnung, wie kann ich den korrekten Wert für die trainer_id
Spalte in der hours
Tabelle in der obigen Abfrage.
Ich auch versucht:
Predicate who = builder.equal(root.get("trainer_id"), "foobar");
Aber nicht, dass mit der Ausnahme:
java.lang.IllegalArgumentException: kann Nicht suchen Sie das Attribut mit dem angegebenen Namen [trainer_id] auf dieser ManagedType [Persistenz.Stunden]
Funktioniert es, wenn ich erhalten eine tatsächliche Entität-Instanz, die Karten auf den "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);
Aber das scheint eine ziemlich dumme (und langsamen) Weg, es zu tun.
Ich bin sicher, ich bin etwas fehlt offensichtlich wirklich hier, aber ich kann es nicht finden.
InformationsquelleAutor a_horse_with_no_name | 2014-02-22
Du musst angemeldet sein, um einen Kommentar abzugeben.
Zunächst JPA-Abfragen verwenden Sie immer Klasse-und Feldnamen. Nie Spaltennamen. So versuchen, zu verwenden
trainer_id
wird nicht funktionieren.Versuchen Sie zu vergleichen, das trainer-Feld der Stunden entity mit dem String "foobar". trainer ist der Typ Trainer. Ein Trainer kann nicht gleich sein, um eine Zeichenfolge. Seine ID, Vorname oder sein Nachname, alle vom Typ String verglichen werden können, um eine Zeichenfolge. SO möchten Sie wahrscheinlich
Sagte, als Sie bemerkte, die Kriterien, die API ist äußerst Komplex und führt zu unlesbar, schwierig zu pflegen-code. Es ist nützlich, wenn Sie haben, um dynamisch zu Komponieren eine Abfrage aus mehreren optionalen Kriterien (daher der name), aber für statische Abfragen, sollten Sie auf jeden Fall gehen Sie mit JPQL, die noch einfacher und kürzer als SQL:
Ich würde dringend empfehlen, gegen die Verwendung von zusammengesetzten Schlüsseln, vor allem, wenn eine Ihrer Komponenten ist eine funktionelle Daten (dateHeld) haben könnte, sich zu ändern. Verwenden Sie numerische, single-Spalte automatisch generierte primary keys und alles wird viel einfacher und effizienter.
root.get(Hours_.trainer).get(Trainer_.id)
hat den trick. In Bezug auf die composite-PK: das ist einer meiner größten Kritik von ORMs: Sie zwingen Sie zu benutzen, PKs, das sind die "wirkliche Sache". Hinzufügen eines automatisch generierten Spalte die Stunden Tabelle keine Verbesserung des Modells aus einer relationalen Sicht. Es erhöht nur den Aufwand der ein weiterer index für die Tabelle ohne Angabe der Leistung (in SQL). Nachdem ich rumprobiert mit dies ich Stimme zu: die Vorteile des Art Sicherheit, dass die API gibt uns garantiert nicht die Komplexität der it.Wie funktioniert JPA Sie zwingen, verwenden Sie einen zusammengesetzten Schlüssel? Im Gegenteil: mit einem single-Spalte-Schlüssel ist viel einfacher. Oder habe ich da was missverstehen Sie? Automatisch generierte key verbessert das Modell: es wird verhindert, dass fremde Schlüssel, mit zwei Spalten in allen Tabellen verweisen auf die Stunden Spalte, macht es schließt sich schneller, und es nicht zwingen, Sie zu ändern, die PK und alle FKs, wenn Sie erkennen, dass ein dateHeld Wert geändert werden muss.
Sorry, war ein Tippfehler. Ich meinte zu schreiben sind nicht das "real thing". Wenn die echte PK ist einen zusammengesetzten Schlüssel, ich sehe keine nutzen in hinzufügen einer zusätzlichen Spalte und der index nur um die ORM das Leben leichter. PKs nicht ändern und in diesem Fall ist Sie wirklich nicht. Und wenn Murphy kommt, dass es viel sauberer ist, löschen Sie die falschen Daten und fügen Sie die richtige zurück
Außer wenn Sie haben Dutzende von FKs auf Sie zeigen. Nehmen wir an, die "Natürliche" Schlüssel für eine andere Tabelle "bar" ein paar "Stunden-foo". Sie werden am Ende mit einem PK mit 3 Spalten. Nun nehmen wir an, die "Natürliche" Schlüssel für eine andere Tabelle "baz" ist das paar "bar-bing", werden Sie am Ende mit einem PK mit 4 Spalten. Ich habe gesehen, apps, wo Sie die "Natürliche" - Tasten, und hatten Tische mit 11 Spalten, von denen 9 wurden Teil der PK und enthalten Daten, die hatten nichts zu tun mit der Tabelle.
Und wenn Sie einen link in Ihre webapp, die auf eine Zeile aus einer solchen Tabelle, die Sie brauchen, 2 oder 3 Parametern in der URL, mit der ein Datum muss analysiert werden, in einer von Ihnen, statt eines einfachen integer. Surrogat-PKs sind ein besseres design, ob oder nicht ein ORM verwendet wird.
InformationsquelleAutor JB Nizet