学习目标
- [7.1 模型开发](#7.1 模型开发)
-
- 7.1.1entity
- [7.1.2 repository](#7.1.2 repository)
- [7.1.3 service](#7.1.3 service)
- [7.2 功能实现](#7.2 功能实现)
-
- [7.2.1 新增](#7.2.1 新增)
- [7.2.2 删除](#7.2.2 删除)
- [7.2.3 修改](#7.2.3 修改)
(如果没有了解可以去我主页看看 第一至六章的内容来学习)我们已经对Spring Boot相关知识点进行了系统学习,包括项目构建、数据访问层、表示层等。考虑到相关技术点的使用热度,及技术点学完之后的练习目的,这章节我们来进行使用JPA+Thymeleaf完成用户的登录及完整的增删改查操作,并将拦截器,Ajax等技术点整合进来,为后面学习Shiro框架打好基础,做好前期准备工作。
7.1 模型开发
模型包括entity、repository、service。
7.1.1entity
在Java Web开发中,结合JPA(Java Persistence API)用于数据库操作与Thymeleaf作为模板引擎来渲染HTML页面是一种常见且强大的组合。下面我将为你提供一个简单的示例,展示如何定义一个JPA实体类,并使用Spring Boot框架结合Thymeleaf来显示这些实体数据。
1. JPA 实体类定义
首先,我们需要定义一个JPA实体类。假设我们正在处理一个简单的User实体,包含id、name和email属性。
java
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// 构造函数、getter和setter省略
public User() {
}
public User(String name, String email) {
this.name = name;
this.email = email;
}
// getter 和 setter
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
2. Repository 接口
在Spring Data JPA中,你可以通过定义一个继承自JpaRepository或CrudRepository的接口来自动实现CRUD操作。
java
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
3. Controller 类
然后,我们需要一个Controller来处理HTTP请求,并使用Thymeleaf来渲染页面。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/")
public String index(Model model) {
List<User> users = userRepository.findAll();
model.addAttribute("users", users);
return "users"; // 返回的Thymeleaf模板名
}
}
4. Thymeleaf 模板
最后,你需要一个Thymeleaf模板来显示用户数据。在src/main/resources/templates目录下创建一个名为users.html的文件。
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Users</title>
</head>
<body>
<h1>Users</h1>
<ul>
<li th:each="user : ${users}">
<span th:text="${user.name}">Name</span> -
<span th:text="${user.email}">Email</span>
</li>
</ul>
</body>
</html>
这个简单的示例展示了如何在Spring Boot应用程序中结合使用JPA和Thymeleaf。当访问根URL(/)时,控制器会查询所有用户,并将它们传递给users.html模板,该模板将使用Thymeleaf语法遍历并显示用户列表。
7.1.2 repository
在JPA与Thymeleaf结合使用的Spring Boot应用程序中,Repository 接口的Java代码通常负责定义与数据库交互的方法。这些接口继承自Spring Data JPA的JpaRepository或CrudRepository,从而自动获得CRUD(创建、读取、更新、删除)操作的能力,而无需编写额外的实现代码。
以下是一个简单的UserRepository接口示例,它扩展了JpaRepository,用于处理User实体的数据库操作:
java
import org.springframework.data.jpa.repository.JpaRepository;
// 指定实体类和主键类型
public interface UserRepository extends JpaRepository<User, Long> {
// 这里可以添加自定义的查询方法,但如果只是需要基本的CRUD操作,则无需添加任何方法
// Spring Data JPA 会根据方法名自动实现查询逻辑
// 例如,如果你想根据用户名查找用户,可以添加以下方法:
// List<User> findByName(String name);
// 注意:这只是一个示例,实际上Spring Data JPA支持更复杂的查询表达式
}
在这个例子中,UserRepository接口扩展了JpaRepository<User, Long>,其中User是实体类,Long是主键的类型。由于JpaRepository已经包含了所有基本的CRUD操作(如save(), findAll(), findById(), delete()等),因此你通常不需要在UserRepository接口中添加任何自定义方法,除非你需要执行更复杂的查询。
如果你需要执行更复杂的查询,你可以在UserRepository接口中添加自定义的方法,并使用Spring Data JPA的查询方法命名规则或@Query注解来定义查询逻辑。但请注意,这些自定义方法并不是必需的,仅当你需要它们时才添加。
例如,如果你想根据用户名查找用户,可以在UserRepository中添加一个自定义方法,如下所示:
java
// 自定义查询方法,根据用户名查找用户
List<User> findByName(String name);
或者,你可以使用@Query注解来编写更复杂的JPQL(Java Persistence Query Language)或原生SQL查询:
java
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
// 使用@Query注解编写自定义查询
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findUserByName(@Param("name") String name);
// 或者,如果你想使用原生SQL(不推荐,因为这会降低可移植性)
// @Query(value = "SELECT * FROM users WHERE name = ?", nativeQuery = true)
// List<User> findUserByNameNative(@Param("name") String name);
}
请注意:在使用原生SQL时,你需要确保查询返回的列与User实体类的属性完全匹配,或者你需要使用@SqlResultSetMapping来映射结果集到实体类或DTO(数据传输对象)。由于这可能会使代码更加复杂且难以维护,因此通常建议尽可能使用JPQL。
7.1.3 service
在JPA与Thymeleaf结合使用的Spring Boot应用程序中,Service层通常负责业务逻辑的处理,它调用Repository层的方法来执行数据访问操作,并可能对这些操作进行封装或添加额外的业务逻辑。然后,Controller层会调用Service层的方法,并将结果传递给Thymeleaf模板进行渲染。
以下是一个简单的UserService类示例,它演示了如何在JPA+Thymeleaf应用程序中编写服务层代码:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 注入UserRepository
// 查找所有用户
public List<User> findAllUsers() {
return userRepository.findAll(); // 调用Repository层的方法
}
// 根据ID查找用户(可选)
public User findUserById(Long id) {
return userRepository.findById(id).orElse(null); // 使用Optional来处理可能找不到用户的情况
}
// 保存用户(可选,如果允许用户创建)
public User saveUser(User user) {
return userRepository.save(user); // 调用Repository层的方法保存用户
}
// 更新用户(可选,通常基于ID更新)
public User updateUser(User user) {
// 注意:这里假设User实体已经包含了ID,JPA会根据ID来更新记录
return userRepository.save(user); // 调用Repository层的方法更新用户
}
// 删除用户(可选)
public void deleteUser(Long id) {
userRepository.deleteById(id); // 调用Repository层的方法删除用户
}
// ... 这里可以添加更多的业务逻辑方法
}
在上面的示例中,UserService类通过@Service注解被标记为一个服务组件,它注入了UserRepository来执行数据访问操作。UserService提供了几个方法,如findAllUsers()、findUserById(Long id)、saveUser(User user)、updateUser(User user)和deleteUser(Long id),这些方法分别用于查找所有用户、根据ID查找用户、保存用户、更新用户和删除用户。
请注意:在updateUser和deleteUser方法中,我们直接调用了userRepository的save和deleteById方法。在save方法中,JPA会根据实体的ID来判断是应该执行插入操作还是更新操作。如果传入的User对象具有一个非空的ID,并且数据库中已经存在具有该ID的记录,则JPA会执行更新操作;否则,会执行插入操作。
此外,在findUserById方法中,我们使用了Optional来处理可能找不到用户的情况。如果数据库中不存在具有指定ID的用户,则findById方法会返回一个空的Optional对象。通过调用orElse(null),我们可以将这个空的Optional对象转换为一个null值,但请注意,在实际应用中,将Optional转换为null可能不是最佳实践,因为它会丢失Optional提供的一些好处(如明确的空值处理)。更好的做法是在Controller层或调用UserService的其他层中直接处理Optional对象。
7.2 功能实现
7.2.1 新增
在JPA与Thymeleaf结合的Spring Boot应用程序中,新增数据通常涉及几个步骤:首先,在前端页面(Thymeleaf模板)上收集用户输入;然后,在控制器(Controller)层处理表单提交并调用服务层(Service)的方法;最后,在服务层中执行数据访问操作,将数据保存到数据库中。
以下是一个简化的示例,展示了在JPA+Thymeleaf应用程序中新增数据的Java代码部分。
1. 前端页面(Thymeleaf模板)
首先,你需要一个Thymeleaf模板来显示表单并收集用户输入。这里只给出表单部分的示例:
html
<!-- src/main/resources/templates/user/create.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Create User</title>
</head>
<body>
<h1>Create New User</h1>
<form action="#" th:action="@{/users}" th:object="${user}" method="post">
<div>
<label for="name">Name:</label>
<input type="text" id="name" th:field="*{name}" />
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" th:field="*{email}" />
</div>
<!-- 可以添加更多字段 -->
<button type="submit">Create User</button>
</form>
</body>
</html>
请注意:这里使用了Thymeleaf的th:action和th:object属性来指定表单的提交URL和表单绑定的对象。
2. 控制器(Controller)层
在控制器层,你需要处理表单的提交,并调用服务层的方法来保存新用户。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/create")
public String createUserForm(Model model) {
// 在模型中添加一个空的User对象,以便在表单中绑定
model.addAttribute("user", new User());
return "user/create"; // 返回Thymeleaf模板名称
}
@PostMapping
public String saveUser(User user) {
// 调用服务层方法来保存用户
userService.saveUser(user);
// 重定向到用户列表页面(或其他适当的页面)
return "redirect:/users";
}
}
3. 服务层(Service)
在服务层,你已经有了saveUser方法(如之前所示),它负责调用UserRepository来保存用户。
java
// UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// ... 其他方法 ...
public User saveUser(User user) {
return userRepository.save(user);
}
}
4. 仓库层(Repository)
仓库层(UserRepository)已经通过继承JpaRepository自动获得了save方法,因此你不需要在这里编写任何额外的代码来保存用户。
java
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
// 如果需要,可以在这里添加自定义查询方法
}
以上就是在JPA+Thymeleaf应用程序中新增数据的Java代码部分的基本流程。前端页面负责收集用户输入,控制器层处理表单提交并调用服务层的方法,服务层调用仓库层的方法将数据保存到数据库中。
7.2.2 删除
在JPA+Thymeleaf的Spring Boot应用程序中,删除数据通常涉及前端页面(Thymeleaf模板)上的删除操作按钮或链接、控制器(Controller)层处理删除请求,以及服务层(Service)层调用仓库层(Repository)来执行实际的删除操作。
以下是一个简化的示例,展示了在JPA+Thymeleaf应用程序中删除数据的Java代码部分。
1. 前端页面(Thymeleaf模板)
在前端页面上,你可能会有一个用户列表,每个用户旁边都有一个删除按钮或链接。这里使用按钮和表单的示例:
html
<!-- src/main/resources/templates/user/list.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>User List</title>
</head>
<body>
<h1>User List</h1>
<ul>
<li th:each="user : ${users}">
<span th:text="${user.name}">User Name</span>
<form th:action="@{/users/{id}/delete(id=${user.id})}" method="post" style="display:inline;">
<button type="submit">Delete</button>
</form>
</li>
</ul>
</body>
</html>
请注意:上面的代码示例使用了Thymeleaf的URL模板语法来构建删除请求的URL,但通常出于安全考虑,我们会使用GET请求进行删除操作的情况较少(尽管技术上可行)。更常见的是使用POST请求,并可能通过隐藏字段或JavaScript来发送请求,以避免直接在URL中暴露敏感信息。不过,为了简化示例,这里使用了POST请求。
2. 控制器(Controller)层
在控制器层,你需要处理删除请求,并调用服务层的方法来执行删除操作。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@Controller
public class UserController {
@Autowired
private UserService userService;
// ... 其他方法 ...
@PostMapping("/users/{id}/delete")
public String deleteUser(@PathVariable Long id) {
// 调用服务层方法来删除用户
userService.deleteUser(id);
// 重定向到用户列表页面(或其他适当的页面)
return "redirect:/users";
}
}
3. 服务层(Service)
在服务层,你已经有了deleteUser方法(如之前所示),它负责调用UserRepository来删除用户。
java
// UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// ... 其他方法 ...
public void deleteUser(Long id) {
// 调用仓库层的方法来删除用户
userRepository.deleteById(id);
}
}
4. 仓库层(Repository)
仓库层(UserRepository)已经通过继承JpaRepository自动获得了deleteById方法,因此你不需要在这里编写任何额外的代码来删除用户。
java
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
// 如果需要,可以在这里添加自定义查询方法
}
注意事项
- 在实际项目中,删除操作可能需要更复杂的逻辑,比如检查用户权限、记录删除日志等。
出于安全考虑,通常不建议直接在URL中暴露敏感操作(如删除),而是应该通过表单提交或AJAX请求来执行这些操作,并可能需要额外的验证或确认步骤。- 在处理删除请求时,务必确保你正在删除正确的记录,并考虑是否需要回滚事务或处理异常情况。
7.2.3 修改
在JPA+Thymeleaf的应用程序中,修改数据的流程与删除数据类似,但涉及到前端页面表单的提交、控制器层接收数据、服务层处理业务逻辑、以及仓库层执行数据库更新操作。以下是一个简化的示例,展示了在JPA+Thymeleaf应用程序中修改数据的Java代码部分。
1. 前端页面(Thymeleaf模板)
在前端页面上,你需要有一个表单,用户可以在其中输入或修改数据。表单的action属性应该指向控制器层中处理修改请求的方法的URL,而method属性应该是POST(或PUT,但POST更常见)。
html
<!-- src/main/resources/templates/user/edit.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Edit User</title>
</head>
<body>
<h1>Edit User</h1>
<form th:action="@{/users/{id}/update(id=${user.id})}" method="post" th:object="${user}">
<input type="hidden" th:field="*{id}" />
<label for="name">Name:</label>
<input type="text" id="name" th:field="*{name}" />
<!-- 其他字段... -->
<button type="submit">Update</button>
</form>
</body>
</html>
请注意:在这个例子中,我使用了Thymeleaf的th:object属性来绑定表单到模型中的user对象,并使用th:field来绑定每个输入字段到user对象的相应属性。
2. 控制器(Controller)层
在控制器层,你需要一个方法来处理修改请求,并从表单数据中提取用户输入,然后调用服务层的方法来处理更新操作。
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
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;
@Controller
public class UserController {
@Autowired
private UserService userService;
// ... 其他方法 ...
@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable Long id, Model model) {
User user = userService.findUserById(id);
model.addAttribute("user", user);
return "user/edit";
}
@PostMapping("/users/{id}/update")
public String updateUser(@PathVariable Long id, @ModelAttribute User user, BindingResult result) {
// 在这里,你可能需要添加一些验证逻辑
// 如果验证失败,可以返回编辑页面并显示错误信息
// 假设验证成功,我们调用服务层来更新用户
userService.updateUser(user);
// 重定向到用户列表页面或其他适当的页面
return "redirect:/users";
}
}
请注意:在这个例子中,我使用了@ModelAttribute来将表单数据绑定到User对象上。你可能还需要添加一些验证逻辑来确保用户输入是有效的。如果验证失败,你可能需要返回编辑页面并显示错误信息。
3. 服务层(Service)
在服务层,你需要一个updateUser方法来处理更新逻辑。这个方法通常会调用仓库层的方法来实际更新数据库中的记录。
java
// UserService.java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
// ... 其他方法 ...
public void updateUser(User user) {
// 调用仓库层的方法来更新用户
// 注意:如果User实体使用了@Id和@GeneratedValue注解,并且JPA配置正确,
// 那么在调用userRepository.save(user)时,Hibernate会自动根据ID来更新现有记录,
// 而不是插入新记录。
userRepository.save(user);
}
}
4. 仓库层(Repository)
仓库层(UserRepository)已经通过继承JpaRepository自动获得了save方法,该方法可以用于保存新实体或更新现有实体(基于实体的ID)。
java
// UserRepository.java
public interface UserRepository extends JpaRepository<User, Long> {
// 如果需要,可以在这里添加自定义查询方法
}
注意事项
- 在实际项目中,你可能需要添加额外的验证逻辑来确保用户输入是有效的。
- 当处理更新请求时,务必确保你正在更新正确的记录。在上面的例子中,我们通过将用户ID作为路径变量传递到控制器方法,并在服务层中使用该ID来查找要更新的用户对象,从而实现了这一点。
- 如果你正在使用Spring Security,那么你可能还需要在控制器方法中添加适当的权限检查来确保只有授权用户才能修改数据。