Spring boot web app with JSP and Spring Data JPA CRUD

Last updated : Jul 30, 2023 12:00 AM

This article explains how to create a Spring Boot web app with Spring Data JPA and perform CRUD operations. CRUD stands for Create, Read, Update, and Delete.

I use a simple Spring boot application built with Spring Data Jpa and Tomcat Support to demonstrate CRUD operations. The application is to log support tickets for customers. A user can create, read, update, and delete support tickets.

Figure 1 : Spring boot demo application
Figure 1 : Spring boot demo application

1. Setting up Spring Boot Project

You can follow the below instructions or download the complete project here.

  1. Navigate to https://start.spring.io
  2. Choose Maven Project as Project and Java as Language
  3. Fill in Project Metadata
  4. Select Jar for packaging and the Java version
  5. Click Generate

Do not add any dependencies. We will add them manually.

Clicking Generate will download your Spring boot template. You will have to unzip it and import it to your preferred IDE.

2. Managing Spring Boot dependencies in pom.xml

Now, open the pom.xml file in your editor. The next code snippet shows the dependencies you need to deploy this project. Compare your pon.xml and add any missing dependencies. The usages of these dependencies are described below.

pom.xmlDescription
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.apache.tomcat.embed</groupId>
   <artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
   <groupId>javax.servlet</groupId>
   <artifactId>jstl</artifactId>
</dependency>

2.1 What is spring-boot-starter-web?

Support for building web applications, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container

2.2 What is tomcat-embed-jasper?

We need tomcat-embed-jasper to compile JSP files. spring-boot-starter-web does not have the capabilities to compile JSP files.

2.3 What is JSTL?

JavaServer Pages Standard Tag Library is a collection of expressions that we can use to process data in JSP files.

3. Adding JSP to Spring boot

The JSP files usually reside in the WEB-INF folder.

  1. Create WEB-INF folder in webapp folder. Our JSP files will reside in WEB-INF/jsp folder
  2. Create home_page.jsp file in WEB-INF/jsp folder
home_page.jspDescription
<!DOCTYPE html>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
    <link href="/css/styles.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <form action="/read" method="post">
      <fieldset>
        <legend>Information:</legend>
        <label for="fname">Project name:</label><br>
        <select id="project" name="project">
            <option value="0">Select Project</option>
            <option value="1">Customers</option>
        </select>
        <input type="submit" value="Submit">
      </fieldset>
    </form>
</div>
</body>
</html>

4. Adding Controller to Spring Boot

Java files reside in the src/main/java file. Our controller will reside in com.web.learnbestcoding package in the src/main/java folder. All of our project-specific Java files will reside in com.web.learnbestcoding package. Therefore, I will bring the Spring boot app initializer file Application.java (DemoApplication.java by default) to the root of com.web.learnbestcoding. That will enable Spring to scan all annotations found within com.web.learnbestcoding package and any sub-packages.

  1. Create a package to hold Java resources (com.web.learnbestcoding)
  2. Move Application.java to the root of the Java source package (com.web.learnbestcoding)
  3. Create a package to hold controllers (controller is our package name)
  4. Create a controller named HomePageController.java (any name you like)
HomePageController.javaDescription
package com.web.learnbestcoding.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping
public class HomePageController {
   @RequestMapping(value = "/", method = RequestMethod.GET)
      public String viewPage(Model model) {
         return "home_page";
      }
}

5. Add a view resolver: this is how Spring finds the views that we specify in controllers

Edit application.properties and add below lines

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

That means Spring finds the specified view in /WEB-INF/jsp/ folder and suffixes it with .jsp. For example return "home_page"; in controller will result in /WEB-INF/jsp/ + home_page + .jsp

Now we have a Spring boot application with a simple Jsp and a Controller.

To test what we have so far, right-click on Application.java and select Run as Java Application or Run Application.main(). Open a browser and type localhost:8080.

6. Enabling Spring Data JPA in Spring boot application

To enable Spring data JPA, we need a few additional dependencies added to the pom.xml.

7. Add Spring Data JPA support to pom.xml

pom.xmlDescription
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>

7.1 What is spring-boot-starter-data-jpa?

Adds Maven support for Spring Data JPA

7.2 What is mysql-connector-java?

Provides a MySql database connector for JPA to connect to the MySql database.

7.3 What is spring-boot-starter-tomcat?

Boot Starter Tomcat provides the default embedded container for Spring Boot Starter Web. That is different from tomcat-embed-jasper.

8. Create a simple MySQL Database Schema

A simple table represents customer support tickets that we use in our application. The schema name is customers, and the table name is support_tickets.

support_tickets tableDescription
CREATE DATABASE `customers`;
USE `customers`;
CREATE TABLE `support_tickets` (
  `id` int NOT NULL AUTO_INCREMENT,
  `ticket_id` int NOT NULL,
  `description` varchar(45) NOT NULL,
  `status` tinyint NOT NULL DEFAULT '0',
  `date` datetime NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ticket_id_UNIQUE` (`ticket_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

9. Spring Data JPA Data source

In spring boot, we can specify the data base connection settings in the application.properties file.

application.propertiesDescription
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/customers
spring.datasource.username=root
spring.datasource.password=password

Make sure that your database schema name matches spring.datasource.url.

10. Creating Spring Data JPA Entity and Repository

I put all my domain objects in the domain folder located in com.web.learnbestcoding package.

10.1 Create the JPA entity class

My entity class SupportTickets.java represents the support_tickets table. The SupportTickets.java is in the domain folder.

SupportTickets.javaDescription
package com.web.learnbestcoding.domaim;
import javax.persistence.*;
import java.util.Date;

@Entity
@Table(name = "support_tickets")

public class SupportTickets {
   @Id
   @GeneratedValue(strategy=GenerationType.IDENTITY)
   @Column(name="id")
   private Long id;
	
   @Column(name="ticket_id")
   private Long ticketId;
	
   @Column(name="description")
   private String description;
	
   @Column(name="status")
   private boolean status;
	
   @Column(name="date")
   @Temporal(TemporalType.TIMESTAMP)
   private Date date;

   public Long getId() {
      return id;
   }
   public void setId(Long id) {
      this.id = id;
   }
   public Long getTicketId() {
      return ticketId;
   }
   public void setTicketId(Long ticketId) {
      this.ticketId = ticketId;
   }
   public String getDescription() {
      return description;
   }
   public void setDescription(String description) {
      this.description = description;
   }
   public boolean isStatus() {
      return status;
   }
   public void setStatus(boolean status) {
      this.status = status;
   }
   public Date getDate() {
      return date;
   }
   public void setDate(Date date) {
      this.date = date;
   }
}

10.2 Create the JPA Repository

My SupportTicketRepository extends CrudRepository. The SupportTicketRepository inherits several useful methods by extending the CrudRepository.

SupportTicketRepositoryDescription
package com.web.learnbestcoding.domaim;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface SupportTicketRepository extends CrudRepository<SupportTickets, Long>{
}

10.3 Create Service interface and its implementation

As a best practice, I use a service layer between the controller and the JPA repository. If you pay close attention to the SupportTicketServiceImpl.java, you will see the inherited methods I mentioned earlier. All the methods I use in the service layer are provided by the CrudRepository.

SupportTicketService.javaDescription
package com.web.learnbestcoding.domaim;
import java.util.List;
import java.util.Optional;

public interface SupportTicketService {
   public List<SupportTickets> findSupportTickets();
   public void save(SupportTickets customer);
   public void delete(SupportTickets customer);
   public Optional<SupportTickets> findSupportTicket(Long id);
   void update(SupportTickets customer);
}
supportTicketServicImpl.javaDescription
package com.web.learnbestcoding.domaim;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

@Service("supportTicketServicImpl")
public class SupportTicketServiceImpl implements SupportTicketService {

   @Autowired
   private SupportTicketRepository supportTicketRepository;
   @Override
   public List<SupportTickets> findSupportTickets() {
      return (List) supportTicketRepository.findAll();
   }
   @Override
   public Optional<SupportTickets> findSupportTicket(Long id) {
      return supportTicketRepository.findById(id);
   }
   @Override
   public void update(SupportTickets customer) {
      supportTicketRepository.save(customer);
   }
   @Override
   @Transactional
   public void save(SupportTickets customer) {
      supportTicketRepository.save(customer);
   }
   @Override
   @Transactional
   public void delete(SupportTickets customer) {
      supportTicketRepository.delete(customer);
   }
}

Now we have integrated Spring Data JPA into our application.

11. Using Spring Data JPA repository in Controller

As I mentioned earlier, I access the repository through the service layer. My controller has an auto-wired reference to the service SupportTicketServiceImpl.java.

Complete listing of HomePageController.java

HomePageController.javaDescription
package com.web.learnbestcoding.controller;
import com.web.learnbestcoding.domaim.SupportTickets;
import com.web.learnbestcoding.domaim.SupportTicketService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;

@Controller
   @RequestMapping
   public class HomePageController {
   @Resource(name="supportTicketServicImpl")
   SupportTicketService supportTicketService;

   @RequestMapping(value = "/", method = RequestMethod.GET)
      public String viewPage(Model model) {
         return "home_page";
   }
   @RequestMapping(value = "/read", method = RequestMethod.POST)
   public String customers(HttpServletRequest req) {
      if(req.getParameter("project").equals("1")) {
         List tickets = supportTicketService.findSupportTickets();
         req.setAttribute("tickets", tickets);
      }
      else{
         req.setAttribute("error", "Please select a project");
      }
      return "home_page";
   }
   @RequestMapping(value = "/createPage", method = RequestMethod.GET)
   public String createPage(Model model) {
      model.addAttribute("ticket", new SupportTickets());
      model.addAttribute("pageTitle", "Create Ticket");
      model.addAttribute("formAction", "/create");
      return "create_page";
   }
   @RequestMapping(value = "/create", method = RequestMethod.POST)
   public String create(@ModelAttribute("ticket") SupportTickets customer, Model model) {
      customer.setDate(new Date());
      supportTicketService.save(customer);
      List tickets = supportTicketService.findSupportTickets();
      model.addAttribute("tickets", tickets);
      return "home_page";
   }
   @RequestMapping(value = "/updatePage", method = RequestMethod.GET)
   public String updatePage(HttpServletRequest req) {
      Long ticketId = Long.valueOf(req.getParameter("id"));
      req.setAttribute("ticket", supportTicketService.findSupportTicket(ticketId));
      req.setAttribute("pageTitle", "Update Ticket");
      req.setAttribute("formAction", "/update");
      return "create_page";
   }
   @RequestMapping(value = "/update", method = RequestMethod.POST)
   public String update(@ModelAttribute("ticket") SupportTickets customer, Model model) {
      customer.setDate(new Date());
      supportTicketService.update(customer);
      List tickets = supportTicketService.findSupportTickets();
      model.addAttribute("tickets", tickets);
      return "home_page";
   }
   @RequestMapping(value = "/deletePage", method = RequestMethod.GET)
   public String deletePage(HttpServletRequest req) {
      Long ticketId = Long.valueOf(req.getParameter("id"));
      req.setAttribute("ticket", supportTicketService.findSupportTicket(ticketId));
      req.setAttribute("pageTitle", "Delete Ticket");
      req.setAttribute("formAction", "/delete");
      return "create_page";
   }
   @RequestMapping(value = "/delete", method = RequestMethod.POST)
   public String delete(HttpServletRequest req) {
      Long ticketId = Long.valueOf(req.getParameter("id"));
      SupportTickets customer = supportTicketService.findSupportTicket(ticketId).get();
      supportTicketService.delete(customer);
      List tickets = supportTicketService.findSupportTickets();
      req.setAttribute("tickets", tickets);
      return "home_page";
   }
}

Complete listing of home_page.jsp for reads

homr_page.jspDescription
<!DOCTYPE html>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
    <link href="/css/styles.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <form action="/read" method="post">
      <fieldset>
        <legend>Information:</legend>
        <label for="fname">Project name:</label><br>
        <select id="project" name="project">
            <option value="0">Select Project</option>
            <option value="1">Customers</option>
        </select>
        <input type="submit" value="Submit">
      </fieldset>
    </form>

<c:if test="${not empty error}">
    <br/>
    <div class="error">${error}</div>
</c:if>

<c:if test="${not empty tickets}">
<div>
<br/>
<table id="customers">
  <tr>
    <th>Ticket Id</th>
    <th>Description</th>
    <th>Status</th>
    <th>Date</th>
    <th>Actions</th>
  </tr>
  <c:forEach items="${tickets}" var="ticket">
      <tr>
          <td><c:out value="${ticket.ticketId}"/></td>
          <td><c:out value="${ticket.description}"/></td>
          <td><c:out value="${ticket.status eq 'true' ? 'Closed' : 'Open'}"/></td>
          <td><fmt:formatDate value="${ticket.date}" pattern="MMM-dd-yyyy"/></td>
          <td><a href="/updatePage?id=${ticket.id}">Update</a>   <a href="/deletePage?id=${ticket.id}">Delete</a></td>
      </tr>
  </c:forEach>
  <tr>
    <td colspan="5">
       <form action="/createPage">
          <input type="submit" value="Add New">
       </form>
    </td>
  </tr>
</table>
</div>
</c:if>
</div>
</body>
</html>

Complete listing of crete_page.jsp for creates, updates, and deletes

create_page.jspDescription
<!DOCTYPE html>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
    <link href="/css/styles.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <form:form action="${formAction}" method="POST" modelAttribute="ticket">
      <fieldset>
        <legend>${pageTitle}:</legend>
        <br/>
        <form:input type="hidden" path="id"/>
        <form:label path="ticketId">Ticket Id:</form:label><br>
        <form:input type="text" path="ticketId"/>
        <form:label path="description">Description:</form:label><br>
        <form:input type="text" path="description"/>
        <form:label path="status">Ticket Status:</form:label><br>
        <form:select path="status">
            <form:option value="false">Open</form:option>
            <form:option value="true">Closed</form:option>
        </form:select>
        <input type="submit" value="${pageTitle}">
      </fieldset>
    </form:form>
</div>
</body>
</html>

I have demonstrated all four CRUD operations. There are further changes to the JSP pages to accommodate those additional functions. I am not going to explain those in detail as it is out of the scope of this tutorial. But you can download this project and deploy it to see how they work.

Download Spring boot JSP CRUD Project

Lance

By: Lance

Hi, I'm Lance Raney, a dedicated Fullstack Developer based in Oklahoma with over 15 years of exp

Read more...