使用@Query自定义查询

这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,
将查询直接在相应的接口方法中声明,结构更为清晰,这是 Spring data 的特有实现。

  1. //@RepositoryDefinition(domainClass = Person.class, idClass = Integer.class)
  2. public interface PersonRepository extends Repository<Person, Integer> {
  3. // 通过lastName获取Person对象
  4. Person getByLastName(String lastName);
  5. //WHERE lastName LIKE ?% AND id < ?
  6. List<Person> getByLastNameStartingWithAndIdLessThan(String lastName, Integer id);
  7. //WHERE lastName LIKE %? AND id < ?
  8. List<Person> getByLastNameEndingWithAndIdLessThan(String lastName, Integer id);
  9. //WHERE email IN (?, ?, ?) OR birth < ?
  10. List<Person> getByEmailInOrBirthLessThan(List<String> emails, Date birth);
  11. //WHERE a.id > ?
  12. List<Person> getByAddress_IdGreaterThan(Integer id);
  13. // 查询id值最大的那个person
  14. // 使用@Query注解可以自定义JPQL语句可以实现更灵活的查询
  15. @Query("select p from Person p where p.id = (select max(p2.id) from Person p2)")
  16. Person getMaxIdPerson();
  17. // 为 @Query 注解传递参数的方式1: 使用占位符.(参数需要按顺序)
  18. @Query("SELECT p FROM Person p WHERE p.lastName = ?1 AND p.email = ?2")
  19. List<Person> testQueryAnnotationParams1(String lastName, String email);
  20. //为 @Query 注解传递参数的方式2: 命名参数的方式.
  21. @Query("SELECT p FROM Person p WHERE p.lastName = :lastName AND p.email = :email")
  22. List<Person> testQueryAnnotationParams2(@Param("email") String email, @Param("lastName") String lastName);
  23. //SpringData 允许在占位符上添加 %%.
  24. @Query("SELECT p FROM Person p WHERE p.lastName LIKE %?1% OR p.email LIKE %?2%")
  25. List<Person> testQueryAnnotationLikeParam(String lastName, String email);
  26. //SpringData 允许在命名参数上添加 %%.
  27. @Query("SELECT p FROM Person p WHERE p.lastName LIKE %:lastName% OR p.email LIKE %:email%")
  28. List<Person> testQueryAnnotationLikeParam2(@Param("email") String email, @Param("lastName") String lastName);
  29. //设置 nativeQuery=true 即可以使用原生的 SQL 查询
  30. @Query(value="SELECT count(id) FROM jpa_persons", nativeQuery=true)
  31. long getTotalCount();
  32. }

Modifying 注解和事务

Query Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下:

  1. //可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
  2. //在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
  3. //UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.
  4. //默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
  5. @Modifying
  6. @Query("UPDATE Person p SET p.email = :email WHERE id = :id")
  7. void updatePersonEmail(@Param("id") Integer id, @Param("email") String email);

注意:
方法的返回值应该是 int,表示更新语句所影响的行数
在调用的地方必须加事务,没有事务不能正常执行
如果不加上@Modifying就会报错“Not supported for DML operations ”
加上了@Modifying还是会报另外一个错误nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
这个因为需要事务,我需要把这个update()放到service中,其中加上 @Transactional注解

CrudRepository 接口

  1. // CrudRepository 接口提供了最基本的对实体类的添删改查操作
  2. T save(T entity);//保存单个实体
  3. Iterable<T> save(Iterable<? extends T> entities);//保存集合
  4. T findOne(ID id);//根据id查找实体
  5. boolean exists(ID id);//根据id判断实体是否存在
  6. Iterable<T> findAll();//查询所有实体,不用或慎用!
  7. long count();//查询实体数量
  8. void delete(ID id);//根据Id删除实体
  9. void delete(T entity);//删除一个实体
  10. void delete(Iterable<? extends T> entities);//删除一个实体的集合
  11. void deleteAll();//删除所有实体,不用或慎用!

保存还是需要放到service,加上事务的。

  1. public interface PersonCrudRepository extends CrudRepository<Person, Integer> {
  2. }
  3. @Service
  4. public class PersonService {
  5. @Autowired
  6. private PersonRepository personRepository;
  7. @Autowired
  8. private PersonCrudRepository personCrudRepository;
  9. @Transactional
  10. public void savePersons(List<Person> persons) {
  11. personCrudRepository.saveAll(persons);
  12. }
  13. @Transactional
  14. public void update(Integer id, String email) {
  15. personRepository.updatePersonEmail(id, email);
  16. }
  17. }

PagingAndSortingRepository接口

  1. 该接口提供了分页与排序功能
  2. Iterable<T> findAll(Sort sort); //排序
  3. Page<T> findAll(Pageable pageable); //分页查询(含排序功能)
  1. public interface PersonPagingAndSortingRepository extends PagingAndSortingRepository<Person, Integer> {
  2. }
  1. @Test
  2. public void testPagingAndSortintRespository() {
  3. // pageNo 从0开始
  4. int pageNo = 6 - 1;
  5. int pageSize = 5;
  6. // Pageable 接口通常使用其PageRequest实现类,其中封装了需要分页的信息
  7. //排序相关的. Sort 封装了排序的信息
  8. //Order 是具体针对于某一个属性进行升序还是降序.
  9. Order order1 = new Order(Direction.DESC, "id");
  10. Order order2 = new Order(Direction.ASC, "email");
  11. Sort sort = new Sort(order1, order2);
  12. PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
  13. Page<Person> page = PersonPagingAndSortingRepository.findAll(pageable);
  14. System.out.println("总记录数: " + page.getTotalElements());
  15. System.out.println("当前第几页: " + (page.getNumber() + 1));
  16. System.out.println("总页数: " + page.getTotalPages());
  17. System.out.println("当前页面的 List: " + page.getContent());
  18. System.out.println("当前页面的记录数: " + page.getNumberOfElements());
  19. }
  1. 控制台:
  2. Hibernate:
  3. select
  4. count(*) as col_0_0_
  5. from
  6. jpa_persons person0_
  7. Hibernate:
  8. select
  9. person0_.id as id1_1_,
  10. person0_.address_id as address_6_1_,
  11. person0_.add_id as add_id2_1_,
  12. person0_.birth as birth3_1_,
  13. person0_.email as email4_1_,
  14. person0_.last_name as last_nam5_1_
  15. from
  16. jpa_persons person0_
  17. order by
  18. person0_.id desc,
  19. person0_.email asc limit ?,
  20. ?
  21. 总记录数: 29
  22. 当前第几页: 6
  23. 总页数: 6
  24. 当前页面的 List: [Person [id=4, lastName=dd, email=dd@qq.com, brith=2019-06-06 21:13:44.0], Person [id=3, lastName=cc, email=cc@qq.com, brith=2019-06-06 21:13:44.0], Person [id=2, lastName=bb, email=bb@qq.com, brith=2019-06-06 21:13:44.0], Person [id=1, lastName=aa, email=aa@qq.com, brith=2019-06-06 21:13:44.0]]
  25. 当前页面的记录数: 4

JpaRepository接口

  1. 该接口提供了JPA的相关功能
  2. List<T> findAll(); //查找所有实体
  3. List<T> findAll(Sort sort); //排序、查找所有实体
  4. List<T> save(Iterable<? extends T> entities);//保存集合
  5. void flush();//执行缓存与数据库同步
  6. T saveAndFlush(T entity);//强制执行持久化
  7. void deleteInBatch(Iterable<T> entities);//删除一个实体集合
  1. @Test
  2. public void testJpaRespository() {
  3. Person person = new Person();
  4. person.setBirth(new Date());
  5. person.setEmail("qweqwe.com");
  6. person.setLastName("qwe");
  7. // 如果设置了people.setId(29);,会先查询,然后在更新的。
  8. // person.setId(29);
  9. Person person1 = personRepository.saveAndFlush(person);
  10. System.out.println(person == person1);
  11. }

JpaSpecificationExecutor接口

不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象

  1. public interface JpaSpecificationExecutor<T> {
  2. // Specification:封装 JPA Criteria 查询条件。
  3. // 通常使用匿名内部类的方式来创建该接口的对象
  4. Optional<T> findOne(@Nullable Specification<T> spec);
  5. List<T> findAll(@Nullable Specification<T> spec);
  6. Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
  7. List<T> findAll(@Nullable Specification<T> spec, Sort sort);
  8. long count(@Nullable Specification<T> spec);
  9. }
  1. /**
  2. * 目标: 实现带查询条件的分页. id > 5 的条件
  3. * <p>
  4. * 调用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
  5. * Specification: 封装了 JPA Criteria 查询的查询条件
  6. * Pageable: 封装了请求分页的信息: 例如 pageNo, pageSize, Sort
  7. */
  8. @Test
  9. public void testJpaSpecificationExecutor() {
  10. int pageNo = 3 - 1;
  11. int pageSize = 5;
  12. PageRequest pageable = new PageRequest(pageNo, pageSize);
  13. //通常使用 Specification 的匿名内部类
  14. Specification<Person> specification = new Specification<Person>() {
  15. /**
  16. * @param root: 代表查询的实体类.
  17. * @param query: 可以从中可到 Root 对象, 即告知 JPA Criteria 查询要查询哪一个实体类. 还可以来添加查询条件, 还可以结合 EntityManager 对象得到最终查询的 TypedQuery 对象.
  18. * @param cb: CriteriaBuilder 对象. 用于创建 Criteria 相关对象的工厂. 当然可以从中获取到 Predicate 对象
  19. * @return: Predicate 类型, 代表一个查询条件.
  20. */
  21. @Override
  22. public Predicate toPredicate(Root<Person> root,
  23. CriteriaQuery<?> query, CriteriaBuilder cb) {
  24. Path path = root.get("id");
  25. Predicate predicate = cb.gt(path, 5);
  26. return predicate;
  27. }
  28. };
  29. Page<Person> page = personRepository.findAll(specification, pageable);
  30. System.out.println("总记录数: " + page.getTotalElements());
  31. System.out.println("当前第几页: " + (page.getNumber() + 1));
  32. System.out.println("总页数: " + page.getTotalPages());
  33. System.out.println("当前页面的 List: " + page.getContent());
  34. System.out.println("当前页面的记录数: " + page.getNumberOfElements());
  35. }

自定义 Repository 方法

为某一个 Repository 上添加自定义方法
步骤:

  1. 定义一个接口: 声明要添加的, 并自实现的方法
  1. 提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法
  1. 声明 Repository 接口, 并继承 1) 声明的接口

使用.
注意: 默认情况下, Spring Data 会在 base-package 中查找 “接口名Impl” 作为实现类. 也可以通过 repository-impl-postfix 声明后缀.
先定义一个接口

  1. public interface PersonDao {
  2. void test();
  3. }

然后弄一个实现类,类名后缀要是Impl

  1. public class PersonRepositoryImpl implements PersonDao {
  2. @PersistenceContext
  3. private EntityManager entityManager;
  4. @Override
  5. public void test() {
  6. Person person = entityManager.find(Person.class, 1);
  7. System.out.println("--->"+person);
  8. }
  9. }

还要有个对应的接口PeopleRepo

  1. public interface PersonRepository extends PersonDao, Repository<Person, Integer> {
  2. }

03 接口查询代码 - 图1
为所有的 Repository 都添加自实现的方法
步骤:

  1. 声明一个接口, 在该接口中声明需要自定义的方法, 且该接口需要继承 Spring Data 的 Repository.
  1. 提供 1) 所声明的接口的实现类. 且继承 SimpleJpaRepository, 并提供方法的实现
  1. 定义 JpaRepositoryFactoryBean 的实现类, 使其生成 1) 定义的接口实现类的对象
  1. 修改 节点的 factory-class 属性指向 3) 的全类名

注意: 全局的扩展实现类不要用 Imp 作为后缀名, 或为全局扩展接口添加 NoRepositoryBean 注解告知 Spring Data: Spring Data 不把其作为 Repository