1、SpringDataJPA
JPA(Java Persistence API) 是指Java中数据对象持久化的过程。所谓持久化是指将内存中创建的数据对象永久的保存在数据库中,即在Java中我们通过JDBC连接数据库并进行增删改查的过程。在JDBC的基础上又出现了许多持久层框架用于简化Java和数据库的交互,其中两个较为流行的框架–Hibernate和Mybatis。但是我们依旧需要在Java中编写调用数据库的接口函数,为了统一和简化这个过程,SpringDataJPA在Hibernate的基础上基于JPA标准统一定义了操作数据库函数的相关接口,这样我们可以按照规范直接使用定义好的接口函数来操作数据库。
首先通过gradle引入SpringDataJPA的依赖以及JDBC数据库连接依赖
dependencies {
implementation('org.springframework.boot:spring-boot-starter-data-jpa') //Spring Data JPA
implementation('mysql:mysql-connector-java')
......
}
接着在application.properties文件中配置数据库连接和设置SpringJPA。其中ddl-auto
代表对数据表的操作方式,如果为create
则每次都会为实体对象创建新的数据表,例如为User对象在数据库中创建user表,如果已经存在则会删除原有表。若为update
代表在原有表的基础上进行更新。
spring jpa默认使用下划线的方式转换Java属性和数据库字段,例如userName会被映射到数据表的user_name字段,若想关闭可以设置hibernate.naming.physical-strategy为PhysicalNamingStrategyStandardImpl
# 数据库配置
spring.datasource.url=jdbc:mysql://localhost/blog
spring.datasource.username=root
spring.datasource.password=123456
# 控制台打印数据库查询语句
spring.jpa.show-sql=true
# 在原有数据表上进行更新
spring.jpa.hibernate.ddl-auto=update
# 命名映射策略
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
2、实体类
如下所示创建一个Java实体类User
,在其中定义五个字段并生成对应的getter/setter方法,并且需要创建一个空的构造函数User()
。
使用@Entity
表明这是一个实体类,并使用@Id
指明主键,@GeneratedValue
指明主键的生成策略,这里使用IDENTITY代表由数据库自动生成自增主键。通过@Column
注解指定属性在数据库中对应的列名,并设置是否唯一、是否不为空。
通过注解@Size
对属性进行校验,@NotEmpty
对为空进行校验并返回提示信息,@Email
对邮箱格式进行校验。
通过@OneToOne
可以实现数据表之间的一对一映射,类似地还有@OneToMany、@ManyToMany
其中CascadeType
属性指定级联操作,例如
- PERSIST:在保存User类时相关的UserImage也会保存到数据表中
- REMOVE:删除当前实体时,与它有映射关系的实体也会跟着被删除
- DETACH:删除当前实体和其外键,但不会删除映射的实体
- REFRESH:操作数据表时先进行刷新操作
- MERGE:当User中的数据改变,会相应地更新UserImage中的数据
- ALL:拥有以上所有级联操作权限
通过@JoinColumn
指定连接表的外键所在列
package com.tory.blog.entity;
import org.hibernate.annotations.CreationTimestamp;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Date;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import javax.validation.constraints.Email;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", unique = true, nullable = false)
private String username;
private String password;
@NotEmpty(message = "邮箱不能为空")
@Size(max=50)
@Email(message= "邮箱格式不对" )
private String email;
@OneToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
@JoinColumn(name = "img_id")
private UserImage userImg;
@CreationTimestamp // 由数据库自动创建时间
private Date createTime;
public User() {
}
//... getter/seter方法...
}
3、定义JPA接口
SpringDataJPA把对数据库的相关操作都封装在JPA接口中,通过继承接口就可以直接使用操作数据库的方法。按照不同用途SpringJPA提供了不同的接口,比如CrudRepository
封装了常用的数据库增删改查操作的接口,在此基础上PagingAndSortingRepository
接口封装了数据分页的函数。进一步JpaRepository
实现了实体查找、排序等相关方法。
如下所示为CrudRepository接口定义的方法
public interface CrudRepository<T, ID> extends Repository<T, ID> {
//保存实体对象
<S extends T> S save(S entity);
//保存多个实体对象
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
//根据id查找对象
Optional<T> findById(ID id);
//根据id判断对象是否存在
boolean existsById(ID id);
//返回所有实体
Iterable<T> findAll();
//根据id查询多个实体
Iterable<T> findAllById(Iterable<ID> ids);
//统计实体个数
long count();
//根据id进行删除
void deleteById(ID id);
//根据实体对象进行删除
void delete(T entity);
//删除多个实体
void deleteAll(Iterable<? extends T> entities);
//删除所有
void deleteAll();
}
我们可以在上面接口的基础上自定义操作数据库的接口函数,如下所示为继承自CrudRepository的UserRepo
接口,并且新增自定义了一些数据库操作函数。
在继承CrudRepository时通过泛型<User, Long>
指定返回主键为Long类型的User对象,接口中的方法会默认将返回的数据映射为User类型,并且将主键映射为Long。如果查询结果返回其他对象类型会出现映射错误。
SpringJPA可以根据规范的方法名自动生成相应的数据库操作方法,例如我们定义了findByUsername()
方法,它会自动根据username字段查找并返回User对象数组。Distinct
可以返回不重复的结果,OrderBy
可以对返回结果进行排序。
可以使用@Query
注解来自定义查询函数,其中占位符?1代表第一个参数。也可以使用占位符:firstname,并在之后用@Param指定
package com.tory.blog.repository;
import com.tory.blog.entity.User;
import org.springframework.data.repository.CrudRepository;
import java.util.List;
public interface UserRepo extends CrudRepository<User, Long> {
//根据方法名生成操作函数
List<User> findByUsername(String username);
//使用Distinct
List<User> findDistinctByUsername(String username);
//使用OrderBy
List<User> findByUsernameOrderByCreateTime(String username);
//自定义查询
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
//使用@Param
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname);
}
如下所示UserRepository继承自JpaRepository,并定义findByNameLike()
根据用户名进行模糊查询并返回分页的查询结果Page
,该方法除了名字外还需要传入分页对象Pageable。在UserServiceImpl类中构建分页对象pageable并调用该方法实现分页查询。
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findByNameLike(String name, Pageable pageable);
}
public class UserServiceImpl implements UserService, UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public List<User> listUsersByNameLike(String name, int pageIndex, int pageSize) {
name = "%" + name + "%"; // 模糊查询
Sort sort = Sort.by(Sort.Order.desc("id")); //根据用户id进行排序
Pageable pageable = PageRequest.of(pageIndex, pageSize, sort); //分页对象
Page<User> page = userRepository.findByNameLike(name, pageable);
List<User> userList = page.getContent(); //获取数据
return userList;
}
4、Spring中使用接口
如下所示直接在Spring的controller层中调用JPA接口完成和数据库的交互,首先通过@Autowire实例化userRepo
对象完成数据库相关操作。
其中用userRepo.findAll()
查询所有用户的信息。
userRepo.findById()
根据id查询用户信息,需要注意的是该方法返回的是Optional
类型的结果,如果查询到结果会放在Optional中,若无则Optional为空。可以通过isPresent()
判断结果集是否为空,若非空可以通过get()
获取结果集中的数据。
通过userRepo.save()
实现对象数据的保存。userRepo.deleteById()
实现对象的删除。
package com.tory.blog.controller;
import com.tory.blog.entity.User;
import com.tory.blog.repository.UserRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import java.util.Date;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserRepo userRepo;
//查询所有用户信息
@GetMapping("list")
public ModelAndView index() {
return new ModelAndView("user/list", "userList", userRepo.findAll());
}
//根据id查询单个用户信息
@GetMapping("{id}")
public ModelAndView getById(@PathVariable("id") Long id) {
ModelAndView modelAndView = new ModelAndView();
if (userRepo.findById(id).isPresent()) { //若查询结果不为空
modelAndView.addObject("user", userRepo.findById(id).get());
}
modelAndView.setViewName("user/user");
return modelAndView;
}
//新增用户
@GetMapping("add")
public ModelAndView add() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user", new User());
modelAndView.addObject("title", "添加用户");
modelAndView.setViewName("user/form");
return modelAndView;
}
//修改用户信息
@GetMapping("modify/{id}")
public ModelAndView modify(@PathVariable("id") Long id) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user", userRepo.findById(id).get());
modelAndView.addObject("title", "修改信息");
modelAndView.setViewName("user/form");
return modelAndView;
}
//保存用户user对象
@PostMapping("save")
public ModelAndView saveUser(User user) {
user.setCreateTime(new Date());
userRepo.save(user);
return new ModelAndView("redirect:/user/list");
}
//删除用户
@GetMapping("delete/{id}")
public ModelAndView delete(@PathVariable("id") Long id) {
userRepo.deleteById(id);
return new ModelAndView("redirect:/user/list");
}
}
前端表单页面如下
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title th:text="${title}"></title>
<script th:replace="common/head::static"></script>
</head>
<body>
<form action="/user/save" method="post" th:object="${user}">
<input type="hidden" name="id" th:value="*{id}"/>
姓名:<input type="text" name="username" th:value="*{username}"/><br>
邮箱:<input type="text" name="email" th:value="*{email}"/> <br>
密码:<input type="password" name="password" th:value="*{password}"/> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
当用户访问/user/add页面,填写并提交表单后会发送post请求到/user/save,来到Controller的saveUser()
方法,这时Spring会根据表单中的name属性生成相应的User对象,之后调用userRepo.save()
将user对象持久化到数据库中。数据表user如下所示: