Mastering Spring framework 5, Part 1: Spring MVC

Build a Java web application using Spring MVC with Spring Boot

1 2 3 Page 2
Page 2 of 3

Listing 3. Widget.java


package com.geekcap.javaworld.spring5mvcexample.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Widget {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String description;
    public Widget() {
    }
    public Widget(Long id, String name, String description) {
        this.id = id;
        this.name = name;
        this.description = description;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}    

The Widget class is a plain old Java object (POJO) that is annotated with JPA (Java Persistence API) annotations. In this case there are three annotations:

  • @Entity identifies Widget as an entity that can be persisted to a database.
  • @Id identifies the id field as the primary key of the entity.
  • @GeneratedValue tells the JPA EntityManager that the key should be automatically generated in the database.

Note that EntityManager is created automatically for us because we included JPA in the Spring Initializr.

The Widget class manages three fields:

  • id is the ID, or primary key of the widget.
  • name is the name of the widget.
  • description is a description of the widget.

To persist widgets to and from our embedded database, we need to leverage Spring Data. Basically, we'll define an interface that extends one of Spring Data's interfaces, such as CrudRepository, and Spring Data will provide an implementation of that interface at runtime.

The CrudRepository interface includes the following methods:

  • findById finds the entity in the database with the specified ID.
  • findAll returns all entities of the repository type from the database (note that there are other Spring Data repository interfaces, such as PagingAndSortingRepository, that can help manage larger data sets).
  • findAllById passed a collection of IDs, this method returns all entities for those IDs.
  • save persists an entity to the database (create or update).
  • saveAll saves a collection of entities to the database.
  • delete deletes the specified entity.
  • deleteById deletes the entity with the specified ID.
  • deleteAll deletes all entities managed by the repository.
  • count returns the number of entities that are in the database.
  • existsById returns true if an entity with the specified ID exists in the database.

For our example, we'll create a WidgetRepository interface that extends CrudRepository, as shown in Listing 4.

Listing 4. WidgetRepository.java


package com.geekcap.javaworld.spring5mvcexample.repository;
import com.geekcap.javaworld.spring5mvcexample.model.Widget;
import org.springframework.data.repository.CrudRepository;
public interface WidgetRepository extends CrudRepository<Widget, Long> {
}    

The WidgetRepository allows us to perform all create, read, update, and delete (CRUD) operations on widgets. The two parameters passed to the CrudRepository are Widget, which represents the type of entity that the repository manages, and Long, which is the type of the primary key for the Widget entity. (This is a simple implementation that scratches the surface of what you can do with Spring Data. I encourage you to learn more about this useful Spring project.)

The controller in Spring MVC

With our Widget entity and WidgetRepository in hand, we're ready to build a controller. Listing 5 shows the source code for the WidgetController.

Listing 5. WidgetController.java


package com.geekcap.javaworld.spring5mvcexample.web;
import com.geekcap.javaworld.spring5mvcexample.model.Widget;
import com.geekcap.javaworld.spring5mvcexample.repository.WidgetRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
@Controller
public class WidgetController {
    @Autowired
    private WidgetRepository widgetRepository;
    /**
     * Load the new widget page.
     * @param model
     * @return
     */
    @GetMapping("/widget/new")
    public String newWidget(Model model) {
        model.addAttribute("widget", new Widget());
        return "widgetform";
    }
    /**
     * Create a new widget.
     * @param widget
     * @param model
     * @return
     */
    @PostMapping("/widget")
    public String createWidget(Widget widget, Model model) {
        widgetRepository.save(widget);
        return "redirect:/widget/" + widget.getId();
    }
    /**
     * Get a widget by ID.
     * @param id
     * @param model
     * @return
     */
    @GetMapping("/widget/{id}")
    public String getWidgetById(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widget";
    }
    /**
     * Get all widgets.
     * @param model
     * @return
     */
    @GetMapping("/widgets")
    public String getWidgets(Model model) {
        model.addAttribute("widgets", widgetRepository.findAll());
        return "widgets";
    }
    /**
     * Load the edit widget page for the widget with the specified ID.
     * @param id
     * @param model
     * @return
     */
    @GetMapping("/widget/edit/{id}")
    public String editWidget(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widgetform";
    }
    /**
     * Update a widget.
     * @param widget
     * @return
     */
    @PostMapping("/widget/{id}")
    public String updateWidget(Widget widget) {
        widgetRepository.save(widget);
        return "redirect:/widget/" + widget.getId();
    }
    /**
     * Delete a widget by ID.
     * @param id
     * @return
     */
    @GetMapping("/widget/delete/{id}")
    public String deleteWidget(@PathVariable  Long id) {
        widgetRepository.deleteById(id);
        return "redirect:/widgets";
    }
}

The WidgetController is annotated with the @Controller annotation. When Spring performs a package scan of the classes, it will find the @Controller annotation, create an instance of this class, and add it to the Spring context, configured to handle web requests. The WidgetController defines a WidgetRepository instance that is annotated with the @Autowired annotation. When Spring sees this, it will find the WidgetRepository implementation created by Spring Data and automatically wire the repository into the controller.

The WidgetController then defines a set of methods to handle requests, using the @GetMapping and @PostMapping annotations. These annotations accept the URI path that each method is handling, defining the HTTP verb accordingly. Because we are using the Thymeleaf template engine, each method returns a String naming the template to render. Templates are stored in src/main/resources/templates as HTML files with specific Thymeleaf markup, which is shown below.

The methods that return a direct template name are all passed a Model object. The Model object implements a Spring UI interface and provides methods to add attributes to the model. These attributes will be made available to the template to be rendered. For example, in the getWidgetById method, we retrieve a Widget from the database and set it in the Model with the key "widget". The template can then access that Widget using the widget key.

Controller methods and the view template

Next let's walk through each WidgetController method and its corresponding view template. The homepage for the application is the /widgets page, which shows a list of all widgets in the database. The getWidgets method retrieves all widgets by calling the WidgetRepository's findAll method, sets it as the "widgets" attribute in the model, and returns "widgets". Listing 6 shows the widgets.html template file.

Listing 6. widgets.html


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Widgets</title>
    <link href="../static/css/bootstrap.min.css" th:href="@{css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
    <div class="container">
        <table class="table">
            <h2>Widgets</h2>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Description</th>
                <th>View</th>
                <th>Edit</th>
                <th>Delete</th>
            </tr>
            <tr th:each="widget : ${widgets}">
                <td th:text="${widget.id}">Widget ID</td>
                <td th:text="${widget.name}">Widget Name</td>
                <td th:text="${widget.description}">Widget Description</td>
                <td><a class="btn btn-default" href="#" th:href="${'/widget/' + widget.id}">View</a> </td>
                <td><a class="btn btn-default" href="#" th:href="${'/widget/edit/' + widget.id}">Edit</a> </td>
                <td><a class="btn btn-default" href="#" th:href="${'/widget/delete/' + widget.id}">Delete</a> </td>
            </tr>
        </table>
        <a th:href="${'/widget/new'}" class="btn btn-default">New</a>
    </div>
    <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    <script src="../static/js/bootstrap.min.js" th:src="@{js/bootstrap.min.js}"></script>
</body>
</html>

Thymeleaf templates are HTML files that import the "http://www.thymeleaf.org" XML namespace, as seen in the html tag. With this namespace included (as "th"), Thymeleaf allows us to add tags to existing HTML elements. This enables you to create and style your HTML files without running your application in Tomcat.

For this example, we've added the Twitter Bootstrap library, which makes it easier to create a nice layout. Rather than constantly restarting Tomcat to view pages, this lets us build the page using an editor, then render the page in a browser. In order to work with your JavaScript and CSS locally you also need to include the Javascript href and script source src values that point to your relative CSS and Javascript files, respectively. The th:href and th:src attributes will be resolved to actual URI paths by Thymeleaf in a live application, and will overwrite the href and src attribute values when the page is rendered.

The HTML file in Listing 6 contains a table that shows a summary of each widget, with buttons to view the details of a widget, edit a widget, or delete a widget. We use the model to pass in a collection of widgets, and we access those by adding the th:each attribute to the tr (table row) tag, where th:each is Thymeleaf's implementation of a for-each construct. This will create one row for each item in our collection. The th:each attribute is passed the value "widget : ${widgets}". This retrieves the "widgets" collection from the model and assigns each widget to a local variable, "widget", that will be used inside the row. We can then extract values from the widget using the "${widget.propertyName}" syntax.

For the first three rows, we include the th:text attribute in each table cell (td), extracting fields from the widget. For the last three rows, we add hyperlinks to our other controller URIs:

  • /widget/{id} shows the widget with the specified ID.
  • /widget/edit/{id} shows the widget with the specified ID in an editable form.
  • /widget/delete/{id} deletes the widget with the specified ID, then shows the updated list of widgets.

The bottom of the page adds a New button that allows the user to create a new widget. Widgets are created through the /widget/new URL and presented in the same form as the /widget/edit/{id} URI.

Figure 4 shows a screenshot of the widget list page.

jw springmvc5 fig04 Steven Haines

Figure 4. Widget list page

When the user presses the View button, the getWidgetById method is called:


    @GetMapping("/widget/{id}")
    public String getWidgetById(@PathVariable Long id, Model model) {
        model.addAttribute("widget", widgetRepository.findById(id).orElse(new Widget()));
        return "widget";
    }

This method is mapped to a GET request for /widget/{id} and the id is extracted using the @PathVariable annotation. If you have multiple variables in your path, you can include them using the @PathVariable annotation with a variable name that matches the path variable name. In this case, the getWidgetById method retrieves the widget with the specified ID from the widgetRepository, then assigns it to the model's "widget" property.

You might have noted that the WidgetRepository.findById method returns an Optional<Widget>. Optionals in Java wrap objects: if the object is present then you can retrieve it using the get() method; if the object is not present then the Optional will be equal to Optional.empty(). You can determine if the result has an object by calling the isPresent() method. Alternatively, as I did in this case, you could leverage the orElse() method, which returns the object if it is present. Otherwise, it returns the widget created inside the orElse method. In a production application you would want to return a valid widget or return an error page. For now, we can simply return an empty widget. Finally, this method returns "widget', which tells Spring MVC to render this Widget using the "widget.html" template, shown in Listing 7.

1 2 3 Page 2
Page 2 of 3