Example查询翻译过来叫“按例查询(QBE)”。是一种用户界面友好的查询技术。 它允许动态创建查询,并且不需要编写包含字段名称的查询。 而且按示例查询不需要使用特定的数据库的查询语言来编写查询语句。
官方文档有一个优劣势的说明:
优势:
劣势:
简单来说,Example查询具有更好的可读性。但是笔者使用了一下,觉得功能太弱,不如Specification查询。就是使用很简单,但功能很弱。
默认是所有字段都精确匹配。
@Test
void couldGetUser_givenExampleUser() {
entityManager.persist(User.builder()
.username("example_user")
.name("Bob")
.locked(true)
.age(5)
.build());
User user = User.builder().name("Bob").build();
Optional userOptional = userRepository.findOne(Example.of(user));
assertThat(userOptional.isPresent()).isTrue();
userOptional.ifPresent(x -> assertThat(x.getName()).isEqualTo("Bob"));
}
就是自己先创建一个实体,给它一些属性。然后把它传入Example.of()方法,就可以构造一个Example对象。然后就可以直接查询了。
这里我们的Repository是继承的JpaRepository,它继承了QueryByExampleExecutor,这个方法里定义了一些用于Example查询的基本方法:
public interface QueryByExampleExecutor {
Optional findOne(Example example);
Iterable findAll(Example example);
Iterable findAll(Example example, Sort sort);
Page findAll(Example example, Pageable pageable);
long count(Example example);
boolean exists(Example example);
}
刚刚说了,对字符串以外的字段只支持精确匹配,字符串默认也是精确匹配。但对字符串还支持其他的匹配方式:
@Test
void couldGetUser_givenExampleWithProbAndMatcher() {
Address address = entityManager.persist(Address.builder()
.detail("xxx Street")
.build());
entityManager.persist(User.builder()
.username("example_user")
.name("Bob")
.address(address)
.build());
Address exampleAddress = Address.builder().detail("Street").build();
User exampleUser = User.builder()
.name("B")
.username("Example_User")
.address(exampleAddress)
.build();
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", m -> m.startsWith())
.withMatcher("address.detail", m -> m.endsWith())
.withMatcher("username", m -> m.ignoreCase());
Optional userOptional = userRepository.findOne(Example.of(exampleUser, matcher));
assertThat(userOptional.isPresent()).isTrue();
userOptional.ifPresent(x -> assertThat(x.getName()).isEqualTo("Bob"));
}
这里注意Address是User里面的一个OneToOne的属性,在使用Matcher的时候,可以使用“address.detail”这种方式来指定内联属性。
从生成的SQL语句我们可以看到,它的判断条件是根据实体的属性来生成查询语句的。如果实体的属性是null,它就会忽略它;如果不是,就会取其值作为匹配条件。
在第一个例子中,输出的sql:
select
user0_.id as id1_2_,
user0_.address_id as address_7_2_,
user0_.age as age2_2_,
user0_.locked as locked3_2_,
user0_.name as name4_2_,
user0_.password as password5_2_,
user0_.username as username6_2_
from
user user0_
where
user0_.name=?
这里有一个坑,
如果一个字段是不是包装类型,而是原始类型,它也会参与where条件中,其值是默认值。
我们现在的User实体中,locked是Boolean类型,age是Integer类型。如果我们把它们改成boolean和int,就会输出下面的sql:
select
user0_.id as id1_2_,
user0_.address_id as address_7_2_,
user0_.age as age2_2_,
user0_.locked as locked3_2_,
user0_.name as name4_2_,
user0_.password as password5_2_,
user0_.username as username6_2_
from
user user0_
where
user0_.age=0
and user0_.name=?
and user0_.locked=?
这就会导致查询失败。所以我们在定义实体的时候,最好是使用包装类型。