vlambda博客
学习文章列表

微服务框架【第5期】--SpringBoot整合SpringDataJpa进阶

微服务框架【第5期】--SpringBoot整合SpringDataJpa进阶

导读:

    大家好,我是老田。今天我们继续学习SpringDataJpa进阶内容。主要涉及SpringDataJpa的分页查询和排序,动态多条件查询。

1.JPA分页和排序

在SpringDataJpa的Dao类上我们一般会继承JpaRepository接口,而JpaRepository接口继承了PagingAndSortingRepository接口。PagingAndSortingRepository接口中定义实现了一组用于分页排序的相关方法。

分页

Pageable 是一个由 SpringDataJpa 定义的接口,它拥有一个实现 PageRequest。让我们看看如何创建一个 PageRequest。

 Pageable pageable = PageRequest.of(0, 10);
 Page<Employee> page = repository.findAll(pageable);
 // 也可以合并
 Page<Employee> page = repository.findAll(PageRequest.of(0, 10));

排序

Spring Data JPA 提供一个Sort对象提供排序机制。让我们看一下排序的方式。

 repository.findAll(Sort.by("fistName"));
 
 repository.findAll(Sort.by("fistName").ascending().and(Sort.by("lastName").descending());

同时排序和分页

 Pageable pageable = PageRequest.of(0, 20, Sort.by("firstName"));
 
 Pageable pageable = PageRequest.of(0, 20, Sort.by("fistName").ascending().and(Sort.by("lastName").descending());

2.按例查询

Example:按例查询(QBE)是一种用户界面友好的查询技术。它允许动态创建查询,并且不需要编写包含字段名称的查询。实际上,按示例查询不需要使用特定的数据库的查询语言来编写查询语句。

Example api的组成

  1. Probe: 含有对应字段的实例对象。

  2. ExampleMatcher:ExampleMatcher携带有关如何匹配特定字段的详细信息,相当于匹配条件。

  3. Example:由Probe和ExampleMatcher组成,用于查询。

Example的限制

  1. 属性不支持嵌套或者分组约束,比如这样的查询 firstname = ?0 or (firstname = ?1 and lastname = ?2)

  2. 灵活匹配只支持字符串类型,其他类型只支持精确匹配

最简单的按例查询

用example可以最快速的完成支持所有参数的筛选功能,像这样的代码:

 Example<User> example = Example.of(user);

Example会将为null的字段自动过滤掉,不会作为筛选条件,但是使用这种方式会遇到一个问题,它没有办法实现 id > startId && id < endId 这样的操作。

自定匹配器规则

ExampleMatcher实例查询三要素

  1. 实体对象:在ORM框架中与Table对应的域对象,一个对象代表数据库表中的一条记录,如上例中User对象,对应user表。在构建查询条件时,一个实体对象代表的是查询条件中的“数值”部分。如:要查询姓“X”的客户,实体对象只需要存储条件值“X”。

  2. 匹配器:ExampleMatcher对象,它是匹配“实体对象”的,表示了如何使用“实体对象”中的“值”进行查询,它代表的是“查询方式”,解释了如何去查的问题。如:要查询姓“X”的客户,即姓名以“X”开头的客户,该对象就表示了“以某某开头的”这个查询方式,如上例中:withMatcher(“userName”, GenericPropertyMatchers.startsWith())

  3. 实例:即Example对象,代表的是完整的查询条件。由实体对象(查询条件值)和匹配器(查询方式)共同创建。最终根据实例来findAll即可。

 @Test
 public void contextLoads() {
     User user = new User();
     user.setUsername("李");
     user.setAddress("北");
     user.setPassword("123456");
     ExampleMatcher matcher = ExampleMatcher.matching()
        .withMatcher("username", ExampleMatcher.GenericPropertyMatchers.startsWith())//模糊查询匹配开头,即{username}%
        .withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains())//全部模糊查询,即%{address}%
        .withIgnorePaths("password");//忽略字段,即不管password是什么值都不加入查询条件
     Example<User> example = Example.of(user ,matcher);
     List<User> list = userRepository.findAll(example);
     System.out.println(list);
 }

Matching生成的语句说明

  • DEFAULT (case-sensitive) firstname = ?0 默认(大小写敏感)

  • DEFAULT (case-insensitive) LOWER(firstname) = LOWER(?0) 默认(忽略大小写)

  • EXACT (case-sensitive) firstname = ?0 精确匹配(大小写敏感)

  • EXACT (case-insensitive) LOWER(firstname) = LOWER(?0) 精确匹配(忽略大小写)

  • STARTING (case-sensitive) firstname like ?0 + ‘%’ 前缀匹配(大小写敏感)

  • STARTING (case-insensitive) LOWER(firstname) like LOWER(?0) + ‘%’ 前缀匹配(忽略大小写)

  • ENDING (case-sensitive) firstname like ‘%’ + ?0 后缀匹配(大小写敏感)

  • ENDING (case-insensitive) LOWER(firstname) like ‘%’ + LOWER(?0) 后缀匹配(忽略大小写)

  • CONTAINING (case-sensitive) firstname like ‘%’ + ?0 + ‘%’ 模糊查询(大小写敏感)

  • CONTAINING (case-insensitive) LOWER(firstname) like ‘%’ + LOWER(?0) + ‘%’ 模糊查询(忽略大小写)

3.Criteria API

Criteria 查询是以元模型的概念为基础的,元模型是为具体持久化单元的受管实体定义的,这些实体可以是实体类,嵌入类或者映射的父类。

CriteriaQuery接口:代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select 、from、where、group by、order by等。注意:CriteriaQuery对象只对实体类型或嵌入式类型的Criteria查询起作用

Root接口:代表Criteria查询的根对象,Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似

  1. Root实例是类型化的,且定义了查询的FROM子句中能够出现的类型

  2. 查询根实例能通过传入一个实体类型给 AbstractQuery.from方法获得。

  3. Criteria查询,可以有多个查询根。

  4. AbstractQuery是CriteriaQuery 接口的父类,它提供得到查询根的方法。

  5. CriteriaBuilder接口:用来构建CritiaQuery的构建器对象

  6. Predicate:一个简单或复杂的谓词类型,其实就相当于条件或者是条件组合

示例代码

 @Service
 public class ItemServiceImpl implements ItemService {
     //利用entitymanager构建Criteria查询
     //其实和sql一样,就是换成了面向对象的方式。这种方式的好处就是可以防止sql拼写错误。当然这种方式也不是特别直观
     @Resource
     private EntityManager entityManager;
 
     @Override
     public List<Item> findByConditions(String name, Integer price, Integer stock) {
         //创建CriteriaBuilder安全查询工厂
         //CriteriaBuilder是一个工厂对象,安全查询的开始.用于构建JPA安全查询.
         CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
         //创建CriteriaQuery安全查询主语句
         //CriteriaQuery对象必须在实体类型或嵌入式类型上的Criteria 查询上起作用。
         CriteriaQuery<Item> query = criteriaBuilder.createQuery(Item.class);
         //Root 定义查询的From子句中能出现的类型
         Root<Item> itemRoot = query.from(Item.class);
         //Predicate 过滤条件 构建where字句可能的各种条件
         //这里用List存放多种查询条件,实现动态查询
         List<Predicate> predicatesList = new ArrayList<>();
         //name模糊查询 ,like语句
         if (name != null) {
             predicatesList.add(
                 criteriaBuilder.and(
                     criteriaBuilder.like(
                         itemRoot.get(Item_.itemName), "%" + name + "%")));
        }
         // itemPrice 小于等于 <= 语句
         if (price != null) {
             predicatesList.add(
                 criteriaBuilder.and(
                     criteriaBuilder.le(
                         itemRoot.get(Item_.itemPrice), price)));
        }
         //itemStock 大于等于 >= 语句
         if (stock != null) {
             predicatesList.add(
                 criteriaBuilder.and(
                     criteriaBuilder.ge(
                         itemRoot.get(Item_.itemStock), stock)));
        }
         //where()拼接查询条件
         query.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
         TypedQuery<Item> typedQuery = entityManager.createQuery(query);
         List<Item> resultList = typedQuery.getResultList();
         return resultList;
    }
 }

如果每个动态查询的地方都这么写,那就。感觉太麻烦了.

4.JpaSpecificationExecutor

SpringData JPA 为了实现 "Domain Driven Design" 中的规范概念,提供了一系列的 Specification 接口,其中最常用的便是 :JpaSpecificationExecutor。

使用 SpringData JPA 构建复杂查询(join操作,聚集操作等等)都是依赖于 JpaSpecificationExecutor 构建的 Specification 。

 public interface Specification<T> {
     Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
 }

这个方法的参数和返回值都是JPA标准里面定义的对象。

在Dao层继承JpaSpecificationExecutor,这个接口是不需要再去实现的,到了Repository层就行,再对此进行扩充(比Mybatis简单多了)

 //JpaRepository定义了简单的增删改查 简单分页
 //JpaSpecificationExecutor 定义了分页 和多条件动态查询(重点是使Dao层支持CQL)
 public interface UserDao extends JpaRepository<User,Integer>, JpaSpecificationExecutor {
 
 }

示例代码

 @RunWith(SpringRunner.class)
 @SpringBootTest
 public class UserDaoTest {
 
     @Autowired
     private UserDao ud;
 
     @Test
     public void userDaoTestDemo(){
         //多条件动态查询 比如说有一个表单   里边有一个搜索条件列表 包含用户名 性别   info
         User user = new User();
         user.setUsername("donghao");
         user.setSex(0);
         user.setInfo("董老了一岁");
         //多条件查询中 JPA提供了好几种方式
         //1.最简单的
         //Example<User> example = Example.of(user);//有值的属性 会充当条件 没有值的属性忽略
         //System.out.println(ud.findAll(example));
         //但是有问题 第一个案例做完以后发现全部都是等值
         //另外 能不能写 大于 小于...... 不能
         //2.ExampleMatcher自定义匹配规则,但是 对非字符串只能怪精确匹配   = 不能使用 > < 等等....
         //ExampleMatcher exampleMatcher = ExampleMatcher.matching().withMatcher("username",ExampleMatcher.GenericPropertyMatchers.contains());
         //Example<User> example = Example.of(user,exampleMatcher);
         //System.out.println(ud.findAll(example));
         //3.就是Hibernate中 CQL   Criteria是一个完全面向对象的查询 针对的就是类和对象及属性
 
         Specification specification = new Specification() {
             @Override
             public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
                 //构建规则有两种方式
                 //一种是利用CriteriaQuery
                 //一种是利用CriteriaBuilder
                 //创建一个过滤规则集合
                 List<Predicate> pres  = new ArrayList<Predicate>();
                 if(null!=user.getUsername()&&!"".equals(user.getUsername())){
                     Predicate p1= criteriaBuilder.like(root.get("username").as(String.class),"%"+user.getUsername());
                     pres.add(p1);
                }
                 if(null!=user.getSex()){
                     Predicate p2= criteriaBuilder.gt(root.get("sex").as(Integer.class),user.getSex());
                     pres.add(p2);
                }
                 Predicate[] p = new Predicate[pres.size()];
                 return criteriaBuilder.and(pres.toArray(p));
            }
        };
         Pageable pageable = PageRequest.of(1,1);
         System.out.println(ud.findAll(specification,pageable));
    }
 
 }



博观而约取,厚积而薄发!



--END--