Spring Data JPA的查询方式
4.1 使用Spring Data JPA中接口定义的方法进行查询
在继承JpaRepository,和JpaRepository接口后,我们就可以使用接口中定义的方法进行查询
继承JpaRepository后的方法列表
继承JpaSpecificationExecutor的方法列表
测试count()方法和exists()方法
/**
* 测试统计查询: 查询客户的总数量
* count():通过主键id进行统计客户总数量
* */
@Test
public void testCount(){
long count = customerDao.count(); //查询全部客户的总数量
System.out.println(count);
}
/**
* 测试 : 判断id为4的客户是否存在
* 方法一
* 可以查询一下id为4的客户,
* 如果返回值为空,代表不存在,如果不为空,代表存在。
*
* 方法二:
* 判断数据库中id为4的客户的数量
* 如果数量为0,代表不存在,如果大于0,代表存在。
*/
@Test
public void testExists(){
boolean exists = customerDao.exists(4);
System.out.println("id为4的客户是否存在: "+exists );
}
测试getOne()方法
/**
* 根据id从数据库查询
* @Transactional: 保证getone方法能正常运行
*
* findOne:
* 底层调用的是: em.findOne()方法 em是EntiyManager 立即加载
* getOne:
* 底层调用的是: em.getReference() :延迟加载
* *返回的是一个客户的动态代理对象
* * 什么时候用,什么时候查询
*/
@Test
@Transactional
public void testGetOne(){
Customer customer = customerDao.getOne(1);
System.out.println(customer);
}
使用JPQL的方式查询
jpql的查询方式:
- jpql: jpa query language (jpq查询语言)
- 特点: 语法或关键字和sql语句类似, 查询的是类和类中的属性
- 需要将JPQL语句配置到接口方法上
- 特有的查询:需要在dao接口上配置方法
- 在新添加的方法上,使用注解的形式配置jpql查询语句
- 注解: @Query
使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可
public interface CustomerDao extends JpaRepository<Customer, Long>,JpaSpecificationExecutor<Customer> {
//@Query 使用jpql的方式查询。
@Query(value="from Customer")
public List<Customer> findAllCustomer();
//@Query 使用jpql的方式查询。?1代表参数的占位符,其中1对应方法中的参数索引
@Query(value="from Customer where custName = ?1")
public Customer findCustomer(String custName);
}
此外,也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询
@Query(value="update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateCustomer(String custName,Long custId);
测试Jpql查询
在CustomerDao文件中定义Jpql查询方法
/**
* 符合SpringDataJpa的dao层接口规范
* JpaRepository<操作的实体类类型,实体类中主键属性的类型>
* * 用来完成基本CRUD操作
* JpaSpecificationExecutor<操作的实体类类型>:
* * 用于复杂查询(分页等查询操作)
*
* */
public interface CustomerDao extends JpaRepository<Customer,Integer>,JpaSpecificationExecutor<Customer>{
/**
* 案例: 根据客户名查询客户信息
* 使用jpql进行查询
*
* jpql: from Customer where cust_name =?
*
* 配置jpql语句,使用的@Query注解
*/
@Query(value = "from Customer where cust_name =?1")
public Customer findJpql(String custName);
}
在JpqlTest.java中测试
@RunWith(SpringJUnit4ClassRunner.class) //声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:applicationContext.xml")//指定spring容器的配置信息
public class JpqlTest {
//从容器中获取CustomerDao对象
@Autowired
private CustomerDao customerDao;
//测试jpql语句
@Test
public void testJpql(){
Customer customer = customerDao.findJpql("小鑫仙女");
System.out.println(customer);
}
}
多占位符赋值
在CustomerDao文件中定义Jpql方法
/**
* 案例,根据客户名称和客户id查询客户
* jpql: from Customer where cust_name = ? an cust_id = ?
*
* 对于多个占位符参数
* 赋值的时候,默认的情况下,占位符的位置需要和方法参数中的位置保存一致
*
* 可以指定占位符参数的位置
* ? 索引的方式: 指定此占位的取值来源
* 比如 ?2 表示取方法中参数列表的第二个参数值
* 在下列方法中 ?1 表示取 方法中name参数的值 ?2表示取方法中id参数的值
* */
@Query(value = "from Customer where cust_name = ?1 and cust_id = ?2" )
public Customer findCustNameAndId(String name,int id);
在JpqlTest.java中测试
//根据客户名称和客户id查询客户
@Test
public void testFindCustNameAndId(){
Customer customer = customerDao.findCustNameAndId("小鑫鑫", 1);
System.out.println(customer);
}
使用jpql完成更新操作
在CustomerDao文件中定义Jpql更新方法
/**
* 使用jpql完成更新操作
* 案例: 根据id更新,客户的名称
* 更新4号客户的名称,将名称改为 小鑫鑫大美女
*
* sql: update cst_customer set cust_name = ? where cust_id = ?
* jpql: update Customer set cust_name = ? where cust_id=?
*
* @Query: 代表的是进行查询
* *需要声明此方法是用来进行更新操作
* @Modifying
* * 当前执行的是一个更新操作
* */
@Query(value = "update Customer set cust_name = ?2 where cust_id=?1")
@Modifying
public void updateCustomer(int custId,String custName);
在JpqlTest.java中测试
/**测试jpql的更新操作
* springDataJpa中使用jpql完成 更新/删除操作
* *需要手动添加事务的支持
* *默认会执行结束之后,回滚事务
* @Rollback: 设置是否自动回滚
* 取值: false|true
*/
@Test
@Transactional //添加事务的支持
@Rollback(value = false)
public void updateCustomer(){
customerDao.updateCustomer(4,"小鑫鑫大美女");
}
使用sql语句查询
Spring Data JPA同样也支持sql语句的查询,如下:
/**
* nativeQuery : 取值false(使用jpql查询 默认值)|true(使用本地查询:sql查询)
是否使用本地查询
* 使用sql的形式查询:
* 查询全部的客户
* sql: select * from cst_customer
* Query: 配置sql查询
* value: sql语句
* nativeQuery: 查询方式
* true: sql查询
* false: jpql查询
* */
@Query(value="select * from cst_customer",nativeQuery=true)
public List<Object[]> findAllCustomer();
在JpqlTest.java中测试
//测试sql查询:查询全部的客户
@Test
public void findAllCustomer(){
List<Customer> customers = customerDao.findAllCustomer();
for (Customer customer : customers) {
System.out.println(customer);
}
}
使用SQL条件查询
在CustomerDao文件中定义sql查询方法
@Query(value = "select * from cst_customer where cust_name like ?1",nativeQuery = true)
public List<Customer> findAllCustomer(String name);
在JpqlTest.java中测试
//测试sql查询:查询全部的客户
@Test
public void findAllCustomer(){
List<Customer> customers = customerDao.findAllCustomer("小鑫鑫%");
for (Customer customer : customers) {
System.out.println(customer);
}
}
方法命名规则查询
顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
/**
* 方法中的约定:
* findBy: 查询
* 对象中的属性名(首字母大写): 查询的条件
* Cust_name
* * 默认情况,使用等于的方式查询
* 特殊的查询方式
*
*findByCust_name -- 根据客户名称查询
*
* 在springDataJpa的运行阶段
* 会根据方法名称进行解析 findBy 翻译成 from xxx(实体类)
* 属性名称 where custName
*
* 1.findBy + 属性名称 (根据属性名称进行完成匹配的查询)
* 2.findBy + 属性名称 + "查询方式(Like | isnull)"
* findByCustNameLike-- 按照客户名称进行模糊匹配
* 3.多条件查询
* findBy + 属性名 + "查询方式" + "多条件的连接符"(and|or) + 属性名+"查询方式"
* */
public Customer findByCust_id(Integer cust_id);
public List<Customer> findByCust_nameLike(String cust_name);
//使用客户名称模糊匹配和客户所属行业精确匹配的查询
public List<Customer> findByCust_nameLikeAndCust_industry(String cust_name,String cust_industry);
在JpqlTest.java中测试
//测试方法命名规则的查询
@Test
public void testNaming(){
Customer customer = customerDao.findByCust_id(1);
System.out.println(customer);
}
//测试方法命名规则的模糊匹配
@Test
public void testFindByCustNameLike(){
List<Customer> customer = customerDao.findByCust_nameLike("小鑫鑫%");
for (Customer customer1 : customer) {
System.out.println(customer1);
}
}
//测试使用客户名称模糊匹配和客户所属行业精确匹配的查询
@Test
public void testFindByCustNameLikecustIndustry(){
List<Customer> customer = customerDao.findByCust_nameLikeAndAndCust_industry("小鑫鑫%","UI设计");
for (Customer customer1 : customer) {
System.out.println(customer1);
}
}
具体的关键字,使用方法和生产成SQL如下表所示
Specifications动态查询
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
/**
* JpaSpecificationExecutor中定义的方法
**/
public interface JpaSpecificationExecutor<T> {
//根据条件查询一个对象
T findOne(Specification<T> spec);
//根据条件查询集合
List<T> findAll(Specification<T> spec);
//根据条件分页查询
//pageable:分页参数
//返回值: 分页pageBean(page:是springdatajpa提供的)
Page<T> findAll(Specification<T> spec, Pageable pageable);
//排序查询查询
//sort: 排序参数
List<T> findAll(Specification<T> spec, Sort sort);
//统计查询
long count(Specification<T> spec);
}
对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件
Specification接口中只定义了如下一个方法:
//构造查询条件
/**
* root :Root接口,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询(了解:一般不用)
* cb :用来构建查询,此对象里有很多条件方法
**/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
1.1 使用Specifications完成查询单个对象
/**
* 根据条件:查询单个对象
*
* */
@Test
public void testSpec(){
//匿名内部类
/**
* 自定义查询条件
* 1.实现Specification接口(提供泛型:查询的对象类型)
* 2.实现toPredicate方法(构造查询条件)
* 3.需要借助方法参数中两个参数(
* root: 获取需要查询的对象属性
* CriteriaBuilder: 构成查询条件的,内部封装了很多的查询条件(模糊匹配,精准匹配)
* )
* 案例: 根据客户名称查询,查询客户名为小鑫鑫大美女的客户
* 查询条件
* 1.查询方式
* CriteriaBuilder对象中
* 2.比较的属性名称
* root对象中
* */
Specification<Customer> spec = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//1.获取比较的属性
Path<Object> name = root.get("custName");
//2.构造查询条件
/**
* 第一个参数,需要比较的属性(path对象)
* 第二个参数,当前需要比较属性的取值。
* */
Predicate predicate = criteriaBuilder.equal(name,"小鑫鑫大美女");//进行精确的匹配 (比较的属性,比较的属性的取值)
return predicate;
}
};
Customer customer = customerDao1.findOne(spec);
System.out.println(customer);
}
1.2 动态查询完成多条件拼接
/**
* 多条件查询
* 案例:根据客户名(小鑫鑫大美女)和客户所属行业(UI设计)查询
* */
@Test
public void testSpec1(){
/**
* root: 获取属性
* 1.客户名称
* 2.所属行业
* CriteriaBuilder: 构成查询
* 1.构造客户名的精准匹配查询
* 2.构造所属行业的精准匹配查询
* 3.将以上两个查询联系起来
* */
Specification<Customer> spec = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path<Object> custName = root.get("custName");
Path<Object> custIndustry = root.get("custIndustry");
//构造查询
//1.构造客户名的精准匹配查询
Predicate p1 = criteriaBuilder.equal(custName,"小鑫鑫大美女"); //第一个参数,path(属性),第二个参数,属性的取值
//2.构造所属行业的精准匹配查询
Predicate p2 = criteriaBuilder.equal(custIndustry,"UI设计");
//3.将多个查询条件组合到一起:组合(
// 满足条件1并且满足条件2:与关系
// 满足条件1或满足条件2:或关系
// )
Predicate and = criteriaBuilder.and(p1, p2);//以与的方式拼接多个查询条件
Predicate or = criteriaBuilder.or(p1, p2);//以或的方式拼接多个查询条件
return and;
}
};
Customer customer = customerDao1.findOne(spec);
System.out.println(customer);
}
1.3 模糊匹配查询列表
/**
* 案例: 完成根据客户名称的模糊匹配,返回客户列表
* 客户名称以 "小鑫鑫"开头的
*
* equal:直接得到path对象(属性),然后进行比较即可。
* gt、lt、ge、le、like:得到path对象,根据path对象指定比较的参数类型,再进行比较
* 指定的方式参数类型:path.as(类型的字节码对象)
* 比如在这个案例的模糊匹配参数是String类型,所以like的第一个参数:custName.as(String.class)
* 第二个参数是:模糊匹配参数的取值
* */
@Test
public void testSpec3(){
//构造查询条件
Specification<Customer> spec = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//查询属性: 客户名
Path<Object> custName = root.get("custName");
//查询方式,模糊匹配
/**
*
* */
Predicate p1 = criteriaBuilder.like(custName.as(String.class),"小鑫鑫%");
return p1;
}
};
List<Customer> customers = customerDao1.findAll(spec);
for (Customer customer : customers) {
System.out.println(customer);
}
}
1.4排序
/**
* 案例: 完成根据客户名称的模糊匹配,返回客户列表
* 客户名称以 "小鑫鑫"开头的
*
* equal:直接得到path对象(属性),然后进行比较即可。
* gt、lt、ge、le、like:得到path对象,根据path对象指定比较的参数类型,再进行比较
* 指定的方式参数类型:path.as(类型的字节码对象)
* 比如在这个案例的模糊匹配参数是String类型,所以like的第一个参数:custName.as(String.class)
* 第二个参数是:模糊匹配参数的取值
* */
@Test
public void testSpec3(){
//构造查询条件
Specification<Customer> spec = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//查询属性: 客户名
Path<Object> custName = root.get("custName");
//查询方式,模糊匹配
Predicate p1 = criteriaBuilder.like(custName.as(String.class),"小鑫鑫%");
return p1;
}
};
//添加排序
//创建排序对象,需要调用构造方法实例化sort对象
//第一个参数,排序的顺序(倒序,正序)
//Sort.Direction.DESC: 倒序
//Sort.Direction.ASC, 升序
//第二个参数, 排序的属性名称
Sort sort = new Sort(Sort.Direction.DESC,"custId");
List<Customer> customers = customerDao1.findAll(spec, sort);
for (Customer customer : customers) {
System.out.println(customer);
}
}
1.5 分页查询
/**
* 分页查询
* Specification:查询条件
* Pageable: 分页参数
* 分页参数: 查询的页面,每页查询的条数
* findAll(Specification,Pageable)方法,带有条件的分页
* findAll(Pageable): 没有条件的分页
* 返回: Page(SpringDataJpa为我们封装好的pageBean对象,
* 可以通过它的方法获取数据列表,获取总条数)
* */
@Test
public void testSpec4(){
Specification<Customer> spec = null;
//PageRequest对象是Pageable接口的实现类
/**
* 创建PageRequest的过程中,需要调用它的构成方法传入两个参数
* 第一个参数,当前查询的页数(从0开始)
* 第二个参数,每页查询的数量
* 返回结果Page对象
* customers.getTotalElements():获取总条数
* customers.getTotalPages():获取总页数
* customers.getContent(): 获取数据集合列表
* */
Pageable pageable = new PageRequest(0,2);
Page<Customer> customers = customerDao1.findAll(spec, pageable);
System.out.println(customers.getTotalElements());
System.out.println(customers.getTotalPages());
System.out.println(customers.getContent());
}
对于Spring Data JPA中的分页查询,是其内部自动实现的封装过程,返回的是一个Spring Data JPA提供的pageBean对象。其中的方法说明如下:
//获取总页数
int getTotalPages();
//获取总记录数
long getTotalElements();
//获取列表数据
List<T> getContent();
CriteriaBuilder中方法对应关系