二八佳人体似酥,腰间仗剑斩愚夫。虽然不见人头落,暗里教君骨髓枯。
上一章简单介绍了SpringBoot整合JdbcTemplate(五),如果没有看过,请观看上一章
日常生活中,我们并不使用 JdbcTemplate, 而是使用 JPA和MyBatis,MyBatis-Plus. 这一章节,我们讲解一下, JPA的相关操作。
一. SpringBoot 整合 JPA前期准备
按照老蝴蝶以前讲解的方式,采用Maven 构建SpringBoot项目。
一.一 pom.xml 添加依赖
<!--引入MySql的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入springboot与jpa整合的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
一.二 application.yml 添加JPA的配置
# 引入 数据库的相关配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: abc123
# JPA的相关配置
jpa:
# 设置数据库平台
database-platform: org.hibernate.dialect.MySQLDialect
# 设置数据库
database: mysql
# 是否展示SQL语句
show-sql: true
hibernate:
# 持久化规则是 update
ddl-auto: update
naming:
# 物理命名策略类的全限定名称
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
一.三 创建 User 表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(15) DEFAULT NULL,
`sex` varchar(20) DEFAULT NULL,
`age` int(6) DEFAULT NULL,
`description` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
二. 整合JPA应用
SpringBoot整合JPA时,需要创建相应的 实体类,工厂接口。
工厂接口有 Crud接口,有 PagingAndSorting,有 Jpa接口,也有 Specification 动态查询接口。
每一种接口,都有其特殊的功能。
二.一 创建 POJO 类和业务类
在 pojo 包下,创建 User.java 类
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
@Entity
public class User implements Serializable {
/**
* @param id id编号
* @param name 姓名
* @param sex 性别
* @param age 年龄
* @param description 描述
*/
@Id
//指定生成策略
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String name;
@Column(name="sex")
private String sex;
@Column(name="age")
private Integer age;
@Column(name="description")
private String description;
}
在 service包下,创建相应的接口和实现类
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
二.二 Crud 工厂接口
二.二.一 接口的相关定义
org.springframework.data.repository.CrudRepository
@NoRepositoryBean
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);
| 插入或者更新多个对象,批量操作
|
Optional findById(ID id);
| 根据id 查询对象
|
boolean existsById(ID id);
| 是否存在此id的记录
|
Iterable findAll();
| 查询所有的记录
|
Iterable findAllById(Iterable ids);
| 根据id集合查询相关的记录
|
long count();
| 统计数目
|
void deleteById(ID id);
| 根据id进行删除
|
void delete(T entity);
| 根据实体对象进行删除
|
void deleteAll(Iterable<? extends T> entities);
| 根据实体对象集合进行删除,批量操作
|
void deleteAll();
| 删除所有的记录
|
二.二.二. Crud 接口实现
在 repository 包下,创建 UserCrudRepository 接口。
public interface UserCrudRepository extends CrudRepository<User,Integer> {
}
二.二.三 Crud 测试
在 test测试目录下,创建对应的测试类和测试方法,进行测试。
@SpringBootTest
@Log4j2
public class CrudRepositoryTests {
@Autowired
private UserService userService;
}
二.二.三.一 插入 save 方法
测试方法:
@Test
public void addTest(){
//1. 构建对象
User user=new User();
user.setName("欢欢");
user.setAge(22);
user.setSex("女");
user.setDescription("一个非常可爱的女孩纸");
//2. 添加方法
userService.addUser(user);
log.info("添加成功,{}",user);
}
省略接口方法,其对应的实现方法
添加Crud接口注入
@Autowired
private UserCrudRepository userCrudRepository;
@Override
public void addUser(User user) {
userCrudRepository.save(user);
}
控制台打印输出:
二.二.三.二 更新 save 方法
测试方法
@Test
public void updateTest(){
//1. 构建对象
User user=new User();
user.setId(1); //id不存在,会添加
user.setName("欢欢");
user.setDescription("岳泽霖最好的朋友");
//2. 修改方法
userService.updateUser(user);
log.info("修改成功,{}",user);
}
接口实现方法
@Override
public void updateUser(User user) {
userCrudRepository.save(user);
}
控制台打印输出
先查询,发现有,就更新,如果没有的话,就插入。
二.二.三.三 删除 delete 方法
测试方法
@Test
public void deleteTest(){
userService.deleteUser(1);
}
接口实现方法
@Override
public void deleteUser(Integer id) {
userCrudRepository.deleteById(id);
}
数据库里面,没有此条数据了。
二.二.三.四 批量更新数据 saveAll
重新执行一下, save() 方法,插入一条数据, id=2.
测试方法:
@Test
public void batchAddTest(){
//1. 构建对象
User user=new User();
user.setName("小欢欢");
user.setAge(22);
user.setSex("女");
user.setDescription("一个小坏蛋");
User user1=new User();
user1.setName("小泽霖");
user1.setAge(25);
user1.setSex("男");
user1.setDescription("一个大坏蛋");
//这是修改的操作,id=2已经存在这条记录了。
User user2=new User();
user2.setName("岳泽霖");
user2.setId(2);
user2.setAge(25);
user2.setSex("男性");
user2.setDescription("一个快乐的程序员");
//2. 放置到集合里面
List<User> userList=new ArrayList<>();
userList.add(user);
userList.add(user1);
userList.add(user2);
userService.batchAddUser(userList);
}
接口实现方法:
@Override
public void batchAddUser(List<User> userList) {
userCrudRepository.saveAll(userList);
}
会插入前两条,更新第三条记录。
二.二.三.五 根据id 进行查询 findById
@Test
public void findByIdTest(){
User user=userService.findById(3);
log.info(user);
}
接口实现方法
@Override
public User findById(Integer id) {
Optional<User> optional= userCrudRepository.findById(id);
if(optional.isPresent()){
return optional.get();
}
return null;
}
二.二.三.六 查询所有的数据 findAll
@Test
public void findAllTest(){
List<User> userList=userService.findAll();
userList.forEach(n->log.info(n));
}
接口实现方法
@Override
public List<User> findAll() {
Iterable<User> iterator= userCrudRepository.findAll();
List<User> userList=new ArrayList<>();
//将 Iterable 转换成 list
iterator.forEach(n->userList.add(n));
return userList;
}
二.二.三.七 根据id集合进行查询 findAllById
@Test
public void findByIdsTest(){
List<Integer> ids= Arrays.asList(3,4,6);
List<User> userList=userService.findAllByIds(ids);
userList.forEach(n->log.info(n));
}
接口实现方法
@Override
public List<User> findAllByIds(List<Integer> ids) {
Iterable<User> iterator= userCrudRepository.findAllById(ids);
List<User> userList=new ArrayList<>();
iterator.forEach(n->userList.add(n));
return userList;
}
二.二.三.八 查询数目 count
@Test
public void countTest(){
Long count=userService.count();
log.info("总数目{}",count);
}
接口实现方法
@Override
public Long count() {
return userCrudRepository.count();
}
这就是 Crud仓库接口的基本用法。
二.三 PagingAndSorting 工厂接口
二.三.一 接口的相关定义
org.springframework.data.repository.PagingAndSortingRepository
分页和排序的接口,继承了 Crud的接口。
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
方法签名
| 方法解释
|
Iterable findAll(Sort sort);
| 排序
|
Page findAll(Pageable pageable);
| 分页
|
二.三.二 PagingAndSorting 接口实现
在 repository 包下,创建 UserPagingAndSortingRepository 接口
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {
}
二.三.三 PagingAndSorting 测试
在 test目录下,创建 PagingAndSortingRepositoryTests 测试类和测试方法。
多添加几条数据,好便于分页和排序。
二.三.三.一 排序,按照性别和年龄
@Autowired
private UserPagingAndSortingRepository userPagingAndSortingRepository;
@Test
public void sortTest(){
List<User> userList=userService.findAllOrderBySexAndAge();
userList.forEach(n->log.info(n));
}
业务接口方法
@Override
public List<User> findAllOrderBySexAndAge() {
Sort.Order sort1= Sort.Order.desc("sex");
Sort.Order sort2 = Sort.Order.asc("age");
Sort sort=Sort.by(sort1,sort2);
Iterable<User> userIterable=userPagingAndSortingRepository.findAll(sort);
List<User> userList=new ArrayList<>();
userIterable.forEach(n->userList.add(n));
return userList;
}
二.三.三.二 排序并分页
@Test
public void pageTest(){
Page<User> page=userService.pageAll();
log.info("总页数:{}",page.getTotalPages());
log.info("总的数目:{}",page.getTotalElements());
log.info("当前页数:{}",page.getNumber()+1);
log.info("当前页的数目:{}",page.getNumberOfElements());
List<User> userList= page.getContent();
userList.forEach(n->log.info(n));
}
业务接口方法
@Override
public Page<User> pageAll() {
Sort sort=Sort.by(Sort.Direction.DESC,"id");
// 默认从0开始的
Pageable pageable= PageRequest.of(2-1, 4, sort);
Page<User> userPage=userPagingAndSortingRepository.findAll(pageable);
return userPage;
}
这就是 PageAndSorting仓库接口的基本用法。
二.四 JpaRepository 工厂接口
二.四.一接口的相关定义
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
}
继承了分页,还有一个查询的接口。
方法签名
| 方法解释
|
List findAll();
| 查询所有,返回对象集合
|
List findAll(Sort sort);
| 查询所有,可排序,返回对象集合
|
List findAllById(Iterable ids);
| 根据id集合,查询所有的记录,返回对象集合
|
<S extends T> List*lt;S> saveAll(Iterable<S> entities);
| 批量保存数据
|
void flush();
| 刷新缓存
|
S saveAndFlush(S entity);
| 保存并刷新
|
void deleteInBatch(Iterable entities);
| 根据对象集合
|
void deleteAllInBatch();
| 批量删除所有
|
T getOne(ID id);
| 根据id查询当前对象,如果没有的话,返回null
|
<S extends T> List<S> findAll(Example<S> example);
| 根据条件,查询数据,返回对象集合
|
<S extends T> List<S> findAll(Example<S> example, Sort sort);
| 根据条件,支持排序,查询数据,返回对象集合
|
二.四.二 Jpa 接口实现
在repository包下,创建 UserJpaRepository 接口。 通常使用的是这个Jpa的接口,会在这接口内添加相应的接口定义。
当接口的方法名称定义符合相应的规范的时候,不用写相应的实现,Jpa会帮我们自动匹配生成相应的sql语句。
public interface UserJpaRepository extends JpaRepository<User,Integer> {
}
二.四.三 JPA测试
在test目录下,创建JpaRepositoryTests 进行测试。
二.四.三.一 查询全部
@Test
public void findAllTest(){
List<User> userList=userService.jpaFindAll();
userList.forEach(n->log.info(n));
}
业务方法
@Autowired
private UserJpaRepository userJpaRepository;
@Override
public List<User> jpaFindAll() {
return userJpaRepository.findAll();
}
不用往 UserJpaRepository 添加方法。
二.四.三.二 Example 查询 (通常不用)
测试方法
@Test
public void findByExampleTest(){
User user=new User();
user.setName("泽霖");
user.setAge(25);
user.setSex("男");
//1.创建匹配器
ExampleMatcher exampleMatcher=ExampleMatcher.matching()
.withMatcher("sex",matcher -> matcher.contains())
.withMatcher("age",matcher -> matcher.exact())
.withMatcher("name",matcher ->matcher.contains());
//2. 生成Example 对象
Example example=Example.of(user,exampleMatcher);
//3. 进行查询
List<User>userList=userService.findByExample(example);
userList.forEach(n->log.info(n));
}
业务实现接口
@Override
public List<User> findByExample(Example example) {
return userJpaRepository.findAll(example);
}
不用往 UserJpaRepository 添加方法。
二.四.三.三 根据名称进行查询
@Test
public void findByNameTest(){
List<User> userList=userService.findByName("小欢欢");
userList.forEach(n->log.info(n));
}
业务方法
@Override
public List<User> findByName(String name) {
return userJpaRepository.findByName(name);
}
需要往 UserJpaRepository 里面添加方法,需要符合一定的规则
List<User> findByName(String name);
二.四.三.四 根据性别和年龄
@Test
public void findBySexAndAgeTest(){
List<User> userList=userService.findBySexAndAge("男",25);
userList.forEach(n->log.info(n));
}
业务方法
@Override
public List<User> findBySexAndAge(String sex, Integer age) {
return userJpaRepository.findBySexAndAge(sex,age);
}
需要往 UserJpaRepository 里面添加方法,需要符合一定的规则
List<User> findBySexAndAge(String sex, Integer age);
二.四.三.五 根据性别查询,年龄排序
@Test
public void findAllOrderByTest(){
List<User> userList=userService.findBySexOrderByAge("女");
userList.forEach(n->log.info(n));
}
业务方法
@Override
public List<User> findBySexOrderByAge(String sex) {
return userJpaRepository.findBySexOrderByAgeDesc(sex);
}
需要往 UserJpaRepository 里面添加方法,需要符合一定的规则
List<User> findBySexOrderByAgeDesc(String sex);
二.四.三.六 使用原始SQL进行查询部分字段
@Test
public void findQueryNameTest(){
List<Map<String,Object>> userMapList=userService.findQueryByName("小欢欢");
for(Map<String,Object> map:userMapList){
log.info("id是:{},name是{}",map.get("id"),map.get("name"));
}
}
业务方法
@Override
public List<Map<String,Object>> findQueryByName(String name) {
return userJpaRepository.findQueryByName(name);
}
需要往 UserJpaRepository 里面添加方法,需要符合一定的规则
@Query(value="select id as id,name as name from user where name=:name",nativeQuery = true)
List<Map<String,Object>> findQueryByName(@Param("name") String name);
二.四.三.七 使用原始SQL进行查询全部字段
@Test
public void findQueryNameTest(){
List<Map<String,Object>> userMapList=userService.findQueryByName("小欢欢");
for(Map<String,Object> map:userMapList){
log.info("id是:{},name是{}",map.get("id"),map.get("name"));
}
}
业务方法
@Override
public List<User> jpaFindAllSql(String name) {
return userJpaRepository.findAllSql(name);
}
需要往 UserJpaRepository 里面添加方法,需要符合一定的规则
@Query(value="select * from user where name=:name",nativeQuery = true)
List<User> findAllSql(@Param("name")String name);
这就是 jpa的一些基本的用法。
二.五 JpaSpecificationExecutor 动态查询接口
二.五.一 接口的相关定义
public interface JpaSpecificationExecutor<T> {
}
方法签名
| 方法解释
|
Optional findOne(@Nullable Specification spec);
| 根据条件,查询最多只有一条记录
|
List findAll(@Nullable Specification spec);
| 根据条件,查询多条记录
|
Page findAll(@Nullable Specification spec, Pageable pageable);
| 根据条件,分页查询,可包含排序
|
List findAll(@Nullable Specification spec, Sort sort);
| 根据条件,排序查询
|
long count(@Nullable Specification spec);
| 查询数目
|
二.五.二. JpaSpecificationExecutor实现
通常都是与 JpaRepository 一起使用的。
public interface UserSpecificationRepository extends JpaRepository<User, Integer>,
JpaSpecificationExecutor<User>{
}
二.五.三 Specification 动态查询测试
@Test
public void nameAndSexAndDescTest(){
User user=new User();
user.setName("小欢欢1");
user.setSex("女");
user.setAge(27);
user.setDescription("小坏蛋");
List<User> userList=userService.findByNameSexAndDesc(user);
userList.forEach(n->log.info(n));
}
业务接口方法
@Override
public List<User> findByNameSexAndDesc(User user) {
//1. 根据条件创建 Specification 对象信息
Specification<User> specification=new Specification<User>(){
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
//1. 用于接收封装的查询对象
List<Predicate> predicateList = new ArrayList<>();
if(user!=null){
//1.如果name 不为空的话,对name 进行精确匹配
if(!StringUtils.isEmpty(user.getName())){
Predicate namePredicate = criteriaBuilder.equal(root.get("name"), user.getName());
predicateList.add(namePredicate);
}
//2.如果sex 不为空的话,也是精确匹配
if(!StringUtils.isEmpty(user.getSex())){
Predicate sexPredicate=criteriaBuilder.equal(root.get("sex"),user.getSex());
predicateList.add(sexPredicate);
}
//3.如果age不为空的话,就是 < 匹配
if(!StringUtils.isEmpty(user.getAge())){
Predicate agePreDicate=criteriaBuilder.lt(root.get("age"),user.getAge());
predicateList.add(agePreDicate);
}
//4. 如果description 不为空的话,进行模糊匹配
if(!StringUtils.isEmpty(user.getDescription())){
Predicate descPredicate=criteriaBuilder.like(root.get("description"),"%"+user.getDescription()
+"%");
predicateList.add(descPredicate);
}
}
return criteriaBuilder.and(predicateList.toArray(
new Predicate[predicateList.size()]
));
}
};
//传入条件,也可以传入分页信息。这儿就不举例分页了。
return userSpecificationRepository.findAll(specification);
}
如果将 user条件的属性全部去掉,是一种sql查询,sex去掉,又是一种sql查询,会根据属性的不同,动态的处理。
动态查询,也可以传入分页和排序的相关信息。
这个非常重要,需要重点掌握一下。
本章节的代码放置在 github 上:
https://github.com/yuejianli/springboot/tree/develop/Jpa
谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!