Heute gibt es ein Java-Tutorial, das zeigt, wie man eine kleine AJAX basierende und datenbankgestützte Web-Anwendung in Apache Wicket, Google Guice (Guice Persist) und JPA 2.0 entwickelt.

Unsere kleine in Java geschriebe Webanwendung besteht aus drei Komponenten: Apache Wicket 1.4.15 als Web-Applikations-Framework, Google Guice 3.0-rc2 bzw. Guice Persist für Dependency Injection und Transaktionen, sowie JPA 2.0 als Persistenzschnittstelle. Die JPA-Implementierung übernimmt der Hibernate EntityManager in Version 3.6.1. Als Datenbank steht uns HSQLDB zur Verfügung.

Diese Web-Anwendung soll ein kleines Formular bereitstellen in dem man einen Vor- und Nachnamen eingeben kann. Nach dem Abschicken sollen die Daten in der Datenbank gespeichert werden. Gleichzeitig soll unter dem Formular eine Liste der eingegebenen Namen erscheinen bzw. automatisch mittels AJAX aktualisiert werden.

Fangen wir zuerst mit den Konfigurationsdateien an. Da ich mit Maven als Build-Management-Tool arbeite, folgt zunächst die pom.xml.

Pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     <modelVersion>4.0.0</modelVersion>
     <groupId>de.witi</groupId>
     <artifactId>jpa</artifactId>
     <packaging>war</packaging>
     <version>1.0-SNAPSHOT</version>
     <name>Wicket Guice JPA</name>
     <repositories>
         <repository>
             <id>jboss</id>
             <name>JBoss Repository</name>
             <url>https://repository.jboss.org/nexus/content/groups/public/</url>
         </repository>
     </repositories>
     <dependencies>
        <!--  WICKET DEPENDENCIES -->
         <dependency>
             <groupId>org.apache.wicket</groupId>
             <artifactId>wicket</artifactId>
             <version>${wicket.version}</version>
         </dependency>
         <dependency>
             <groupId>org.apache.wicket</groupId>
             <artifactId>wicket-guice</artifactId>
             <version>${wicket.version}</version>
             <exclusions>
                 <exclusion>
                     <artifactId>cglib-nodep</artifactId>
                     <groupId>cglib</groupId>
                 </exclusion>
             </exclusions>
         </dependency>
        <!-- LOGGING DEPENDENCIES - LOG4J -->
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
             <version>${slf4j.version}</version>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
             <version>${slf4j.version}</version>
             <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>log4j</groupId>
             <artifactId>log4j</artifactId>
             <version>1.2.16</version>
             <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>org.hsqldb</groupId>
             <artifactId>hsqldb</artifactId>
             <version>2.0.0</version>
             <scope>runtime</scope>
         </dependency>
         <dependency>
             <groupId>com.google.inject</groupId>
             <artifactId>guice</artifactId>
             <version>3.0-rc2</version>
         </dependency>
         <dependency>
             <groupId>com.google.inject.extensions</groupId>
             <artifactId>guice-persist</artifactId>
             <version>3.0-rc2</version>
         </dependency>
         <dependency>
             <groupId>com.google.inject.extensions</groupId>
             <artifactId>guice-servlet</artifactId>
             <version>3.0-rc2</version>
         </dependency>
         <!-- JPA -->
         <!-- Hibernate impl -->
         <dependency>
             <groupId>org.hibernate</groupId>
             <artifactId>hibernate-entitymanager</artifactId>
             <version>3.6.1.Final</version>
         </dependency>
         <dependency>
             <groupId>org.eclipse.jetty.aggregate</groupId>
             <artifactId>jetty-all-server</artifactId>
             <version>${jetty.version}</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.8.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <version>1.8.5</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
     <build>
         <resources>
             <resource>
                 <directory>src/main/resources</directory>
                 <includes>
                     <include>**</include>
                 </includes>
                 <excludes>
                     <exclude>**/*.java</exclude>
                 </excludes>
             </resource>
             <resource>
                 <directory>src/main/java</directory>
                 <includes>
                     <include>**</include>
                 </includes>
                 <excludes>
                     <exclude>**/*.java</exclude>
                 </excludes>
             </resource>
         </resources>
         <testResources>
             <testResource>
                 <directory>src/test/java</directory>
                 <includes>
                     <include>**</include>
                 </includes>
                 <excludes>
                     <exclude>**/*.java</exclude>
                 </excludes>
             </testResource>
             <testResource>
                 <directory>src/test/resources</directory>
                 <includes>
                     <include>**</include>
                 </includes>
                 <excludes>
                     <exclude>**/*.java</exclude>
                 </excludes>
             </testResource>
         </testResources>
         <plugins>
             <plugin>
                 <inherited>true</inherited>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <version>2.3.2</version>
                 <configuration>
                     <source>1.6</source>
                     <target>1.6</target>
                     <optimize>true</optimize>
                     <debug>true</debug>
                 </configuration>
             </plugin>
             <plugin>
                 <groupId>org.mortbay.jetty</groupId>
                 <artifactId>jetty-maven-plugin</artifactId>
                 <version>${jetty.version}</version>
             </plugin>
         </plugins>
     </build>
     <properties>
         <slf4j.version>1.6.1</slf4j.version>
         <wicket.version>1.4.15</wicket.version>
         <jetty.version>7.3.0.v20110203</jetty.version>
     </properties>
</project>

Persistence.xml - Die Standard Konfigurationsdatei für JPA

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence       http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
     <!-- A JPA Persistence Unit -->
     <persistence-unit name="jpaUnit" transaction-type="RESOURCE_LOCAL">
         <provider>org.hibernate.ejb.HibernatePersistence</provider>
         <properties>
             <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
             <property name="hibernate.hbm2ddl.auto" value="create"/>
             <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
             <property name="hibernate.connection.url" value="jdbc:hsqldb:mem:test"/>
             <property name="hibernate.show_sql" value="true"/>
             <property name="hibernate.current_session_context_class" value="thread"/>
         </properties>
     </persistence-unit>
</persistence>

Hier beschreiben wir u.a. dass wir Hibernate als Implementierung nutzen möchten und geben ihm einige Parameter mit, wie den Datenbanktreiber - in unserem Fall HSQLDB - und dass wir generierte SQL-Statements sehen möchten.

web.xml

    <?xml version="1.0" encoding="UTF-8"?>
 <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
     <display-name>Wicket Guice JPA</display-name>
     <filter>
         <filter-name>wicket</filter-name>
         <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
         <init-param>
             <param-name>applicationClassName</param-name>
             <param-value>de.witi.WicketApplication</param-value>
         </init-param>
     </filter>
     <filter-mapping>
         <filter-name>wicket</filter-name>
         <url-pattern>/*</url-pattern>
     </filter-mapping>
 </web-app>

Gemäß der Java Servlet Specifikation konfigurieren wir die web.xml. Alle Web- Request werden von WicketFilter behandelt. Als Parameter bekommt sie eine von WebApplication abgeleitet Applikationsklasse, zu der wir hinterher kommen.

Persistenzschicht

Fangen wir unten in der Persistenzschicht an. Wir möchten einen Vor- und Nachnamen einer Person bzw. Benutzers speichern, also definieren wir eine Entität mit entsprechenden Attributen:

 package de.witi.entitiy;
 import java.io.Serializable;
 import javax.persistence.Entity;
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
 @Entity(name = "users")
 public class User implements Serializable {
     private static final long serialVersionUID = 2959377496669050427L;
     @Id
     @GeneratedValue
     private Short id;
     private String name;
     private String surname;
     public Short getId() {
         return id;
     }
     public void setId(Short id) {
         this.id = id;
     }
     public String getName() {
         return name;
     }
     public void setName(String name) {
         this.name = name;
     }
     public String getSurname() {
         return surname;
     }
     public void setSurname(String surname) {
         this.surname = surname;
     }
     @Override
     public boolean equals(Object obj) {
         if (obj == null) {
             return false;
         }
         if (!(obj instanceof User)) {
             return false;
         }
         final User other = (User) obj;
         if (this.getId() != other.getId() &amp;&amp; (this.getId() == null
                 || !this.getId().equals(other.getId()))) {
             return false;
         }
         if ((this.getName() == null) ? (other.getName() != null) : !this.getName().equals(other.getName())) {
             return false;
         }
         if ((this.getSurname() == null) ? (other.getSurname() != null)
                 : !this.getSurname().equals(other.getSurname())) {
             return false;
         }
         return true;
     }
     @Override
     public int hashCode() {
         int hash = 3;
            hash = 11 * hash + (this.getId() != null ? this.getId().hashCode() : 0);
            hash = 11 * hash + (this.getName() != null ? this.getName().hashCode() : 0);
            hash = 11 * hash + (this.getSurname() != null ? this.getSurname().hashCode() : 0);
            return hash;
     }
}

Um die Person direkt referenzieren zu können habe ich der Klasse User eine ID hinzugefügt. Das Attribut name der Annotation Entity besagt, dass wir unsere Benutzer in der Tabelle users speichern möchten.

Manche, vor allem PHP-Entwickler (hust), werden sich jetzt fragen wo die Beschreibung, also die DDL SQL-Statements, der Tabelle bleibt. Benötigen wir nicht. Aus diesen Informationen (und wegen <property name="hibernate.hbm2ddl.auto" value="create"/>) erzeugt Hibernate automatisch die entsprechende Tabelle in unserer Datenbank! Equals- und hashCode-Methode habe ich von der IDE - in meinem Fall Netbeans - generieren lassen. Aber achtet auf getClass in equals!

Als nächstes folgt mittels des DAO-Entwurfsmusters (Data Access Object) ein generisches Interface und eine konkrete JPA-Implementierung, das uns einfache CRUD (Create, Read, Update, Delete)-Methoden zur Verfügung stellt.

 package de.witi.data.dao;
 import java.io.Serializable;
 import java.util.List;
 public interface Dao {
     public <E extends Serializable> void save(final E o);
     public <E extends Serializable> void update(final E o);
     public <E extends Serializable> E load(final Class<E> clazz, Serializable id);
     public <E extends Serializable> void delete(final E o);
     public <E extends Serializable> List<E> findAll(final Class<E> clazz);
     public <E extends Serializable> int countAll(final Class<E> clazz);
 }
 package de.witi.data.dao.jpa;
 import com.google.inject.persist.Transactional;
 import java.io.Serializable;
 import java.util.List;
 import javax.inject.Inject;
 import javax.persistence.Entity;
 import javax.persistence.EntityManager;
 import de.witi.data.dao.Dao;
 public class JPADao implements Dao {
     @Inject
     private EntityManager em;
     @Override
     @Transactional
     public <E extends Serializable> void delete(final E o) {
         em.remove(o);
     }
     @Override
     @Transactional
     public <E extends Serializable> E load(final Class<E> clazz, final Serializable id) {
         return em.find(clazz, id);
     }
     @Override
     @Transactional
     public <E extends Serializable> void save(final E o) {
         em.persist(o);
     }
     @Override
     @Transactional
     public <E extends Serializable> void update(final E o) {
         em.merge(o);
     }
     @Override
     @Transactional
     public <E extends Serializable> List<E> findAll(final Class<E> clazz) {
         return em.createQuery("from " + clazz.getAnnotation(Entity.class).name(), clazz).getResultList();
     }
     @Override
     @Transactional
     public <E extends Serializable> int countAll(final Class<E> clazz) {
         final String query = "count (e) from " + clazz.getSimpleName() + " e";
         return em.createQuery(query, Long.class).getSingleResult().intValue();
     }
 }

In der Klasse JPADao sehen wir neben JPA zum ersten Mal Guice Persist in Aktion. Alle Methoden, die mit der Annotation Transactional ausgestattet sind, werden von Guice Persist automatisch in einer Transaktion ausgeführt. Der EntityManager von JPA wandelt die übergebenen Operationen in SQL um und übergibt sie an die darunterliegende Datenbank.

Google Guice

JPA muss noch an irgendeiner Stelle gestartet bzw. initialisiert werden. Hier kommt Google Guice ins Spiel. Üblicherweise nimmt man dafür eine simple Klasse, die in einem Google Guice Modul konfiguriert (bind) wird. Das sieht im Quellcode wie folgt aus:

 package de.witi;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.persist.PersistService;
 @Singleton
 public class JPAInitializer {
     @Inject
     public JPAInitializer(final PersistService service) {
         service.start();
     }
 }
 package de.witi.config.modules;
 import com.google.inject.persist.PersistFilter;
 import com.google.inject.persist.jpa.JpaPersistModule;
 import com.google.inject.servlet.ServletModule;
 import de.witi.JPAInitializer;
 import de.witi.data.dao.Dao;
 import de.witi.data.dao.jpa.JPADao;
 public class JPAModule extends ServletModule {
     @Override
     protected void configureServlets() {
         install(new JpaPersistModule("jpaUnit"));
         filter("/*").through(PersistFilter.class);
         bind(JPAInitializer.class).asEagerSingleton();
         //dao stuff
         bind(Dao.class).to(JPADao.class);
     }
 }

Unser Initializer ist ganz trivial aufgebaut und ruft lediglich die Methode start der Klasse PersistService auf. Mittels des vorherigen Aufrufes install(new JpaPersistModule("jpaUnit")) weiß Guice Persist, dass es dabei die in unserer persistence.xml definierte "Persistence Unit" starten soll. Schließlich teilen wir Google Guice noch unsere Initializer-Klasse mit, die selbstständig aufgerufen wird.

Der Aufruf filter("/*").through(PersistFilter.class) ist für die Konfiguration des Entwurfsmusters Open-Session-View zuständig. Hierbei wird der EntityManager nicht bei jeder Datenbanktransaktion erstellt und zerstört, sondern während eines HTTP-Requests.

Der letzte Aufruf bind(Dao.class).to(JPADao.class) ermöglicht das sogenannte Dependecy Injection. Jedes Mal wenn wir auf das Interface Dao zugreifen, soll in Wahrheit die Klasse JPADao aufgerufen werden.

Testdaten

Der Vollständigkeit halber noch die Konfiguration einiger Testdaten:

 package de.witi;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import de.witi.data.dao.Dao;
 import de.witi.entity.User;
 @Singleton
 public class DataInitializer {
     private static final String[] NAMES = {"Hans", "Willy", "Manfred", "Peter"};
     private static final String[] SURNAMES = {"Schuhmacher", "Müller", "Schneider", "Zimmer"};
     @Inject
     public DataInitializer(final Dao dao) {
         for (int i = 0; i < 10; ++i) {
             final User user = new User();
                user.setName(NAMES[(int) (Math.random() * NAMES.length)]);
                user.setSurname(SURNAMES[(int) (Math.random() * SURNAMES.length)]);
                dao.save(user);
         }
     }
 }
 package de.witi.config.modules;
 import com.google.inject.AbstractModule;
 import de.witi.DataInitializer;
 public class DataModule extends AbstractModule {
     @Override
     protected void configure() {
         if (initData()) {
             bind(DataInitializer.class).asEagerSingleton();
         }
     }
     protected boolean initData() {
         return true;
     }
 }

Frontend

Manch anderer würde dazu auch Präsentationsschicht sagen. Wie dem auch sei kommt hier endlich Wicket ins Spiel.

Konfiguration

Wir beginnen mit der bereits in der web.xml erwähnten WebApplication-Klasse, die für die Konfiguration unser Web-Anwendung zuständig ist:

 package de.witi;
 import org.apache.wicket.guice.GuiceComponentInjector;
 import org.apache.wicket.protocol.http.WebApplication;
 import com.google.inject.Guice;
 import de.witi.pages.HomePage;
 import de.witi.config.modules.DataModule;
 import de.witi.config.modules.JPAModule;
 import de.witi.pages.UserPage;
 public class WicketApplication extends WebApplication {
     public WicketApplication() {
     }
     protected GuiceComponentInjector getGuiceInjector() {
         return new GuiceComponentInjector(this, Guice.createInjector(new JPAModule(), new DataModule()));
     }
     @Override
     protected void init() {
         super.init();
         mountBookmarkablePage("user", UserPage.class);
         addComponentInstantiationListener(getGuiceInjector());
     }
     @Override
     public Class<HomePage> getHomePage() {
         return HomePage.class;
     }
 }

In der Methode init wird unsere Web-Applikation konfiguriert. Unsere Seite UserPage, die noch folgt, soll unter /user erreichbar sein. Der Aufruf addComponentInstantiationListener konfiguriert - einfach erklärt - Google Guice.

Wie man erkennt bietet Wicket mittels der Klasse GuiceComponentInjector direkt eine Schnittstelle zu Guice. In unserer Methode getGuiceInjector teilen wir Guice unsere Module mit, die wir dann an die Wicket-Schnittstelle übergeben.

Erwähnenswert ist noch die Methode getHomePage, in der wir unsere Startseite mitteilen.

Webseiten

Ja, das wichtigste fehlt ja noch! Ohne Webseite, keine Website! ;)

 package de.witi.pages;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 public class HomePage extends WebPage {
     private static final long serialVersionUID = 1L;
     public HomePage() {
         // Add link
         add(new BookmarkablePageLink<Void>("user", UserPage.class));
     }
 }
 <!-- HomePage.html -->
 <!doctype html>
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
     <head>
         <title>50226.de - Wicket Guice JPA 2.0</title>
         <meta charset="utf-8">
     </head>
     <body>
         <p><strong><a href="/">50226.de - Witi's Blog</a>: <a href="http://wicket.apache.org/">Wicket</a> <a href="http://code.google.com/p/google-guice/">Guice</a> <a href="http://de.wikipedia.org/wiki/Java_Persistence_API">JPA 2.0</a></strong></p>
         <p><a wicket:id="user">User Page</a></p>
     </body>
 </html>

Die HomePage dürfte sich von selbst erklären, sie enthält lediglich einen Link auf die UserPage.

 package de.witi.pages;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.model.CompoundPropertyModel;
 import com.google.inject.Inject;
 import de.witi.data.dao.Dao;
 import de.witi.entity.User;
 import java.util.List;
 import org.apache.wicket.model.LoadableDetachableModel;
 public class UserPage extends WebPage {
     private static final long serialVersionUID = 1L;
     @Inject
     private Dao dao;
     public UserPage(final PageParameters pp) {
         final Form<User> form = new Form<User>("form", new CompoundPropertyModel<User>(new User()));
         form.add(new TextField<String>("name").setRequired(true));
         form.add(new TextField<String>("surname").setRequired(true));
         final WebMarkupContainer wmc = new WebMarkupContainer("list");
         wmc.add(new ListView<User>("list", new LoadableDetachableModel<List<User>>() {
             private static final long serialVersionUID = 1L;
             @Override
             protected List<User> load() {
                 return dao.findAll(User.class);
             }
         }) {
             private static final long serialVersionUID = 1L;
             @Override
             protected void populateItem(final ListItem<User> item) {
                 final User user = item.getModelObject();
                 item.add(new Label("name", user.getName()));
                 item.add(new Label("surname", user.getSurname()));
             }
         });
         wmc.setOutputMarkupId(true);
         add(wmc);
         form.add(new AjaxSubmitLink("submit") {
             private static final long serialVersionUID = 1L;
             @Override
             protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
                 final User u = (User) form.getModelObject();
                 final User user = new User();
                 user.setName(u.getName());
                 user.setSurname(u.getName());
                 dao.save(user);
                 target.addComponent(wmc);
             }
         });
         add(form);
     }
 }
 <!-- UserPage.html -->
 <!doctype html>
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd">
     <head>
         <title>50226.de - Wicket Guice JPA 2.0</title>
         <meta charset="utf-8">
     </head>
     <body>
         <p><strong><a href="/">50226.de - Witi's Blog</a>: <a href="http://wicket.apache.org/">Wicket</a> <a href="http://code.google.com/p/google-guice/">Guice</a> <a href="http://de.wikipedia.org/wiki/Java_Persistence_API">JPA 2.0</a></strong></p>
         <h1>User Page</h1>
         <form wicket:id="form">
             <fieldset>
                 <label>Name</label><input wicket:id="name" /><br>
                 <label>Nachname</label><input wicket:id="surname" /><br>
                 <a wicket:id="submit">Speichern</a>
             </fieldset>
         </form>
         <ul wicket:id="list">
             <li wicket:id="list"><span wicket:id="name"></span> - <span wicket:id="surname"></span></li>
         </ul>
     </body>
 </html>

Die UserPage enthält im Prinzip in nur wenigen Zeilen unsere gesamte moderne und dynamische Web-Anwendung!

Zuerst fügen wir ein Formular mit zwei Textfeldern, die wir beide als Pflichtfelder definieren und einen Link ein. Darunter soll eine Liste mit allen in der Datenbank vorhandenen Einträgen erscheinen. Anschließend folgt ein Link, bei dem beim Klick die Liste automatisch mittels AJAX aktualisiert werden soll.

Beinahe schon magisch kümmert sich Guice komplett um die Konfiguration und Initialisierung unseres Backends. Die beiden Initializer-Klassen werden selbstständig aufgerufen und ausgeführt.

In unseren Webseiten müssen wir lediglich @Inject private Dao dao definieren um einen direkten Zugriff zur Datenbank zu erhalten. Durch Dependency Injection ist das Objekt auch direkt verfügbar. Wir erinnern uns, das Attribut dao ist eine Instanz der Klasse JPADao. So haben wir es Guice mitgeteilt.

Wir nutzen lediglich zwei Aufrufe, um mit der Datenbank zu kommunizieren: Einmal dao.findAll(User.class), um alle Benutzer aus der Datenbank zu laden und dao.save(user), um einen neuen Benutzer zu speichern.

Eine dynamische AJAX-Seite ist in Wicket ziemlich trivial zu realisieren. Unser Link der Klasse AjaxSubmitLink implementiert die Methode onSubmit, mit der wir Zugriff auf eine Instanz der Klasse AjaxRequestTarget erhalten. Im oberen Beispiel nutzen wir den Aufruf target.addComponent(wmc), um die Liste nach dem Speichern eines Benutzers bzw. nach einem Klick auf den Speichern- Link zu aktualisieren.

Und das war es bereits. Die kleine Anwendung sieht dann wie folgt aus: Userpage

Den Quellcode (Maven-Projekt in Netbeans) könnt ihr hier (wicket-guice-jpa.7z) downloaden.

Fazit

War es jemals schöner und einfacher eine dynamische und datenbankgestützte (Transaktionssicher!) Web-Anwendung zu entwickeln? Ich denke nicht. Apache Wicket und Google Guice sind zwei erstaunliche Frameworks, die einem eine Menge Schreibaufwand abnehmen können und vor allem wirklichen Spaß beim Entwickeln bereiten!

blogroll
tags