在前面的章节已经讲述了SpringDataJpa的CRUD操作以及其底层代理实现的分析,下面介绍SpringDataJpa中的复杂查询和动态查询,多表查询。(保姆级教程)
文章字数较多,请各位按需阅读。😂
不清楚JPA的小伙伴可以参考这篇文章:JPA简介;
不清楚SpringDataJPA环境搭建的小伙伴可以参考这篇文章:SpringDataJPA入门案例;
想了解SpringDataJPA代理类实现过程可以参考这篇文章:SpringDadaJPA底层实现原理
如需转载,请注明出处。
1.复杂查询
i.方法名称规则查询
方法名查询:只需要按照SpringDataJpa提供的方法名称规则去定义方法,在dao接口中定义方法即可。
其中对于方法的名称有一套约定。
KeyWord | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | where x.lastname = ?1 or x.firstname = ?2 |
Between | findByAgeBetween | where x.Age between ?1 and ?2 |
LessThan | findByAgeLessThan | where x.age < ?1 |
GreaterThan | findByAgeGreaterThan | where x.age > ?1 |
Like | findByFirstnameLike | where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | where x.firstname not like ?1 |
TRUE | findByActiveTrue() | where x.active = true |
FALSE | findByActiveFalse() | where x.active = false |
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 方法名的约定:
* findBy:查询
* 对象中的属性名(首字母大写):查询条件
* *默认情况:使用 =的方式查询
* 特殊的查询方式,比如模糊查询
* findByCustName-----根据客户名称查询 findBy表示要查询 CustName属性名
* springDataJpa在运行阶段
* 会根据方法名称进行解析 findBy from XXX(实体类)
* 属性名称 where custName
* 1. findBy+属性名称(根据属性名称进行完成匹配任务)
* 2. findBy+属性名称+查询方式(Like|isnull)
* 3. 多条件查询
* findBy+属性名称+查询方式+多条件连接符(and|or)+属性名+查询方式
*/
public List<Customer> findByCustName(String name);
//查询id为3且name中含有大学的用户
public Customer findByCustId(Long id);
public Customer findByCustIdAndCustNameLike(Long id,String name);
}
ii.JPQL查询
使用 Spring Data JPA 提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来
说,我们还需要灵活的构造查询条件,这时就可以使用@Query 注解,结合 JPQL 的语句方式完成
查询 。
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个 JPQL 查询语句即可
注意:
通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询 。
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
/**
* 1.根据客户名称查询客户
* jpql:from Customer where custName=?
*/
@Query(value="from Customer where custName =?")
public List<Customer> findCustomerJpql(String name);
/**
* 2.根据客户名称和客户id查询
* 对多个占位符参数
* 默认情况下,占位符的位置需要和方法参数中的位置保持一致
* 也可以指定占位符参数的位置(注意:中间不要有空格)
* ? 索引的方式,指定此占位符的取值来源 eg ?2表示此占位符对应第二个参数
*/
@Query(value="from Customer where custName=?2 and custId=?1")
public Customer findByNameAndId(Long id,String name);
/**
* 3.根据id更新客户的name
* sql:update cst_customer set cust_name=? where cust_id=?
* jpql:update Customer set custName=? where custId=?
*
* @query:代表的是进行查询
* 需要声明此方法是执行更新操作
* 使用 @Modifying
*/
@Query(value = "update Customer set custName=? where custId=?")
@Modifying
public void updateCustomerName(String name,Long id);
}
注意:在执行springDataJpa中使用jpql完成更新,删除操作时,需要手动添加事务的支持 必须的;因为默认会执行结束后,回滚事务。
@Test
@Transactional//添加事务的支持
@Rollback(value = false)
public void updateCustomerName(){
customerDao.updateCustomerName("学生公寓",4L);
}
iii.SQL查询
Spring Data JPA 同样也支持 sql 语句的查询,如下:
/**
* 查询所有用户:使用sql查询
* Sql:select * from cst_customer
* nativeQuery = true配置查询方式,true表示Sql查询,false表示Jpql查询
* 注意:返回值是一个Object[]类型的list
*/
// @Query(value = "select * from cst_customer",nativeQuery = true)
// public List<Object []>findSql();
@Query(value = "select * from cst_customer where cust_name like ?",nativeQuery = true)
public List<Object []>findSql(String name);
2.动态查询
springdatajpa的接口规范:
-
JpaRepository<操作的实体类型,实体类型中的 主键 属性的类型>
封装了基本的CRUD的操作,分页等;
-
JpaSpecificationExecutor<操作的实体类类型>
封装了复杂查询。
上述查询方法使用到的是接口JpaRepository中的方法,下面分析JpaSpecificationExecutor中的方法。
i.为什么需要动态查询
可能有些许疑惑,为什么还需要动态查询呢?有时候我们在查询某个实体的时候哦,给定的查询条件不是固定的,这个时候就需要动态构建相应的查询语句,可以理解为上述的查询条件是定义在dao接口中的,而动态查询条件定义在实现类中。
ii.JpaSpecificationExecutor中定义的方法
public interface JpaSpecificationExecutor<T> {
T findOne(Specification<T> var1);
List<T> findAll(Specification<T> var1);
Page<T> findAll(Specification<T> var1, Pageable var2);
List<T> findAll(Specification<T> var1, Sort var2);
long count(Specification<T> var1);
}
在上述方法中,我们可以看到接口Specification。可以简单理解为,Specification构造的就是查询条件。我们看看Specification中定义的方法。
/*
* root :T表示查询对象的类型,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
与上述查询方法不同,复杂查询定义在dao接口中,而动态查询定义在实现类中。
1)单条件查询
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
@Test
public void conditionTest(){
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询对象类型,需要那个对象就写哪个泛型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借书方法参数中的两个形参
* root:用于获取查询的对象属性
* CriteriaBuilder:构造查询条件,内部封装了很多的查询条件(例如:模糊匹配,精准匹配)
* 需求:根据客户名称查询,查询客户名称为大学
* 查询条件
* 1.查询方法 (精准匹配,是否为空...)
* CriteriaBuilder对象
* 2.比较的属性名称(与哪个字段去以什么方式去比较)
* root对象
*/
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//1.获取比较的属性(不是字段名)
Path<Object> custName = root.get("custName");
//2.构造查询条件
/**
* 第一个参数:需要比较的属性(Path)
* 第二个参数:当前比较的取值
*/
Predicate predicate = cb.equal(custName, "三峡大学");//进行精准匹配 (比较的属性,比较的属性的取值)
return predicate;
}
};
//根据返回的对象个数选择findOne或者findAll
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
}
2)多条件查询
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 多条件查询:根据用户名和所属行业进行查询
* root:获取属性
* 用户名
* 所属行业
* cb:构造查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3,将以上两个查询联系起来
*/
@Test
public void findByNmaeAndIndustray(){
Specification<Customer> spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
//1.获取属性
Path<Object> custName = root.get("custName");
Path<Object> industry = root.get("custIndustry");
//2.构造查询
Predicate p1 = cb.equal(custName, "6测试数据-coderxz");
Predicate p2 = cb.equal(industry, "6测试数据-java工程师");
//3。将多个查询条件组合到一起(and/or)
Predicate predicate = cb.and(p1, p2);
return predicate;
}
};
Customer customer = customerDao.findOne(spec);
System.out.println(customer);
}
}
3)模糊查询
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpecTest {
@Autowired
private CustomerDao customerDao;
/**
* 案例:根据客户名称进行模糊配置,返回客户列表
*
* equal:直接的path对象(属性),然后直接进行比较即可
*
* 对于gt,lt,le,like:得到path对象,根据path对象指定比较参数的类型(字符串or数字...),再进行比较
* 指定参数类型 path.as(类型的字节码对象)
*/
@Test
public void findVagueCustomer(){
Specification<Customer>spec=new Specification<Customer>() {
@Override
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Predicate predicate = criteriaBuilder.like(custName.as(String.class), "%大学%");
return predicate;
}
};
List<Customer> customers = customerDao.findAll(spec);
for(Customer c:customers){
System.out.println(c);
}
}
}
4)分页查询
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(