Maven, Hibernate, PrimeFaces and Jetty


The final project structure

In this post, we will make a (very) simple CRUD application using Maven, Hibernate, PrimeFaces and Jetty. It won’t be the best application out there, but enough to get started. Run ‘mvn jetty:run’ to start the application.

Click here to download the actual project.

In this case, we’ll be making a simple addressbook, with each contact having a first and last name, and an address. The address will have a city, street, housenumber and postalcode. They will each have an id, because they need to be stored somewhere. This is what they look like:

package nl.ghyze.contacts;

public class Contact {

    private Long id;
    private String firstname;
    private String lastname;
    private Address address;

    public void setId(Long id){
        this.id = id;
    }

    public Long getId(){
        return id;
    }

    public void setFirstname(String firstname){
        this.firstname = firstname;
    }

    public void setLastname(String lastname){
        this.lastname = lastname;
    }

    public String getFirstname() {
        return firstname;
    }

    public String getLastname() {
        return lastname;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public Address getAddress() {
        return address;
    }
}
package nl.ghyze.contacts;

public class Address {

    private Long id;
    private String city;
    private String street;
    private String housenumber;
    private String postalcode;

    public void setId(Long id) {
        this.id = id;
    }

    public Long getId() {
        return id;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCity() {
        return city;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getStreet() {
        return street;
    }

    public void setHousenumber(String housenumber) {
        this.housenumber = housenumber;
    }

    public String getHousenumber() {
        return housenumber;
    }

    public void setPostalcode(String postalcode) {
        this.postalcode = postalcode;
    }

    public String getPostalcode() {
        return postalcode;
    }
}

Of course there needs to be a database schema to store the records in. We will use a MySQL database for this. For simplicity, all keys are bigints, and all other fields will be varchars.

create schema if not exists contacts;
use contacts;

create table if not exists address
(id bigint primary key,
city varchar(100),
postalcode varchar(10),
housenumber varchar(10),
street varchar(100)
);

create table if not exists contact
(id bigint primary key,
firstname varchar(255),
lastname varchar(255),
addressId bigint);

Now we will need to tell Hibernate how to map the database tables to objects. These days you’ll probably use annotations for that, but to keep database-related code out of our entities, we’re going for XML configuration.
The mapping for contact will look like this:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="nl.ghyze.contacts">
    <class name="Contact" table="contact">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="firstname" column="firstname" />
        <property name="lastname" column="lastname" />
        <one-to-one name="address" class="nl.ghyze.contacts.Address" cascade="save-update" />
    </class>
</hibernate-mapping>

And the mapping for Address:

<?xml version="1.0"?>

<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="nl.ghyze.contacts">
    <class name="Address" table="address">
        <id name="id" column="id">
            <generator class="increment" />
        </id>
        <property name="city" column="city" />
        <property name="postalcode" column="postalcode" />
        <property name="housenumber" column="housenumber" />
        <property name="street" column="street" />
    </class>
</hibernate-mapping>

Now we need to configure hibernate, telling it how to connect to our database and which mapping files to use.

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/contacts?useUnicode=true&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC</property>
        <property name="connection.username">root</property>
        <property name="connection.password">password</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Disable the second-level cache -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

        <!-- Do not show executed SQL -->
        <property name="show_sql">false</property>

        <!-- Validate the database schema on startup -->
        <property name="hbm2ddl.auto">validate</property>

        <!-- Use these mapping files -->
        <mapping resource="nl/ghyze/contacts/Contact.hbm.xml"/>
        <mapping resource="nl/ghyze/contacts/Address.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

Next step, setting up the connection from our application. Since we want only one place to manage the database connectivity in the application, this will be set up as a Singleton.

package nl.ghyze.contacts.repository;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;

public class DatabaseSessionProvider {

	private static final DatabaseSessionProvider instance = new DatabaseSessionProvider();

	private SessionFactory sessionFactory;
	
	private DatabaseSessionProvider(){
		setUp();
	}
	
	public static DatabaseSessionProvider getInstance(){
		return instance;
	}
	
	public Session getSession(){
		Session session = sessionFactory.openSession();
		session.beginTransaction();
		return session;
	}

	public void end(Session session){
		if (session.getTransaction().getRollbackOnly()){
			rollback(session);
		} else {
			commit(session);
		}
	}

	private void commit(Session session){
		session.getTransaction().commit();
		session.close();
	}

	private void rollback(Session session){
		session.getTransaction().rollback();
		session.close();
	}

	public void shutDown(){
		sessionFactory.close();
	}

	private void setUp() {
		final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
				.configure()
				.build();
		try {
			sessionFactory = new MetadataSources( registry ).buildMetadata().buildSessionFactory();
		}
		catch (Exception e) {
			e.printStackTrace();
			StandardServiceRegistryBuilder.destroy( registry );
		}
	}
}

At this point we’re ready to read, write and delete contacts from and to the database. We’re going to use HQL for this purpose.

package nl.ghyze.contacts.repository;

import nl.ghyze.contacts.Contact;
import org.hibernate.Session;
import org.hibernate.query.Query;

import java.util.List;

public class ContactRepository {

    private final DatabaseSessionProvider provider;
    private Session session;

    public ContactRepository(){
        this.provider = DatabaseSessionProvider.getInstance();
    }

    public Contact saveContact(Contact contact){
        session = provider.getSession();
        session.saveOrUpdate(contact);
        provider.end(session);
        return contact;
    }

    public List<Contact> getAllContacts(){
        String queryString = "from Contact";
        Query<Contact> query = createQuery(queryString);
        List<Contact> result = query.getResultList();
        provider.end(session);
        return result;
    }

    public void deleteContact(Contact contact){
        session = provider.getSession();
        session.delete(contact);
        provider.end(session);
    }

    public Contact getContactWithId(Long id){
        if (id == null){
            return null;
        }
        String queryString = "from Contact where id = :id";
        Query<Contact> query = createQuery(queryString);
        query.setParameter("id", id);
        Contact contact = query.getSingleResult();
        provider.end(session);
        return contact;
    }

    private Query<Contact> createQuery(String queryString) {
        session = provider.getSession();
        return session.createQuery(queryString, Contact.class);
    }
}

With the database stuff out of the way, let’s create a simple service for communicating with the repository. At this point we’re getting into PrimeFaces. We’re making this a ManagedBean, so it can be injected into the Action classes that we’ll discuss next.

package nl.ghyze.contacts.service;

import nl.ghyze.contacts.Address;
import nl.ghyze.contacts.Contact;
import nl.ghyze.contacts.repository.ContactRepository;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import java.util.List;

@ManagedBean(name="contactService")
@SessionScoped
public class ContactService {

    private ContactRepository contactRepository = new ContactRepository();

    public List<Contact> getContactList(){
        return contactRepository.getAllContacts();
    }

    public Contact saveContact(Contact contact){
        return contactRepository.saveContact(contact);
    }

    public Contact getOrCreateContact(Long id){
        Contact contact =  contactRepository.getContactWithId(id);
        if (contact == null){
            contact = new Contact();
        }
        if (contact.getAddress() == null){
            contact.setAddress(new Address());
        }
        return contact;
    }

    public void deleteContact(Contact contact) {
        contactRepository.deleteContact(contact);
    }
}

Before we continue, let’s take a moment to think about the front-end of the application. We’re going to make two pages: the first one listing all the contacts that we have, and the second one listing the details of one single contact. Each page will have its own backing class, handling the server side actions of the requests.
The list action will be simple: it only needs to provide a list of Contacts that is currently in our database.

package nl.ghyze.contacts.web;

import nl.ghyze.contacts.Contact;
import nl.ghyze.contacts.service.ContactService;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import java.util.List;

@ManagedBean(name="contactListAction")
@SessionScoped
public class ContactListAction {

    @ManagedProperty(value="#{contactService}")
    private ContactService contactService;

    public List<Contact> getContactList(){
        return contactService.getContactList();
    }

    public void setContactService(ContactService contactService){
        this.contactService = contactService;
    }

    public ContactService getContactService(){
        return contactService;
    }
}

The details action will be a bit more complicated. It will need to be able to:
– get the correct contact
– save the edited contact
– delete a contact
– Since everything is SessionScoped, when the user wants to go back to the list, it needs to clear its values
The methods that receive an ActionEvent as parameter are called from PrimeFaces. Pay attention to the method signature, as this is something that lots of developers struggle with.

package nl.ghyze.contacts.web;

import nl.ghyze.contacts.Contact;
import nl.ghyze.contacts.service.ContactService;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.SessionScoped;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import java.io.IOException;

@ManagedBean
@SessionScoped
public class ContactDetailAction {

    @ManagedProperty(value="#{contactService}")
    private ContactService contactService;

    private Contact contact;

    private Long contactId;

    public Contact getContact(){
        if (contact == null){
            contact = contactService.getOrCreateContact(contactId);
        }
        return contact;
    }

    public void save(ActionEvent actionEvent){
        contact = contactService.saveContact(contact);
        redirectToList();
    }

    public void delete(ActionEvent actionEvent){
        contactService.deleteContact(contact);
        redirectToList();
    }

    public void back(ActionEvent actionEvent){
        redirectToList();
    }

    private void redirectToList() {
        reset();
        ExternalContext context = FacesContext.getCurrentInstance().getExternalContext();
        try {
            context.redirect(context.getRequestContextPath() + "/contactList.xhtml");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void reset() {
        contact = null;
        contactId = null;
    }

    public void setContactService(ContactService contactService){
        this.contactService = contactService;
    }

    public ContactService getContactService(){
        return contactService;
    }

    public void setContactId(Long id){
        this.contactId = id;
    }

    public Long getContactId(){
        return contactId;
    }
}

Now we’re ready to create the .xhtml pages. For the list, we’re going to create a table for listing all contacts. The last column will have buttons, so we can edit the contact in that row. Clicking this button will navigate to the details page, supplying the ID of the contact. Below the table there will be a button for adding new contacts.

<?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://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:p="http://primefaces.org/ui">

    <h:head>
        <title>Addressbook</title>
    </h:head>
    <h:body>
        <h:form>
            <p:panel header="Contact list">
               <p:dataTable var="contact" value="#{contactListAction.contactList}">
                   <p:column headerText="First name">
                       <h:outputText value="#{contact.firstname}"/>
                   </p:column>
                   <p:column headerText="Last name">
                       <h:outputText value="#{contact.lastname}"/>
                   </p:column>
                   <p:column headerText="Street">
                       <h:outputText value="#{contact.address.street}"/>
                   </p:column>
                   <p:column headerText="Housenumber">
                       <h:outputText value="#{contact.address.housenumber}"/>
                   </p:column>
                   <p:column headerText="Postal code">
                       <h:outputText value="#{contact.address.postalcode}"/>
                   </p:column>
                   <p:column headerText="City">
                       <h:outputText value="#{contact.address.city}"/>
                   </p:column>
                   <p:column style="width:32px;text-align: center">
                       <p:button icon="ui-icon-edit" title="Edit" outcome="contactDetail">
                            <f:param name="contactId" value="#{contact.id}" />
                       </p:button>
                   </p:column>
               </p:dataTable>
                <p:button outcome="contactDetail" value="new Contact"/>
            </p:panel>
        </h:form>
    </h:body>
</html>

The details page is, again, a bit more complicated. This page receives the contactId, and sets it in the action. This part is a bit confusing, and it takes a while to understand. The f:metadata tag is responsible for this.
The p:commandButton tags are used to call methods in ContactDetailsAction. This is how those methods that take the ActionEvent parameters are called.

<?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://java.sun.com/jsf/html" xmlns:p="http://primefaces.org/ui" xmlns:c="http://java.sun.com/jstl/core" xmlns:f="http://java.sun.com/jsf/core">

    <h:head>
        <title>Edit Contact</title>
    </h:head>

    <h:body>
        <f:metadata>
            <f:viewParam name="contactId" value="#{contactDetailAction.contactId}" />
        </f:metadata>
        <h:form>
            <c:set var="contact" value="#{contactDetailAction.contact}"/>
            <p:panel header="Edit Contact">
                <h:panelGrid columns="2" cellpadding="4">
                    <h:outputText value="First name"/>
                    <h:inputText value="#{contact.firstname}"/>

                    <h:outputText value="Last name"/>
                    <h:inputText value="#{contact.lastname}"/>

                    <h:outputText value="Street" />
                    <h:inputText value="#{contact.address.street}"/>

                    <h:outputText value="Housenumber" />
                    <h:inputText value="#{contact.address.housenumber}"/>

                    <h:outputText value="Postal code" />
                    <h:inputText value="#{contact.address.postalcode}"/>

                    <h:outputText value="City" />
                    <h:inputText value="#{contact.address.city}"/>
                </h:panelGrid>
                <p:commandButton value="Save" actionListener="#{contactDetailAction.save}" ajax="false" />
                <p:commandButton value="Delete" actionListener="#{contactDetailAction.delete}" ajax="false" />
                <p:commandButton value="Back to list" actionListener="#{contactDetailAction.back}" ajax="false" />
            </p:panel>
        </h:form>
    </h:body>
</html>

The Deployment Descriptor (web.xml) tells the application server how to handle incomming requests.

<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_3_1.xsd" version="3.1">

    <!-- File(s) appended to a request for a URL that is not mapped to a web component -->
    <welcome-file-list>
        <welcome-file>contactList.xhtml</welcome-file>
    </welcome-file-list>

    <!-- Define the JSF servlet (manages the request processing life cycle for JavaServer Faces) -->
    <servlet>
        <servlet-name>faces-servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Map following files to the JSF servlet -->
    <servlet-mapping>
        <servlet-name>faces-servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
</web-app>

The maven project configuration (pom.xml) tells maven how to build this project. It contains all the dependencies. Pay attention to the last part of the configuration, the maven-jetty-plugin. This is how we start, and test, the application.

<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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>nl.ghyze</groupId>
  <artifactId>addressbook</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>addressbook</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>1.8</java.version>

    <servlet.version>3.1.0</servlet.version>
    <jsf.version>2.2.13</jsf.version>
    <primefaces.version>6.0</primefaces.version>

    <maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version>
    <jetty-maven-plugin.version>9.4.0.M0</jetty-maven-plugin.version>

    <hibernate.version>5.2.1.Final</hibernate.version>
    <mysql.version>6.0.3</mysql.version>
  </properties>

  <dependencies>
    <!-- PrimeFaces -->
    <dependency>
      <groupId>org.primefaces</groupId>
      <artifactId>primefaces</artifactId>
      <version>${primefaces.version}</version>
    </dependency>
    <!-- Servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>${servlet.version}</version>
      <scope>provided</scope>
    </dependency>
    <!-- JSF -->
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-api</artifactId>
      <version>${jsf.version}</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>com.sun.faces</groupId>
      <artifactId>jsf-impl</artifactId>
      <version>${jsf.version}</version>
      <scope>compile</scope>
    </dependency>

    <!-- Database-->
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>${hibernate.version}</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.version}</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.5.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>${jetty-maven-plugin.version}</version>
        <configuration>
          <httpConnector>
            <port>9090</port>
          </httpConnector>
          <webApp>
            <contextPath>/address</contextPath>
          </webApp>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *