En el tutorial anterior, desarrollamos una aplicación web CRUD utilizando Spring Boot y Thymeleaf.
En este tutorial, ampliaremos la aplicación web CRUD Spring Boot e implementaremos operaciones de paginación y clasificación utilizando Spring Boot, thymeleaf, Spring Data JDBC y la base de datos MySQL.
Como sabe, la paginación permite a los usuarios ver una pequeña porción de datos (una página) a la vez, y la clasificación les permite a los usuarios ver los datos de una manera más organizada. Tanto la paginación como la clasificación pueden ayudar a los usuarios a consumir información de forma más fácil y cómoda.
Comencemos con el proyecto del sistema de gestión de empleados que se puede descargar desde este tutorial , que se basa en la base de datos Spring Boot, Spring Data JDBC, Thymeleaf y MySQL.
requisitos previos
base de datos mysql
-- --------------------------------------------------------
-- 主机: 127.0.0.1
-- 服务器版本: 8.0.22 - MySQL Community Server - GPL
-- 服务器操作系统: Win64
-- HeidiSQL 版本: 11.3.0.6295
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
-- 导出 testdb 的数据库结构
CREATE DATABASE IF NOT EXISTS `testdb` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `testdb`;
-- 导出 表 testdb.employees 结构
CREATE TABLE IF NOT EXISTS `employees` (
`id` bigint NOT NULL AUTO_INCREMENT,
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`first_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`last_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 正在导出表 testdb.employees 的数据:~7 rows (大约)
/*!40000 ALTER TABLE `employees` DISABLE KEYS */;
INSERT IGNORE INTO `employees` (`id`, `email`, `first_name`, `last_name`) VALUES
(1, '1', '1', '1'),
(2, '2', '2', '2'),
(3, '3', '3', '3'),
(4, '4', '4', '4'),
(5, '5', '5', '5'),
(6, '6', '6', '6'),
(7, '7', '7', '7'),
(8, '8', '8', '8'),
(9, '9', '9', '9');
/*!40000 ALTER TABLE `employees` ENABLE KEYS */;
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */;
Requerimientos del proyecto
Este es un requisito avanzado del proyecto para implementar la paginación y clasificación de páginas de lista de empleados (sistema de gestión de empleados) .
Los usuarios deberían poder:
- Realizar paginación en la página de lista de empleados
- Ordenar en la página de lista de empleados
¿Qué construiremos?
Usaremos Spring Boot, Thymeleaf, Spring Data JDBC y la base de datos MySQL para implementar la paginación y la clasificación.
La siguiente captura de pantalla resume las operaciones de paginación y clasificación que desarrollaremos en este tutorial.
Paginación:
Ordenar por:
Primero, implementaremos la operación de paginación paso a paso y luego completaremos la operación de clasificación.
1. Implementación de paginación
Comprender la API de paginación de Spring Data JDBC
Para utilizar la API de paginación y clasificación proporcionada por Spring Data JDBC, la interfaz de su repositorio debe extender la interfaz PagingAndSortingRepository, que define los siguientes métodos (T se refiere a la clase de entidad):
@NoRepositoryBean
public interface PagingAndSortingRepository < T, ID > extends CrudRepository < T, ID > {
/**
* Returns all entities sorted by the given options.
*
* @param sort
* @return all entities sorted by the given options
*/
Iterable < T > findAll(Sort sort);
/**
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
*
* @param pageable
* @return a page of entities
*/
Page < T > findAll(Pageable pageable);
}
Por ejemplo, utilice el siguiente comando para obtener la primera página con 5 elementos por página de la base de datos:
int pageNumber = 1;
int pageSize = 5;
Pageable pageable = PageRequest.of(pageNumber, pageSize);
Page<Product> page = repository.findAll(pageable);
Luego puede obtener el contenido real de la siguiente manera:
List<Employee> listEmployees = page.getContent();
Usando el objeto Página, puede conocer el número total de filas y el total de páginas en la base de datos en función de un tamaño de página determinado:
long totalItems = page.getTotalElements();
int totalPages = page.getTotalPages();
Esta información es útil para implementar la paginación en vistas usando plantillas de Thymeleaf.
Cambios de backend para la paginación
Cambios en la interfaz EmployeeService.java
Agregue los siguientes métodos a esta interfaz:
Page<Employee> findPaginated(int pageNo, int pageSize);
Código completo:
package net.javaguides.springboot.service;
import java.util.List;
import org.springframework.data.domain.Page;
import net.javaguides.springboot.model.Employee;
public interface EmployeeService {
List < Employee > getAllEmployees();
void saveEmployee(Employee employee);
Employee getEmployeeById(long id);
void deleteEmployeeById(long id);
Page < Employee > findPaginated(int pageNo, int pageSize);
}
Cambios en la clase EmployeeServiceImpl.java
Agregue el siguiente método a la clase EmployeeServiceImpl:
@Override
public Page<Employee> findPaginated(int pageNo, int pageSize) {
Pageable pageable = PageRequest.of(pageNo - 1, pageSize);
return this.employeeRepository.findAll(pageable);
}
Cambios en la clase EmployeeController.java
Agregue el siguiente método de controlador a la clase EmployeeController para realizar la paginación:
@GetMapping("/page/{pageNo}")
public String findPaginated(@PathVariable(value = "pageNo") int pageNo, Model model) {
int pageSize = 5;
Page < Employee > page = employeeService.findPaginated(pageNo, pageSize);
List < Employee > listEmployees = page.getContent();
model.addAttribute("currentPage", pageNo);
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("listEmployees", listEmployees);
return "index";
}
Además, debemos realizar cambios en el método existente de la siguiente manera:
// display list of employees
@GetMapping("/")
public String viewHomePage(Model model) {
return findPaginated(1, model);
}
Cambios en la interfaz de paginación
Agregue el siguiente código de paginación a index.html:
<div th:if="${totalPages > 1}">
<div class="row col-sm-10">
<div class="col-sm-2">
Total Rows: [[${totalItems}]]
</div>
<div class="col-sm-1">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span>
</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}}">Next</a>
<span th:unless="${currentPage < totalPages}">Next</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}}">Last</a>
<span th:unless="${currentPage < totalPages}">Last</span>
</div>
</div>
</div>
Código completo:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Employee Management System</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>
<body>
<div class="container my-2">
<h1>Employees List</h1>
<a th:href="@{/showNewEmployeeForm}" class="btn btn-primary btn-sm mb-3"> Add Employee </a>
<table border="1" class="table table-striped table-responsive-md">
<thead>
<tr>
<th>Employee First Name</th>
<th>Employee Last Name</th>
<th>Employee Email</th>
<th> Actions </th>
</tr>
</thead>
<tbody>
<tr th:each="employee : ${listEmployees}">
<td th:text="${employee.firstName}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td> <a th:href="@{/showFormForUpdate/{id}(id=${employee.id})}" class="btn btn-primary">Update</a>
<a th:href="@{/deleteEmployee/{id}(id=${employee.id})}" class="btn btn-danger">Delete</a>
</td>
</tr>
</tbody>
</table>
<div th:if="${totalPages > 1}">
<div class="row col-sm-10">
<div class="col-sm-2">
Total Rows: [[${totalItems}]]
</div>
<div class="col-sm-1">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span>
</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}}">Next</a>
<span th:unless="${currentPage < totalPages}">Next</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}}">Last</a>
<span th:unless="${currentPage < totalPages}">Last</span>
</div>
</div>
</div>
</div>
</body>
</html>
2. Implementación de clasificación
Comprender la API de clasificación de Spring Data JPA
Spring Data JDBC proporciona la interfaz PagingAndSortingRepository, que admite la clasificación y paginación utilizando las siguientes API:
@NoRepositoryBean
public interface PagingAndSortingRepository < T, ID > extends CrudRepository < T, ID > {
/**
* Returns all entities sorted by the given options.
*
* @param sort
* @return all entities sorted by the given options
*/
Iterable < T > findAll(Sort sort);
/**
* Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object.
*
* @param pageable
* @return a page of entities
*/
Page < T > findAll(Pageable pageable);
}
Los usuarios podrán ordenar la lista de empleados haciendo clic en los encabezados de las columnas de la tabla.
Primero, crea un objeto Ordenar como este :
Sort sort = Sort.by(“fieldName”).ascending();
Esto ordenará los resultados por nombre de campo en orden ascendente. fieldName debe coincidir con el nombre del campo declarado en la clase de entidad.
También podemos ordenar por múltiples campos, por ejemplo:
Sort sort = Sort.by("fieldName1").ascending().and(Sort.by("fieldName2").descending());
Luego creamos un Pageable a través del objeto Ordenar , como se muestra a continuación:
Pageable pageable = PageRequest.of(pageNum - 1, pageSize, sort);
Aprendamos más en la sección de ejemplos.
Cambios de backend para ordenar
Cambios en la interfaz EmployeeService.java
Agreguemos dos campos al método existente:
Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection);
Código completo:
package net.javaguides.springboot.service;
import java.util.List;
import org.springframework.data.domain.Page;
import net.javaguides.springboot.model.Employee;
public interface EmployeeService {
List < Employee > getAllEmployees();
void saveEmployee(Employee employee);
Employee getEmployeeById(long id);
void deleteEmployeeById(long id);
Page < Employee > findPaginated(int pageNo, int pageSize, String sortField, String sortDirection);
}
Cambios en la clase EmployeeServiceImpl.java
Lógica de clasificación implementada en los siguientes métodos:
@Override
public Page<Employee> findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) {
Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() :
Sort.by(sortField).descending();
Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
return this.employeeRepository.findAll(pageable);
}
Código completo:
package net.javaguides.springboot.service;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.repository.EmployeeRepository;
@Service
public class EmployeeServiceImpl implements EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public List < Employee > getAllEmployees() {
return employeeRepository.findAll();
}
@Override
public void saveEmployee(Employee employee) {
this.employeeRepository.save(employee);
}
@Override
public Employee getEmployeeById(long id) {
Optional < Employee > optional = employeeRepository.findById(id);
Employee employee = null;
if (optional.isPresent()) {
employee = optional.get();
} else {
throw new RuntimeException(" Employee not found for id :: " + id);
}
return employee;
}
@Override
public void deleteEmployeeById(long id) {
this.employeeRepository.deleteById(id);
}
@Override
public Page < Employee > findPaginated(int pageNo, int pageSize, String sortField, String sortDirection) {
Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortField).ascending() :
Sort.by(sortField).descending();
Pageable pageable = PageRequest.of(pageNo - 1, pageSize, sort);
return this.employeeRepository.findAll(pageable);
}
}
Cambios en EmployeeController.java
Cambiemos el método existente para brindar soporte para la clasificación:
@GetMapping("/page/{pageNo}")
public String findPaginated(@PathVariable(value = "pageNo") int pageNo,
@RequestParam("sortField") String sortField,
@RequestParam("sortDir") String sortDir,
Model model) {
int pageSize = 5;
Page < Employee > page = employeeService.findPaginated(pageNo, pageSize, sortField, sortDir);
List < Employee > listEmployees = page.getContent();
model.addAttribute("currentPage", pageNo);
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("sortField", sortField);
model.addAttribute("sortDir", sortDir);
model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc");
model.addAttribute("listEmployees", listEmployees);
return "index";
}
También proporciona campos de clasificación predeterminados e instrucciones de clasificación para la página de inicio:
// display list of employees
@GetMapping("/")
public String viewHomePage(Model model) {
return findPaginated(1, "firstName", "asc", model);
}
Código completo:
package net.javaguides.springboot.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
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;
import net.javaguides.springboot.model.Employee;
import net.javaguides.springboot.service.EmployeeService;
@Controller
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
// display list of employees
@GetMapping("/")
public String viewHomePage(Model model) {
return findPaginated(1, "firstName", "asc", model);
}
@GetMapping("/showNewEmployeeForm")
public String showNewEmployeeForm(Model model) {
// create model attribute to bind form data
Employee employee = new Employee();
model.addAttribute("employee", employee);
return "new_employee";
}
@PostMapping("/saveEmployee")
public String saveEmployee(@ModelAttribute("employee") Employee employee) {
// save employee to database
employeeService.saveEmployee(employee);
return "redirect:/";
}
@GetMapping("/showFormForUpdate/{id}")
public String showFormForUpdate(@PathVariable(value = "id") long id, Model model) {
// get employee from the service
Employee employee = employeeService.getEmployeeById(id);
// set employee as a model attribute to pre-populate the form
model.addAttribute("employee", employee);
return "update_employee";
}
@GetMapping("/deleteEmployee/{id}")
public String deleteEmployee(@PathVariable(value = "id") long id) {
// call delete employee method
this.employeeService.deleteEmployeeById(id);
return "redirect:/";
}
@GetMapping("/page/{pageNo}")
public String findPaginated(@PathVariable(value = "pageNo") int pageNo,
@RequestParam("sortField") String sortField,
@RequestParam("sortDir") String sortDir,
Model model) {
int pageSize = 5;
Page < Employee > page = employeeService.findPaginated(pageNo, pageSize, sortField, sortDir);
List < Employee > listEmployees = page.getContent();
model.addAttribute("currentPage", pageNo);
model.addAttribute("totalPages", page.getTotalPages());
model.addAttribute("totalItems", page.getTotalElements());
model.addAttribute("sortField", sortField);
model.addAttribute("sortDir", sortDir);
model.addAttribute("reverseSortDir", sortDir.equals("asc") ? "desc" : "asc");
model.addAttribute("listEmployees", listEmployees);
return "index";
}
}
Cambios de interfaz ordenados
índice.html
Hacemos que la columna del encabezado de la tabla se pueda ordenar agregando un hipervínculo usando el siguiente código:
<th>
<a th:href="@{'/page/' + ${currentPage} + '?sortField=firstName&sortDir=' + ${reverseSortDir}}">
Employee First Name</a>
</th>
<th>
<a th:href="@{'/page/' + ${currentPage} + '?sortField=lastName&sortDir=' + ${reverseSortDir}}">
Employee Last Name</a>
</th>
<th>
<a th:href="@{'/page/' + ${currentPage} + '?sortField=email&sortDir=' + ${reverseSortDir}}">
Employee Email</a>
</th>
<th> Actions </th>
También necesitamos cambiar la parte de paginación para brindar soporte de clasificación, por ejemplo:
<div th:if = "${totalPages > 1}">
<div class = "row col-sm-10">
<div class = "col-sm-2">
Total Rows: [[${totalItems}]]
</div>
<div class = "col-sm-1">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span>
</span>
</div>
<div class = "col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Next</a>
<span th:unless="${currentPage < totalPages}">Next</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Last</a>
<span th:unless="${currentPage < totalPages}">Last</span>
</div>
</div>
</div>
Código completo:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="ISO-8859-1">
<title>Employee Management System</title>
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">
</head>
<body>
<div class="container my-2">
<h1>Employees List</h1>
<a th:href = "@{/showNewEmployeeForm}" class="btn btn-primary btn-sm mb-3"> Add Employee </a>
<table border="1" class = "table table-striped table-responsive-md">
<thead>
<tr>
<th>
<a th:href="@{'/page/' + ${currentPage} + '?sortField=firstName&sortDir=' + ${reverseSortDir}}">
Employee First Name</a>
</th>
<th>
<a th:href="@{'/page/' + ${currentPage} + '?sortField=lastName&sortDir=' + ${reverseSortDir}}">
Employee Last Name</a>
</th>
<th>
<a th:href="@{'/page/' + ${currentPage} + '?sortField=email&sortDir=' + ${reverseSortDir}}">
Employee Email</a>
</th>
<th> Actions </th>
</tr>
</thead>
<tbody>
<tr th:each="employee : ${listEmployees}">
<td th:text="${employee.firstName}"></td>
<td th:text="${employee.lastName}"></td>
<td th:text="${employee.email}"></td>
<td> <a th:href="@{/showFormForUpdate/{id}(id=${employee.id})}" class="btn btn-primary">Update</a>
<a th:href="@{/deleteEmployee/{id}(id=${employee.id})}" class="btn btn-danger">Delete</a>
</td>
</tr>
</tbody>
</table>
<div th:if = "${totalPages > 1}">
<div class = "row col-sm-10">
<div class = "col-sm-2">
Total Rows: [[${totalItems}]]
</div>
<div class = "col-sm-1">
<span th:each="i: ${#numbers.sequence(1, totalPages)}">
<a th:if="${currentPage != i}" th:href="@{'/page/' + ${i}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">[[${i}]]</a>
<span th:unless="${currentPage != i}">[[${i}]]</span>
</span>
</div>
<div class = "col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${currentPage + 1}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Next</a>
<span th:unless="${currentPage < totalPages}">Next</span>
</div>
<div class="col-sm-1">
<a th:if="${currentPage < totalPages}" th:href="@{'/page/' + ${totalPages}+ '?sortField=' + ${sortField} + '&sortDir=' + ${sortDir}}">Last</a>
<span th:unless="${currentPage < totalPages}">Last</span>
</div>
</div>
</div>
</div>
</body>
</html>
Repositorio de empleados.java
package net.javaguides.springboot.repository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import net.javaguides.springboot.model.Employee;
@Repository
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long> {
}
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.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>net.javaguides</groupId>
<artifactId>springboot-thymeleaf-crud-web-app-spring-data-jdbc</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-thymeleaf-crud-web-app-spring-data-jdbc</name>
<description>Demo project for Spring Boot and thymeleaf</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Enlace al repositorio de GitHub
GitHub - allwaysoft/springboot-thymeleaf-crud-pagination-sorting-webapp-spring-data-jdbc