Spring Boot + Mustache CRUD Example

Mustache is a web template system with implementations available for many programming languages including Java. Mustache is described as a "logic-less" system because it lacks any explicit control flow statements, like if and else conditionals or for loops; however, both looping and conditional evaluation can be achieved using section tags processing lists and lambdas.

It is named "Mustache" because of heavy use of braces, { }, that resemble a sideways moustache.

In this tutorial, we will learn on how to build a simple CRUD Spring Boot application with Mustache as server side templating engine.

Create a Spring Boot Project

To generate Spring Boot application we can use Spring Initializr (https://start.spring.io/ ) or Spring Boot CLI. Please refer to Scaffolding Spring Boot Application for details to create new Spring Boot application. For this tutorial, we need (at least) these five dependencies:

  • Spring Web
    Build web, including RESTful, applications using Spring MVC. Uses Apache Tomcat as the default embedded container.
  • Mustache
    Logic-less Templates. There are no if statements, else clauses, or for loops. Instead there are only tags.
  • Spring Data JPA
    Persist data in SQL stores with Java Persistence API using Spring Data and Hibernate.
  • MySQL Driver
    MySQL JDBC and R2DBC driver.
  • Lombok
    Java annotation library which helps to reduce boilerplate code.

Here the final 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>spring-boot-mustache-example</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-mustache-example</name>
    <description>Demo project for Spring Boot with FreeMarker template</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mustache</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
                    

Prepare MySQL

In this tutorial, we will use MySQL as data source. The application that we will create is a simple User Management application. The application able to create, retrieve, update and delete a User. Here the structure of User class:

  1. ID (Primary key of this entity)
  2. Name
  3. Email address (Unique key of this entity)
  4. Phone number
  5. Address

Assuming we will use a database called "dariawan", here the DDL for table users:

users.sql
CREATE TABLE `users` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `email` VARCHAR(255) NOT NULL,  
  `phone` VARCHAR(255),
  `address` VARCHAR(255), 
  PRIMARY KEY (`id`)
) ENGINE=MYISAM DEFAULT CHARSET=latin1;

CREATE UNIQUE INDEX id_user_email ON users (email(255));
                    

For initial data, let's insert one record in this table:

insert into users (`name`, email, phone, address) values ('Charlie Croker', '[email protected]', '98765432', 'Los Angeles, California'), ('Stella Bridger', '[email protected]', '87654321', 'Los Angeles, California'), ('Handsome Rob', '[email protected]', '76543210', 'Beverly Hills, California'), ('Lyle', '[email protected]', '65432102', 'Los Angeles, California'), ('Gilligan "Left Ear"', '[email protected]', '54321023', null), ('Wrench', '[email protected]', '43210234', null)

Now, let’s configure Spring Boot to use MySQL as our data source by adding MySQL database url, username, and password in the src/main/resources/application.properties file:

# Connection url for the database "dariawan" spring.datasource.url = jdbc:mysql://localhost:3306/dariawan?serverTimezone=Asia/Singapore # Username and password spring.datasource.username = barista spring.datasource.password = cappuccino # Allows Hibernate to generate SQL optimized for a particular DBMS spring.jpa.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect spring.jpa.hibernate.naming_strategy = org.hibernate.cfg.ImprovedNamingStrategy # spring.jpa.hibernate.ddl-auto = update

Spring Boot Application

UserApplication is the main entry point of our Spring Boot application:

UserApplication.java
package com.dariawan.userapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class UserApplication {

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}
                    

SpringApplication.run() bootstraps a spring application as a stand-alone application from the main method. It creates an appropriate ApplicationContext instance and load beans. It also runs embedded Tomcat server in Spring web application, which once started the application can be accessed at localhost:8080 (default configuration, you can change it)

Entity Class

For table users, we will create a User class:

User.java
package com.dariawan.userapp.entity;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Entity @Table(name = "users", indexes = @Index(columnList = "email"))
@Getter @Setter @EqualsAndHashCode(of = {"email"}) @ToString
public class User implements Serializable {
    
    private static final long serialVersionUID = 63453822723859663L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @NotNull
    private String name;
    
    @NotNull
    @Column(unique = true)
    private String email;
    
    private String phone;
    private String address;
}
                    

Refer to Create Domain Models section of Spring Boot + JPA/Hibernate + PostgreSQL RESTful CRUD API Example article to understand more about annotations used for our entities/models.

Repository Class

A repository class is used to access User’s data from users table in MySQL database. UserRepository extends PagingAndSortingRepository, an interface that provides generic CRUD operations and add methods to retrieve entities using the pagination and sorting abstraction.

UserRepository.java
package com.dariawan.userapp.repository;

import com.dariawan.userapp.entity.User;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;

public interface UserRepository extends PagingAndSortingRepository<User, Long>, 
        JpaSpecificationExecutor<User> {
}
                    

Service Class

UserService is the place where we place our business logic and calculations, also bridging the communication between controller and repository.

UserService.java
package com.dariawan.userapp.service;

import com.dariawan.userapp.entity.User;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.dariawan.userapp.repository.UserRepository;

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    private boolean existsById(Long id) {
        return userRepository.existsById(id);
    }
    
    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    public List<User> findAll(int pageNumber, int rowPerPage) {
        List<User> users = new ArrayList<>();
        Pageable sortedByLastUpdateDesc = PageRequest.of(pageNumber - 1, rowPerPage, 
                Sort.by("id").ascending());
        userRepository.findAll(sortedByLastUpdateDesc).forEach(users::add);
        return users;
    }
    
    public User save(User user) throws Exception {
        if (StringUtils.isEmpty(user.getName())) {
            throw new Exception("Name is required");
        }
        if (StringUtils.isEmpty(user.getEmail())) {
            throw new Exception("Email is required");
        }
        if (user.getId() != null && existsById(user.getId())) { 
            throw new Exception("User with id: " + user.getId() + " already exists");
        }
        return userRepository.save(user);
    }
    
    public void update(User user) throws Exception {
        if (StringUtils.isEmpty(user.getName())) {
            throw new Exception("Name is required");
        }
        if (StringUtils.isEmpty(user.getEmail())) {
            throw new Exception("Email is required");
        }
        if (!existsById(user.getId())) {
            throw new Exception("Cannot find User with id: " + user.getId());
        }
        userRepository.save(user);
    }
    
    public void deleteById(Long id) throws Exception {
        if (!existsById(id)) { 
            throw new Exception("Cannot find User with id: " + id);
        }
        else {
            userRepository.deleteById(id);
        }
    }
    
    public Long count() {
        return userRepository.count();
    }
}
                    

Controller Class

The Controller in MVC architecture handles any incoming URL request. Controllers are the ones that interpret user input and transform it into a model that is represented to the user by the view. In this example, we will create UserController.

UserController.java
package com.dariawan.userapp.controller;

import com.dariawan.userapp.entity.User;
import com.dariawan.userapp.service.UserService;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final int ROW_PER_PAGE = 5;

    @Autowired
    private UserService userService;

    @Value("${msg.title}")
    private String title;

    @GetMapping(value = {"/", "/index"})
    public String index(Model model) { ... }

    @GetMapping(value = "/users")
    public String getUsers(Model model,
            @RequestParam(value = "page", defaultValue = "1") int pageNumber) { ... }

    @GetMapping(value = "/users/{userId}")
    public String getUserById(Model model, @PathVariable long userId) { ... }

    @GetMapping(value = {"/users/add"})
    public String showAddUser(Model model) { ... }

    @PostMapping(value = "/users/add")
    public String addUser(Model model,
            @ModelAttribute("user") User user) { ... }

    @GetMapping(value = {"/users/{userId}/edit"})
    public String showEditUser(Model model, @PathVariable long userId) { ... }

    @PostMapping(value = {"/users/{userId}/edit"})
    public String updateUser(Model model,
            @PathVariable long userId,
            @ModelAttribute("user") User user) { ... }

    @GetMapping(value = {"/users/{userId}/delete"})
    public String showDeleteUser(
            Model model, @PathVariable long userId) { ... }

    @PostMapping(value = {"/users/{userId}/delete"})
    public String deleteUserById(
            Model model, @PathVariable long userId) { ... }
}
                    

Let's check every pages and mustache templates this controller associated with, we will run through item by item:

Index Page

The index page is a simple page with the title of application and link to User List page.

@Value("${msg.title}")
private String title;

@GetMapping(value = {"/", "/index"})
public String index(Model model) {
    model.addAttribute("title", title);
    return "index";
}
                    

The function returning String, which is the template name which will be used to render the response. The template that will be rendered in this function is index.mustache which is available in Mustache default templates location for Spring Boot in src/main/resources/templates/

index.mustache
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{{title}}</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css"/>
    </head>
    <body>
        <h1>{{title}}</h1>
        <a href="/users">User List</a>  
        {{>footer}}
    </body>
</html>
                    

The default template suffix is .mustache. In index.mustache, we see several mustache tag. Let's check different tags types in mustache:

Tags TypesDescription
{{tag_key}}
typical variable tag
{{{tag_key}}}typical variable tag without HTML escaping
{{#tag_key}} ... {{/tag_key}}pair of tags define a section block
{{^tag_key}} ... {{/tag_key}}pair of tags define an inverted section block
{{> tag_key}}partials
{{! comment }}comments

In our templates, we will find {{>footer}}. It's one example of partial, to render footer in each page. Here the content of footer.mustache:

footer.mustache
<br/><br/>
<div>Spring Boot + Mustache © dariawan.com</div>
                    

The title attribute is extracted from property file with the @Value annotation of msg.title. Here the value in application.properties:

msg.title=Spring Boot + Mustache CRUD Example

Result in http://localhost:8080:

http://localhost:8080

http://localhost:8080 (Index Page)

Users Page

Users page will show list of users in paged mode, in our example is every five records.

@GetMapping(value = "/users")
public String getUsers(Model model,
        @RequestParam(value = "page", defaultValue = "1") int pageNumber) {
    List<User> users = userService.findAll(pageNumber, ROW_PER_PAGE);

    long count = userService.count();
    boolean hasPrev = pageNumber > 1;
    boolean hasNext = (pageNumber * ROW_PER_PAGE) < count;
    model.addAttribute("users", users);
    model.addAttribute("hasPrev", hasPrev);
    model.addAttribute("prev", pageNumber - 1);
    model.addAttribute("hasNext", hasNext);
    model.addAttribute("next", pageNumber + 1);
    return "user-list";
}
                    

The controller then will render user-list.mustache

user-list.mustache
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>User List</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css"/>
    </head>
    <body>
        <h1>User List</h1>
        
        <div>
            <nobr>
                <a href="/users/add">Add User</a> |
                <a href="/">Back to Index</a>
            </nobr>
        </div>
        <br/><br/>
        <div>
            <table border="1">
                <tr>
                    <th>Id</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Phone</th>
                    <th>Address</th>
                    <th>Edit</th>                    
                </tr>
                {{#users}}
                    <tr>
                        <td><a href="/users/{{id}}">{{id}}</a></td>
                        <td><a href="/users/{{id}}">{{name}}</a></td>
                        <td><a href="/users/{{id}}">{{email}}</a></td>
                        <td>{{#phone}}{{phone}}{{/phone}}</td>
                        <td>{{#address}}{{address}}{{/address}}</td>
                        <td><a href="/users/{{id}}/edit">Edit</a></td>
                    </tr>
                {{/users}}
            </table>          
        </div>
        <br/><br/>
        <div>
            <nobr>
                {{#hasPrev}}<a href="/users?page={{prev}}">Prev</a>&nbsp;&nbsp;&nbsp;{{/hasPrev}}
                {{#hasNext}}<a href="/users?page={{next}}">Next</a>{{/hasNext}}
            </nobr>
        </div>
        {{>footer}}
    </body>
</html>
                    

Result of http://localhost:8080/users:

http://localhost:8080/users

http://localhost:8080/users (Users Page)

Clicking the "id", "Name" and "Email" link will lead us to User Page, and clicking the "edit" link will lead to Edit User Page. Add User Page is available by clicking "Add User" link.

User Page

User Page used to show user in read-only mode. From this page, we can "Edit" or "Delete" a user.

@GetMapping(value = "/users/{userId}")
public String getUserById(Model model, @PathVariable long userId) {
    User user = null;
    try {
        user = userService.findById(userId);
        model.addAttribute("allowDelete", false);
    } catch (Exception ex) {
        model.addAttribute("errorMessage", ex.getMessage());
    }
    model.addAttribute("user", user);        
    return "user";
}
                    

The controller then will render user.mustache:

user.mustache
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>User Details</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css"/>
    </head>
    <body>
        <h1>View User</h1>
        <a href="/users">Back to User List</a>
        <br/><br/>
        {{#user}}
            <table border="0">
                <tr>
                    <td>ID</td>
                    <td>:</td>
                    <td>{{user.id}}</td>          
                </tr>
                <tr>
                    <td>Name</td>
                    <td>:</td>
                    <td>{{user.name}}</td>             
                </tr>
                <tr>
                    <td>Email</td>
                    <td>:</td>
                    <td>{{user.email}}</td>              
                </tr>
                <tr>
                    <td>Phone</td>
                    <td>:</td>
                    <td>{{#user.phone}}{{user.phone}}{{/user.phone}}</td>              
                </tr>
                <tr>
                    <td>Address</td>
                    <td>:</td>
                    <td>{{#user.address}}{{user.address}}{{/user.address}}</td>              
                </tr>
            </table>
            <br/><br/>
            {{#allowDelete}}
                <form action="/users/{{user.id}}/delete" method="POST">
                    Delete this user? <input type="submit" value="Yes" />
                </form>
            {{/allowDelete}}
            {{^allowDelete}}
                <div>
                    <a href="/users/{{user.id}}/edit">Edit</a> |
                    <a href="/users/{{user.id}}/delete">Delete</a>
                </div>
            {{/allowDelete}}
        {{/user}}
        {{#errorMessage}}
            <div class="error">{{errorMessage}}</div>
        {{/errorMessage}}
        {{>footer}}
    </body>
</html>
                    

http://localhost:8080/users/1 rendered in browser:

http://localhost:8080/users/1

http://localhost:8080/users/1 (User Page)

Choosing "Edit" will lead to Edit User Page, and choosing "Delete" will lead to Delete User Page.

Add and Edit User Page

For Add and Edit User Page, we will use following flow:

  1. GET request to show/render the page, represented by functions showAddUser(...) and showEditUser(...)
  2. POST request to save the user data to the server, represented by functions addUser(...) and updateUser(...)
@GetMapping(value = {"/users/add"})
public String showAddUser(Model model) {
    User user = new User();
    model.addAttribute("add", true);
    model.addAttribute("user", user);

    return "user-edit";
}

@PostMapping(value = "/users/add")
public String addUser(Model model,
        @ModelAttribute("user") User user) {        
    try {
        User newUser = userService.save(user);
        return "redirect:/users/" + String.valueOf(newUser.getId());
    } catch (Exception ex) {
        // log exception first, 
        // then show error
        String errorMessage = ex.getMessage();            
        logger.error(errorMessage);
        model.addAttribute("errorMessage", errorMessage);

        model.addAttribute("add", true);
        return "user-edit";
    }        
}

@GetMapping(value = {"/users/{userId}/edit"})
public String showEditUser(Model model, @PathVariable long userId) {
    User user = null;
    try {
        user = userService.findById(userId);
    } catch (Exception ex) {
        model.addAttribute("errorMessage", ex.getMessage());
    }
    model.addAttribute("add", false);
    model.addAttribute("user", user);
    return "user-edit";
}

@PostMapping(value = {"/users/{userId}/edit"})
public String updateUser(Model model,
        @PathVariable long userId,
        @ModelAttribute("user") User user) {
    try {
        user.setId(userId);
        userService.update(user);
        return "redirect:/users/" + String.valueOf(user.getId());
    } catch (Exception ex) {
        // log exception first, 
        // then show error
        String errorMessage = ex.getMessage();            
        logger.error(errorMessage);
        model.addAttribute("errorMessage", errorMessage);

        model.addAttribute("add", false);
        return "user-edit";
    }
}
                    

For GET request, both functions will render user-edit.mustache:

user-edit.mustache
<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{{#add}}Create new User{{/add}}{{^add}}Edit User{{/add}}</title>
        <link rel="stylesheet" type="text/css" href="/css/style.css"/>
    </head>
    <body>
        <h1>{{#add}}Create new User:{{/add}}{{^add}}Edit User:{{/add}}</h1>
        <a href="/users">Back to User List</a>
        <br/><br/>
        <form action="{{#add}}/users/add{{/add}}{{^add}}/users/{{user.id}}/edit{{/add}}" name="user" method="POST">
            <table border="0">
                {{#user.id}}
                <tr>
                    <td>ID</td>
                    <td>:</td>
                    <td>{{user.id}}</td>             
                </tr>
                {{/user.id}}
                <tr>
                    <td>Name</td>
                    <td>:</td>
                    <td><input type="text" name="name" value="{{#user.name}}{{user.name}}{{/user.name}}" /></td>              
                </tr>
                <tr>
                    <td>Email</td>
                    <td>:</td>
                    <td><input type="text" name="email" value="{{#user.email}}{{user.email}}{{/user.email}}" /></td>              
                </tr>
                <tr>
                    <td>Phone</td>
                    <td>:</td>
                    <td><input type="text" name="phone" value="{{#user.phone}}{{user.phone}}{{/user.phone}}" /></td>              
                </tr>
                <tr>
                    <td>Address</td>
                    <td>:</td>
                    <td><input type="text" name="address" value="{{#user.address}}{{user.address}}{{/user.address}}" /></td>              
                </tr>
            </table>
            <input type="submit" value="{{#add}}Create{{/add}}{{^add}}Update{{/add}}" />
        </form>

        <br/>
        <!-- Check if errorMessage is not null and not empty -->       
        <div class="error">{{#errorMessage}}{{errorMessage}}{{/errorMessage}}</div>
        {{>footer}}
    </body>
</html>
                    

Attribute add is used to control if the page is in "add mode" or "edit mode". Below is screenshot of Add User Page that available in http://localhost:8080/users/add :

http://localhost:8080/users/add

http://localhost:8080/users/add (Add User Page)

Upon successful add, the controller will redirect to User Page to view new created user.

Next is the example of Edit User Page (for User with id=1) which is available in http://localhost:8080/users/1/edit:

http://localhost:8080/users/1/edit

http://localhost:8080/users/1/edit (Edit User Page)

Upon successful update, the controller also will redirect to User Page to view updated User.

Delete User Page

Delete User Page using same scenario as Add/Edit User Page:

  1. GET request to show/render the page, represented by functions showDeleteUser(...) to confirm deletion
  2. POST request to delete user from the server, represented by functions deleteUserById(...)
@GetMapping(value = {"/users/{userId}/delete"})
public String showDeleteUser(
        Model model, @PathVariable long userId) {
    User user = null;
    try {
        user = userService.findById(userId);
    } catch (Exception ex) {
        model.addAttribute("errorMessage", ex.getMessage());
    }
    model.addAttribute("allowDelete", true);
    model.addAttribute("user", user);
    return "user";
}

@PostMapping(value = {"/users/{userId}/delete"})
public String deleteUserById(
        Model model, @PathVariable long userId) {
    try {
        userService.deleteById(userId);
        return "redirect:/users";
    } catch (Exception ex) {
        String errorMessage = ex.getMessage();
        logger.error(errorMessage);
        model.addAttribute("errorMessage", errorMessage);
        return "user";
    }
}
                    

Confirm deletion in http://localhost:8080/users/1/delete:

http://localhost:8080/users/1/delete

http://localhost:8080/users/1/delete (Delete User Page)

Spring Boot's static resources default folder is in \src\main\resources\static\. The css used for this example is available in css\style.css

Test Files

There are two test files for our project. One of them, UserServiceJPATest used to test JPA and database operations in UserService:

UserServiceJPATest.java
package com.dariawan.userapp.service;

import com.dariawan.userapp.entity.User;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.List;
import javax.sql.DataSource;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceJPATest {

    @Autowired 
    private DataSource dataSource;
    
    @Autowired 
    private UserService userService;
    
    @Before
    public void cleanTestData() throws Exception {
        try (Connection conn = dataSource.getConnection()) {
            String sql = "delete from users where email not like ?";
            PreparedStatement ps = conn.prepareStatement(sql);
            ps.setString(1, "%@cappucino.com");
            ps.executeUpdate();
        }
    }    
    
    @Test
    public void testFindAllUsers() {
        List<User> users = userService.findAll(1, 20);
        assertNotNull(users);
        assertTrue(users.size() == 6);
        for (User user : users) {
            assertNotNull(user.getId());
            assertNotNull(user.getName());
            assertNotNull(user.getEmail());
        }
    }
    
    @Test
    public void testSaveUpdateDeleteUser() throws Exception{
        User u = new User();
        u.setName("Charlize Theron");
        u.setEmail("[email protected]");
        
        userService.save(u);
        assertNotNull(u.getId());
        
        User findUser = userService.findById(u.getId());
        assertEquals(u.getName(), findUser.getName());
        assertEquals(u.getEmail(), findUser.getEmail());
        
        // update record
        u.setEmail("[email protected]");
        userService.update(u);
        
        // test after update
        findUser = userService.findById(u.getId());
        assertEquals(u.getEmail(), findUser.getEmail());
        
        // test delete
        userService.deleteById(u.getId());
        
        // query after delete
        User uDel = userService.findById(u.getId());
        assertNull(uDel);
    }
}
                    

Final Project Structure

At the end, our project structure will be similar like this:

spring-boot-mustache-example │ .gitignore │ HELP.md │ mvnw │ mvnw.cmd │ pom.xml │ ├───.mvn │ └───wrapper │ maven-wrapper.jar │ maven-wrapper.properties │ MavenWrapperDownloader.java │ └───src ├───main │ ├───java │ │ └───com │ │ └───dariawan │ │ └───userapp │ │ │ UserApplication.java │ │ │ │ │ ├───controller │ │ │ UserController.java │ │ │ │ │ ├───entity │ │ │ User.java │ │ │ │ │ ├───repository │ │ │ UserRepository.java │ │ │ │ │ └───service │ │ UserService.java │ │ │ └───resources │ │ application.properties │ │ │ ├───static │ │ └───css │ │ style.css │ │ │ └───templates │ footer.mustache │ index.mustache │ user-edit.mustache │ user-list.mustache │ user.mustache │ ├───sql │ users.sql │ └───test └───java └───com └───dariawan └───userapp │ UserApplicationTests.java │ └───service UserServiceJPATest.java