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.
You can follow the below instructions or download the complete project here.
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.
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.
<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>
Support for building web applications, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container
We need tomcat-embed-jasper to compile JSP files. spring-boot-starter-web does not have the capabilities to compile JSP files.
JavaServer Pages Standard Tag Library is a collection of expressions that we can use to process data in JSP files.
The JSP files usually reside in the WEB-INF folder.
<!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>
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.
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";
}
}
Edit application.properties and add below lines
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.
To enable Spring data JPA, we need a few additional dependencies added to the pom.xml.
<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>
Adds Maven support for Spring Data JPA
Provides a MySql database connector for JPA to connect to the MySql database.
Boot Starter Tomcat provides the default embedded container for Spring Boot Starter Web. That is different from tomcat-embed-jasper.
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.
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;
In spring boot, we can specify the data base connection settings in the application.properties file.
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.
I put all my domain objects in the domain folder located in com.web.learnbestcoding package.
My entity class SupportTickets.java represents the support_tickets table. The SupportTickets.java is in the domain folder.
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;
}
}
My SupportTicketRepository extends CrudRepository. The SupportTicketRepository inherits several useful methods by extending the CrudRepository.
package com.web.learnbestcoding.domaim;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface SupportTicketRepository extends CrudRepository<SupportTickets, Long>{
}
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.
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);
}
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.
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
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
<!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
<!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