Spring Data JPA的查询方式

4.1 使用Spring Data JPA中接口定义的方法进行查询

在继承JpaRepository,和JpaRepository接口后,我们就可以使用接口中定义的方法进行查询

继承JpaRepository后的方法列表

java jpa 子查询 jpa查询数据_List

继承JpaSpecificationExecutor的方法列表

java jpa 子查询 jpa查询数据_Test_02

测试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的查询方式:

  1. jpql: jpa query language (jpq查询语言)
  2. 特点: 语法或关键字和sql语句类似, 查询的是类和类中的属性
  3. 需要将JPQL语句配置到接口方法上
  1. 特有的查询:需要在dao接口上配置方法
  2. 在新添加的方法上,使用注解的形式配置jpql查询语句
  3. 注解: @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如下表所示

java jpa 子查询 jpa查询数据_List_03


java jpa 子查询 jpa查询数据_List_04

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中方法对应关系

java jpa 子查询 jpa查询数据_List_05