Here we go again with an addition to my previous post. I really ended up behind the power curve and it has truly been a busy few weeks with getting adjusted to the changing circumstances. I started off with using Alpakka-Slick for Java, but soon became uncomfortable with the idea that it would be extremely hard to prevent SQL Injection. Due to this, I swapped back to my trusted ORM, Hibernate. As an added bonus, we will also go through how to use h2 as an in-memory database to test our Data Access Objects without messing up our database. So let's get started down our yellow brick road to the Emerald City!

Some basic setup is required for this. It should be covered in the README.md file located in the repository. As an added plus the code is available in it's entirety there as well. You will need to add PostgreSQL, create a database, create a user, assign them a password, and make sure they have access to sign in to that database.

Before we get too far, let's discuss some of the added dependencies. Just adding those in from the beginning makes everything work so much easier in your preferred IDE. I also think you will find that our dependencies have started to grow, which is normal.

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.11</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.200</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>5.4.14.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-c3p0</artifactId>
    <version>5.4.14.Final</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-testkit_2.13</artifactId>
    <version>2.5.29</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-stream-testkit_2.13</artifactId>
    <version>2.5.29</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.6.0</version>
    <scope>test</scope>
</dependency>

I will try to quickly go through the different dependencies we have added and why. We added the JDBC Drivers for both PostgreSQL and h2. We will use PostgresSQL for our production database and h2 for our Unit Tests. Hibernate is used to create our tables if they don't exist and also access the database. We are using the C3P0 connection pool due to it being well established and the fact it is battle tested in the field. Logback is used as our logger. It just gives us a bit more information. The Akka Testkits will be used in order to test our actors and streams once we get there. Finally, we added JUnit to perform unit tests.

Before we go too far we need to create our configuration for Hibernate. We create an XML file name hibernate.cfg.xml in our resources folder.

<?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 -->
        <!-- Update databaseName, user, and password -->
        <property name="connection.driver_class">org.postgresql.Driver</property>
        <property name="connection.url">jdbc:postgresql://localhost/databaseName</property>
        <property name="connection.username">user</property>
        <property name="connection.password">password</property>
        <property name="show_sql">false</property>

        <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.timeout">120</property>

        <property name="hibernate.hbm2ddl.auto">update</property>

        <!-- If you change the package structure, make sure to update this so it matches your program. -->
        <mapping class="com.pelaghisoftware.data.entity.User" />

    </session-factory>
</hibernate-configuration>

You will need to update the databaseName, user, and password to fit the database you created. We set up the connection pool using C3P0. More information can be found here on that. We also set hibernate.hbmddl.auto to update. This will update the table in the event you make changes to the entity without losing the data that is already in the table. Lastly, you will notice we add a mapping to our User Entity. Hibernate needs this so it can map it correctly to the database.

So let us start with our User Entity. If you remember it was just a simple class with a username and password. We actually do not change the class too much. We added some Hibernate annotations and setters for our variables.

package com.pelaghisoftware.data.entity;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.pelaghisoftware.data.TableInitConstants;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * Class to store a User's information. Set up to use Jackson to
 * marshall/unmarshall data to/from JSON.
 */
@Entity
@Table(name = TableInitConstants.SITE_USERS)
public class User
{
    private String userName;
    private String password;

    /**
     * Create an empty User
     */
    public User(){};

    /**
     * Create a User with the specified information
     * @param userName The user's username
     * @param password The user's password
     */
    @JsonCreator
    public User(
            @JsonProperty("userName") String userName,
            @JsonProperty("password") String password)
    {
        this.userName = userName;
        this.password = password;
    }

    /**
     * Gets the User's username
     * @return String. The user's username
     */
    @Id
    @Column(name = "userName")
    @JsonGetter("userName")
    public String getUserName()
    {
        return userName;
    }

    /**
     * Sets the User's username
     * @param userName The user's username
     */
    public void setUserName(String userName)
    {
        this.userName = userName;
    }


    /**
     * Gets the User's password
     * @return String. The user's password
     */
    @JsonGetter("password")
    public String getPassword()
    {
        return password;
    }

    /**
     * Sets the User's password
     * @param password The user's password
     */
    public void setPassword(String password)
    {
        this.password = password;
    }
}

The @Entity annotation tells Hibernate that this is an Entity to be used for data transfer operations. The @Table annotation tells Hibernate which table this should be mapped to and also serves to build/update the table in the database as we make changes. Hibernate magically maps the entire class to a database table. Since I want to use the username as the ID field, I give it the @Id annotation and to specifically name the column we use that @Column annotation.  That is it though for the annotations for the time being. Hibernate also plays extremely well with our Jackson JSON annotations. Pretty easy right? There is no reason to make things complicated if we do not need to.

So now that we have an entity, how do we access the database? The process generally follows the same pattern. Create a SessionFactory, create a Session, open a transaction, perform an operation, commit the transaction, and then close the session. I went a little above this by creating a couple classes that will serve as helpers. First, is the TableInitConstants. It is just a class with public static strings to put all of the table names in one spot.

package com.pelaghisoftware.data;

/**
 * This class contains all of the constants necessary to initialize the tables in
 * the database
 */
public class TableInitConstants
{
    //Section to specify the table name for the DAO classes

    public final static String SITE_USERS= "SITE_USERS";
}

The second one is a class that contains some common operations that will be used across the data access classes and I found that it was just easier to not duplicate all of the code.

package com.pelaghisoftware.data;

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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import java.util.List;
import java.util.Optional;

/**
 * This class contains the static methods for common database 
 * operations. 
 *
 * Note: Class is not meant to be instantiated. 
 */
public class DatabaseCommonOps
{
    private static final Logger logger = 
            LoggerFactory.getLogger(DatabaseCommonOps.class);

    /**
     * Creates a Session Factory for the database.
     * @return Optional of Session Factory
     */
    public static Optional<SessionFactory> createSessionFactory()
    {
        SessionFactory sessionFactory;

        final StandardServiceRegistry registry = 
                new StandardServiceRegistryBuilder()
                .configure()
                .build();
        try
        {
            sessionFactory = new MetadataSources(registry)
                    .buildMetadata()
                    .buildSessionFactory();

            return Optional.of(sessionFactory);
        }
        catch (Exception e)
        {
            logger.error(e.getMessage());
            StandardServiceRegistryBuilder.destroy(registry);
            return Optional.empty();
        }
    }

    /**
     * Returns a list of all of the specified entities in the 
     * database
     * @param type Entity Class to search the db for.
     * @param session A current session
     * @param <T> The type of Entity
     * @return List of the entities specified by the type param
     */
    public static <T> List<T> loadAllData(Class<T> type, 
                                          Session session)
    {
        CriteriaBuilder builder = session.getCriteriaBuilder();
        CriteriaQuery<T> criteria = builder.createQuery(type);
        criteria.from(type);

        List<T> data = session
                .createQuery(criteria)
                .getResultList();
        return data;
    }
}

So far we can create a new SessionFactory and then I got a little creative with using generics to be able to get a list of all items of a specific entity from the database. This is simply a step so we do not repeat code. In future developments, I may add other operations in here, but I am not quite sure that is the route I wanted to go yet.

To standardize our Data Access Objects, we created an interface that would more or less force all Data Access Objects to have the same methods to perform CRUD operations. This way even as we create new entities, we should be able to run all of our Data Access Object using the same familiar methods making it easier for developers.

package com.pelaghisoftware.data.dao;

import org.hibernate.Session;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import java.util.List;
import java.util.Optional;

/**
 * Interface for all DAO to implement. Creates a specified API for
 * data access
 * @param <T> The type of Object to be used in the implementation
 */
public interface Dao<T>
{
    Optional<T> get(String id);
    List<T> getAll();
    boolean insert(T t);
    boolean update(T t);
    boolean delete(T t);
}

We now create our first Data Access Object. This is the bread and butter that reads/writes the information to/from the database.

package com.pelaghisoftware.data.dao.impl;
import com.pelaghisoftware.data.dao.Dao;
import com.pelaghisoftware.data.DatabaseCommonOps;
import com.pelaghisoftware.data.entity.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * DAO to perform crud operations for Users.
 */
public class UserDao implements Dao<User>
{
    private final static Logger logger =
            LoggerFactory.getLogger(UserDao.class);

    protected SessionFactory sessionFactory;

    /**
     * Creates an UserDao object that will query the
     * SITE_USERS table
     */
    public UserDao(SessionFactory sessionFactory)
    {
        this.sessionFactory = sessionFactory;
    }

    /**
     * Gets the specified user from the Database
     * @param id The userName for the specified user
     * @return Optional. Will return a blank User if no user was
     *         found.
     */
    @Override
    public Optional<User> get(String id)
    {
        Session session = sessionFactory.openSession();

        User user = session.get(User.class, id);

        session.close();

        if(user == null)
        {
            return Optional.empty();
        }

        return Optional.of(user);
    }

    /**
     * Gets all users from the Database
     * @return List with all Users in the Database
     */
    @Override
    public List<User> getAll()
    {
        Session session = sessionFactory.openSession();

        List<User> users =
                DatabaseCommonOps.loadAllData(User.class, session);

        session.close();

        return users;
    }

    /**
     * Inserts a new user into the database
     * @param user The user to insert into the database.
     * @return True if the insertion was successful.
     *         False otherwise
     */
    @Override
    public boolean insert(User user)
    {
        Session session = sessionFactory.openSession();

        Transaction tx = null;
        try
        {
            tx = session.beginTransaction();

            session.save(user);

            tx.commit();

            session.close();
        }
        catch (Exception e)
        {
            if (tx != null)
            {
                tx.rollback();
            }
            session.close();
            logger.error(e.getMessage());
            return false;
        }

        return true;
    }

    /**
     * Updates the specified user's information.
     * @param user The user to update
     * @return True if the update was successful.
     *         False otherwise
     */
    @Override
    public boolean update(User user)
    {
        Session session = sessionFactory.openSession();

        Transaction tx = null;
        try
        {
            tx = session.beginTransaction();

            session.update(user);

            tx.commit();

            session.close();
        }
        catch (Exception e)
        {
            if (tx != null)
            {
                tx.rollback();
            }
            session.close();
            logger.error(e.getMessage());
            return false;
        }

        return true;
    }

    /**
     * Delete's the specified user's information
     * @param user The user to delete
     * @return True if the deletion was successful.
     *         False otherwise
     */
    @Override
    public boolean delete(User user)
    {
        Session session = sessionFactory.openSession();

        Transaction tx = null;
        try
        {
            tx = session.beginTransaction();

            session.delete(user);

            tx.commit();

            session.close();
        }
        catch (Exception e)
        {
            if (tx != null)
            {
                tx.rollback();
            }
            session.close();
            logger.error(e.getMessage());
            return false;
        }

        return true;
    }
}

So lets break it down here a little. We place the management of the SessionFactory to the class needing the information. I like this especially with Akka because it allows system to be able to auto close the SessionFactory when it terminates. Within each method we open a session. With the get methods we don't explicitly open a transaction because we are only getting information from the database. For inserting, updating, or deleting an entity, we begin a transaction, perform, our operation, commit the transaction, and then close the session. It is extremely important that every time you open a Session, you also close it. Otherwise we will keep the Sessions open and run the risk of producing deadlock. It is also key here that if an exception is thrown in our operations, we catch it and rollback that transaction. We don't want garbled data or incorrect operations to accidentally get committed into our database. With that, we have set up our ability to read and write to our database. So far this going pretty smooth I would say.

As we go to implement this into our main class, it helps to have a helper to produce error messages for us. This also gives us the ability to add Jackson JSON support to the messages so they can easily be serialized into our responses. But, for real, who likes typing the same error messages over and over? Here I included a couple of messages that were used multiple times in the endpoint logic.

package com.pelaghisoftware.data.entity.response.message;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
 * Class to get error messages for HTTP Responses. Set up to use
 * Jackson to marshall/unmarshall data to/from JSON.
 */
public class ErrorMessage
{
    private String error;

    /**
     * Create an empty ErrorMessage
     */
    public ErrorMessage(){};

    /**
     * Create an ErrorMessage with the specified message
     * @param error Error Message
     */
    @JsonCreator
    public ErrorMessage(
            @JsonProperty("error") String error)
    {
        this.error = error;
    }

    /**
     * Gets the error message.
     * @return String. The error message.
     */
    @JsonGetter("error")
    public String getError()
    {
        return error;
    }

    /**
     * Get an ErrorMessage object for Resource Not Found
     * @return ErrorMessage
     */
    public static ErrorMessage resourceNotFoundMessage()
    {
        return new ErrorMessage("Resource not found");
    }

    /**
     * Get an ErrorMessage object for Bad Request
     * @return ErrorMessage
     */
    public static ErrorMessage badRequestMessage()
    {
        return new ErrorMessage("Bad Request made. " +
                "Double check your request");
    }

    /**
     * Get an ErrorMessage object for when the User Already Exists
     * @return ErrorMessage
     */
    public static ErrorMessage userAlreadyExistsMessage()
    {
        return new ErrorMessage("User Already Exists. " +
                "Double check your request");
    }

    /**
     * Get an ErrorMessage object for User Does Not Exist
     * @return ErrorMessage
     */
    public static ErrorMessage userDoesNotExistMessage()
    {
        return new ErrorMessage("User does not Exist. " +
                "Double check your request");
    }
}

Now for the last piece to get the system up and running so we can actually use it. We change this class up just a little. We create a SessionFactory and pass that to our createRoute method.  Keep in mind that the program is meant to fail if we fail to get a SessionFactory. We also changed up our createRoute method to perform our crud operations at different endpoints using multiple different HTTP Verbs.

package com.pelaghisoftware.server;

import akka.NotUsed;
import akka.actor.ActorSystem;
import akka.http.javadsl.ConnectHttp;
import akka.http.javadsl.Http;
import akka.http.javadsl.ServerBinding;
import akka.http.javadsl.marshallers.jackson.Jackson;
import akka.http.javadsl.model.*;
import akka.http.javadsl.server.AllDirectives;
import akka.http.javadsl.server.Route;
import akka.stream.ActorMaterializer;
import akka.stream.javadsl.Flow;
import com.pelaghisoftware.data.dao.Dao;
import com.pelaghisoftware.data.dao.impl.UserDao;
import com.pelaghisoftware.data.DatabaseCommonOps;
import com.pelaghisoftware.data.entity.response.message.ErrorMessage;
import com.pelaghisoftware.data.entity.User;

import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Optional;
import java.util.concurrent.CompletionStage;

import static akka.http.javadsl.server.PathMatchers.*;

public class Main extends AllDirectives
{
    final static Logger logger = 
            LoggerFactory.getLogger(Main.class);
    /**
     * Main Method
     * @param args Arguments passed in when starting the program
     * @throws Exception Shutsdown the system if an Exception is thrown in the
     *  main method.
     */
    public static void main(String[] args) throws Exception
    {
        //Create the parent actor system that will be used to process everything.
        ActorSystem system = ActorSystem.create("routes");

        //Server instance
        final Http http = Http.get(system);

        //Used to process the requests into responses
        final ActorMaterializer materializer = 
                ActorMaterializer.create(system);

        //Set up the database session factory
        SessionFactory sessionFactory =
                DatabaseCommonOps.createSessionFactory().get();

        //Ensures that the session factory is closed when the system terminates
        system.registerOnTermination(sessionFactory::close);

        //In order to access all directives we need an instance where the routes
        // are defined. I used Main as my class name.
        // Use whatever you name your class.
        Main app = new Main();

        //Maps the routes into the system allowing the HttpRequest to be used
        //and output a HttpRequest back to the User.
        final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow =
                app.createRoute(sessionFactory)
                .flow(system, materializer);

        //Binds the server to a port to start accepting requests
        final CompletionStage<ServerBinding> binding =
                http.bindAndHandle(routeFlow,
                                   ConnectHttp.toHost("localhost",
                                                 8099),
                                   materializer);

        logger.info("Server online at http://localhost:8099/");
        logger.info("Press Return to stop...");

        System.in.read(); //let it run until user presses return

        binding
                //trigger unbinding from the port
                .thenCompose(ServerBinding::unbind)
                // and shutdown when done
                .thenAccept(unbound -> system.terminate()); 
    }

    private Route createRoute(SessionFactory sessionFactory)
    {
        //Create the database access object
        Dao<User> userDao = new UserDao(sessionFactory);

        return concat(
            get(() ->
                path(segment("user"), () ->
                {
                    return complete(
                        StatusCodes.OK,
                        userDao.getAll(),
                        Jackson.marshaller()
                    );
                })
            ),
            get(() ->
                path(segment("user")
                        .slash()
                        .concat(segment()), (String userName) ->
                {
                    Optional<User> user = userDao.get(userName);

                    if(user.isEmpty())
                    {
                        return complete(
                            StatusCodes.NOT_FOUND,
                            ErrorMessage.resourceNotFoundMessage(),
                            Jackson.marshaller()
                        );
                    }
                    else
                    {
                        return complete(
                            StatusCodes.OK,
                            user.get(),
                            Jackson.marshaller()
                        );
                    }
                })
            ),
            post(() ->
                path(segment("user").slash().concat("add"), () ->
                    entity(Jackson.unmarshaller(User.class), 
                           user ->
                    {

                        Optional<User> userCheck = 
                                userDao.get(user.getUserName());

                        if(userCheck.isEmpty())
                        {
                            boolean status = userDao.insert(user);

                            if(!status)
                            {
                                return complete(
                                    StatusCodes.BAD_REQUEST,
                                    ErrorMessage.badRequestMessage(),
                                    Jackson.marshaller()
                                );
                            }
                            else
                            {
                                return complete(StatusCodes.NO_CONTENT);
                            }
                        }
                        else
                        {
                            return complete(
                                StatusCodes.BAD_REQUEST,
                                ErrorMessage.userAlreadyExistsMessage(),
                                Jackson.marshaller()
                            );
                        }
                    }))
            ),
            put(() ->
                path(segment("user").slash().concat("update"), () ->
                    entity(Jackson.unmarshaller(User.class), 
                           user ->
                    {
                        Optional<User> userCheck = 
                                userDao.get(user.getUserName());

                        if(userCheck.isEmpty())
                        {
                            return complete(
                                StatusCodes.NOT_FOUND,
                                ErrorMessage.userDoesNotExistMessage(),
                                Jackson.marshaller()
                            );
                        }
                        else
                        {
                            boolean status = userDao.update(user);

                            if(!status)
                            {
                                return complete(
                                    StatusCodes.BAD_REQUEST,
                                    ErrorMessage.badRequestMessage(),
                                    Jackson.marshaller()
                                );
                            }
                            else
                            {
                                return complete(StatusCodes.NO_CONTENT);
                            }
                        }
                    }))
            ),
            delete(() ->
                path(segment("user").slash().concat("delete"), () ->
                    entity(Jackson.unmarshaller(User.class), 
                           user ->
                    {
                        Optional<User> userCheck = 
                                userDao.get(user.getUserName());

                        if(userCheck.isEmpty())
                        {
                            return complete(
                                StatusCodes.NOT_FOUND,
                                ErrorMessage.userDoesNotExistMessage(),
                                Jackson.marshaller()
                            );
                        }
                        else
                        {
                            boolean status = userDao.delete(user);

                            if(!status)
                            {
                                return complete(
                                        StatusCodes.BAD_REQUEST,
                                        ErrorMessage.badRequestMessage(),
                                        Jackson.marshaller()
                                );
                            }
                            else
                            {
                                return complete(StatusCodes.NO_CONTENT);
                            }
                        }
                    })
                )
            )
        );
    }
}

With Akka HTTP we will start off with the HTTP Verb and pass it a path. This part can be the most confusing so I will walk through the sections to get a user and to add a user.

When we use the path "user/username" it will be filtered to that path. We start with the appropriate method for the HTTP Verb we want to use. In this instance it is get(). We pass in a lambda that will match the Path. We are using segments which is the text between the slashes. We add the slashes to the PathMatcher with .slash(). Since we are expecting the username to be in the url we end our path with .concat(segment()) and use that segment in our second parameter, which is another lambda. We then get the user and check that our Optional is not empty. If it is, we return a 400 Status Code with our Resource Not Found Message. If it is good to go, we respond with our 200 Status Code and return the entity.

Note: I understand it is not good practice to return the password from the endpoint. Please don't ever do this in production. This is just done for simplicity and instructional purposes. I promise it will change in the next tutorial or two.

When we would like to add a user we will use the path "user/add". This endpoint expects the incoming message to use Content-Type: application/JSON and the following JSON to be in the body in the form of:

{
	"userName": "Test",
	"password": "test"
}

You will notice we are expecting the POST HTTP Verb to be used. The path does not change on this. We unmarshall the JSON to a User Object. We then check if the user already exists in the database. If it does not, we add the user and check to make sure the insert was successful. If the insert was successful, we return a 204 status code. If not, we return a 404 status code and our Bad Request Message. If the user already existed, we return a 404 status code with our User Already Exists Message.

With the code up and running, we now discuss the Testing portion. We will not discuss testing the Actor System at this point. It was just too much to throw in one article with all of this amazing content already.

First we will create a second hibernate.cfg.xml but place it in our src/test/resources folder. This will ensure that when we run our tests it will use our h2 database. I prefer to use as close to the actual production environment as possible, but for development and testing this works to make sure our database operations are running as planned.

<?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">org.h2.Driver</property>
        <property name="connection.url">jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</property>
        <property name="connection.username">sa</property>
        <property name="connection.password">sa</property>

        <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
        <property name="hibernate.c3p0.min_size">5</property>
        <property name="hibernate.c3p0.max_size">20</property>
        <property name="hibernate.c3p0.timeout">120</property>

        <property name="hibernate.hbm2ddl.auto">create</property>

        <mapping class="com.pelaghisoftware.data.entity.User" />

    </session-factory>
</hibernate-configuration>

Once that is created, it is time to create our tests. I am only creating tests for our DatabaseCommonOperations and UserDao classes for the time being. In reality we should try to get as close to full coverage as possible and don't worry this will change in the future. So let us start with the DatabaseCommonOperations.

This is fairly easy as there are only 2 static methods in this class. The key parts to remember is that we need to initialize our tables at the beginning of each test and clean the database at the end of each test. Since we are actually testing our SessionFactory creation, we need to make sure we open and close one in each test. This is good practice due to the fact that we don't want any data from one test to bleed over into another one anyway.

package com.pelaghisoftware.data;

import com.pelaghisoftware.data.entity.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Tests the BaseDaoImpl abstract class.
 */
public class DatabaseCommonOpsTest
{
    private static final Logger logger =
            LoggerFactory.getLogger(DatabaseCommonOpsTest.class);

    private static final List<User> users =
            IntStream.range(0,40)
                    .boxed()
                    .map((i) -> new User("Name" + i,
                                         "Password" + i))
                    .collect(Collectors.toList());

    /**
     * Test createSession
     */
    @Test
    public void testCreateSession()
    {
        Optional<SessionFactory> sessionFactory =
                DatabaseCommonOps.createSessionFactory();

        assertTrue(sessionFactory.isPresent());
        assertTrue(sessionFactory.get() instanceof SessionFactory);

        sessionFactory.get().close();
    }

    /**
     * Test initTables success
     */
    @Test
    public void testLoadAllData()
    {
        SessionFactory sessionFactory =
                DatabaseCommonOps.createSessionFactory().get();

        initUserTable(sessionFactory);

        Session session = sessionFactory.openSession();
        List<User> users =
                DatabaseCommonOps.loadAllData(User.class, session);

        assertEquals(40, users.size());

        cleanUserTable(sessionFactory);
        sessionFactory.close();
    }

    /**
     * Initializes the SITE_USERS table in the h2 database
     * @param sessionFactory
     */
    private static void initUserTable(SessionFactory sessionFactory)
    {
        for(User user : users)
        {
            Session session = sessionFactory.openSession();

            Transaction tx = null;
            try
            {
                tx = session.beginTransaction();

                session.save(user);

                tx.commit();
            }
            catch (Exception e)
            {
                if (tx != null)
                {
                    tx.rollback();
                }
                logger.error(e.getMessage());
            }
            finally
            {
                session.close();
            }

        }
    }

    /**
     * Clears the SITE_USERS table in the h2 database
     * @param sessionFactory
     */
    private static void cleanUserTable(SessionFactory sessionFactory)
    {
        Session session = sessionFactory.openSession();
        List<User> currentUsers =
                DatabaseCommonOps.loadAllData(User.class, session);
        session.close();

        for(User user : currentUsers)
        {
            session = sessionFactory.openSession();

            Transaction tx = null;
            try
            {
                tx = session.beginTransaction();

                session.delete(user);

                tx.commit();
            }
            catch (Exception e)
            {
                if (tx != null)
                {
                    tx.rollback();
                }
                logger.error(e.getMessage());
            }
            finally
            {
                session.close();
            }
        }
    }
}

Likewise we will test all of the methods in our UserDao class.

package com.pelaghisoftware.data.dao.impl;

import com.pelaghisoftware.data.DatabaseCommonOps;
import com.pelaghisoftware.data.entity.User;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.junit.jupiter.api.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class UserDaoTest
{
    private static final Logger logger =
            LoggerFactory.getLogger(UserDaoTest.class);

    private static SessionFactory sessionFactory;

    //Sets up a list of Test Users to be inserted in the database
    private static final List<User> users =
            IntStream.range(0,40)
                .boxed()
                .map((i) -> new User("Name" + i,
                                     "Password" + i))
                .collect(Collectors.toList());

    /**
     * Setup the testing environment
     */
    @BeforeAll
    public static void setup()
    {
        sessionFactory =
                DatabaseCommonOps.createSessionFactory().get();
    }

    /**
     * Clean up after testing is done. Removes the Test Table
     * from the Database and stops the Actor System.
     */
    @AfterAll
    public static void teardown()
    {
        cleanUserTable();
        sessionFactory.close();
    }

    /**
     * Initializes the tables in the database before each
     * test is run
     */
    @BeforeEach
    public void initDb()
    {
        initUserTable();
    }

    /**
     * Clears out the data in the table after each
     * test is run
     */
    @AfterEach
    public void cleanDb()
    {
        cleanUserTable();
    }

    /**
     * Tests the get method
     */
    @Test
    public void testGet()
    {
        UserDao userDao = new UserDao(sessionFactory);

        User user = userDao.get("Name0").get();
        assertEquals("Name0", user.getUserName());
        assertEquals("Password0", user.getPassword());
    }

    /**
     * Tests the getAll method
     */
    @Test
    public void testGetAll()
    {
        UserDao userDao = new UserDao(sessionFactory);

        List<User> currentUser = userDao.getAll();
        assertEquals(40, currentUser.size());
    }

    /**
     * Tests the insert method
     */
    @Test
    public void testInsert()
    {
        UserDao userDao = new UserDao(sessionFactory);

        User user = new User("Test", "Test");
        userDao.insert(user);

        User newUser = userDao.get("Test").get();

        assertEquals(user.getUserName(), newUser.getUserName());
        assertEquals(user.getPassword(), newUser.getPassword());
    }

    /**
     * Tests the update method
     */
    @Test
    public void testUpdate()
    {
        UserDao userDao = new UserDao(sessionFactory);

        User user = new User("Name0", "1234");
        userDao.update(user);

        User newUser = userDao.get("Name0").get();

        assertEquals(user.getUserName(), newUser.getUserName());
        assertEquals(user.getPassword(), newUser.getPassword());
    }

    /**
     * Tests the delete method
     */
    @Test
    public void testDelete()
    {
        UserDao userDao = new UserDao(sessionFactory);

        User user = new User("Name0", "Password0");
        userDao.delete(user);

        Optional<User> newUser = userDao.get("Name0");

        assertTrue(newUser.isEmpty());
    }

    /**
     * Initializes the SITE_USERS Table in the h2 database
     */
    private static void initUserTable()
    {
        for(User user : users)
        {
            Session session = sessionFactory.openSession();

            Transaction tx = null;
            try
            {
                tx = session.beginTransaction();

                session.save(user);

                tx.commit();
            }
            catch (Exception e)
            {
                if (tx != null)
                {
                    tx.rollback();
                }
                logger.error(e.getMessage());
            }
            finally
            {
                session.close();
            }

        }
    }

    /**
     * Removes all users from the SITE_USERS h2 database
     */
    private static void cleanUserTable()
    {
        Session session = sessionFactory.openSession();
        List<User> currentUsers =
                DatabaseCommonOps.loadAllData(User.class, session);
        session.close();

        for(User user : currentUsers)
        {
            session = sessionFactory.openSession();

            Transaction tx = null;
            try
            {
                tx = session.beginTransaction();

                session.delete(user);

                tx.commit();
            }
            catch (Exception e)
            {
                if (tx != null)
                {
                    tx.rollback();
                }
                logger.error(e.getMessage());
            }
            finally
            {
                session.close();
            }
        }
    }
}

I won't bore you with the JUnit 5 stuff being done here, however if you would like to learn a bit more about something I have done above, look at the JUnit 5 Website here.

If you made it this far, thank you for sticking with me. Please don't forget if you have questions or suggestions, drop me a line in the comments. Again the code for this can be found in my GitHub repository here.

Ci vediamo presto!!!