前文已经讲述了如何搭建一个Springboot环境和集成mysql数据库
基于SpringBoot框架的开发环境搭建:项目创建+集成数据库
三. SpringData JPA的查询操作
SpringData JPA有5个核心接口:
-
Repository:是最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别
-
CrudRepository:是Repository的子接口,提供CRUD的功能
-
PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能
-
JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等
-
JpaSpecificationExecutor:用来做复杂查询的接口
Repository->CrudRepository->PagingAndSortingRepository->JpaRepository是逐级继承的关系,JpaRepository拥有最全的功能。JpaSpecificationExecutor需要与Repository搭配才能实现负责条件的查询。
查询
- 基于方法名称命名规则
在上篇文章中我们在UserRepository接口中新增了一个接口
User findByUserName(String username);
我们知道user的Entity中userName字段,并且这个字段映射到数据库中的user_name这个字段,在UserRepository新增这个接口,然后实际调用时会去执行 select * from user where user_name=‘xxx’ 这条sql语句。
规则: findBy(关键字) + 属性名称(属性名称的首字母大写) + 查询条件(首字母大写)
常用规则:
关键字 | SQL符号 | 方法命名 | sql where 子句 |
---|---|---|---|
And | and | findByNameAndPwd | where name= ? and pwd =? |
Or | or | findByNameOrSex | where name= ? or sex=? |
Is、Equals | = | findById、findByIdEquals | where id = ? |
Between | between xx and xx | findByIdBetween | where id between ? and ? |
LessThan | < | findByIdLessThan | where id < ? |
LessThanEqual | <= | findByIdLessThanEqual | where id <= ? |
GreaterThan | > | findByIdGreaterThan | where id > ? |
GreaterThanEqual | >= | findByIdGreaterThanEqual | where id > = ? |
After | > | findByIdAfter | where id > ? |
Before | < | findByIdBefore | where id < ? |
IsNull | is null | findByNameIsNull | where name is null |
isNotNull,NotNull | is not null | findByNameNotNull | where name is not null |
Like | like | findByNameLike | where name like ? |
NotLike | not like | findByNameNotLike | where name not like ? |
StartingWith | like ‘xx%’ | findByNameStartingWith | where name like ‘?%’ |
EndingWith | like ‘%xx’ | findByNameEndingWith | where name like ‘%?’ |
Containing | like ‘%xx%’ | findByNameContaining | where name like ‘%?%’ |
OrderBy | order by | findByIdOrderByXDesc | where id=? order by x desc |
Not | <>、!= | findByNameNot | where name <> ? |
In | in() | findByIdIn(Collection<?> c) | where id in (?) |
NotIn | not in() | findByNameNot | where name <> ? |
True | =true | findByAaaTue | where aaa = true |
False | =false | findByAaaFalse | where aaa = false |
IgnoreCase | upper(x)=upper(y) | findByNameIgnoreCase | where UPPER(name)=UPPER(?) |
top | top、rownum<=x | findTop10 | top 10 / where ROWNUM <=10 |
- 使用@Query查询与更新
public interface UserRepository extends JpaRepository<User,Long> {
User findByUserName(String username);
@Query("from User where userName= ?1")
User findByUserNameJPQL(String username);
@Query("from User where userName like :username")
List<User> queryUserByLikeUsernameUseJPQL(@Param("username")String username);
// 通过原生sql去执行查询
@Query(value = "select * from user where user_name = ?1",nativeQuery=true)
List<User> queryUserByUsernameUseSQL(String username);
@Query(value = "update user set user_name = ? where id = ?",nativeQuery=true)
@Modifying // 需要执行一个更新操作
Integer updateUsernameById(String username,Long id);
}
总结:
- 可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。 注意: JPQL 不支持使用 INSERT(新增操作)
- 在 @Query 注解中编写 JPQL 语句, 如果是 UPDATE 或 DELETE 操作,必须使用 @Modifying 修饰
- UPDATE 或 DELETE 操作需要使用事务,此时需要的 Service 层的方法上添加事务操作
- 默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务。 他们不能完成修改操作
排序
- 基于方法名称命名规则
/**
* 假如你需要所有的数据而不需要按照条件查询,就要按照这样来写:
* findBy/getBy/queryBy.. + OrderBy + 排序字段 + 排序方式
* 按照 username 默认正序排序
* ..order by username
*/
List<User> findByOrderByUsername();
/**
* 查询全部数据:先按age字段倒序再按照id字段正序排序
* ..order by age desc, id asc
*/
List<User> findByOrderByAgeDescIdAsc();
/**
* 查询名称结果集按正序排序:
* ...where username = ? order by age asc
*/
List<User> findByUsernameOrderByAgeAsc(String username);
/**
* 查询名称结果集按倒序排序:
* ...where username = ? order by id desc
*/
List<User> findByUsernameOrderByIdDesc(String username);
demo:
/**
* 排序操作:
* 1.对单列做排序处理
* 2.多列的排序处理
*/
@Test
public void test2(){
/**
* Sort:该对象封装了排序规则以及指定的排序字段(对象的属性来表示)
* direction:排序规则(Sort.Direction.DESC)
* properties:指定做排序的属性(id)
*/
// 1.对单列做排序处理,对id做降序排序
Sort sortOne = Sort.by(Sort.Direction.DESC,"id");
Iterable iterable = this.userPagingAndSortingRepository.findAll(sortOne);
iterable.forEach(System.out::println);
System.err.println("--------------华丽的分割线-------------------");
/**
* 可以用如下两种方式得到Order
* 1.new Sort.Order()
* 2.Sort.Order.desc()/asc()/by()
*/
// 2.多列的排序处理,先对age列做降序处理,然后对id字段做升序处理
Sort.Order age = Sort.Order.desc("age");
Sort.Order id = new Sort.Order(Sort.Direction.ASC,"id");
Sort sortMany = Sort.by(age,id);
this.userPagingAndSortingRepository.findAll(sortMany).forEach(System.out::println);
}
分页
- 基于方法名称命名规则
/**
* 说明:对表的全部数据根据age进行Asc(升序)排序后再选择第一条数据返回
* SQL:...order by age asc limit 1
* 注意:findFirst3ByOrderByAgeAsc()/findTop3ByOrderByAgeAsc()则每次返回3条 limit 3
*/
User findFirstByOrderByAgeAsc();
User findTopByOrderByAgeAsc();
/**
* 说明:首先进行数据查询并通过Sort进行排序后 再筛选数据列表中最前面的2行数据返回 limit 2
* SQL:where username = ? order by field asc/desc
*/
List<User> findFirst2ByUsername(String username, Sort sort);
List<User> findTop2ByUsername(String username,Sort sort);
/**
* 说明:首先进行数据查询,查询全部指定的username后进行分页,分页完后进行数据控制
* 控制说明:
* 关于带分页的控制是,假设分页过后查询的数据id为3,4,5 查询出这三条数据后进行数据控制,
* 本案例是控制为2条,那返回的id数据就是3,4两条记录,id为5的就舍弃,
* 那么如果数据控制是5条(Top5),那么就会打印3,4,5另外再去添加6,7补充数据长度
*/
Page<User> queryFirst2ByUsername(String username, Pageable pageable);
List<User> queryTop2ByUsername(String username, Pageable pageable);
demo:
/**
* 分页操作
*/
@Test
public void test1(){
/**
* Pageable:封装了分页的参数,当前页,每页显示的条数。注意:当前页是从0开始
* PageRequest(page,size):page表示当前页,size表示每页显示多少条
*/
Pageable pageable = PageRequest.of(0, 2);
Page<User> page = this.userPagingAndSortingRepository.findAll(pageable);
System.out.println("总记录数: " + page.getTotalElements());
System.out.println("当前第几页: " + (page.getNumber() + 1));
System.out.println("总页数: " + page.getTotalPages());
System.out.println("当前页面的 List: " + page.getContent());
System.out.println("当前页面的记录数: " + page.getNumberOfElements());
page.getContent().forEach(System.out::println);
}
/**
* 分页 + 排序 处理
*
*/
@Test
public void test3(){
// 排序操作对象
Sort.Order age = Sort.Order.desc("age");
Sort.Order id = Sort.Order.asc("id");
Sort sort = Sort.by(age,id);
// 排序操作还是没变化,只需要在PageRequest.of内多加一个Sort参数
Pageable pageable = PageRequest.of(0, 2 ,sort);
Page<User> page = this.userPagingAndSortingRepository.findAll(pageable);
System.out.println("总记录数: " + page.getTotalElements());
System.out.println("当前第几页: " + (page.getNumber() + 1));
System.out.println("总页数: " + page.getTotalPages());
System.out.println("当前页面的记录数: " + page.getNumberOfElements());
System.out.println("当前页面的 List: " + page.getContent());
System.err.println("--------------华丽的分割线-------------------");
page.getContent().forEach(System.out::println);
}
计数
- 基于方法名称命名规则
/**
* 计数 返回总数
* countBy()等同于countAllBy()
* SQL:
* select count(id) from sys_user where username = ?
* select count(id) from sys_user
*/
Long countByUsername(String username);
Long countAllBy();
删除
- 基于方法名称命名规则
/**
* 说明:必须添加事务和回滚,这样根据条件找到几条就删几条
* 对应的SQL语句:
* Hibernate: select * from sys_user where username = ?
* Hibernate: delete from sys_user where id=?
* Hibernate: delete from sys_user where id=?
* ....
* 关于removeBy和deleteBy方法区别:
* 他们是一样的 .选择哪一个取决于您的个人偏好.
*/
void deleteByUsername(String username);
void removeByUsername(String username);
接口方法
CrudRepository 接口:
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { // T 是要操作的实体类,ID 是实体类主键的类型
<S extends T> S save(S entity); // 保存
<S extends T> Iterable<S> saveAll(Iterable<S> entities); // 批量保存
Optional<T> findById(ID id); // 根据id查询一个Optional
boolean exists(ID id); // 根据id判断对象是否存在
Iterable<T> findAll(); // 查询所有的对象
Iterable<T> findAllById(Iterable<ID> ids); // 根据id列表查询所有的对象
long count(); // 计算对象的总个数
void delete(ID id); // 根据id删除
void delete(T entity); // 删除一个对象
void delete(Iterable<? extends T> entities); // 批量删除,集合对象(后台执行时,一条一条删除)
void deleteAll(); // 删除所有 (后台执行时,一条一条删除)
}
PagingAndSortingRepository 接口:
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort); // 仅排序
Page<T> findAll(Pageable pageable); // 分页和排序
}
JpaRepository 接口:
@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
@Override
List<T> findAll(); // 查询所有对象,返回List
@Override
List<T> findAll(Sort sort); // 查询所有对象,并排序,返回List
@Override
List<T> findAllById(Iterable<ID> ids); // 根据id列表 查询所有的对象,返回List
@Override
<S extends T> List<S> saveAll(Iterable<S> entities); // 批量保存,并返回对象List
<S extends T> S saveAndFlush(S entity); // 保存并强制同步数据库
void flush(); // 强制缓存与数据库同步
void deleteInBatch(Iterable<T> entities); // 批量删除 集合对象(后台执行时,生成一条语句执行,用多个or条件)
void deleteAllInBatch(); // 删除所有 (执行一条语句,如:delete from user)
T getOne(ID id); // 根据id查询对象,返回对象的引用(区别于findOne)。当对象不存时,返回引用不是null,但各个属性值是null
@Override
<S extends T> List<S> findAll(Example<S> example); // 根据实例查询
@Override
<S extends T> List<S> findAll(Example<S> example, Sort sort);// 根据实例查询并排序
}
JpaSpecificationExecutor
接口定义:
/**
* JpaSpecificationExecutor:用来做动态查询的接口
* Specification:是SpringDataJPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件。
* JpaSpecificationExecutor接口下一共就5个接口方法
*/
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> spec);// 查询单个
List<T> findAll(Specification<T> spec);// 查询全部
Page<T> findAll(Specification<T> spec, Pageable pageable);// 查询全部【分页】
List<T> findAll(Specification<T> spec, Sort sort);// 查询全部【排序】
long count(Specification<T> spec);// 统计总数
}
使用方法
- dao层集成JpaSpecificationExecutor
public interface UserJpaSpecificationExecutor extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}
- service层
/**
* 条件查询——单条件
* Predicate 过滤条件
* jpql:from User where username = '隔壁小王'
* sql:select * from sys_user where username = '隔壁小王'
*/
@Test
public void test1(){
Specification<User> spec = new Specification<User>() {
/**
* @param root 根对象。封装了查询条件的对象
* @param criteriaQuery 定义了一个基本的查询。一般使用较少
* @param criteriaBuilder 创建一个查询条件
* @return Predicate:定义了查询条件
*/
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> username = root.get("username");
Predicate predicate = criteriaBuilder.equal(username, "隔壁小王");
return predicate;
}
};
List<User> userList = userJpaSpecificationExecutor.findAll(spec);
userList.forEach(System.out::println);
}
/**
* 多条件查询二
* jpql:from User where username like '%小王' and password = 'password' and age < 30
* sql:select * from sys_user where username like '%小王' and password = 'password' and age < 30
*/
@Test
public void test3(){
Specification<User> spec = (root, criteriaQuery, criteriaBuilder) -> {
Predicate username = criteriaBuilder.like(root.get("username"), "%小王");
Predicate password = criteriaBuilder.equal(root.get("password"), "password");
Predicate age = criteriaBuilder.lt(root.get("age"), 30);
Predicate predicate = criteriaBuilder.and(username, password);
predicate = criteriaBuilder.and(predicate, age);
return predicate;
};
List<User> userList = userJpaSpecificationExecutor.findAll(spec);
userList.forEach(System.out::println);
}
tips
- 对比:deleteAll() 和 deleteAllInBatch() 区别:
- deleteAll()是删除全部,先findALL查找出来,再一条一条删除,最后提交事务
- deleteAllInBatch()是删除全部,一条sql。显然:deleteAllInBatch()方法效率更高
- 在进行批量删除操作时尽量使用JpaRepository自带的批量删除方法deleteInBatch()及deleteAllInBatch()
- getOne()与findById()方法总结与区别:
- 1、getOne() 是延迟加载。findById() 是立马加载
- 2、getOne()返回一个实体的引用,无结果会抛出异常。findById()返回一个Optional对象
- 3、getOne()属于JpaRepository,底层调用getReference()方法,懒加载的模式,当调用对象时才会发送SQL
- 4、findById()属于CrudRepository,底层调用了find()方法,当调用该方法的时就直接发送SQL
本文借鉴了CSDN博主「路面烧卖」的部分代码和文案,对博主表示非常感谢。
原文链接:https://blog.csdn.net/qq_36259143/article/details/120207737