Spring Boot CRUD Tutorial with embedded H2 and JSP

Tutorial describing how to create the simple CRUD (Create Read Update Delete) application using Spring Boot, JSP template engine and H2 as embedded database.

Technologies used:

  1. Spring Boot 2.1.7.RELEASE
  2. JDK 1.8
  3. Maven 3
  4. JSP
  5. Spring Data JPA
  6. H2 database

Project setup

Project dependencies managed by the Maven’s pom.xml configuration file:

<?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.7.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.devcases.springboot</groupId>
    <artifactId>springboot-crud</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <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-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

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

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

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

</project>

The explanation of the above dependencies:

  • spring-boot-starter-web – packages all the necessary dependencies together with the auto configuration for running simple web application
  • spring-boot-starter-tomcat – provides the embedded tomcat application server
  • spring-boot-starter-data-jpa – provides auto configuration for using Spring Data JPA with Hibernate
  • tomcat-embed-jasper – enables support for JSP
  • jstl – provides JSP Standard Tag Library (JSTL) – collection of useful JSP tags. We will use the form tag.
  • h2 – provides auto configured embedded H2 database
  • spring-boot-maven-plugin – plugin prividing commands to work with a Spring Boot application (we will be usine the spring-boot:run command)

Application Initializer

@SpringBootApplication
@EnableAutoConfiguration
@ComponentScan(basePackages={"com.devcases.springboot.crud.library"})
@EnableJpaRepositories(basePackages="com.devcases.springboot.crud.library.repository")
public class Application extends SpringBootServletInitializer {

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

The only non standard annotation is @EnableJpaRepositories, which instantiates Spring Data repositories defined in the specified directory.

Controller

@Controller
public class LibraryController {

    private BookService service;

    @Autowired
    public LibraryController(BookService service) {
        this.service = service;
    }

    @GetMapping
    public String showAllBooks(Model model) {
        model.addAttribute("books", service.findAll());
        return "books";
    }

    @GetMapping("/new-book")
    public String showBookCreationForm(Model model) {
        model.addAttribute("book", new Book());
        return "new-book";
    }

    @PostMapping(value = "/add", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String addNewBook(@Valid @ModelAttribute Book book, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "new-book";
        }
        service.save(book);
        model.addAttribute("books", service.findAll());
        return "books";
    }

    @GetMapping("/{id}")
    public String showBookdById(@PathVariable Long id, Model model) {
        Book book = service.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Invalid book Id:" + id));
        model.addAttribute("book", book);
        return "edit-book";
    }

    @PostMapping("/{id}/update")
    public String updateBook(@PathVariable Long id, @Valid @ModelAttribute Book book, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "edit-book";
        }
        service.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Invalid book Id:" + id));
        service.save(book);
        model.addAttribute("books", service.findAll());
        return "books";
    }

    @PostMapping("/{id}/delete")
    public String deleteBook(@PathVariable Long id, Model model) {
        service.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Invalid book Id:" + id));
        service.deleteById(id);
        model.addAttribute("books", service.findAll());
        return "books";
    }
}

The controller contains set of methods repsonsible for CRUD operations:

  • showAllBooks – finds and shows all the persisted books in the “books” view
  • showBookCreationForm – creates empty book and allows user to fill the book in the “new-book” view
  • addNewBook – fired when data of new book are requested to persist. Thanks to the @Valid annotation, the Spring validates (according to the validation annotations in the Book class) passed book and provides errors in the BindingResult object. Based on the result we can return to the same view with the errors or save the book in the database. After successull operation method shows the “books” view
  • showBookdById – finds the book with the specified id and shows its data in the “edit-book” view.
  • updateBook – saves existing book (with the id passed in the url – we use PathVariable annotation to resolve this id), validates data in the same way as the showBookCreationForm method.
  • deleteBook – deletes book with specified id.

In our case the controller contains only GET and POST methods. But a real RESTful controller, should have a PUT updateBook and DELETE deleteBook methods. This is because the view (described later) is simple form without any additional scripts. Html Form can’t perform any PUT or DELETE methods – it can only make GET or POST requests.

View

books.jsp template:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<%@ page isELIgnored="false" %>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Book Library</title>
</head>
<body>
<div>
<div>
<h2>Library</h2>
<hr/>
<a href="/new-book">
<button type="submit">Add new book</button>
</a>
<br/><br/>
<div>
<div>
<div>Book list</div>
</div>
<div>
<table>
<tr>
<th>Id</th>
<th>Author</th>
<th>Name</th>
</tr>
<c:forEach var="book" items="${books}">
<tr>
<td>${book.id}</td>
<td>${book.author}</td>
<td>${book.name}</td>
<td>
<a href="/${book.id}">Edit</a>
<form action="/${book.id}/delete" method="post">
<input type="submit" value="Delete" />
</form>
</td>
</tr>
</c:forEach>
</table>
</div>
</div>
</div>
</div>
</body>
</html>

new-book.jsp template:

<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html lang="en">
<head>
<%@ page isELIgnored="false" %>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Book Library</title>
</head>
<body>
<div>
<h2>New User</h2>
<div>
<div>
<form:form action="/add" modelAttribute="book" method="post">
<div>
<div>
<form:label path="author">Author</form:label>
<form:input type="text" id="author" path="author"/>
<form:errors path="author" />
</div>
<div>
<form:label path="name">Name</form:label>
<form:input type="text" id="name" path="name"/>
<form:errors path="name" />
</div>
</div>
<div>
<div>
<input type="submit" value="Add User">
</div>
</div>
</form:form>
</div>
</div>
</div>
</body>
</html>

edit-book.jsp template:

<!DOCTYPE html>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html lang="en">
<head>
<%@ page isELIgnored="false" %>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Book Library</title>
</head>
<body>
<div>
<h2>New User</h2>
<div>
<div>
<form:form action="${book.id}/update" modelAttribute="book" method="post">
<div>
<div>
Id: ${book.id}
</div>
<div>
<form:label path="author">Author</form:label>
<form:input type="text" id="author" path="author"/>
<form:errors path="author" />
</div>
<div>
<form:label path="name">Name</form:label>
<form:input type="text" id="name" path="name"/>
<form:errors path="name" />
</div>
</div>
<div>
<div>
<input type="submit" value="Update User">
</div>
</div>
</form:form>
</div>
</div>
</div>
</body>
</html>

The JSP templates contain plain html without any additional scripts. If we used script, we could use the PUT and DELETE http requests.

In our templates we use the form tag (privided in the jstl library) – thanks to it we can easily define label, input tag and erros for book fields.

Configuration

The configuration in application.properties file contains only information how to resolve JSP files.

spring.mvc.view.prefix: /WEB-INF/jsp/
spring.mvc.view.suffix: .jsp

Running

The project can be started by the running the following command:

mvn clean spring-boot:run

The command deploys the apllication on the embedded tomcat server.

By visiting the localhost:8080 url, we should see the main page containing empty list of books with the ability to add new ones. Because we used the in memory H2 database, restart of the application resets the database – all our data is lost.

The example code you can download from here