Archive for the ‘Hibernate’ Category

Wenn man Suchformulare hat, bei denen alle oder viele Suchparameter optional sind, hat man immer das Problem, wie man die SQL Abfragen dafür gestalltet.

Man könnte entweder für jede Kombination eine eigene Query schreiben oder eine Query von Hand mit unzähligen ifs zusammenschustern.

Ein solches Vorgehen ist jedoch sehr fehleranfällig, weil sich so leicht Fehler in die SQL Query, die am Ende herauskommt, einschleichen.

Hibernate bietet auch hier eine elegante Lösung an: die Criteria API. Hiermit lassen sich Querys auf einfache Art und Weise zusammenbauen, mit allem was dazugehört (Joins usw.). Um die eigentliche Generierung des SELECT Statements kümmert sich das erprobte Framework.

Solch ein Criteria Objekt ist eigentlich nichts anderes als eine leere Hibernate Query, an die man Suchkriteren anhängen kann.

//Das Criteria Object erzeugen
Criteria purchaserCriteria = hibSession.createCriteria(Einkaeufers.class);

//Normales Kriterium
if (einkaeuferName != null)
{
purchaserCriteria.add(Expression.like(„einkaeuferName“, einkaeuferName);
}

Expression.like() gibt ein Criterion zurück, dass für die add-Methode des Criteria Objektes benötigt wird. Expression hat für alle Standard-SQL Operatoren statische Methoden, wie eq (=), le (<=), gt (>) usw.

Für Joins wird einfach ein weiteres Criteria Objekt erzeugt, um dafür wiederum eine Expression angeben zu können.
Der erste Parameter der createCriteria() Methode ist der Name des Properties in der hbm-Datei (funktionenSet), der zweite ein frei wählbarer Alias, den man im Criterion nutzen kann (funktionen.funtkionId).

//Kriterium mit einem Join
if (functionId > 0)
{
purchaserCriteria.createCriteria(„funktionenSet“, „funktionen“)
.add(Expression.eq(„funktionen.funktionId“, new Integer(functionId)));
}

//Kriterium mit zwei Joins
if (countryId > 0)
{
purchaserCriteria.createCriteria(„standort“, „standort“).createCriteria(„laender“, „laender“).add(Expression.eq(„laender.laenderId“, new Integer(countryId)));
}

ResultTransformer können ebenfalls praktisch sein (hier DISTINCT):

//Ergebnis muss Distinct sein
purchaserCriteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

Sortierung ist natürlich auch möglich:

//Order hinzufügen (über einen Join mit Alias (hier wird kein extra Criteria Object erzeugt))
purchaserCriteria.createAlias(„tEinkaeufer“, „tEinkaeufer“).addOrder(Order.asc(„tEinkaeufer.telvName“));

Wenn alle Suchparameter angegeben sind, kann wie bei der Query API list() ausgeführt werden.

//Abfrage ausführen
List purchaserList = purchaserCriteria.list();

Alles in allem lässt sich sagen, dass man sich mit dieser API sehr viel Arbeit und Ärger ersparen kann.

Wieder ein Grund mehr für die Verwendung von Hibernate!

Eine sehr gute Einführung in die API gibt es bei devx.

Advertisements

Nach längerem Suchen habe ich endlich verstanden, wie man n:m-Beziehungen in Hibernate am besten mappt.
Für die Verwaltung von solchen Beziehungen benötigt man in relationalen Datenbanken eine Beziehungstabelle.

Nehmen wir an es gibt 3 Tabellen:

News, Category und NewsCategory.

Eine News kann mehreren Categories zugeordnet sein und die Categories können natürlich in verschiedenen News benutzt werden. NewsCategory ist somit die Relationship-Tabelle.

Folgende Felder sind definiert (Auszug):

  • News-Tabelle: newsId(PK)
  • Category-Tabelle: categoryId(PK)
  • NewsCategory-Tabelle: newsCategoryId(PK), newsId(FK), categoryId(FK)

In der news.hbm.xml-Mapping-Datei definiert man ein Set, das die Beziehung zu den Categories abbildet:

<set name=“categories“ table=“NewsCategory“>
<key column=“newsId“ />
<many-to-many class=“net.vland.demo.Category“ column=“categoryId“/>
</set>

Das gleiche in der category.hbm.xml

<set name=“news“ table=“NewsCategory“>
<key column=“categoryId“ />
<many-to-many class=“net.vland.demo.News“ column=“newsId“/>
</set>

Nun muss man nur noch in den POJOs einfache Sets mit den definierten Namen erzeugen und das war’s. Es ist jetzt möglich ohne Umwege über die Beziehungstabelle die Daten auszulesen, z.B.:

Set categories = news.getCategories();

Anmerkung: Hibernate wirft eine Exception, wenn für einen Datensatz kein „Beziehungs-Set“ da ist. Dies kann durch not-found=“ignore“ im <many-to-many>-Tag unterbunden werden.

Wenn ein Hibernate-Property mit einem Kleinbuchstaben anfängt und darauf ein Groß-Buchstabe folgt, funktionieren die Getter und Setter, die Eclipse generiert, nicht mehr.

Das Property heißt tEinkaeufer.

Dafür hat Eclipse diese Getter und Setter generiert:

public TEinkaeufer getTEinkaeufer()
{
return tEinkaeufer;
}

public void setTEinkaeufer(TEinkaeufer einkaeufer)
{
tEinkaeufer = einkaeufer;
}

Beim initialisieren von Hibernate wurde dann folgender Fehler geworfen:

Initial SessionFactory creation failed.org.hibernate.PropertyNotFoundException: Could not find a getter for tEinkaeufer in class …

Daraufhin habe ich den Code folgendermaßen abgeändert:

public TEinkaeufer gettEinkaeufer()
{
return tEinkaeufer;
}

public void settEinkaeufer(TEinkaeufer einkaeufer)
{
tEinkaeufer = einkaeufer;
}

Und nun funktioniert es, was mich ein bisschen wundert.

Ich weiß nicht ob das ein Fehler in Hibernate ist, oder ob das so gewollt ist. Kann mir nicht vorstellen wieso…

Für den Web Service, der die Einkäuferdaten liefern soll, hab ich mit den HibernateTools Klassen und hbm.xml-Dateien generieren lassen.

Das ging soweit auch ganz gut. Nun gibt es aber anscheinend Tabellen in der zu integrierenden Datenbank, die keinen Primary Key haben!

Aus diesen machen die Tools dann zusammengesetze Primärschlüssel, und zwar aus allen Attributen der Tabelle.

<!– Generated 11.07.2006 15:42:45 by Hibernate Tools 3.1.0.beta5 –>
<hibernate-mapping>
<class name=”net.cpu.diplomarbeit.data.db.TEinkaeufer” table=”T_Einkaeufer” schema=”dbo” catalog=”EinkaeuferDB”>
<composite-id name=”id” class=”net.cpu.diplomarbeit.data.db.TEinkaeuferId”>
<key-property name=”telvPersnr” type=”string”>
<column name=”telvPersnr” length=”9″ />
</key-property>
<key-property name=”telvName” type=”string”>
<column name=”telvName” length=”50″ />
</key-property>
<key-property name=”telvVorname” type=”string”>
<column name=”telvVorname” length=”30″ />
</key-property>
<key-property name=”telvKostenstelle” type=”string”>
<column name=”telvKostenstelle” length=”10″ />
</key-property>
<key-property name=”telvAbteilung” type=”string”>
<column name=”telvAbteilung” length=”40″ />
</key-property>
<key-property name=”telvEmail” type=”string”>
<column name=”telvEmail” length=”128″ />
</key-property>
<key-property name=”telvTelefon” type=”string”>
<column name=”telvTelefon” length=”63″ />
</key-property>
<key-property name=”telvFax” type=”string”>
<column name=”telvFax” length=”63″ />
</key-property>
<key-property name=”telvMobExt” type=”string”>
<column name=”telvMobExt” length=”63″ />
</key-property>
<key-property name=”telvFirma” type=”string”>
<column name=”telvFirma” length=”60″ />
</key-property>
<key-property name=”telvPostfach” type=”java.lang.Integer”>
<column name=”telvPostfach” />
</key-property>
<key-property name=”telvStrasse” type=”java.lang.Integer”>
<column name=”telvStrasse” />
</key-property>
<key-property name=”telvPlz” type=”java.lang.Integer”>
<column name=”telvPLZ” />
</key-property>
<key-property name=”telvOrt” type=”string”>
<column name=”telvOrt” length=”60″ />
</key-property>
<key-property name=”telvLand” type=”string”>
<column name=”telvLand” length=”3″ />
</key-property>
<key-property name=”telvGesellschaft” type=”string”>
<column name=”telvGesellschaft” length=”40″ />
</key-property>
<key-property name=”telvBereichKurz” type=”string”>
<column name=”telvBereichKurz” length=”40″ />
</key-property>
<key-property name=”telvBereichDe” type=”string”>
<column name=”telvBereich_de” />
</key-property>
<key-property name=”telvBereichEn” type=”string”>
<column name=”telvBereich_en” />
</key-property>
<key-property name=”telvWerkName” type=”string”>
<column name=”telvWerkName” length=”60″ />
</key-property>
</composite-id>
</class>
</hibernate-mapping>

Ich muss allerdings eine 1:n Relation zu einer solchen Tabelle erstellen und da gibt es näturlich eine Fehlermeldung, da der Primärschlüssel mehre Attribute enthält und der Fremdschlüssel darauf nur eines. Deshalb musste ich diese Klasse von Hand umstrukturieren und einen Primärschlüssel setzen.

<id name=”telvEmail” type=”string”>
<column name=”telvEmail” length=”128″/>
</id>

Da ich die Daten nur auslesen muss, sollte es keine Probleme geben. Beim Schreiben muss dieser Primärschlüssel dann eben von Hand gesetzt und auf Eindeutigkeit geprüft werden.