Spring Data JPA 通过不同的方式实现查询操作

环境搭建

1. 搭建Spring Data JPA的开发环境Spring Data JPA快速入门
在这里插入图片描述

2. 新建一个测试类,测试查询方法

/**
 * @Author: Ly
 * @Date: 2020-11-28 16:23
 */
@RunWith(SpringJUnit4ClassRunner.class)  //声明spring提供的单元测试
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class CustomerFindTest {

    @Autowired
    private CustomerDao customerDao;

   
}

Spring Data JPA使用JPQL自定义查询

使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

1. 根据客户名称查询客户,使用jpql形式实现

配置jpql语句,使用应使用@Query注解, jpql语句 : from Customer where custName = ?

/**
 * 在CustomerDao接口中添加查询方法
 */
@Query(value = "from Customer where custName = ?")
public Customer findByName(String custName);

/**
 * 在CustomerFindTest中测试根据name查询
 */
@Test
public void testFindJPQL(){
    Customer customer=customerDao.findByName("客户1");
    System.out.println(customer);
}

2. 根据客户名称与客户id查询客户,使用jpql形式实现

配置jpql语句,使用应使用@Query注解, jpql语句 : jpql : from Customer where custName = ? and custId = ?
对于多个占位符参数:赋值的时候,默认的情况下,占位符的位置需要和方法参数中的位置保持一致,但是可以指定占位符参数的位置 ? 索引 的方式,指定次占位的取值来源。

/**
 * 在CustomerDao接口中添加查询方法
 */
@Query(value = "from Customer where custName = ? and custId = ?")
//@Query(value = "from Customer where custId = ?2 and custName = ?1")
public Customer findCustNameAndId(String name,Long id);

/**
 * 测试根据客户名称与id查询
 */
@Test
public void testFindCustNameAndId(){
    Customer customer=customerDao.findCustNameAndId("客户1",1l);
    System.out.println(customer);
}

3. 使用jpql语句实现更新操作

配置jpql语句,使用应使用@Query注解, jpql语句 : jpql : update Customer set custName = ? where custId = ?
于查询语句不同,我们还需要对更新操作进行声明@Modifying :当前执行的更新操作,同时springDataJpa中使用jpql完成更新或删除操作需要手动添加事务的支持,因为方法在执行结束后默认会执行回滚事务,我们还需要使用@Rollback(value =false) 取消回滚

/**
 * 在CustomerDao接口中添加更新语句的方法
 */
@Query(value = "update Customer set custName = ?2 where custId = ?1")
@Modifying
public void updateCustomer(long custId,String custName);

/**
 *  测试jpql的更新操作
 */
@Test
@Transactional//添加注解支持
@Rollback(value =false)
public void testUpdateCustomer(){
    customerDao.updateCustomer(1l,"黑马程序员");
}

4. 使用sql语句形式进行查询

使用@Query注解,配置sql查询,添加nativeQuery属性修改查询方式:true:sql查询、 false:jpql查询
sql语句 :sql:select * from cst_customer

/**
 * 在CustomerDao接口中添加查询全部的客户的方法
 */
@Query(value = "select * from cst_customer",nativeQuery = true)
public List<Object []> findSql();

//测试sql查询
@Test
public void testFindSql(){
    List<Object[]> list = customerDao.findSql();
    for (Object []obj:list) {
        System.out.println(Arrays.toString(obj));
    }
}

5. 使用sql语句实现模糊查询

/**
 * 在CustomerDao接口中添加查询全部的客户的方法
 */
@Query(value = "select * from cst_customer where cust_name like ?",nativeQuery = true)
public List<Object []> findLikeSql(String name);

//测试sql模糊查询
@Test
public void testFindLikeSql(){
    List<Object[]> list = customerDao.findLikeSql("%程序员");
    for (Object []obj:list) {
        System.out.println(Arrays.toString(obj));
    }
    SimpleJpaRepository s=null;
}

按照方法命名规则查询

我们在Dao层定义方法时,可以根据方法命名规则编写方法。这是我们只需要根据方法的名字,就能执行相应的查询语句。(前提是需要按照Spring Data JPA提供的方法命名规则定义方法的名称)

Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字 (AND|OR) 连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

1. 测试通过客户名称查询
语法:findBy +属性名称(根据属性名称进行完成匹配的查询)

//按照命名规则定义方法,不在需要添加注解
public Customer findByCustName(String name);

//测试方法
@Test
public void testNaming(){
    Customer customer = customerDao.findByCustName("黑马程序员");
    System.out.println(customer);

}

2. 测试模糊查询
语法:findBy +属性名称 + 方式(like|isnull)

//按照命名规则定义方法,实现模糊查询
public List<Customer> findByCustNameLike(String name);

//测试方法
@Test
public void testFindByCustNameLike(){
    List<Customer> byCustNameLike = customerDao.findByCustNameLike("%程序员");
    for (Customer customer:byCustNameLike){
        System.out.println(customer);
    }

}

3. 测试复合查询
语法: findBy +属性名称 + 查询方式 + 多条件连接符(AND|OR)+属性名+ 查询方式

//使用客户名称模糊匹配,和客户所属行业精准匹配
public List<Customer> findByCustNameLikeAndCustIndustry(String custName,String custIndustry);

//测试方法
@Test
public void testFindByCustNameLikeAndCustIndustry(){
    List<Customer> byCustNameLike = customerDao.findByCustNameLikeAndCustIndustry("%程序员","程序员");
    for (Customer customer:byCustNameLike){
        System.out.println(customer);
    }
}

具体的关键字,使用方法和生产成SQL如下表所示:

KeywordSampleJPQL
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)

Specifications动态查询

有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

/**
 *	JpaSpecificationExecutor中定义的方法
 **/
 public interface JpaSpecificationExecutor<T> {
   	//根据条件查询一个对象
 	T findOne(Specification<T> spec);	
   	//根据条件查询集合
 	List<T> findAll(Specification<T> spec);
   	//根据条件分页查询
 	Page<T> findAll(Specification<T> spec, Pageable pageable);
   	//排序查询查询
 	List<T> findAll(Specification<T> spec, Sort sort);
   	//统计查询
 	long count(Specification<T> spec);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

/**Specification接口 
 * root   :比较的属性
 * query  :用来自定义查询
 * cb	  :查询的方式public interface Specification<T> {
 */
public interface Specification<T> {
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}

1.通过Specification实现条件查询

实现步骤:

  • 1.通过匿名内部类的方式实现Specification接口(提供泛型,查询对象的类型)
  • 2.实现toPredicate方法(构造查询条件)
  • 3.通过控制参数(root:比较的属性,cb:查询的方式),实现匹配,获得Predicate对象
  • 4.返回Predicate对象
@Test
public void testSpecFindByName(){
    /**
     * 自定义查询条件
     *   案例:根据客户名称查询
     */
    Specification<Customer> spec= new Specification<Customer>() {
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
            //1.获取比较的属性
            Path<Object> custName = root.get("custName");
            //2.构造查询: select * from where cust_name='程序员'
            /**
             * 第一个参数:需要比较的属性(path对象)
             * 第二个参数:当前需要比较的值
             */
            Predicate predicate = criteriaBuilder.equal(custName, "黑马程序员");//进行精准匹配
            return predicate;
        }
    };
    Customer customer=customerDao.findOne(spec);
    System.out.println(customer);
}

2. 通过Specification实现多条件查询

实现步骤:

  • 1.通过匿名内部类的方式实现Specification接口(提供泛型,查询对象的类型)
  • 2.实现toPredicate方法(构造查询条件)
  • 3.通过控制参数(root:比较的属性,cb:查询的方式),实现匹配,获得Predicate对象
  • 4.将匹配到的两个结果结合起来
  • 5.返回Predicate对象
@Test
public void testSpecFindByNameAndIndustry(){
    /**
     * 将两个查询结果联系起来,实现多条件查询
     */
    Specification<Customer> spec= new Specification<Customer>() {
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
            //1.获取客户名的属性
            Path<Object> custName = root.get("custName");
            //2.获取所属行业的属性
            Path<Object> custIndustry = root.get("custIndustry");

            //2.构造查询
            //1.构造客户名的精准匹配查询
            Predicate p1 = criteriaBuilder.equal(custName, "黑马程序员");//进行精准匹配
            Predicate p2 = criteriaBuilder.equal(custIndustry, "程序员");//进行精准匹配
            //2.组合条件(AND、OR)
            Predicate predicate = criteriaBuilder.and(p1, p2);
            return predicate;
        }
    };
    Customer customer=customerDao.findOne(spec);
    System.out.println(customer);
}

3. 通过Specification实现模糊查询

实现步骤:

  • 1.通过匿名内部类的方式实现Specification接口(提供泛型,查询对象的类型)
  • 2.实现toPredicate方法(构造查询条件)
  • 3.通过控制参数(root:比较的属性,cb:查询的方式),实现匹配,获得Predicate对象
    对于equal方法:直接得到path对象,然后进行比较即可,但是对于gt,lt,ge,le,like想要得到path对象,需要根据path指定比较参数的类型,指定参数类型:path.as(类型的字节码对象)
  • 4.返回Predicate对象
@Test
public void testSpecFindByNameLike(){
	/**
	 * 实现模糊查询
	 */
    Specification<Customer> spec= new Specification<Customer>() {
        public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
            //1.获取客户名的属性
            Path<Object> custName = root.get("custName");

            //2.构造查询
            Predicate like = criteriaBuilder.like(custName.as(String.class), "%程序员");

            return like;
        }
    };
    List<Customer>list=customerDao.findAll(spec);
    for (Customer customer:list) {
        System.out.println(customer);
    }

}

方法对应关系:

方法名称Sql对应关系
equlefiled = value
notEqulefiled != value
likefiled like value
notLikefiled not like value
gt greaterThan )filed> value
ge (greaterThanOrEqualTo)filed >= value
lt(lessThan )filed< value
le( lessThanOrEqualTo)filed <= value

3. 通过Specification实现模糊查询

实现步骤:

  • 1.添加排序,创建排序对象,需要调用构造方法实例化sort对象

  • 2.设置两个参数:第一个参数:排序的顺序(升序、降序)
    Sort.Direction.DESC:倒序、Sort.Direction.ASC:升序

  • 3.设置两个参数:第二个参数:排序的属性名称

  • 4.结合Specification实现带条件的将查询结果排序

@Test
public void testSpecDESC(){
    Specification<Customer> spec= null;
    //添加排序
    Sort sort =new Sort(Sort.Direction.DESC,"custId");
    List<Customer>list=customerDao.findAll(spec,sort);
    for (Customer customer:list) {
        System.out.println(customer);
    }
}

4. 通过Specification实现分页查询

实现步骤:

  • 1.使用Pageable实现分页,通过PageRequest对象实现Pageable接口
  • 2.设置两个参数:第一个参数:当前查询的页数(从0开始)
  • 3.设置两个参数:第二个参数:每页查询的数量
  • 4.结合Specification实现带条件的将查询结果排序
    findAll(Pageable) //无条件分页、findAll(Specification,Pageable)//带条件分页
/**
 * 分页参数查询的页码,每页查询的条数
 */
@Test
public void testSpecPages(){
    Specification<Customer> spec= null;

    /**
     * PageRequest对象是Pageable接口的实现类
     * 创建PageRequest的过程中,需要调用它的构造方法传入两个参数
     * 第一个参数:当前查询的页数(从0开始)
     * 第二个参数:每页查询的数量
     */
    Pageable pageable =new PageRequest(0,3);

    Page<Customer> page = customerDao.findAll(spec, pageable);
    System.out.println(page.getContent());//得到数据集合列表
    System.out.println(page.getTotalElements());//得到总条数
    System.out.println(page.getTotalPages());//得到总页数
}
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙源lll

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值