《JPA 从入门到精通》系列包含以下文章:
- Java 持久层 API:JPA
- 认识 JPA 的接口
- JPA 的查询方式
- 基于 JPA 开发的文章管理系统(CRUD)
- 关系映射开发(一):一对一映射
- 关系映射开发(二):一对多映射
- 关系映射开发(三):多对多映射
JPA 的查询方式
1.使用约定方法名
约定方法名一定要根据命名规范来写,Spring Data 会根据前缀、中间连接词(
Or
、
And
、
Like
、
NotNull
等类似 SQL 中的关键词)、内部拼接 SQL 代理生成方法的实现。
约定方法名的方法见下表(不完全示例):
SQL
方法例子
JPQL 语句
and
findByLastnameAndFirstname
where x.lastname = ?1 and x.firstname = ?2
or
findByLastnameOrFirstname
where x.lastname = ?1 or x.firstname = ?2
=
findByFirstname
/
findByFirstnameIs
/
findByFirstnameEquals
where x.firstname = ?1
接口方法的命名规则也很简单,只要明白
And
、
Or
、
Is
、
Equal
、
Greater
、
StartingWith
等英文单词的含义,就可以写接口方法。具体用法如下:
publicinterfaceUserRepositoryextendsRepository<User,Long>{List<User>findByEmailOrName(String email,String name);}
上述代码表示,通过
email
或
name
来查找 User 对象。
约定方法名还可以支持以下几种语法:
User findFirstByOrderByNameAsc()
Page<User> queryFirst100ByName(String name, Pageable pageable)
Slice<User> findTop100ByName(String name, Pageable pageable)
List<User> findFirst100ByName(String name, Sort sort)
List<User> findTop100ByName(String name, Pageable pageable)
2.用 JPQL 进行查询
JPQL 语言(
Java Persistence Query Language
)是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 语言,从而屏蔽不同数据库的差异。
JPQL 语言通过 Query 接口封装执行,Query 接口封装了执行数据库查询的相关方法。调用 EntityManager 的 Query、NamedQuery 及 NativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。
JPQL 是面向对象进行查询的语言,可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。JPQL 不支持使用 INSERT。对于 UPDATE 或 DELETE 操作,必须使用注解 @Modifying 进行修饰。
JPQL 的用法见以下两段代码。
(1)下面代码表示根据
name
值进行查找。
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>(@Query("select u from User u where u.name = ?1")UserfindByName(String name);}
(2)下面代码表示根据
name
值进行模糊查找。
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{@Query("select u from User u where u.name like %?1")List<User>findByName(String name);}
3.用原生 SQL 进行查询
在使用原生 SQL 查询时,也使用注解 @Query。此时,nativeQuery 参数需要设置为
true
。 下面先看一些简单的查询代码。
3.1 根据 ID 查询用户
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>(// 根据 ID 查询用户@Query(value ="select * from user u where u.id=:id", nativeQuery =true)UserfindById(@Param("id")Long id);)
3.2 查询所有用户
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>{// 查询所有用户@Query(value ="select * from user", nativeQuery =true)List<User>findAllNative();}
3.3 根据 email 查询用户
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>(// 根据 email 查询用户@Query(value ="select * from user where email= ?1", nativeQuery =true)UserfindByEmail(String email);)
3.4 根据 name 查询用户,并返回分页对象 Page
publicinterfaceUserRepositoryextendsJpaRepository<User,Long>(@Query(value ="select * from user where name= ?1",
countQuery ="select count(*) from user where name= ?1", nativeQuery =true)Page<User>findByName(String name,Pageable pageable);}
3.5 根据名字来修改 email 的值
@Modifying@Query("update user set email = :email where name =:name")VoidupdateUserEmailByName(@Param("name")String name,@Param("email")String email);
3.6 使用事务
UPDATE 或 DELETE 操作需要使用事务。此时需要先定义 Service 层,然后在 Service 层的方法上添加事务操作。
对于自定义的方法,如果需要改变 Spring Data 提供的事务默认方式,则可以在方法上使用注解 @Transactional,如以下代码:
@Servicepublic classUserService {@AutowiredprivateUserRepository userRepository;@TransactionalpublicvoidupdateEmailByName(String name,String email){
userRepository.updateUserEmaiByName(name, email);})
测试代码:
@TestpublicvoidtestUsingModifingAnnotation(){
userService.updateEmailByName("pipi","[email protected]");}
在进行多个 Repository 操作时,也应该使这些操作在同一个事务中。按照分层架构的思想,这些操作属于业务逻辑层,因此需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。
4.用 Specifications 进行查询
如果要使 Repository 支持 Specification 查询,则需要在 Repository 中继承 JpaSpecificationExecutor 接口,具体使用见如下代码:
publicinterfaceCardRepositoryextendsJpaRepository<Card,Long>,JpaSpecificationExecutor<Card>{CardfindById(long id);}
下面以一个例子来说明 Specifications 的具体用法:
@SpringBootTest@RunWith(SpringRunner.class)publicclass testJpaSpecificationExecutor (@AutowiredprivateCardRepository cardRepository;@TestpublicvoidtestJpaSpecificationExecutor()(int pageNo =0;int pageSize =5;PageRequest pageable =PageRequest.of(pageNo, pageSize);// 通常使用 Specification 的匿名内部类Specification<Card> specification =newSpecification<Card>(){@OverridepublicPredicatetoPredicate(Root<Card> root,CriteriaQuery<?> query,CriteriaBuilder cb){Path path = root.get("id");// gt 是大于的意思。这里代表 id 大于 2Predicate predicate1 = cb.gt(path,2);// equal 是等于的意思,代表查询 num 值为 422803 的数据记录Predicate predicate2 = cb.equal(root.get("num"),422803);// 构建组合的 PredicatePredicate predicate = cb.and(predicate1, predicate2);return predicate;}};Page<Card> page = cardRepository.findAll(specification, pageable);System.out.println("总记录数: "+ page.getTotalElements());System.out.println("当前第: "+(page.getNumber()+1)+"页");System.out.println("总页数: "+ page.getTotalPages());System.out.println("当前页面的 List: "+ page.getContent());System.out.println("当前页面的记录数: "+ page.getNumberOfElements());})
代码解释如下:
- CriteriaQuery 接口:
specific
的顶层查询对象,它包含查询的各个部分,比如,select
、from
、where
、group by
、order by
等。CriteriaQuery 对象只对实体类型或嵌入式类型的 Criteria 查询起作用。 root
:代表查询的实体类是 Criteria 查询的根对象。Criteria 查询的根定义了实体类型,能为将来的导航获得想要的结果。它与 SQL 查询中的 From 子句类似。Root 实例是类型化的, 且规定了 From 子句中能够出现的类型。查询根实例通过传入一个实体类型给 AbstractQuery.from 方法获得。query
:可以从中得到 Root 对象,即告知 JPA Criteria 查询要查询哪一个实体类。还可以添加查询条件,并结合 EntityManager 对象得到最终查询的 TypedQuery 对象。- CriteriaBuilder 对象:用于创建 Criteria 相关对象的工厂,可以从中获取到 Predicate 对象。
- Predicate 类型:代表一个查询条件。
运行上面的测试代码,在控制台会输出如下结果(确保数据库已经存在数据):
Hibernate: select card0_.id as id1_0_, card0_.num as num2_0_ from card cardO_ where card0_.id>2 and card0_.num=422803 limit ?Hibernate: select count(card0_.id) as col_0_0_ from card card0_ where card0_.id>2 and card0_.num=422803
总记录数:6
当前第:1页
总页数:2
当前页面的 List:[Card(id=4, num=422803),Card(id=8, num=422803),Card(id=10, num=422803),Card(id=20, num=422803),Card(id=23, num=422803)]
当前页面的记录数:5
5.用 ExampleMatcher 进行查询
Spring Data 可以通过 Example 对象来构造 JPQL 查询,具体用法见以下代码:
User user =newUser();//构建查询条件
user.setName("pipi");
ExampleMatcher matcher =ExampleMatcher.matching()// 创建一个 ExampleMatcher, 不区分大小写匹配 name.withIgnorePaths("name")// 包括 null 值.withIncludeNullValues()// 执行后缀匹配.withStringMatcherEnding();// 通过 Example 构建查询Example<User> example =Example.of(user, matcher);List<User> list = userRepository.findALL(example);
默认情况下,ExampleMatcher 会匹配所有字段。
可以指定单个属性的行为(如
name
或内嵌属性
name.user
)。如:
withMatcher("name", endsWith())
withMatcher("name", startsWith().ignoreCase())
6.用谓语 QueryDSL 进行查询
QueryDSL 也是基于各种 ORM 之上的一个通用查询框架,它与 Spring Data JPA 是同级别的。使用 QueryDSL 的 API 可以写出 SQL 语句(Java 代码,非真正标准 SQL),不需要懂 SQL 语句。它能够构建类型安全的查询。这与 JPA 使用原生查询时有很大的不同,可以不必再对
Object[]
进行操作。它还可以和 JPA 联合使用。
7.用 NamedQuery 进行查询
官方不推荐使用 NamedQuery,因为它的代码必须写在实体类上面,这样不够独立。其使用方法见以下代码:
©Entity@NamedQuery(name ="User.findByName", query ="select u from User u where u.name = ?1")publicclassUser{}
版权归原作者 G皮T 所有, 如有侵权,请联系我们删除。