3 查询方法
3.1 查询方法配置
3.2 方法查询策略设置
@EnableJpaRepositories
3.5 查询策略的属性表达式
对于以下两个实体类, 在 JPA 可以以遍历嵌套属性定义约束创建查询,其创建及查找过程:
具体实现参见:PartTree 及 PartTreeJpaQuery
1 @Entity 2 @Data 3 public class Person { 4 @Id 5 @GeneratedValue 6 private Long id; 7 8 private Address address; 9 }
1 @Entity 2 @Data 3 public class Address { 4 private Long id; 5 6 private String zipCode; 7 8 }
1 public interface PersonRepository extends JpaRepository<Person, Long> { 2 3 Person findByAddress_ZipCode(String zipCode); 4 }
3.6 查询结果的处理
类定义
1 @Entity 2 @Data 3 @Builder 4 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 5 public class Person { 6 @Id 7 @GeneratedValue(strategy = GenerationType.IDENTITY) 8 private Long id; 9 @EqualsAndHashCode.Include 10 private String firstName; 11 @EqualsAndHashCode.Include 12 private String lastName; 13 @OneToOne(targetEntity = Address.class, mappedBy = "person", cascade = CascadeType.ALL) 14 private Address address; 15 16 17 @Entity 18 @Data 19 @Builder 20 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 21 static class Address { 22 @Id 23 @GeneratedValue(strategy = GenerationType.IDENTITY) 24 private Long id; 25 @EqualsAndHashCode.Include 26 private String zipCode; 27 @EqualsAndHashCode.Include 28 private String city; 29 30 @OneToOne(targetEntity = Person.class) 31 @ToString.Exclude 32 private Person person; 33 34 } 35 36 37 }
3.6.1 Pageable 和 Slice
1 Page<Person> findByLastName(String lastName, Pageable pageable); 2 Slice<Person> findByLastName(String lastName, Pageable pageable); 3 List<Person> findByLastName(String lastName, Sort sort); 4 List<Person> findByLastName(String lastName, Pageable pageable);
3.6.2 流式查询与异步查询
1 // 需要调用 BaseStream#close() 关闭流 2 3 Stream<Person> findByFirstName(String firstName); 4 5 @Async 6 Future<Person> findByAddress(Person.Address address); 7 8 @Async 9 CompletableFuture<Person> findByLastName(String lastName); 10 11 @Async 12 ListenableFuture<Person> findDistinctByAddress_ZipCode(String zipCode);
3.6.3 Projections 扩展查询结果
3.6.3.1 结果映射
当只需要 firstName 和 lastName 的时候, 可以定义一个映射接口, 让 Spring Data 映射结果到这个接口上:
1 interface NamesOnly { 2 3 String getFirstName(); 4 5 String getLastName(); 6 7 }
在 Repository 中使用该接口
1 List<NamesOnly> findByLastName(String lastName);
测试类: 见 3.6.3.2
3.6.3.2 @Value 支持
interface NamesOnly { @Value("#{target.firstName+'_'+target.lastName}") String summary(); String getFirstName(); String getLastName(); }
上边的接口等同于 NamesOnlyImpl , 可使用 NamesOnlyImpl 代替
1 @Getter 2 @Setter 3 @AllArgsConstructor 4 class NamesOnlyImpl implements NamesOnly { 5 private final String firstName; 6 private final String lastName; 7 8 @Override 9 public String fullName() { 10 return firstName.concat("_").concat(lastName); 11 } 12 }
测试类:
1 @SpringBootTest 2 @RunWith(SpringRunner.class) 3 @Transactional 4 class PersonRepositoryTest { 5 @Autowired 6 private PersonRepository personRepository; 7 8 //NamesOnly 9 @Test 10 void testFindByLastName() { 11 List<Person> peoples = new ArrayList<>(); 12 13 final int count = 3; 14 //添加4个待测数据 15 // F0 L0 Z0 C0 16 // F1 L1 Z1 C1 17 // F2 L2 Z2 C2 18 // F3 L3 Z3 C3 19 for (int i = 0; i < count; i++) { 20 Person person = Person.builder() 21 .firstName("F" + i) 22 .lastName("L" + i) 23 .address(Person.Address.builder() 24 .zipCode("Z" + i) 25 .city("C" + i).build()).build(); 26 27 Person.Address address = Person.Address.builder() 28 .zipCode("Z" + i) 29 .city("C" + i).person(person).build(); 30 31 person.setAddress(address); 32 peoples.add(person); 33 } 34 //设置第二个数据 F1 L1 Z1 C1 -> F1 L0 Z1 C1 35 peoples.get(1).setLastName("L0"); 36 // 保存4条待测数据 37 personRepository.saveAll(peoples); 38 39 // 查找出来已保存的数据 40 List<NamesOnly> namesOnlies = personRepository.findByLastName("L0"); 41 42 //筛选元数据中的两条目标数据 , 作与 持久化结果 的比较 43 // F0 L0 Z0 C0 44 // F1 L0 Z1 C1 45 List<Person> targetPeopleInList = peoples.stream().filter(person -> person.getFirstName().contentEquals("L0")).collect(Collectors.toList()); 46 47 // 断言符合 lastName = 'L0' 的持久化结果只有两条 48 Assert.assertEquals(2, namesOnlies.size()); 49 50 // 在目标数据表中删除对应的条目 51 targetPeopleInList.removeIf( 52 person -> 53 person.getFirstName().concat("_").concat(person.getLastName()).contentEquals(namesOnlies.get(0).summary()) || 54 person.getFirstName().concat("_").concat(person.getLastName()).contentEquals(namesOnlies.get(1).summary()) 55 ); 56 // 目标数据此时应该为空 57 Assert.assertTrue(targetPeopleInList.isEmpty()); 58 59 60 } 61 }
3.6.3.3 对 动态 Projections 的支持
1 public interface PersonRepository extends CrudRepository<Person, Long> { 2 3 Collection<NamesOnly> findByLastName(String lastName); 4 5 6 <T> List<T> findByLastName(String lastName, Class<T> type);7 8 }
4 注解式查询
4.1 @Query
4.1.1 JPQL
@Query(value = "select p from Person p where p.firstName=?1")
List<Person> findByFirstName(String firstName);
4.1.2 JPQL+LIKE
@Query(value = "select p from Person p where p.firstName like %?1")
List<Person> findByFirstNameEndingWith(String ends);
4.1.3 原生 SQL查询
@Query(nativeQuery = true, value = "" + "select * " + "from person inner join person$address p$a " + "on person.id = p$a.person_id where p$a.city=?1") Person findByAddress_City(String city);
4.1.4 @Param 绑定参数
@Query(value = "select p from Person p where p.lastName=:lastName and p.firstName=:firstName")
List<Person> findByFirstNameAndLastName(@Param("lastName") String lastName, @Param("firstName") String firstName);
4.1.5 JPQL 分页
@Query(value = "select p from Person p where p.firstName=?1")
Page<Person> findByFirstName(String firstName, Pageable pageable);
5 常用注解
5.1 JPA层次结构
5.2 常用注解
5.2.1 @Entity 持久化实体
5.2.2 @Table 表定义
5.2.3 @Id 主键
5.2.4 @IdClass 联合主键
在下例中, title 和 createUserId 是联合主键, 主要 BlogKey 需要实现 Equals 和 HashCode 方法
1 @Entity 2 @Table(name = "blog") 3 @IdClass(BlogKey.class) 4 public class Blog implements Serializable{ 5 6 @Column(nullable = false) 7 private String title; 8 @Column(nullable = false) 9 private Integer createUserId; 10 11 @Lob 12 private String content; 13 14 } 15 16 @Data 17 class BlogKey implements Serializable { 18 private String title; 19 private Integer createUserId; 20 21 }
其 Repository
interface BlogRepository extends Repository<Blog, BlogKey> { }
5.2.5 @GeneratedValue
生成策略(strategy)有4种
- GenerationType.TABLE:Hibernate 在数据库中新建一个表 hibernate_sequences, 用来表示下一个插入的主键数值 , 易于不同数据库间的移植;
- GenerationType.SEQUENCE:通过序列产生主键,不适用于 MySQL ,一般用于 Oracle 主键生成规则, 需要配合 @SequenceGenerator ;
- GenerationType.IDENTITY: 采用数据库ID自增长,一般用于 MySQL ;
- GenerationType.AUTO: 让 Hibernate 自行选择,默认选项;
5.2.6 @Basic 持久化字段
表示实体到数据库的映射,对于没有任何JPA注解的字段,默认即为 @Basic
5.2.7 @Transient 非持久化字段
表示该字段并非一个到数据库表的映射
5.2.8 @Column 列属性
5.2.9 @Temporal Date 类型精度
5.2.10 @Enumerated 枚举类型
5.2.11 @Lob 大字段
5.2.12 @OrderBy 排序
5.2.13 附: 完整的类实例
1 @Entity 2 @Table(name = "blog") 3 @IdClass(BlogKey.class) 4 @Data 5 @EqualsAndHashCode(onlyExplicitlyIncluded = true) 6 public class Blog implements Serializable { 7 @Id 8 @Column(nullable = false) 9 @EqualsAndHashCode.Include 10 private String title; 11 @Id 12 @Column(nullable = false) 13 @EqualsAndHashCode.Include 14 private Integer createUserId; 15 16 @OrderBy("page_creatDate ASC ") 17 @OneToMany(targetEntity = Page.class, cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "blog") 18 private List<Page> pages; 19 20 @OneToOne(targetEntity = Owner.class, cascade = CascadeType.ALL, mappedBy = "blog") 21 private Owner owner; 22 } 23 24 @Data 25 class BlogKey implements Serializable { 26 private String title; 27 private Integer createUserId; 28 29 } 30 31 @Entity 32 @Data 33 class Page { 34 @Id 35 @GeneratedValue(strategy = GenerationType.IDENTITY) 36 private Long id; 37 @Basic(fetch = FetchType.LAZY) 38 @Lob 39 private String content; 40 @Temporal(TemporalType.TIMESTAMP) 41 private Date createDate; 42 43 44 @ManyToOne(targetEntity = Blog.class, cascade = {CascadeType.REFRESH, CascadeType.MERGE}) 45 private Blog blog; 46 47 48 } 49 50 @Entity 51 @Data 52 class Owner { 53 @Id 54 @GeneratedValue(strategy = GenerationType.IDENTITY) 55 private Long id; 56 @Column(nullable = false, length = 30) 57 private String name; 58 59 @OneToOne(targetEntity = Blog.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) 60 private Blog blog; 61 62 @Enumerated(EnumType.STRING) 63 private USERTYPE usertype; 64 } 65 66 @AllArgsConstructor 67 enum USERTYPE { 68 /* 69 一般使用者 70 */ 71 USER_ROMAL("USER_ROMAL"), 72 /* 73 管理员 74 */ 75 USER_ADMIN("USER_ADMIN"); 76 private String name; 77 } 78 79 80 interface BlogRepository extends Repository<Blog, BlogKey> { 81 82 }