Use JPA in Spring Boot

什么是JPA

​ JPA 的全称叫做 Java Persistence API,是一个基于 O/R 映射的标准规范。目前 JPA 的主要实现有Hibernate、EclipseLink、OpenJPA等,事实上,由于 Hibernate 在数据访问解决技术领域的绝对霸主地位,JPA的标准基本是由 Hibernate 来主导的。Spring 框架为我们提供了 Spring Data JPA,可以减少我们使用 JPA 时的代码量。

配置

  1. spring.jpa.show-sql=true:日志中打印 SQL 语句。

  2. spring.jpa.hibernate.ddl-auto=create:配置实体类维护数据库表结构的具体行为。

    • update:当实体类的属性发生变化时,表结构跟着更新。
    • create:启动的时候删除现有的表,并根据实体类重新生成表。
    • create-drop:启动时根据实体类生成表,但是当 sessionFactory 关闭的时候表会被删除。
    • validate:启动时验证实体类和数据表是否一致,不会创建新表,但是会插入新值。
    • none:什么都不做。
  3. spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

    在 SrpingBoot 2.0 版本中,Hibernate 创建数据表的时候,默认的数据库存储引擎选择的是 MyISAM 。这个参数是在建表的时候,将默认的存储引擎切换为 InnoDB 用的。

  4. jpa.generate-ddl=true:开启 DDL 功能。Initialize a Database Using JPA

使用

Spring Data JPA 2.1.0.RELEASE API

原生SQL语句

  1. 使用@Query注解
  2. 设置 nativeQuery 属性值为 true
1
2
@Query(value="SELECT count(*) FROM user", nativeQuery=true)
int getTotalRow();

JPQL

1
2
@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();

JPQL 不支持使用 INSERT

多条件查询

【一目了然】Spring Data JPA使用Specification动态构建多表查询、复杂查询及排序示例

  1. dao 层接口继承 JpaSpecificationExecutor 接口。

  2. 调用 List findAll(Specification spec);

  3. 实现 Specification

    1
    2
    3
    4
    5
    6
    7
     /**
    * @param root 根对象,即条件将被封装到该对象中。where 列名 = label.getXXX
    * @param query 查询关键字的封装。例如group by、order by等。几乎不用!
    * @param cb 用来封装条件对象。
    * @return 返回null代表没有任何条件
    */
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
  4. eg:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    public List<Student> getStudent(String studentNumber,String name ,String nickName,
    Date birthday,String courseName,float chineseScore,float mathScore,
    float englishScore,float performancePoints) {
    Specification<Student> specification = new Specification<Student>(){

    @Override
    public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    //用于暂时存放查询条件的集合
    List<Predicate> predicatesList = new ArrayList<>();
    //--------------------------------------------
    //查询条件示例
    //equal示例
    if (!StringUtils.isEmpty(name)){
    Predicate namePredicate = cb.equal(root.get("name"), name);
    predicatesList.add(namePredicate);
    }
    //like示例
    if (!StringUtils.isEmpty(nickName)){
    Predicate nickNamePredicate = cb.like(root.get("nickName"), '%'+nickName+'%');
    predicatesList.add(nickNamePredicate);
    }
    //between示例
    if (birthday != null) {
    Predicate birthdayPredicate = cb.between(root.get("birthday"), birthday, new Date());
    predicatesList.add(birthdayPredicate);
    }

    //关联表查询示例
    if (!StringUtils.isEmpty(courseName)) {
    Join<Student,Teacher> joinTeacher = root.join("teachers",JoinType.LEFT);
    Predicate coursePredicate = cb.equal(joinTeacher.get("courseName"), courseName);
    predicatesList.add(coursePredicate);
    }

    //复杂条件组合示例
    if (chineseScore!=0 && mathScore!=0 && englishScore!=0 && performancePoints!=0) {
    Join<Student,Examination> joinExam = root.join("exams",JoinType.LEFT);
    Predicate predicateExamChinese = cb.ge(joinExam.get("chineseScore"),chineseScore);
    Predicate predicateExamMath = cb.ge(joinExam.get("mathScore"),mathScore);
    Predicate predicateExamEnglish = cb.ge(joinExam.get("englishScore"),englishScore);
    Predicate predicateExamPerformance = cb.ge(joinExam.get("performancePoints"),performancePoints);
    //组合
    Predicate predicateExam = cb.or(predicateExamChinese,predicateExamEnglish,predicateExamMath);
    Predicate predicateExamAll = cb.and(predicateExamPerformance,predicateExam);
    predicatesList.add(predicateExamAll);
    }
    //--------------------------------------------
    //排序示例(先根据学号排序,后根据姓名排序)
    query.orderBy(cb.asc(root.get("studentNumber")),cb.asc(root.get("name")));
    //--------------------------------------------
    //最终将查询条件拼好然后return
    Predicate[] predicates = new Predicate[predicatesList.size()];
    return cb.and(predicatesList.toArray(predicates));
    }

    };
    return repository.findAll(specification);
    }

使用 jpa-spec 能够减少一些代码量

分页

  1. dao 层接口继承 JpaSpecificationExecutor接口。

  2. 调用 Page findAll(@Nullable Specification spec, Pageable pageable)

  3. 实现 Specification,及 Pageable

    1
    2
    3
    4
    5
    6
    7
    8
     /**
    * @param root 根对象,即条件将被封装到该对象中。where 列名 = label.getXXX
    * @param query 查询关键字的封装。例如group by、order by等。几乎不用!
    * @param cb 用来封装条件对象。
    * @param pageable 用来封装分页的对象。
    * @return 返回null代表没有任何条件
    */
    public Page<T> toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb, Pageable pageable);
  4. eg:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public Page<User> selectAll(int pageNum, int pageSize) {
    Pageable pageable = new PageRequest(pageNum, pageSize);
    Page<User> users = userRepository.findAll(new Specification<Student>(){

    @Override
    public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
    List<Predicate> predicatesList = new ArrayList<>();
    if (!StringUtils.isEmpty(name)){
    Predicate namePredicate = cb.equal(root.get("name"), name);
    predicatesList.add(namePredicate);
    }
    Predicate[] predicates = new Predicate[predicatesList.size()];
    return cb.and(predicatesList.toArray(predicates));
    }, pageable);
    }
    }

事务

JPA事务问题Executing an update/delete query

用Spring boot jpa update modify delete 数据和事务管理的那些坑

JPA 要求没有事务支持,不能执行更新和删除操作。默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务

所以在 Service 层上必须加上事务注解@Transactional,如果没加则会报如下异常:Executing an update/delete query

@Modifyig

在 @Query 注解中编写 JPQL 语句时,必须使用 @Modifying 进行修饰。以通知 SpringData,这是一个 UPDATE 或 DELETE 操作。同时需要在 service 层的方法上添加事务注解。