«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:// локальный: 8080.
Если сборка успешно скомпилирована, мы должны увидеть базовую пользовательскую панель управления CRUD со ссылками для добавления новых объектов, а также для редактирования и удаления существующих.
8. Заключение
В этом руководстве мы узнали, как создать базовое веб-приложение CRUD с помощью Spring Boot и Thymeleaf.
Как обычно, все примеры кода, показанные в статье, доступны на GitHub.