Spring Data JPA查询方式及方法名查询规则介绍

目录
  • Spring Data JPA查询方式及方法名查询规则
    • 一、通过解析方法名创建查询
    • 二、使用 @Query 创建查询
  • JPA 常用查询方法记录
    • CrudRepository 默认带的查询方法
    • 简单的扩展-以字段为关键字进行查询
    • 使用@Query 进行复杂查询
    • 使用 Specification 进行复杂查询
      • Predicate
      • CriteriaBuilder
      • Root

Spring Data JPA查询方式及方法名查询规则

Spring Data JPA

一、通过解析方法名创建查询

在执行查询时,Spring Data JPA框架会把方法名进行解析,解析到前缀比如 get、getBy、find、findBy、read、readBy时,会先把这些前缀截取掉,然后对剩下部分进行解析,剩下部分分为两种:一是只有属性名,二是属性名+条件;条件很好解析,解析的关键在于属性名,下面拿一个具体的例子来帮助大家更好的理解属性名解析规则。

解析规则例子:比如实体为Product,方法为findByGoodsTypeDetail ();

1、首先截取掉 findBy,然后对剩下的属性进行解析;

2、先判断 goodsTypeDetail(根据 POJO 规范,首字母变为小写,下同)是否为 Product的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第三步;

3、从右往左截取第一个大写字母开头的字符串(本方法为 Detail),然后对比剩下的字符串(本方法为goodsType)是否为 Product的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第三步,继续从右往左截取(此处为TypeDetail,剩下goods),就这样一直循环到最终;假设 goods为 Product的一个属性,则说明goods不是常量类型,而是一个对象类型;

4、此时剩下字符串 TypeDetail,先判断goods对象中是否有 typeDetail属性,如果有,则表示该方法最终是根据 "Product.goods.typeDetail" 的值进行查询;如果没有该属性,则继续按照第三步的规则从右往左截取,最终表示根据 "Product.goods.type.detail" 的值进行查询。

不过这种解析规则不是完美的,也存在bug,不注意可能会掉到这个坑里,比如Product中有一个属性叫goods,同时还有一个属性叫goodsType,这时在解析时会出现混乱,不过可以在属性之间加上 "_"来解决这个问题,注意:"_"是加在查询方法上的,不是加在属性名上的;比如 "findByGoods_TypeDetail()" (当Product中不存在goods_TypeDetail时,是给解析器说明Goods为一个对象)或"findByGoodsType_Detail()"(当Product中不存在goodsType_Detail时,是给解析器说明GoodsType为一个对象)。

查询时,很多时候需要同时使用多个属性进行查询,而且查询的条件也各不相同,Spring Data JPA 为此提供了一些条件查询的关键字,我把常用的都整理了一下,如下表:


关键字


对应SQL关键字


示例


列名


根据列名查询


findByName(String name);自动解析findBy后面的列名,然后根据列名查询。


In


等价于SQL 中的 in


findByNameIn(Collection<String> nameList) ;参数可以是集合、数组、不定长参数;


Like


等价于SQL 中的 like


findByNameLike(String name);

NotLike 等价于SQL 中的 not like findByNameNotLike(String name);

And


等价于SQL 中的 and


findByNameAndPwd(String name, String pwd);


Or


等价于SQL 中的 or


findByIdOrCode(String id, String code);


Between


等价于SQL 中的 between


findByNumBetween(int max, int min);


OrderBy


等价于SQL 中的 order by


findByNameOrderByNumAsc(String name);


IsNull


等价于SQL 中的 is null


findByNameIsNull();

IsNotNull 等价于SQL 中的 is not null findByNameIsNotNull();
NotNull 等价于SQL 中的 is not null findByNameNotNull();--和IsNotNull 一样,建议使用IsNotNull
Not 等价于SQL 中的 ! = findByNameNot(String name);
NotIn 等价于SQL 中的 not in findByNameNotIn(Collection<String> nameList) ;参数可以是集合、数组、不定长参数;
LessThan 等价于SQL 中的 < findByNumLessThan(int num);

GreaterThan


等价于SQL 中的 >


findByNumGreaterThan(int num);

二、使用 @Query 创建查询

1、使用 @Query 提供的位置编号查询:格式为":位置编号",然后方法中的参数按 JPQL 查询语句的位置编号顺序书写。 如下:

public interface ProductDao extends Repository<Product , Long> {
@Query("select * from Product p where p.id= ?1")
public Product findById(Long id); 

@Query("select * from Product p where p.type = ?1 and p.name =?2")
public Page<Product> findByTypeAndName(
    Integer type,String name,Pageable pageable);
}

2、使用@Query 命名参数查询:格式为": 变量",同时在方法的参数前面使用 @Param 将方法参数与JPQL中的命名参数对应。如下:

public interface ProductDao extends Repository<Product , Long> {
@Query("from Product p where p.goodsName= :name")
public Product findByGoodsName(@Param("name")String name);
@Query("from Product p where p.num < :num")
public Page<Product> findByNumLessThan(
    @Param("num")Integer num,Pageable pageable);
}

3、 使用 @Modifying 将查询操作标识为更新操作:在使用 @Query 的同时使用 @Modifying ,这样会生成一个更新的操作,而非查询。如下:

@Query("update Product p set p.name = ?1 where p.id = ?2")
@Modifying
public int updateName(String name, int id);

JPA 常用查询方法记录

以这张表为例:

+-------------+--------------+------+-----+-------------------+----------------+
| Field       | Type         | Null | Key | Default           | Extra          |
+-------------+--------------+------+-----+-------------------+----------------+
| id          | int(11)      | NO   | PRI | NULL              | auto_increment |
| role        | varchar(45)  | NO   |     | NULL              |                |
| permissions | varchar(512) | NO   |     | NULL              |                |
| create_time | datetime     | NO   |     | CURRENT_TIMESTAMP |                |
| status      | varchar(45)  | NO   |     | NULL              |                |
| role_name   | varchar(45)  | NO   |     | NULL              |                |
+-------------+--------------+------+-----+-------------------+----------------+

CrudRepository 默认带的查询方法

@Repository
public interface RoleRepository extends CrudRepository<RoleData, Integer> {
}
@Entity
@Table(name = "role", catalog = "message_push")
public class RoleData implements java.io.Serializable {

    @Id
    @GeneratedValue
    private Integer id;
    private String role;
    private String permissions;
    private Long create_time;
    private Integer status;
// getter setter 构造函数从略
}

简单的扩展-以字段为关键字进行查询

list<RoleData> findByXXX(xxx) 其中 XXX 对应数据库中的字段,例如:

@Repository
public interface RoleRepository extends CrudRepository<RoleData, Integer> {
    List<RoleData> findByRole(String role);
    List<RoleData> findByStatus(String status);
}

还可以多字段AND 查询:

@Repository
public interface RoleRepository extends CrudRepository<RoleData, Integer> {
    List<RoleData> findByRoleAndStatus(String role, String status);
}

在 application.properties 中加入以下配置 spring.jpa.show-sql=true 可以看到SQL语句:

Hibernate: select roledata0_.id as id1_0_, roledata0_.create_time as create_t2_0_, roledata0_.permissions as permissi3_0_, roledata0_.role as role4_0_, roledata0_.status as status5_0_ from message_push.role roledata0_ where roledata0_.role=? and roledata0_.status=?

当然 or 也是可以:

    List<RoleData> findByRoleOrStatus(String role, String status);

Hibernate: select roledata0_.id as id1_0_, roledata0_.create_time as create_t2_0_, roledata0_.permissions as permissi3_0_, roledata0_.role as role4_0_, roledata0_.status as status5_0_ from message_push.role roledata0_ where roledata0_.role=? or roledata0_.status=?

使用@Query 进行复杂查询

例如:

@Query(value = "select * from role where role = ?1", nativeQuery = true)
List<RoleData> searchByRole(String role);

或 sql in 用法

@Query(value = "select * from role where role in (?1) and status = 'valid'", nativeQuery = true)
List<RoleData> searchByRoleList(List<String> targetList);

又或 sql like 用法:

@Query(value = "select * from role where role like %?1%", nativeQuery = true)
List<RoleData> searchByRole(String keyWord);

使用 Specification 进行复杂查询

先来看一下 JpaSpecificationExecutor 接口

以 findAll(Specification<T>) 为例进行说明:

Specification<T> 可以理解为一个查询条件。findAll 以这个条件为基准进行查询,也就是我们在sql 里写的 whre xxx 转为 Specification 来写。

首先要让我们的 repository 继承 JpaSpecificationExecutor

@Repository
public interface RoleRepository extends CrudRepository<RoleData, Integer>, JpaSpecificationExecutor<RoleData> {

接下来,将这个查询 [ select * from role where role like '%a%' ] 转为一个简单的 Specification。

        final Specification<RoleData> spec = new Specification<RoleData> () {
            @Override
            public Predicate toPredicate(Root<RoleData> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                Predicate predicate = criteriaBuilder.like(root.get("role"), "%a%");
                return predicate;
            }
        };

然后直接按如下方式调用即可:

roleRepository.findAll(spec);

Specification 里又衍生出了好几个类,分别介绍一下:

Predicate

因为我们实现 Specification 接口时,只需要实现 Predicate toPredicate() 方法。而 Specification 上文中我们当做搜索条件来理解了,那么也可以简单的把 Predicate 视为搜索条件。

CriteriaBuilder

用于构建搜索条件 Predicater 的。

回想一下SQL搜索条件怎么写

where attribute = xx
where attribute > xx
where attribute < xx
where attribute like %xx%

注意这里有三要素:

  • attribute 搜索指定的数据库字段
  • 操作符 大于 小于 等于
  • 具体数据

CriteriaBuilder提供了一系列静态方法构建这三要素。

比如

  • CriteriaBuilder.like(数据库字段, 具体数据)
  • CriteriaBuilder.equal(数据库字段, 具体数据)

其中 数据库字段 不能直接写字符串,需要下一个工具类 Root 的 get 方法获取。

Root

root.get( String attributeName ) 参数 attributeName 就是数据库里的字段名

现在相信读者可以理解 我们刚才写的 那个完整的 Specification了。

再下来再上一个稍微复杂点的例子:

[  select * from role where role like '%a%' and (id > 11 or id < 8) ] 
        final Specification<RoleData> spec = new Specification<RoleData> () {
            @Override
            public Predicate toPredicate(Root<RoleData> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                Predicate roleLikeaPredicate = criteriaBuilder.like(root.get("role"), "%a%");
                Predicate idLessThan8Predicate = criteriaBuilder.lessThan(root.get("id"), 8);
                Predicate idGreaterThan12Predicate = criteriaBuilder.greaterThan(root.get("id"), 11);

                Predicate idCombindedPredicate = criteriaBuilder.or(idLessThan8Predicate, idGreaterThan12Predicate);
                Predicate predicate = criteriaBuilder.and(idCombindedPredicate, roleLikeaPredicate);
                return predicate;
            }
        };

其实也很简单,就是多了 criteriaBuilder.or criteriaBuilder.and 来把多个 Predicate 合成一个新的 Predicate

最后一个例子:

可以通过root.get(xx).in(List<> list) 也是可以直接返回 Predicate 的

        final Specification<RoleData> spec2 = new Specification<RoleData> () {
            @Override
            public Predicate toPredicate(Root<RoleData> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<String> alist = new ArrayList<String>();
                alist.add("admin");
                Predicate predicate = root.get("role").in(alist);
                return predicate;
            }
        };

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

时间: 2021-11-22

详解Spring Data Jpa 模糊查询的正确用法

模糊查询 Spring Data Jpa的使用可以减少开发者对sql语句的编写,甚至完全不需要编写sql语句.但是,开发过程中总会遇到各种复杂的场景以及大大小小的坑. 今天项目中某个功能模块需要用到模糊查询.原生sql中模糊查询关键字'Like',而Spring Data Jpa的Repository接口中恰恰也有实体字段对应的Like.但是,如果直接使用它,那么恭喜你,你幸运地掉坑了. Spring Data Jpa 模糊查询正确用法 首先,我们先创建一个实体用来存储我们的数据 /** * 实

Spring Data Jpa的四种查询方式详解

这篇文章主要介绍了Spring Data Jpa的四种查询方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.调用接口的方式 1.基本介绍 通过调用接口里的方法查询,需要我们自定义的接口继承Spring Data Jpa规定的接口 public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> 使用这

Spring Data JPA实现动态查询的两种方法

前言 一般在写业务接口的过程中,很有可能需要实现可以动态组合各种查询条件的接口.如果我们根据一种查询条件组合一个方法的做法来写,那么将会有大量方法存在,繁琐,维护起来相当困难.想要实现动态查询,其实就是要实现拼接SQL语句.无论实现如何复杂,基本都是包括select的字段,from或者join的表,where或者having的条件.在Spring Data JPA有两种方法可以实现查询条件的动态查询,两种方法都用到了Criteria API. Criteria API 这套API可用于构建对数据

详解Spring Data JPA动态条件查询的写法

我们在使用SpringData JPA框架时,进行条件查询,如果是固定条件的查询,我们可以使用符合框架规则的自定义方法以及@Query注解实现. 如果是查询条件是动态的,框架也提供了查询接口. JpaSpecificationExecutor 和其他接口使用方式一样,只需要在你的Dao接口继承即可(官网代码). public interface CustomerRepository extends CrudRepository<Customer, Long>, JpaSpecification

详解Spring Data JPA系列之投影(Projection)的用法

本文介绍了Spring Data JPA系列之投影(Projection)的用法,分享给大家 在JPA的查询中,有一个不方便的地方,@Query注解,如果查询直接是 Select C from Customer c ,这时候,查询的返回对象就是Customer这个完整的对象,包含所有字段,对于我们的示例并没有什么问题,但是对于比较庞大的domain类,这个查询时就比较要命,并不是所有的字段都能用到,比较头疼.另外,如果定义 select c.firstName as firstName,c.la

详解Spring Data Jpa当属性为Null也更新的完美解决方案

开场白 我本来是一名android开发者,突然就对java后端产生了浓烈的兴趣.所以,立马就转到了后端.第一个项目使用的使用Spring Data Jpa来操作数据库的,可是在更新数据的时候发现一个问题,属性值为Null竟然也更新,这就会导致本来没有更新的属性值,全部就成了Null. 原因 经过一番度娘操作,原来Jpa,不知道你是想把属性设置为Null,还是不想. 解决方法 找到一个方法,就是在数据模型上加上注解@DynamicUpdate,可是发现并不好使.而后经过整理,找到以下解决方案 我们

详解Spring Data JPA使用@Query注解(Using @Query)

经过几天的折腾,终于到了学习一个重量级的查询方式上,使用@Query注解,使用注解有两种方式,一种是JPQL的SQL语言方式,一种是原生SQL的语言,略有区别,后者我们更熟悉一些.话不多说,看代码. 1.在CustomerRepository里添加 /** * 模糊匹配 * @param bauer * @return */ @Query("select c from Customer c where c.firstName=?1") Customer findByFirstName2

详解Spring data 定义默认时间与日期的实例

详解Spring data 定义默认时间与日期的实例 前言: 需求是这样的: 1. 创建时间与更新时间只能由数据库产生,不允许在实体类中产生,因为每个节点的时间/时区不一定一直.另外防止人为插入自定义时间时间. 2. 插入记录的时候创建默认时间,创建时间不能为空,时间一旦插入不允许日后在实体类中修改. 3. 记录创建后更新日志字段为默认为 null 表示该记录没有被修改过.一旦数据被修改,修改日期字段将记录下最后的修改时间. 4. 甚至你可以通过触发器实现一个history 表,用来记录数据的历

Spring Data JPA带条件分页查询实现原理

最新Spring Data JPA官方参考手册 Version 2.0.0.RC2,2017-07-25 https://docs.spring.io/spring-data/jpa/docs/2.0.0.RC2/reference/html/ JPA参考手册 (找了半天, 在线版的只找到这个) https://www.objectdb.com/java/jpa Spring Data JPA的Specification类, 是按照Eric Evans的<领域驱动设计>书中Specificat

详解spring boot jpa整合QueryDSL来简化复杂操作

前言 使用过spring data jpa的同学,都很清楚,对于复杂的sql查询,处理起来还是比较复杂的,而本文中的QueryDSL就是用来简化JPA操作的. Querydsl定义了一种常用的静态类型语法,用于在持久域模型数据之上进行查询.JDO和JPA是Querydsl的主要集成技术.本文旨在介绍如何使用Querydsl与JPA组合使用.JPA的Querydsl是JPQL和Criteria查询的替代方法.QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查

Spring Data JPA分页复合查询原理解析

Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库. 此模块处理对基于JPA的数据访问层的增强支持. 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易. 在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦. 必须编写太多样板代码来执行简单查询以及执行分页和审计. Spring Data JPA旨在通过减少实际需要的工作量来显著改善数据访问层的实现. 作为开发人员,您编写repository接口,包括自定义查找器方法,Spring

详解Spring Data操作Redis数据库

Redis是一种NOSQL数据库,Key-Value形式对数据进行存储,其中数据可以以内存形式存在,也可以持久化到文件系统.Spring data对Redis进行了很好的封装,用起来也是十分的得心应手.Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库.缓存和消息中间件. 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, h