Persistierung nach JPA Standard mit Hibernate

In meinem letzte Blog Eintrag habe ich beschrieben wie man den Wildfly Application Server installiert und konfiguriert das er mit einer MySQL Datenbank als Datasource läuft. Heute stelle ich in einem knappen Beispiel vor wie man eine JEE7 Web Application mit Netbeans schreibt die mittels JPA 2.1 und nicht Hibernate spezifischen Funktionen in die Datenbank schreibt und von ihr liest. So ist es später möglich mit relativ einfachen Mitteln den Persistenzprovider auszutauschen.

Dieser Artikel soll wie der letzte als kurzer Einstiegspunkt in das Thema JEE Entwicklung dienen und ein kurze Dokumentation sein über die vielen kleinen Hürden die man zu bewältigen hat bevor man eine lauffähige Applikation hat und richtig entwickeln kann. Diese Hürden finde ich bei JEE nämlich zuweilen sehr frustrierend und in anderen Sprachen und Frameworks sind diese viel niedriger. Trotzdem lohnt es sich meiner Meinung nach diese zu überwinden, danach wird es einfacher 😉

Checkliste – Es läuft nicht!

Dies ist eine kurze Checkliste von typischen Problemen die dazu führen das die Applikation nicht kompiliert werden kann oder zur Laufzeit crasht.

  • beans.xml Muss im WEB-INF Verzeichnis liegen und ist notwendige damit CDI funktioniert. Braucht keine großen Inhalt (siehe unten) muss aber vorhanden sein. Ohne können zur Laufzeit zum Beispiel die Beans nicht für die Templates (xhtml) gefunden werden.
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"
           bean-discovery-mode="annotated">
    </beans>
    
  • persistence.xml Muss im META-INF Verzeichnis liegen und ist notwendig damit Hibernate mit der Datenbank arbeiten kann. Hier ist bei jt-data-source der Name der im Wildfly konfigurierten Datasource einzutragen und der Dialekt für die Datenbank. Ohne diese oder mit falschen Einstellungen gibt es Exceptions von Hibernate die darauf hindeuten das die Konfiguration fehlerhaft ist oder keine Verbindung zu Datenbank herstellt werden kann.
    <?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
      <persistence-unit name="io.kofrezo.sloth" transaction-type="JTA">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>java:/jdbc/sloth</jta-data-source>
        <exclude-unlisted-classes>false</exclude-unlisted-classes>
        <properties>
          <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
        </properties>
      </persistence-unit>
    </persistence>
    
  • Dependencies Damit die Web Application einwandfrei läuft sind ein paar Bibliotheken in Form von Jars notwendig. Diese müssen per Maven eingebunden werden oder in den Ordner kopiert. Fehlt eine Jar wird sich die Applikation nicht kompilieren lassen oder zur Laufzeit crashen. Notwendig sind: hibernate-entitymanager, hibernate-core und primefaces. Die restlichen Abhängigkeiten löst Maven selber auf.

Das Projekt anlegen

Als erstes muss eine neues Projekt in Netbeans erstellt werden. Am besten verwendet man dazu ein Projekt vom Typ Maven Web Application. Dazu wählt man in Netbeans über die Navigation File > New Project > Maven > Web Application aus. Die Voreinstellungen sind soweit in Ordnung können aber auch angepasst werden, es sollte aber mindestens der Name angepasst werden – Ordnung muss sein.

Als nächstes aktiviert man den PrimeFaces Support mit einem Rechtsklick auf das Projekt und dann Properties. Hier wählt man Frameworks aus und aktiviert den Support für Java Server Faces in der Version JSF 2.2. im Reiter Components wählt man PrimeFaces an und schließt dann das Fenster mit einem Klick auf OK.

Damit wir Hibernate benutzen können legt man zunächst die persistence.xml an. Dazu wählt man wieder in der Navigation File > New File > Persistence > Persistence Unit und wählt hier als Persistence Provider Hibernate (JPA 2.1) aus und als Data Source die MySQL Datenbank. als Table Generation Strategy kann Create oder Drop and Create ausgewählt werden. Letzteres löscht bei jedem Start der Applikation alle Tabellen und legt sie neu an.

Als letzter Schritt um Hibernate einzurichten müssen noch mittels Maven die notwendigen Jars eingebunden werden. Da hier Hibernate nach dem JPA 2.1 Standard genutzt werden soll klickt man mit einem Rechtsklick auf Dependencies und wählt Add Dependency und sucht nach hibernate-entitymanager. Diese dann auswählen, andere Abhängigkeiten werden automatisch mitgeladen.

Fast geschafft! Nun muss nur noch die beans.xml angelegt werden damit CDI einwandfrei funktioniert. Dazu wählt man ein letztes Mal File > New File > Contexts and Dependencies > beans.xml (CDI Configuration File) und bestätigt mit einem Klick auf OK.

Damit können folgenden Punkte von der Checkliste gestrichen werden:

  • Ein Maven Projekt vom Typ Web Application anlegen
  • Prime Faces Support für das Projekt aktivieren
  • Hibernate Support für das Projekt aktivieren
  • CDI für das Projekt aktivieren

Bleiben oder nicht bleiben – Etwas zum persistieren schaffen!

Bevor man nun überhaupt etwas in der Datenbank speichern kann oder etwas aus der Datenbank anzeigen kann, muss man erstmal beschreiben was man speichern möchte. Dies wird mittels einer POJO Java Klasse erledigt die mit JPA Annotation ausgestattet wird um Hibernate mitzuteilen was genau es den wie wo speichern soll. Es muss dafür nicht erst ein Datenbankschema auf der MySQL Datenbank angelegt werden (kann aber). Dies erledigt Hibernate durch die Einstellung Create (siehe oben) von selbst. Dies ist bestimmt kein optimales Datenbankschema und bringt einen Datenbankadministrator vielleicht zum weinen 😉 reicht aber für einfache Zwecke und Beispiele vollkommen aus.

package io.kofrezo.sloth.entities;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.GenericGenerator;

@Entity
@Access(AccessType.FIELD)
@Table(name = "todos")
public class Todo implements Serializable {

    @Id
    @GeneratedValue(generator = "increment")
    @GenericGenerator(name = "increment", strategy = "increment")
    private long id;

    private String name;

    private int done;

    @Temporal(TemporalType.TIMESTAMP)
    private Date created;

    @Temporal(TemporalType.TIMESTAMP)
    private Date modified;

    /**
     * @return the id
     */
    public long getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the created
     */
    public Date getCreated() {
        return created;
    }

    /**
     * @param created the created to set
     */
    public void setCreated(Date created) {
        this.created = created;
    }

    /**
     * @return the modified
     */
    public Date getModified() {
        return modified;
    }

    /**
     * @param modified the modified to set
     */
    public void setModified(Date modified) {
        this.modified = modified;
    }   

    /**
     * @return the done
     */
    public int getDone() {
        return done;
    }

    /**
     * @param done the done to set
     */
    public void setDone(int done) {
        this.done = done;
    }
}

Es folgen die Erklärungen zur Java Klasse von oben nach unten

  • package Das Java Paket in dem sich die Klasse befindet auch hier gilt – Ordnung muss sein.
  • import Die Bibliotheken die für die verwendeten Klassen und Annotation benötigt werden. Da wir mit dem JPA 2.1 Standard arbeiten sollte hier überall für die Annotationen javax.persistence.* verwendet werden damit keine Abhängigkeiten zu Hibernate spezifischen Annotationen oder Funktionen gemacht wird.
  • @Entity Markiert die Klasse als Entität
  • @Access(AccessType.FIELD) Definiert wie Hibernate zur Laufzeit auf die Attribute der Entität zugreift. Entweder per Reflection (Attribute können damit auch private sein) wie hier durch AccessType.Field festgelegt oder alternativ per Getter und Setter Methoden mittels AccessType.PROPERTY *
  • @Table(name = “todos”) Gibt den Namen für die Tabelle an in der die Daten für jede Entität gespeichert werden sollen. Im Programm selber wird der Name der Entiätsklasse verwendet also Todo.
  • @Id Definiert das Attribute als Primary Key für die Tabelle.
  • @GeneratedValue(generator = “increment”) Gibt an, dass dieses Attribut automatisch befüllt werden soll, dazu soll der Generator increment verwendet werden um einen eindeutigen neuen Wert zu bekommen.
  • @GenericGenerator(name = “increment”, strategy = “increment”) Definiert den eben genannten Generator welcher die AUTO_INCREMENT Funktion von MySQL abbildet.
  • @Temporal(TemporalType.TIMESTAMP) mappt das Date Attribut auf einen TIMESTAMP Value in der Datenbanktabelle und andersrum.

Die anderen Attribute müssen nicht genauer mit Annotationen versehen werden, da diese primitiven Datentypen eindeutig auf die entsprechenden Datenbanktypen gemappt werden können und dies somit vom Persistenzprovider selbständig erledigt werden kann. Es steht dem Entwickler natürlich frei diese trotzdem anzugeben und genauer zu spezifizieren.

* Die Frage welche Zugriffsart besser und welche schlechter ist driftet meist ins esoterische aber. Vorteil beim Zugriff über Reflection ist das die Klasse mitunter kleiner und übersichtlicher ist. Vorteil beim Zugriff über Getter- und Setter ist die Kapselung bzw. Transparenz wodurch nachträglich einfach zusätzliche Logik in die Getter- und Setter eingefügt werden können. Das Beispiel verwendet den Zugriff über Reflection und implementiert trotzdem Getter- und Setter über welche später die Werte abgefragt und gesetzt werden aus dem ersten und zweiten genannten Grund.

CR – Cread, Read, nicht Update und Delete

Fast geschafft! Die Web Application ist konfiguriert und hat eine Entität. Es fehlt nur noch eine Seite auf der man die gespeicherten Daten sehen kann und neue anlegen. Die Seite muss also die gängigen CRUD Operationen abbilden. Das Beispiel beschränkt sich hierbei allerdings auf Create und Read, es steht dem Leser natürlich frei die Seite weiter auszubauen und die restlichen Operationen zu implementieren.

Aus persönlicher Erfahrung glaube ich das es im Alltag am häufigsten vorkommt das ein Kunde eine Vorstellung von der Oberfläche hat und dabei die Funktionen beschreibt die benötigt werden. Deshalb bietet es sich an zuerst das Template (xhtml) zu erstellen und anschließend die benötigten Funktionen in der zugehörigen Bean zu implementieren. Die Applikation sieht dabei vor, dass auf der Seite neue Todos angelegt werden können und alle Todos aufgelistet werden.

Dazu wird benötigt:

  • Ein Eingabefeld mit dem Namen für das neue Todo
  • Eine Liste in der bestehende Todos angezeigt werden

Das ganze wird in das standard vorhandene Template index.xhtml implementiert und sieht dann etwa so aus

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Sloth Example Page</title>
    </h:head>
    <h:body>
        <h:form>
            <p:inputText id="name" value="#{testBean.name}" />
            <p:commandButton update="todo-list" value="create" actionListener="#{testBean.addTodo()}" />
            
            <p:dataList id="todo-list" value="#{testBean.todos}" var="todo" type="ordered">
                <f:facet name="header">
                    Todos
                </f:facet>
                #{todo.name}
            </p:dataList>
        </h:form>
    </h:body>
</html>

Der Inhalt sollte für eine Webentwickler selbsterklärend sein, interessanter ist die Implementierung der entsprechenden Bean testBean für diese Template mit ihrem Methoden um die Todos zu speichern addTodo() und sie anzuzeigen todos.

package io.kofrezo.sloth.beans;

import io.kofrezo.sloth.entities.Todo;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;

@Named
@SessionScoped
public class TestBean implements Serializable {

    private String name;
    
    @PersistenceContext(name="io.kofrezo.sloth")
    private EntityManager em;
    
    public TestBean() {}
    
    @Transactional
    public void addTodo() {
        System.out.println("Button clicked!");
        
        Todo todo = new Todo();
        todo.setName(this.getName());
        todo.setDone(0);
        todo.setCreated(new Date());
        todo.setModified(new Date());
        
        this.em.persist(todo);
    }
    
    public List<Todo> getTodos() {
        TypedQuery<Todo> query = this.em.createQuery("select t from Todo t", Todo.class);
        return query.getResultList();
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }
}
  • @Named Sorgt dafür das die Beans Klasse im Template unter dem Namen testBean (Klassenname mit Camelcase) zur Verfügung steht.
  • @SessionScoped Definiert den Lebensbereich der Bean. D.h. ab wann, bis wann das Bean Objekt existiert und die entsprechenden Werte beibehält bis es recyclet oder für den Garbage Collector freigegeben wird. SessionScoped bedeutet wie der Name schon andeutet, für die ganze Session. Es gibt noch den Request und Application Scope.
  • private String name; Stellt eine Instanzvariable bereit in der neue Name des Todos gespeichert ist.
  • @PersistenceContext(name=”io.kofrezo.sloth”) Gibt den Namen der Konfiguration für den Persistence Context an die für den Entity Manager verwendet werden soll. Dieser stammt aus der persistence.xml und wird mit dem persistence-unit Attribute definiert.
  • @Transactional Zugriffe auf die Datenbank müssen Transaktionsbasiert passieren damit im Fehlerfall ein Rollback gemacht werden kann.
  • addTodo() Erzeugt ein neues Todo Objekt und speichert es mit der Methode persist vom Entity Manager in der Datenbank.
  • getTodos() Sucht in der Datenbank nach allen Todos und gibt das Ergebnis als Liste zurück.

Mit Abschluss dieses Schrittes ist die Applikation nun fertig und in der Lage Todos in der Datenbank zu speichern und Todos aus der Datenbank anzuzeigen. Ab hier ist es dem Leser freigestellt das Internet um eine tolle neue Applikation reicher zu machen, welche ganz sicher skalieren kann. Besonderen Wert und Achtsamkeit ist dabei auf die Annotationen zu legen. Da diese mitunter eine sehr große Auswirkung auf das Laufzeitverhalten der Applikation haben. Es ist für den Speicherverbrauch der Applikation nämlich ein großer Unterschied ob eine Bean nur für einen Request existieren muss oder die gesamte Laufzeit der Applikation, aber dies ist ein anderes spannendes Thema.

Leave a Reply

Your email address will not be published. Required fields are marked *