Spring Boot 2.0 读书笔记_10:Spring Data JPA 下

一、持久化Entity

首先我们创建一个UserRepository,继承接口JpaRepository(该接口集成了所有常用接口方法),进行基础CURD的操作测试。

注意:
<User, Integer>,分别代表:实体类以及实体类主键属性封装类型
UserRepository也就是对应常规开发模式下的DAO接口

public interface UserRepository extends JpaRepository<User, Integer> {
  
  // 保存(添加)和更新
  @Override
  <S extends User> S save(S s);

  // 根据Id删除
  @Override
  void deleteById(Integer integer);

  // 根据Id查询
  @Override
  Optional<User> findById(Integer integer);
}

Service层实现

  • save:保存(添加)、更新

      // 添加和更新实现
      public Integer addUser(User user){
        user.setName("张三");
        userDao.save(user);
        user.setName(user.getName() + "_");
        userDao.save(user);
        return user.getId();
      }
    

测试结果:

  Hibernate: insert into user (create_time, department_id, name) values (?, ?, ?)
  Hibernate: update user set create_time=?, department_id=?, name=? where id=?

数据库返回结果:
在这里插入图片描述
分析:
在定义User Entity时,对id成员变量进行了@Id、@GeneratedValue(strategy=GenerationType.IDENTITY)标注,表名该成员变量对应数据库表中的主键,并且采用了主键自增的策略。

通过addUser方法save User对象时,没有setId(),主键为空情况下,插入user对象到数据库。看到的SQL语句打印也并没有Id字段,是因为主键自增策略的缘故,插入后主键Id赋值为4。当第二个save方法执行的时候,User对象已经有主键了。因此执行了更新操作,Id是通过user.getId获取的,因此最终结果name字段变为了 '张三_'

  • findById:根据Id进行查询

    public User findUser(int id) {
      Optional<User> user = userDao.findById(id);
      return user;
    }
    

    测试结果:

    Hibernate: select user0_.id as id1_1_0_, user0_.create_time as create_t2_1_0_, 
    user0_.department_id as departme4_1_0_, user0_.name as name3_1_0_, department1_.id 
    as id1_0_1_, department1_.name as name2_0_1_ from user user0_ left outer join 
    department department1_ on user0_.department_id=department1_.id where user0_.id=?
    
  • deleteById:根据Id进行删除

    public boolean deleteUser(int id) {
      userDao.deleteById(id);
      return userDao.existsById(id);
    }
    

    测试结果:false

    Hibernate: delete from user where id=?
    Hibernate: select count(*) as col_0_0_ from user user0_ where user0_.id=?
    

总结下:通过简单的CURD测试发现,基本的CURD功能可以实现,也无需使用XML配置SQL。但是打印的执行SQL着实有点复杂,难道这就是自动生成的SQL该有的样子?当然针对非过多复杂查询的业务场景下是可以的,牵扯复杂查询之后,避免不了SQL语句的自定义。

二、Sort 排序

测试内置排序方法:

List findAll(Sort sort); 返回所有实体,按照指定排序(默认升序)返回。

Sort对象的构造方法:

方法说明
public Sort(String… properties)按照指定的属性列表升序排序
public Sort(Direction direction, String… properties)按照指定属性列表排序,排序方式由Direction指定,Direction是枚举类,有Direction.ASC和Direction.DESC两类
public Sort(Order… orders)Order 可以通过Order静态方法来创建
public static Order asc(String propertyName);
public static Order desc(String propertyName);
  • getAllUserList:返回用户实体集合

    public List<User> getAllUserList() {
      // 按照Id降序查询
      Sort sort = new Sort(Sort.Direction.DESC, "id");
      List<User> all = userDao.findAll(sort);
      return all;
    }
    

    测试结果:

    Hibernate: select user0_.id as id1_1_, user0_.create_time as 
    create_t2_1_, user0_.department_id as departme4_1_, user0_.name as 
    name3_1_ from user user0_ order by user0_.id desc
    

    返回截图:
    在这里插入图片描述

三、Page和Pageable

Pageable接口用于构造翻页查询,PageRequest是其实现类

测试内置分页方法(含排序):

Page findAll(Pageable pageable); 返回实体列表,实体列表的offset、limit通过方法参数 Pageable 控制。

这里采用PageRequest中的如下构造方法实例化Pageable接口:

public PageRequest(int page, int size, 
    Direction direction, String... properties) {
    this(page, size, Sort.by(direction, properties));
}

注意:
page 总是从0开始,标识查询页,size标识每页期望的行数。
Spring Data 分页返回Page对象,Page对象提供了以下常用方法:

方法说明
int getTotalPages()总页数
long getTotalElements()返回元素总数
List getContent()返回此次查询的结果集
  • getAllUser(int page, int size):返回分页实体列表

    public List<User> getAllUser(int page, int size) {
      PageRequest pageable = PageRequest.of(page, size, Sort.Direction.DESC, "id");
      Page<User> pageObject =  userDao.findAll(pageable);
      int totalPage = pageObject.getTotalPages();
      long count = pageObject.getTotalElements();
      return pageObject.getContent();
    }
    

    这里按照size = 2处理,一共5条数据,理论分3页。
    测试前数据:
    在这里插入图片描述
    测试page0&size=2时的请求:
    在这里插入图片描述

四、JPQL
Spring Data 可以通过查询的方法名和参数名自动构造 JPA OQL 查询。
JPA OQL 简称 JPQL:[Java Persistence Query Language] Java持久化查询语言,旨在以面向对象表达式语言的表达式,将SQL语法简单查询语义绑定在一起。这种语言编写的查询是可移植的,可以被编译成主流数据库服务器上的SQL

其特征与原生SQL语句类似,并且完全面向对象,通过类名属性访问,而不是表名表的属性

  • 基于方法名字查询

    public interface UserRepository extends JpaRepository<User, Integer> {
      public User findByName(String name);
    }
    

    注意:方法名和参数名需要遵循一定的规则,Spring Data JPA才能自动转化为JPQL

    • 方法名通常包含多个实体属性(成员变量)用于查询,属性(成员变量)之间可以使用ANDOR连接,也支持BetweenLessThanGreaterThanLike;
    • 方法名可以以findBygetByqueryBy开头;
    • 查询结果可以排序,方法名需要包含OrderBy+属性+Asc(Desc);
    • 可以通过TopFirst来限定查询结果集;
    • 一些特殊的参数可以出现在参数列表(签名)中,如:PageableSort

    举堆栗子:

    • 根据名字查询,结果升序,且分页

      Page<User> findByLastnameOrderByFirstNameAsc(String lastname, Pageable pageable);
      
    • 查询满足条件的前10个用户

      List<User> findFirst10ByLastname(String lastname, Sort sort);
      
    • 使用And联合查询

      List<User> findByLastnameAndFirstname(String lastname, String firstname);
      
    • 使用Or查询

      List<User> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
      
    • 使用like查询,name中必须包含like中的%或者?

      User findByNameLike(String name);
      
  • Spring Data支持的关键字

    关键字例子转化的JPQL片段
    AndfindByLastnameAndFirstname…where x.lastname = ?1 and x.firstname = ?2
    OrfindByLastnameOrFirstname…where x.lastname = ?1 or x.firstname = ?2
    BetweenfindByStartDateBetween…where x.startDate between ?1 and ?2
    LessThanfindByAgeLessThan
    findByAgeLessThanEqual
    …where x.age < ?1
    …where x.age <= ?1
    GreaterThanfindByAgeGreaterThan
    findByAgeGreaterThanEqual
    …where x.age > ?1
    …where x.age >= ?1
    After(DateTime)findByStartDateAfter…where x.startDate > ?1
    Before(DateTime)findByStartDateBefore…where x.startDate < ?1
    IsNullfindByAgeIsNull
    findByAge(Is)NotNull
    …where x.age is null
    …where x.age not null
    OrderByfindByAgeOrderByLastnameDesc…where x.age = ?1 order by x.lastname desc
    NotfindByLastnameNot…where x.lastname <>?1
    InfindByAgeIn(Collection ages)
    findByAgeNotIn(Collection ages)
    …where x.age in ?1
    …where x.age not in ?1
    TruefindByActiveTrue
    finByActiveFlase
    …where x.active = true
    …where x.active = false
    IgnoreCasefindByFirstnameIgnoreCase…where UPPER(x.firstname) = UPPER(?1)
    LikefindByFirstnameLike
    findByFirstnameNotLike
    …where x.firstname like ?1
    …where x.firstname not like ?1

    突然觉得这种JPA查询好像很高大上的样子,完全无SQL查询。真是厉害了我的JPQL!

五、@Query查询

@Query注解允许在方法使用JPQL

  • JPQL:面向对象和对象属性

    @Query(value = "select u from User u where u.name = ?1 and u.department.id = ?2")
    public User findUser(String name, Integer departmentId);
    
  • SQL:面向数据表名和字段名 (nativeQuery = true)

    @Query(value = "select * from User where name = ?1 and department_id = ?2", 
            nativeQuery =  true)
    public User findUser(String name, Integer departmentId);
    
  • 无论是JPQL还是SQL,均支持命名参数。例如:

    @Query(value = "select * from User where name = :name and 
            department_id = :departmentId", nativeQuery =  true)
    public User findUser(String name, Integer departmentId);
    

    命名参数在上一章节JDBC Template 中也提到了,这样有利于SQL语句参数的和方法参数的映射,常规的索引方式固然方便,但是方法参数签名顺序变更后,SQL语句中的?占位符顺序也要变更。各有优劣。

  • Object[]数组非Entity查询返回结果的替代方案

    例如:分组统计每个部门的用户数目

    @Query(value = "select department_id, count(*) from User group by department_id", 
          nativeQuery = true)
    public List<Object[]> queryUserCount();
    
  • @Modifying:JPQL更新、删除操作时搭配使用

    @Modifying
    @Query(value = "update User u set u.name = ?1 where u.id = ?2")
    public int updateName(String name, Integer id);
    

六、Example 查询
上边提到了方法名构造JPQL@Query构造JPQL进行查询,Example对象的查询方案是指:根据实体(Entity)创建一个Example对象,Spring Data 通过 Example对象来构造JPQL

public List<User> getByExample(String name) {
  // 创建对象
  User user = new User();
  Department dept = new Department();
  // 属性赋值
  user.setName(name);
  dept.setId(1);
  // 组合对象user赋值
  user.setDepartment(dept);
  // Exanple.of静态方法构造example对象
  Example<User> example = Example.of(user);
  // 调用findAll进行查询
  List<User> list = userDao.findAll(example);
  return list;
}

总结下:通过构造实体查询条件user,结合Example.of(user)的静态方法构造Example对象,最后将Example对象作为参数传入JpaRepository(UserRepository)实现方法中进行执行查询。

非完全匹配查询:ExampleMatcher

  // ExampleMatcher提供了条件指定。下述代码条件为:名称Tom开头的所有用户
  ExampleMatcher matcher = ExampleMatcher.matching()
    .withMatcher("Tom", GenericPropertyMatchers.startsWith().ignoreCase());
  Example<User> example = Example.of(user, matcher);

七、EntityManager:JPA提供的底层数据库访问接口

书中这里提到了一个例子:动态分页查询中使用EntityManager弥补Repository的不足。

这里就直接上代码说明了:

public Page<User> queryUser(Integer departmentId, Pageable page) {
  //构造基础JPQL,这个JPQL将作为求和以及分页查询的基础JPQL
  StringBuilder baseJpql = new StringBuilder("from User u where 1 = 1 ");
  // 初始化查询参数Map
  Map<String, Object> paras = new HashMap<String, Object>();
  if (departmentId != null) {
      // 添加附加添加参数,拼装JPQL
      baseJpql.append("and u.department.id = :deptId");
      // 配置附加查询参数 deptId
      paras.put("deptId", departmentId);
  }
  //功能1:查询满足条件的总数
  long count = getQueryCount(baseJpql, paras);
  if (count == 0) {
      return new PageImpl(Collections.emptyList(), page, 0);
  }
  //功能2:查询满足分页条件结果集
  List list = getQueryResult(baseJpql, paras, page);
  //返回结果
  Page ret = new PageImpl(list, page, count);
  return ret;
}

通用:查询参数配置方法:setQueryParameter()

private void setQueryParameter(Query query, Map<String, Object> paras) {
  for (Entry<String, Object> entry : paras.entrySet()) {
    query.setParameter(entry.getKey(), entry.getValue());
  }
}

功能1:求和查询 getQueryCount()

private Long getQueryCount(StringBuilder baseJpql, 
                            Map<String, Object> paras) {
  // 创建Query 查询对象 结合传递的JPQL
  Query query = em.createQuery("select count(1) " + baseJpql.toString());
  // 调用通用查询参数配置方法(见上)
  setQueryParameter(query, paras);
  // 调用query对象的查询方法:getSingleResult()得到满足条件的记录总数
  Number number = (Number) query.getSingleResult();
  return number.longValue();
}

功能2:分页查询 getQueryResult()

private List getQueryResult(StringBuilder baseJpql, Map<String, Object> paras, Pageable page) {
  // 创建Query 查询对象 结合传递的JPQL
  Query query = em.createQuery("select u " + baseJpql.toString());
  // 调用通用查询参数配置方法(见上上)
  setQueryParameter(query, paras);
  // 配置分页相关信息
  query.setFirstResult((int) page.getOffset());
  query.setMaxResults(page.getPageNumber());
  // 调用查询方法得到list
  List list = query.getResultList();
  return list;
}

queryUser() 的逻辑梳理为:首先通过拼装好的JPQL进行求和查询,根据返回count是否为 0,为 0 则返回一个空页(Collections.emptyList( )),非 0 便根据总数进行记录分页,分页参数对象作为参数传入分页查询方法,最后返回分页结果数据集。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小透明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值