JPA 入门实战(4)--Spring Data JPA 使用

本文主要介绍 Spring Boot 中如何使用 Sping Data JPA,相关的环境及软件信息如下:Spring Boot 2.6.10。

1、Sping Data JPA 简介

Spring Data JPA 是 Spring Data 家族的一部分,它对基于 JPA 的数据访问提供了增强支持,让 JPA 更加易用。 它使得使用 Spring 构建的应用程序访问数据库变得更加容易。

编写应用程序的数据访问层是一件很麻烦的事, 必须编写太多样板代码来执行简单的查询以及分页和审计。 Spring Data JPA 旨在通过减工作量来显着改进数据访问层的实现。 作为开发人员,编写数据访问层接口,包括自定义查询方法,Spring 将自动提供实现。

1.1、Spring Data JPA 的特点

  • Sophisticated support to build repositories based on Spring and JPA

  • Support for Querydsl predicates and thus type-safe JPA queries

  • Transparent auditing of domain class

  • Pagination support, dynamic query execution, ability to integrate custom data access code

  • Validation of @Query annotated queries at bootstrap time

  • Support for XML based entity mapping

  • JavaConfig based repository configuration by introducing @EnableJpaRepositories.

1.2、Spring Data JPA 与 Hibernate 的关系

 Spring Data JPA 是 Spring 提供的一套对 JPA 操作更加高级的封装,底层依赖 Hibernate 的 JPA 实现。

2、JpaRepository 和 JpaSpecificationExecutor 接口

Spring Data JPA 提供了 JpaRepository 和 JpaSpecificationExecutor 接口,通过它们可以快速定义 DAO 接口,Spring Data JPA 会自动实现该接口。

2.1、JpaRepository

JpaRepository 接口内置了很多方法:

如果不满足业务需求,可以自定义方法。

2.1.1、使用 Query 注解

org.springframework.data.jpa.repository.Query 注解可以定义使用 JPQL 或 SQL 操作数据。 

/**
 * 通过 JPQL 查询
 */
@Query("from Student where name=?1 and age=?2")
Student query(String name, Integer age);

/**
 * SQL 语句查询数据
 */
@Query(value = "select * from a_student where name=? and age=?", nativeQuery = true)
Student queryBySql(String name, Integer age);

2.1.1、按照 Spring Data JPA 规范定义方法名称

Spring Data JPA 提供一些关键词,通过这些关键词和实体属性名称来组装方法名称。
主题关键词:
KeywordDescription

find…Byread…Byget…Byquery…Bysearch…Bystream…By

General query method returning typically the repository type, a Collection or Streamable subtype or a result wrapper such as PageGeoResults or any other store-specific result wrapper. Can be used as findBy…findMyDomainTypeBy… or in combination with additional keywords.

exists…By

Exists projection, returning typically a boolean result.

count…By

Count projection returning a numeric result.

delete…Byremove…By

Delete query method returning either no result (void) or the delete count.

…First<number>……Top<number>…

Limit the query results to the first <number> of results. This keyword can occur in any place of the subject between find (and the other keywords) and by.

…Distinct…

Use a distinct query to return only unique results. Consult the store-specific documentation whether that feature is supported. This keyword can occur in any place of the subject between find (and the other keywords) and by.

条件关键词:
Logical keywordKeyword expressions

AND

And

OR

Or

AFTER

AfterIsAfter

BEFORE

BeforeIsBefore

CONTAINING

ContainingIsContainingContains

BETWEEN

BetweenIsBetween

ENDING_WITH

EndingWithIsEndingWithEndsWith

EXISTS

Exists

FALSE

FalseIsFalse

GREATER_THAN

GreaterThanIsGreaterThan

GREATER_THAN_EQUALS

GreaterThanEqualIsGreaterThanEqual

IN

InIsIn

IS

IsEquals, (or no keyword)

IS_EMPTY

IsEmptyEmpty

IS_NOT_EMPTY

IsNotEmptyNotEmpty

IS_NOT_NULL

NotNullIsNotNull

IS_NULL

NullIsNull

LESS_THAN

LessThanIsLessThan

LESS_THAN_EQUAL

LessThanEqualIsLessThanEqual

LIKE

LikeIsLike

NEAR

NearIsNear

NOT

NotIsNot

NOT_IN

NotInIsNotIn

NOT_LIKE

NotLikeIsNotLike

REGEX

RegexMatchesRegexMatches

STARTING_WITH

StartingWithIsStartingWithStartsWith

TRUE

TrueIsTrue

WITHIN

WithinIsWithin

 
详细说明可参考官网文档:https://docs.spring.io/spring-data/jpa/docs/2.6.6/reference/html/#repository-query-keywords。
命名样例:
方法名 JPQL
findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
findByStartDateBetween … where x.startDate between ?1 and ?2
findByAgeLessThan … where x.age < ?1
findByAgeLessThanEqual … where x.age ⇐ ?1

2.2、JpaSpecificationExecutor

JpaSpecificationExecutor 接口主要用来在 JpaRepository 接口无法满足要求时,可以使用 JPA 的标准 API 来操作数据。

3、Sping Data JPA 使用

这里演示下 Spring Data JPA 的基本使用,工程目录结构如下:

3.1、引入依赖

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.6.10</version>
  <relativePath />
</parent>
<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
  </dependency>

  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.29</version>
  </dependency>

  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
  </dependency>

  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      <scope>provided</scope>
  </dependency>
</dependencies>

3.2、创建实体类

package com.abc.demojpa.entity;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.*;
import java.time.LocalDateTime;

@NoArgsConstructor
@Data
@Entity
@Table(name = "a_student")
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @CreationTimestamp
    @Column(name = "create_time")
    private LocalDateTime createTime;

    @UpdateTimestamp
    @Column(name = "modify_time")
    private LocalDateTime modifyTime;

    private String name;

    private Integer age;

    @Column(name = "home_address")
    private String homeAddress;
}

3.3、编写配置文件(application.yml)

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://10.49.196.10:3306/test?useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update

3.4、定义 DAO 接口

package com.abc.demojpa.dao;

import com.abc.demojpa.entity.Student;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface IStudentRepository extends JpaRepository<Student, Long>, JpaSpecificationExecutor<Student> {
    /**
     * 根据姓名和年龄查询
     */
    List<Student> findByNameAndAge(String name, Integer age);

    /**
     * 根据姓名和年龄查询前5条
     */
    List<Student> findTop5ByNameAndAge(String name, Integer age);

    /**
     * 根据姓名和年龄查询并按id排序
     */
    List<Student> findByNameAndAgeOrderByIdAsc(String name, Integer age);

    /**
     * 根据姓名和年龄查询第一条
     */
    Student findFirstByNameAndAge(String name, Integer age);

    /**
     * 通过 JPQL 查询
     */
    @Query("from Student where name=?1 and age=?2")
    Student query(String name, Integer age);

    /**
     * 通过 JPQL 查询
     */
    @Query("from Student where name=:name and age=:age")
    Student query2(@Param("name") String name, @Param("age") Integer age);

    /**
     * 通过 JPQL 更新数据
     */
    @Query("update Student set homeAddress=?1 where id=?2")
    @Modifying
    void updateHomeAddress(String homeAddress, Long id);

    /**
     * SQL 语句查询数据
     */
    @Query(value = "select * from a_student where name=? and age=?", nativeQuery = true)
    Student queryBySql(String name, Integer age);
}

3.5、编写启动类

package com.abc.demojpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class DemoJpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoJpaApplication.class, args);
    }
}

3.6、编写测试用例

3.6.1、增加数据

/**
 * 增加数据
 * Spring Boot 事务测试时默认会回滚操作避免产生测试数据,如果不需要回滚可使用 @Commit 注解
 */
@Test
@Transactional
@Commit
public void add() {
    Student student = new Student();
    student.setName("小明");
    student.setAge(15);
    student.setHomeAddress("江苏");

    Student student2 = new Student();
    student2.setName("小红");
    student2.setAge(18);
    student2.setHomeAddress("广东");

    studentRepository.save(student);
    studentRepository.save(student2);
}

3.6.2、修改数据

@Transactional
@Commit
@Test
public void modify() {
    Student student = new Student();
    student.setId(3L);
    student.setName("小明2");
    student.setAge(15);
    student.setHomeAddress("江苏");
    //设置了 id 表示更新数据
    studentRepository.save(student);

    //调用自定义的更新方法
    studentRepository.updateHomeAddress("上海", 12L);
}

3.6.3、查询数据

@Test
public void query() {
    //根据 id 查询
    Optional<Student> optional = studentRepository.findById(12L);
    logger.info("student={}", optional.get());

    //查询所有
    List<Student> list = studentRepository.findAll();
    logger.info("list={}", list);

    //分页及排序查询
    List<Sort.Order> orders = new ArrayList<>();
    orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
    orders.add(new Sort.Order(Sort.Direction.ASC, "name"));
    Page<Student> page = studentRepository.findAll(PageRequest.of(0, 10, Sort.by(orders)));
    logger.info("page.getTotalElements={},page.getTotalPages()={},page.toList()={}", page.getTotalElements(), page.getTotalPages(), page.toList());

    //通过 JPA 标准 API 查询,可以用来动态拼接查询条件
    Specification<Student> specification = new Specification<Student>() {
        @Override
        public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
            List<Predicate> list = new ArrayList<>();
            list.add(criteriaBuilder.equal(root.get("name"), "小红"));
            list.add(criteriaBuilder.ge(root.get("age"), 10));
            return criteriaBuilder.and(list.toArray(new Predicate[]{}));
        }
    };
    list = studentRepository.findAll(specification);
    logger.info("list={}", list);

    list = studentRepository.findByNameAndAge("小红", 18);
    logger.info("list={}", list);

    list = studentRepository.findTop5ByNameAndAge("小红", 18);
    logger.info("list={}", list);

    list = studentRepository.findByNameAndAgeOrderByIdAsc("小红", 18);
    logger.info("list={}", list);

    Student student = studentRepository.findFirstByNameAndAge("小红", 18);
    logger.info("student={}", student);

    student = studentRepository.query("小红", 18);
    logger.info("student={}", student);

    student = studentRepository.query2("小红", 18);
    logger.info("student={}", student);

    student = studentRepository.queryBySql("小红", 18);
    logger.info("student={}", student);
}

3.6.4、删除数据

@Test
public void remove() {
    //根据实体删除
    Student student = new Student();
    student.setId(3L);
    studentRepository.delete(student);

    List<Student> students = new ArrayList<>();
    Student student2 = new Student();
    student.setId(4L);
    students.add(student);
    students.add(student2);
    studentRepository.deleteAll(students);

    //根据 id 删除
    studentRepository.deleteById(5L);
    List<Long> ids = new ArrayList<Long>(){{
        add(6L);
        add(7L);
    }};
    studentRepository.deleteAllByIdInBatch(ids);
}

3.6.5、完整代码

package com.abc.demojpa.service;


import com.abc.demojpa.dao.IStudentRepository;
import com.abc.demojpa.entity.Student;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.annotation.Commit;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestService.class);

    @Autowired
    private IStudentRepository studentRepository;

    /**
     * 增加数据
     * Spring Boot 事务测试时默认会回滚操作避免产生测试数据,如果不需要回滚可使用 @Commit 注解
     */
    @Test
    @Transactional
    @Commit
    public void add() {
        Student student = new Student();
        student.setName("小明");
        student.setAge(15);
        student.setHomeAddress("江苏");

        Student student2 = new Student();
        student2.setName("小红");
        student2.setAge(18);
        student2.setHomeAddress("广东");

        studentRepository.save(student);
        studentRepository.save(student2);
    }

    /**
     * 修改数据
     */
    @Transactional
    @Commit
    @Test
    public void modify() {
        Student student = new Student();
        student.setId(3L);
        student.setName("小明2");
        student.setAge(15);
        student.setHomeAddress("江苏");
        //设置了 id 表示更新数据
        studentRepository.save(student);

        //调用自定义的更新方法
        studentRepository.updateHomeAddress("上海", 12L);
    }


    /**
     * 查询数据
     */
    @Test
    public void query() {
        //根据 id 查询
        Optional<Student> optional = studentRepository.findById(12L);
        logger.info("student={}", optional.get());

        //查询所有
        List<Student> list = studentRepository.findAll();
        logger.info("list={}", list);

        //分页及排序查询
        List<Sort.Order> orders = new ArrayList<>();
        orders.add(new Sort.Order(Sort.Direction.DESC, "id"));
        orders.add(new Sort.Order(Sort.Direction.ASC, "name"));
        Page<Student> page = studentRepository.findAll(PageRequest.of(0, 10, Sort.by(orders)));
        logger.info("page.getTotalElements={},page.getTotalPages()={},page.toList()={}", page.getTotalElements(), page.getTotalPages(), page.toList());

        //通过 JPA 标准 API 查询,可以用来动态拼接查询条件
        Specification<Student> specification = new Specification<Student>() {
            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> list = new ArrayList<>();
                list.add(criteriaBuilder.equal(root.get("name"), "小红"));
                list.add(criteriaBuilder.ge(root.get("age"), 10));
                return criteriaBuilder.and(list.toArray(new Predicate[]{}));
            }
        };
        list = studentRepository.findAll(specification);
        logger.info("list={}", list);

        list = studentRepository.findByNameAndAge("小红", 18);
        logger.info("list={}", list);

        list = studentRepository.findTop5ByNameAndAge("小红", 18);
        logger.info("list={}", list);

        list = studentRepository.findByNameAndAgeOrderByIdAsc("小红", 18);
        logger.info("list={}", list);

        Student student = studentRepository.findFirstByNameAndAge("小红", 18);
        logger.info("student={}", student);

        student = studentRepository.query("小红", 18);
        logger.info("student={}", student);

        student = studentRepository.query2("小红", 18);
        logger.info("student={}", student);

        student = studentRepository.queryBySql("小红", 18);
        logger.info("student={}", student);
    }

    /**
     * 删除数据
     */
    @Test
    public void remove() {
        //根据实体删除
        Student student = new Student();
        student.setId(3L);
        studentRepository.delete(student);

        List<Student> students = new ArrayList<>();
        Student student2 = new Student();
        student.setId(4L);
        students.add(student);
        students.add(student2);
        studentRepository.deleteAll(students);

        //根据 id 删除
        studentRepository.deleteById(5L);
        List<Long> ids = new ArrayList<Long>(){{
            add(6L);
            add(7L);
        }};
        studentRepository.deleteAllByIdInBatch(ids);
    }
}
TestService.java

 

posted @ 2022-09-03 19:05  且行且码  阅读(527)  评论(0编辑  收藏  举报