Beginnen Sie mit dem Ruhezustand

Es ist gut zu verstehen, dass in Java-Anwendungen ein Objekt- / relationales Mapping (ORM) erforderlich ist, aber Sie möchten wahrscheinlich den Ruhezustand in Aktion sehen. Wir zeigen Ihnen zunächst ein einfaches Beispiel, das einen Teil seiner Leistungsfähigkeit demonstriert.

Wie Sie wahrscheinlich wissen, beginnt ein Programmierbuch traditionell mit einem "Hello World" -Beispiel. In diesem Kapitel folgen wir dieser Tradition, indem wir Hibernate mit einem relativ einfachen "Hello World" -Programm einführen. Das einfache Drucken einer Nachricht in ein Konsolenfenster reicht jedoch nicht aus, um den Ruhezustand wirklich zu demonstrieren. Stattdessen speichert unser Programm neu erstellte Objekte in der Datenbank, aktualisiert sie und führt Abfragen durch, um sie aus der Datenbank abzurufen.

Zusätzlich zum kanonischen Beispiel "Hello World" stellen wir die Kern-Hibernate-APIs vor und geben Details für eine Grundkonfiguration an.

"Hallo Welt" mit Ruhezustand

Anwendungen im Ruhezustand definieren persistente Klassen, die Datenbanktabellen "zugeordnet" sind. Unser Beispiel "Hallo Welt" besteht aus einer Klasse und einer Zuordnungsdatei. Lassen Sie uns sehen, wie eine einfache persistente Klasse aussieht, wie die Zuordnung angegeben wird und was wir mit Instanzen der persistenten Klasse mithilfe von Hibernate tun können.

Ziel unserer Beispielanwendung ist es, Nachrichten in einer Datenbank zu speichern und zur Anzeige abzurufen. Die Anwendung verfügt über eine einfache persistente Klasse Message, die diese druckbaren Nachrichten darstellt. Unsere MessageKlasse ist in Listing 1 dargestellt.

Listing 1. Message.java: Eine einfache persistente Klasse

Paket Hallo; öffentliche Klasse Nachricht {private Lange ID; privater String-Text; private Nachricht nextMessage; private Message () {} public Message (String text) {this.text = text; } public Long getId () {return id; } private void setId (lange ID) {this.id = id; } public String getText () {Rückgabetext; } public void setText (String text) {this.text = text; } public Message getNextMessage () {return nextMessage; } public void setNextMessage (Nachricht nextMessage) {this.nextMessage = nextMessage; }}

Unsere MessageKlasse hat drei Attribute: das Bezeichnerattribut, den Text der Nachricht und einen Verweis auf ein anderes Message. Mit dem Bezeichnerattribut kann die Anwendung auf die Datenbankidentität - den Primärschlüsselwert - eines persistenten Objekts zugreifen. Wenn zwei Instanzen Messagedenselben Bezeichnerwert haben, repräsentieren sie dieselbe Zeile in der Datenbank. Wir haben Longden Typ unseres Bezeichnerattributs ausgewählt, dies ist jedoch keine Voraussetzung. Der Ruhezustand erlaubt praktisch alles für den Bezeichnertyp, wie Sie später sehen werden.

Möglicherweise haben Sie bemerkt, dass alle Attribute der MessageKlasse über Eigenschaftszugriffsmethoden im JavaBean-Stil verfügen. Die Klasse hat auch einen Konstruktor ohne Parameter. Die persistenten Klassen, die wir in unseren Beispielen verwenden, sehen fast immer ungefähr so ​​aus.

Instanzen der MessageKlasse können verwaltet (persistent gemacht) werden durch Hibernate, aber sie nicht haben zu sein. Da das MessageObjekt keine Hibernate-spezifischen Klassen oder Schnittstellen implementiert, können wir es wie jede andere Java-Klasse verwenden:

Nachricht message = neue Nachricht ("Hello World"); System.out.println (message.getText ());

Dieses Codefragment macht genau das, was wir von "Hello World" -Anwendungen erwarten: Es wird "Hello World"auf der Konsole gedruckt . Es könnte so aussehen, als würden wir hier versuchen, süß zu sein. Tatsächlich demonstrieren wir eine wichtige Funktion, die Hibernate von einigen anderen Persistenzlösungen unterscheidet, z. B. EJB-Entity-Beans (Enterprise JavaBean). Unsere persistente Klasse kann in jedem Ausführungskontext verwendet werden - es wird kein spezieller Container benötigt. Natürlich sind Sie hierher gekommen, um Hibernate selbst zu sehen. Speichern Sie also einen neuen Messagein der Datenbank:

Sitzung session = getSessionFactory (). OpenSession (); Transaktion tx = session.beginTransaction (); Nachricht message = neue Nachricht ("Hello World"); session.save (Nachricht); tx.commit (); session.close ();

Dieser Code ruft den Ruhezustand Sessionund die TransactionSchnittstellen auf. (Wir werden getSessionFactory()bald zu diesem Aufruf kommen.) Dies führt zur Ausführung von etwas ähnlichem wie dem folgenden SQL:

In MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) Werte einfügen (1, 'Hello World', null) 

Warten Sie - die MESSAGE_IDSpalte wird auf einen seltsamen Wert initialisiert. Wir haben das idEigentum von messagenirgendwo festgelegt, also würden wir es erwarten null, oder? Tatsächlich ist die idEigenschaft etwas Besonderes: Es handelt sich um eine Bezeichner-Eigenschaft, die einen generierten eindeutigen Wert enthält. (Wir werden später diskutieren, wie der Wert generiert wird.) Der Wert wird der MessageInstanz von Hibernate zugewiesen, wenn er save()aufgerufen wird.

In diesem Beispiel wird davon ausgegangen, dass die MESSAGESTabelle bereits vorhanden ist. Natürlich möchten wir, dass unser Programm "Hello World" die Nachricht auf der Konsole druckt. Nachdem wir eine Nachricht in der Datenbank haben, können wir dies demonstrieren. Im nächsten Beispiel werden alle Nachrichten in alphabetischer Reihenfolge aus der Datenbank abgerufen und gedruckt:

Sitzung newSession = getSessionFactory (). OpenSession (); Transaktion newTransaction = newSession.beginTransaction (); List messages = newSession.find ("von Nachricht in m Reihenfolge nach m.text asc"); System.out.println (messages.size () + "Nachricht (en) gefunden:"); für (Iterator iter = messages.iterator (); iter.hasNext ();) {Message message = (Message) iter.next (); System.out.println (message.getText ()); } newTransaction.commit (); newSession.close ();

Die Literalzeichenfolge "from Message as m order by m.text asc"ist eine Hibernate-Abfrage, die in Hibernates eigener objektorientierter Hibernate Query Language (HQL) ausgedrückt wird. Diese Abfrage wird beim find()Aufruf intern in die folgende SQL übersetzt :

Wählen Sie m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID aus MESSAGES m Reihenfolge nach m.MESSAGE_TEXT auf 

Das Codefragment druckt:

1 Nachricht (en) gefunden: Hallo Welt 

Wenn Sie noch nie ein ORM-Tool wie Hibernate verwendet haben, haben Sie wahrscheinlich erwartet, dass die SQL-Anweisungen irgendwo im Code oder in den Metadaten angezeigt werden. Sie sind nicht da. Alle SQL-Anweisungen werden zur Laufzeit generiert (tatsächlich beim Start für alle wiederverwendbaren SQL-Anweisungen).

Damit diese Magie auftreten kann, benötigt Hibernate weitere Informationen darüber, wie die MessageKlasse persistent gemacht werden soll. Diese Informationen werden normalerweise in einem XML-Zuordnungsdokument bereitgestellt . Das Zuordnungsdokument definiert unter anderem, wie Eigenschaften der MessageKlasse Spalten der MESSAGESTabelle zugeordnet werden. Schauen wir uns das Mapping-Dokument in Listing 2 an.

Listing 2. Eine einfache XML-Zuordnung im Ruhezustand


  

Das Zuordnungsdokument teilt Hibernate mit, dass die MessageKlasse in der MESSAGESTabelle beibehalten werden soll, dass die Bezeichner-Eigenschaft einer Spalte mit dem Namen zugeordnet ist MESSAGE_ID, dass die Texteigenschaft einer Spalte mit dem Namen zugeordnet MESSAGE_TEXTist und dass die benannte Eigenschaft nextMessageeine Zuordnung zu Many-to-One ist Multiplizität , die einer Spalte mit dem Namen zugeordnet ist NEXT_MESSAGE_ID. (Mach dir vorerst keine Sorgen um die anderen Details.)

Wie Sie sehen, ist das XML-Dokument nicht schwer zu verstehen. Sie können es einfach von Hand schreiben und pflegen. Unabhängig von der gewählten Methode verfügt Hibernate über genügend Informationen, um alle SQL-Anweisungen vollständig zu generieren, die zum Einfügen, Aktualisieren, Löschen und Abrufen von Instanzen der MessageKlasse erforderlich wären . Sie müssen diese SQL-Anweisungen nicht mehr manuell schreiben.

Hinweis
Viele Java-Entwickler haben sich über die "Metadaten-Hölle" beschwert, die mit der J2EE-Entwicklung einhergeht. Einige haben vorgeschlagen, von XML-Metadaten zurück zu einfachem Java-Code zu wechseln. Obwohl wir diesen Vorschlag für einige Probleme begrüßen, stellt ORM einen Fall dar, in dem textbasierte Metadaten wirklich notwendig sind. Der Ruhezustand verfügt über sinnvolle Standardeinstellungen, die die Eingabe minimieren, und eine ausgereifte Dokumenttypdefinition, die für die automatische Vervollständigung oder Validierung in Editoren verwendet werden kann. Sie können sogar automatisch Metadaten mit verschiedenen Tools generieren.

Lassen Sie uns nun unsere erste Nachricht ändern und, während wir gerade dabei sind, eine neue Nachricht erstellen, die der ersten zugeordnet ist, wie in Listing 3 gezeigt.

Listing 3. Aktualisieren einer Nachricht

Sitzung session = getSessionFactory (). OpenSession (); Transaktion tx = session.beginTransaction (); // 1 ist die generierte ID der ersten Nachricht Message message = (Message) session.load (Message.class, new Long (1)); message.setText ("Greetings Earthling"); Nachricht nextMessage = neue Nachricht ("Bring mich zu deinem Anführer (bitte)"); message.setNextMessage (nextMessage); tx.commit (); session.close ();

Dieser Code ruft drei SQL-Anweisungen innerhalb derselben Transaktion auf:

Wählen Sie m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID aus MESSAGES m, wobei m.MESSAGE_ID = 1 in die Werte von MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) einfügen (2, 'Bring mich zu deinem Anführer (bitte)', null) setze MESSAGE_TEXT = 'Greetings Earthling', NEXT_MESSAGE_ID = 2 wobei MESSAGE_ID = 1 

Beachten Sie, wie Hibernate die Änderung an textund nextMessageEigenschaften der ersten Nachricht erkannt und die Datenbank automatisch aktualisiert hat. Wir haben eine Hibernate-Funktion namens automatische Dirty-Checking-Funktion genutzt : Diese Funktion erspart uns den Aufwand, Hibernate explizit aufzufordern, die Datenbank zu aktualisieren, wenn wir den Status eines Objekts innerhalb einer Transaktion ändern. Ebenso können Sie sehen, dass die neue Nachricht dauerhaft erstellt wurde, als aus der ersten Nachricht eine Referenz erstellt wurde. Diese Funktion wird als kaskadierendes Speichern bezeichnet : Sie erspart uns den Aufwand, das neue Objekt durch Aufrufen explizit dauerhaft zu machensave(), solange es von einer bereits persistenten Instanz erreichbar ist. Beachten Sie auch, dass die Reihenfolge der SQL-Anweisungen nicht mit der Reihenfolge übereinstimmt, in der wir Eigenschaftswerte festlegen. Hibernate verwendet einen ausgeklügelten Algorithmus, um eine effiziente Reihenfolge zu bestimmen, die Verstöße gegen Datenbank-Fremdschlüsseleinschränkungen vermeidet, für den Benutzer jedoch ausreichend vorhersehbar ist. Diese Funktion wird als Transaktions-Write-Behind bezeichnet .

Wenn wir "Hello World" erneut ausführen, wird Folgendes gedruckt:

2 Nachricht (en) gefunden: Grüße Erdling Bring mich zu deinem Anführer (bitte) 

Dies ist soweit wir die "Hello World" -Anwendung nehmen. Nachdem wir endlich Code haben, werden wir einen Schritt zurücktreten und einen Überblick über die wichtigsten APIs von Hibernate geben.

Die Architektur verstehen

Die Programmierschnittstellen sind das erste, was Sie über den Ruhezustand lernen müssen, um ihn in der Persistenzschicht Ihrer Anwendung zu verwenden. Ein Hauptziel des API-Designs ist es, die Schnittstellen zwischen Softwarekomponenten so eng wie möglich zu halten. In der Praxis sind ORM-APIs jedoch nicht besonders klein. Mach dir aber keine Sorgen; Sie müssen nicht alle Hibernate-Schnittstellen gleichzeitig verstehen. Die folgende Abbildung zeigt die Rollen der wichtigsten Hibernate-Schnittstellen in der Geschäfts- und Persistenzschicht.

Wir zeigen die Geschäftsschicht über der Persistenzschicht, da die Geschäftsschicht in einer traditionell geschichteten Anwendung als Client der Persistenzschicht fungiert. Beachten Sie, dass einige einfache Anwendungen die Geschäftslogik möglicherweise nicht sauber von der Persistenzlogik trennen. Das ist in Ordnung - es vereinfacht lediglich das Diagramm.

Die in der obigen Abbildung gezeigten Hibernate-Schnittstellen können ungefähr wie folgt klassifiziert werden:

  • Schnittstellen, die von Anwendungen aufgerufen werden, um grundlegende CRUD- (Erstellen / Lesen / Aktualisieren / Löschen) und Abfragevorgänge auszuführen. Diese Schnittstellen sind der Hauptpunkt der Abhängigkeit der Anwendungsgeschäfts- / Steuerlogik vom Ruhezustand. Dazu gehören Session, Transactionund Query.
  • Schnittstellen, die vom Anwendungsinfrastrukturcode aufgerufen werden, um den Ruhezustand zu konfigurieren, vor allem die ConfigurationKlasse.
  • Rückruf - Schnittstellen , die die Anwendung ermöglichen , auf Ereignisse zu reagieren , innerhalb Ruhezustand auftretenden wie Interceptor, Lifecycle, und Validatable.
  • Schnittstellen , die Erweiterung des Hibernate leistungsstarke Mapping - Funktionalität erlauben, wie UserType, CompositeUserTypeund IdentifierGenerator. Diese Schnittstellen werden durch Anwendungsinfrastrukturcode implementiert (falls erforderlich).

Hibernate verwendet vorhandene Java-APIs, einschließlich JDBC (Java Database Connectivity), Java Transaction API (JTA) und Java Naming and Directory Interface (JNDI). JDBC bietet eine rudimentäre Abstraktionsebene für Funktionen, die relationalen Datenbanken gemeinsam sind, sodass nahezu jede Datenbank mit einem JDBC-Treiber von Hibernate unterstützt werden kann. Mit JNDI und JTA kann Hibernate in J2EE-Anwendungsserver integriert werden.

In diesem Abschnitt behandeln wir nicht die detaillierte Semantik der Hibernate-API-Methoden, sondern nur die Rolle der einzelnen primären Schnittstellen. Die meisten dieser Schnittstellen finden Sie im Paket net.sf.hibernate. Lassen Sie uns nacheinander einen kurzen Blick auf jede Schnittstelle werfen.

Die Kernschnittstellen

Die fünf Kernschnittstellen werden in nahezu jeder Hibernate-Anwendung verwendet. Über diese Schnittstellen können Sie persistente Objekte speichern und abrufen sowie Transaktionen steuern.

Sitzungsschnittstelle