1. Обзор
Реализация слоев DAO , которые обеспечивают функциональность CRUD для объектов JPA , может быть повторяющейся и трудоемкой задачей, которую мы хотим избежать в большинстве случаев.
К счастью, Spring Boot позволяет легко создавать приложения CRUD с помощью уровня стандартных репозиториев CRUD на основе JPA.
В этом руководстве мы узнаем, как разработать веб-приложение CRUD с помощью Spring Boot и Thymeleaf .
2. Зависимости Maven
В этом случае мы будем полагаться на spring-boot-starter-parent для простого управления зависимостями, управления версиями и настройки плагинов.
В результате нам не нужно будет указывать версии зависимостей проекта в нашем файле pom.xml
, за исключением переопределения версии Java:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
3. Уровень предметной области
Теперь, когда все зависимости проекта уже установлены, давайте реализуем наивный доменный слой.
Для простоты этот слой будет включать один единственный класс, который будет отвечать за моделирование пользовательских
сущностей:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@NotBlank(message = "Name is mandatory")
private String name;
@NotBlank(message = "Email is mandatory")
private String email;
// standard constructors / setters / getters / toString
}
Не будем забывать, что мы аннотировали класс аннотацией @Entity
. Следовательно, реализация JPA, которой является Hibernate, в этом случае сможет выполнять операции CRUD над сущностями домена. Чтобы получить вводное руководство по Hibernate, посетите наш учебник по Hibernate 5 с Spring .
Кроме того, мы ограничили поля имени и
адреса электронной почты ограничением
@NotBlank
. Это означает, что мы можем использовать Hibernate Validator для проверки ограниченных полей перед сохранением или обновлением объекта в базе данных.
Чтобы узнать об этом, ознакомьтесь с нашим соответствующим руководством по проверке компонентов .
4. Уровень репозитория
На данный момент наше тестовое веб-приложение ничего не делает. Но это скоро изменится.
Spring Data JPA позволяет нам реализовывать репозитории на основе JPA (причудливое название для реализации шаблона DAO) с минимальными усилиями.
Spring Data JPA является ключевым компонентом spring-boot-starter-data-jpa
Spring Boot, который упрощает добавление функциональности CRUD с помощью мощного уровня абстракции, размещенного поверх реализации JPA. Этот уровень абстракции позволяет нам получить доступ к уровню постоянства без необходимости создавать собственные реализации DAO с нуля.
Чтобы предоставить нашему приложению базовые функции CRUD для объектов User
, нам просто нужно расширить интерфейс CrudRepository
:
@Repository
public interface UserRepository extends CrudRepository<User, Long> {}
Вот и все! Расширяя интерфейс CrudRepository
, Spring Data JPA предоставит нам реализации для методов CRUD репозитория.
5. Уровень контроллера
Благодаря уровню абстракции, который spring-boot-starter-data-jpa
размещает поверх базовой реализации JPA, мы можем легко добавить некоторые функции CRUD в наше веб-приложение через базовый веб-уровень.
В нашем случае одного класса контроллера будет достаточно для обработки HTTP-запросов GET и POST и последующего сопоставления их с вызовами нашей реализации UserRepository
.
Класс контроллера опирается на некоторые ключевые функции Spring MVC. Чтобы получить подробное руководство по Spring MVC, ознакомьтесь с нашим руководством по Spring MVC .
Начнем с методов контроллера showSignUpForm()
и addUser()
.
Первый будет отображать форму регистрации пользователя, а второй сохранит новый объект в базе данных после проверки ограниченных полей.
Если объект не проходит проверку, форма регистрации будет отображаться повторно.
В противном случае, как только сущность будет сохранена, список сохраненных сущностей будет обновлен в соответствующем представлении:
@Controller
public class UserController {
@GetMapping("/signup")
public String showSignUpForm(User user) {
return "add-user";
}
@PostMapping("/adduser")
public String addUser(@Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
return "add-user";
}
userRepository.save(user);
return "redirect:/index";
}
// additional CRUD methods
}
Нам также понадобится сопоставление для URL- адреса /index
:
@GetMapping("/index")
public String showUserList(Model model) {
model.addAttribute("users", userRepository.findAll());
return "index";
}
Внутри UserController
у нас также будет метод showUpdateForm()
, который отвечает за выборку объекта User
, соответствующего предоставленному идентификатору
, из базы данных.
Если объект существует, он будет передан как атрибут модели в представление формы обновления.
Итак, форму можно заполнить значениями полей имени
и электронной почты :
@GetMapping("/edit/{id}")
public String showUpdateForm(@PathVariable("id") long id, Model model) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
model.addAttribute("user", user);
return "update-user";
}
Наконец, у нас есть методы updateUser()
и deleteUser()
в классе UserController
.
Первый сохранит обновленный объект в базе данных, а последний удалит данный объект.
В любом случае список сохраняемых сущностей будет соответствующим образом обновлен:
@PostMapping("/update/{id}")
public String updateUser(@PathVariable("id") long id, @Valid User user,
BindingResult result, Model model) {
if (result.hasErrors()) {
user.setId(id);
return "update-user";
}
userRepository.save(user);
return "redirect:/index";
}
@GetMapping("/delete/{id}")
public String deleteUser(@PathVariable("id") long id, Model model) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("Invalid user Id:" + id));
userRepository.delete(user);
return "redirect:/index";
}
6. Слой представления
На данный момент мы реализовали класс функционального контроллера, который выполняет операции CRUD над сущностями пользователя .
Несмотря на это, в этой схеме все еще отсутствует компонент: слой представления.
В папке src/main/resources/templates
нам нужно создать HTML-шаблоны, необходимые для отображения формы регистрации и формы обновления, а также для рендеринга списка постоянных объектов пользователя
.
Как указано во введении, мы будем использовать Thymeleaf в качестве базового механизма шаблонов для анализа файлов шаблонов.
Вот соответствующий раздел файла add-user.html
:
<form action="#" th:action="@{/adduser}" th:object="${user}" method="post">
<label for="name">Name</label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
<label for="email">Email</label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
<input type="submit" value="Add User">
</form>
Обратите внимание, как мы использовали URL-выражение @{/adduser}
для указания атрибута действия формы и выражения переменных
${}
для встраивания в шаблон динамического содержимого, например значений полей имени
и адреса электронной почты
и пост-валидации. ошибки.
Подобно add-user.html
, вот как выглядит шаблон update-user.html
:
<form action="#"
th:action="@{/update/{id}(id=${user.id})}"
th:object="${user}"
method="post">
<label for="name">Name</label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>
<label for="email">Email</label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></span>
<input type="submit" value="Update User">
</form>
Наконец, у нас есть файл index.html
, в котором отображается список сохраненных сущностей вместе со ссылками для редактирования и удаления существующих:
<div th:switch="${users}">
<h2 th:case="null">No users yet!</h2>
<div th:case="*">
<h2>Users</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
<td><a th:href="@{/edit/{id}(id=${user.id})}">Edit</a></td>
<td><a th:href="@{/delete/{id}(id=${user.id})}">Delete</a></td>
</tr>
</tbody>
</table>
</div>
<p><a href="/signup">Add a new user</a></p>
</div>
Для простоты шаблоны выглядят довольно скелетоподобно и предоставляют только необходимую функциональность без добавления ненужных косметических средств .
Чтобы придать шаблонам улучшенный, привлекательный вид, не тратя слишком много времени на HTML/CSS, мы можем легко использовать бесплатный набор пользовательского интерфейса Twitter Bootstrap , такой как Shards .
7. Запуск приложения
Наконец, давайте определим точку входа приложения.
Как и в большинстве приложений Spring Boot, мы можем сделать это с помощью простого старого метода main() :
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Теперь давайте нажмем «Выполнить» в нашей IDE, а затем откроем наш браузер и укажем его на http://localhost:8080
.
Если сборка успешно скомпилирована, мы должны увидеть базовую пользовательскую панель управления CRUD со ссылками для добавления новых объектов, а также для редактирования и удаления существующих.
8. Заключение
В этой статье мы узнали, как создать базовое веб-приложение CRUD с помощью Spring Boot и Thymeleaf.
Как обычно, все примеры кода, показанные в статье, доступны на GitHub .