基于SpringBoot框架的开发环境搭建:SpringData JPA的使用

前文已经讲述了如何搭建一个Springboot环境和集成mysql数据库

基于SpringBoot框架的开发环境搭建:项目创建+集成数据库

三. SpringData JPA的查询操作

SpringData JPA有5个核心接口:

  1. Repository:是最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别

  2. CrudRepository:是Repository的子接口,提供CRUD的功能

  3. PagingAndSortingRepository:是CrudRepository的子接口,添加分页和排序的功能

  4. JpaRepository:是PagingAndSortingRepository的子接口,增加了一些实用的功能,比如:批量操作等

  5. JpaSpecificationExecutor:用来做复杂查询的接口

Repository->CrudRepository->PagingAndSortingRepository->JpaRepository是逐级继承的关系,JpaRepository拥有最全的功能。JpaSpecificationExecutor需要与Repository搭配才能实现负责条件的查询。

查询

  1. 基于方法名称命名规则
    在上篇文章中我们在UserRepository接口中新增了一个接口
 User findByUserName(String username);

我们知道user的Entity中userName字段,并且这个字段映射到数据库中的user_name这个字段,在UserRepository新增这个接口,然后实际调用时会去执行 select * from user where user_name=‘xxx’ 这条sql语句。

规则: findBy(关键字) + 属性名称(属性名称的首字母大写) + 查询条件(首字母大写)

常用规则:

关键字SQL符号方法命名sql where 子句
AndandfindByNameAndPwdwhere name= ? and pwd =?
OrorfindByNameOrSexwhere name= ? or sex=?
Is、Equals=findById、findByIdEqualswhere id = ?
Betweenbetween xx and xxfindByIdBetweenwhere id between ? and ?
LessThan<findByIdLessThanwhere id < ?
LessThanEqual<=findByIdLessThanEqualwhere id <= ?
GreaterThan>findByIdGreaterThanwhere id > ?
GreaterThanEqual>=findByIdGreaterThanEqualwhere id > = ?
After>findByIdAfterwhere id > ?
Before<findByIdBeforewhere id < ?
IsNullis nullfindByNameIsNullwhere name is null
isNotNull,NotNullis not nullfindByNameNotNullwhere name is not null
LikelikefindByNameLikewhere name like ?
NotLikenot likefindByNameNotLikewhere name not like ?
StartingWithlike ‘xx%’findByNameStartingWithwhere name like ‘?%’
EndingWithlike ‘%xx’findByNameEndingWithwhere name like ‘%?’
Containinglike ‘%xx%’findByNameContainingwhere name like ‘%?%’
OrderByorder byfindByIdOrderByXDescwhere id=? order by x desc
Not<>、!=findByNameNotwhere name <> ?
Inin()findByIdIn(Collection<?> c)where id in (?)
NotInnot in()findByNameNotwhere name <> ?
True=truefindByAaaTuewhere aaa = true
False=falsefindByAaaFalsewhere aaa = false
IgnoreCaseupper(x)=upper(y)findByNameIgnoreCasewhere UPPER(name)=UPPER(?)
toptop、rownum<=xfindTop10top 10 / where ROWNUM <=10
  1. 使用@Query查询与更新
public interface UserRepository extends JpaRepository<User,Long> {
    User findByUserName(String username);

    @Query("from User where userName= ?1")
    User findByUserNameJPQL(String username);

	@Query("from User where userName like :username")
    List<User> queryUserByLikeUsernameUseJPQL(@Param("username")String username);

	// 通过原生sql去执行查询
	@Query(value = "select * from user where user_name = ?1",nativeQuery=true)
    List<User> queryUserByUsernameUseSQL(String username);

	@Query(value = "update user set user_name = ? where id = ?",nativeQuery=true)
    @Modifying // 需要执行一个更新操作
    Integer updateUsernameById(String username,Long id);
}

总结:

  1. 可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作。 注意: JPQL 不支持使用 INSERT(新增操作)
  2. 在 @Query 注解中编写 JPQL 语句, 如果是 UPDATE 或 DELETE 操作,必须使用 @Modifying 修饰
  3. UPDATE 或 DELETE 操作需要使用事务,此时需要的 Service 层的方法上添加事务操作
  4. 默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务。 他们不能完成修改操作

排序

  1. 基于方法名称命名规则
    /**
     * 假如你需要所有的数据而不需要按照条件查询,就要按照这样来写:
     * findBy/getBy/queryBy.. + OrderBy + 排序字段 + 排序方式
     * 按照 username 默认正序排序
     * ..order by username
     */
    List<User> findByOrderByUsername();

    /**
     * 查询全部数据:先按age字段倒序再按照id字段正序排序
     * ..order by age desc, id asc
     */
    List<User> findByOrderByAgeDescIdAsc();

    /**
     * 查询名称结果集按正序排序:
     * ...where username = ? order by age asc
     */
    List<User> findByUsernameOrderByAgeAsc(String username);

    /**
     * 查询名称结果集按倒序排序:
     * ...where username = ? order by id desc
     */
    List<User> findByUsernameOrderByIdDesc(String username);

demo:

/**
     * 排序操作:
     *  1.对单列做排序处理
     *  2.多列的排序处理
     */
    @Test
    public void test2(){
        /**
         * Sort:该对象封装了排序规则以及指定的排序字段(对象的属性来表示)
         *   direction:排序规则(Sort.Direction.DESC)
         *   properties:指定做排序的属性(id)
         */
        // 1.对单列做排序处理,对id做降序排序
        Sort sortOne = Sort.by(Sort.Direction.DESC,"id");
        Iterable iterable = this.userPagingAndSortingRepository.findAll(sortOne);
        iterable.forEach(System.out::println);

        System.err.println("--------------华丽的分割线-------------------");

        /**
         * 可以用如下两种方式得到Order
         *   1.new Sort.Order()
         *   2.Sort.Order.desc()/asc()/by()
         */
        // 2.多列的排序处理,先对age列做降序处理,然后对id字段做升序处理
        Sort.Order age = Sort.Order.desc("age");
        Sort.Order id = new Sort.Order(Sort.Direction.ASC,"id");
        Sort sortMany = Sort.by(age,id);
        this.userPagingAndSortingRepository.findAll(sortMany).forEach(System.out::println);
    }

分页

  1. 基于方法名称命名规则
    /**
     * 说明:对表的全部数据根据age进行Asc(升序)排序后再选择第一条数据返回
     * SQL:...order by age asc limit 1
     * 注意:findFirst3ByOrderByAgeAsc()/findTop3ByOrderByAgeAsc()则每次返回3条 limit 3
     */
    User findFirstByOrderByAgeAsc();
    User findTopByOrderByAgeAsc();

    /**
     * 说明:首先进行数据查询并通过Sort进行排序后 再筛选数据列表中最前面的2行数据返回 limit 2
     * SQL:where username = ? order by field asc/desc
     */
    List<User> findFirst2ByUsername(String username, Sort sort);
    List<User> findTop2ByUsername(String username,Sort sort);

    /**
     * 说明:首先进行数据查询,查询全部指定的username后进行分页,分页完后进行数据控制
     * 控制说明:
     *     关于带分页的控制是,假设分页过后查询的数据id为3,4,5 查询出这三条数据后进行数据控制,
     *     本案例是控制为2条,那返回的id数据就是3,4两条记录,id为5的就舍弃,
     *     那么如果数据控制是5条(Top5),那么就会打印3,4,5另外再去添加6,7补充数据长度
     */
    Page<User> queryFirst2ByUsername(String username, Pageable pageable);
    List<User> queryTop2ByUsername(String username, Pageable pageable);

demo:

/**
     * 分页操作
     */
    @Test
    public void test1(){
        /**
         * Pageable:封装了分页的参数,当前页,每页显示的条数。注意:当前页是从0开始
         * PageRequest(page,size):page表示当前页,size表示每页显示多少条
         */
        Pageable pageable = PageRequest.of(0, 2);
        Page<User> page = this.userPagingAndSortingRepository.findAll(pageable);
        System.out.println("总记录数: " + page.getTotalElements());
        System.out.println("当前第几页: " + (page.getNumber() + 1));
        System.out.println("总页数: " + page.getTotalPages());
        System.out.println("当前页面的 List: " + page.getContent());
        System.out.println("当前页面的记录数: " + page.getNumberOfElements());
        page.getContent().forEach(System.out::println);
    }
    /**
     * 分页 + 排序 处理
     *
     */
    @Test
    public void test3(){
        // 排序操作对象
        Sort.Order age = Sort.Order.desc("age");
        Sort.Order id = Sort.Order.asc("id");
        Sort sort = Sort.by(age,id);
        // 排序操作还是没变化,只需要在PageRequest.of内多加一个Sort参数
        Pageable pageable = PageRequest.of(0, 2 ,sort);
        Page<User> page = this.userPagingAndSortingRepository.findAll(pageable);
        System.out.println("总记录数: " + page.getTotalElements());
        System.out.println("当前第几页: " + (page.getNumber() + 1));
        System.out.println("总页数: " + page.getTotalPages());
        System.out.println("当前页面的记录数: " + page.getNumberOfElements());
        System.out.println("当前页面的 List: " + page.getContent());
        System.err.println("--------------华丽的分割线-------------------");
        page.getContent().forEach(System.out::println);
    }

计数

  1. 基于方法名称命名规则
    /**
     * 计数 返回总数
     * countBy()等同于countAllBy()
     * SQL:
     *     select count(id) from sys_user where username = ?
     *     select count(id) from sys_user
     */
    Long countByUsername(String username);
    Long countAllBy();

删除

  1. 基于方法名称命名规则
    /**
     * 说明:必须添加事务和回滚,这样根据条件找到几条就删几条
     * 对应的SQL语句:
     *  Hibernate: select * from sys_user where username = ?
     *  Hibernate: delete from sys_user where id=?
     *  Hibernate: delete from sys_user where id=?
     *  ....
     *  关于removeBy和deleteBy方法区别:
     *  他们是一样的 .选择哪一个取决于您的个人偏好.
     */
    void deleteByUsername(String username);
    void removeByUsername(String username);

接口方法

CrudRepository 接口:

@NoRepositoryBean  
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { // T 是要操作的实体类,ID 是实体类主键的类型

    <S extends T> S save(S entity); // 保存
    <S extends T> Iterable<S> saveAll(Iterable<S> entities); // 批量保存
    
    Optional<T> findById(ID id); // 根据id查询一个Optional
    boolean exists(ID id); // 根据id判断对象是否存在
    Iterable<T> findAll(); // 查询所有的对象  
    Iterable<T> findAllById(Iterable<ID> ids); // 根据id列表查询所有的对象  
 
    long count(); // 计算对象的总个数  

    void delete(ID id); // 根据id删除  
    void delete(T entity); // 删除一个对象 
    void delete(Iterable<? extends T> entities); // 批量删除,集合对象(后台执行时,一条一条删除)
    void deleteAll(); // 删除所有 (后台执行时,一条一条删除)
}

PagingAndSortingRepository 接口:

@NoRepositoryBean  
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {  
    Iterable<T> findAll(Sort sort); // 仅排序
    Page<T> findAll(Pageable pageable); // 分页和排序
} 

JpaRepository 接口:

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable>
        extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
    
    @Override
    List<T> findAll(); // 查询所有对象,返回List
    @Override
    List<T> findAll(Sort sort); // 查询所有对象,并排序,返回List
    @Override
    List<T> findAllById(Iterable<ID> ids); // 根据id列表 查询所有的对象,返回List

    @Override
    <S extends T> List<S> saveAll(Iterable<S> entities); // 批量保存,并返回对象List
    <S extends T> S saveAndFlush(S entity); // 保存并强制同步数据库
    void flush(); // 强制缓存与数据库同步 

    void deleteInBatch(Iterable<T> entities); // 批量删除 集合对象(后台执行时,生成一条语句执行,用多个or条件)
    void deleteAllInBatch(); // 删除所有 (执行一条语句,如:delete from user)

    T getOne(ID id); // 根据id查询对象,返回对象的引用(区别于findOne)。当对象不存时,返回引用不是null,但各个属性值是null
    
    @Override
    <S extends T> List<S> findAll(Example<S> example); // 根据实例查询
    @Override
    <S extends T> List<S> findAll(Example<S> example, Sort sort);// 根据实例查询并排序
}

JpaSpecificationExecutor

接口定义:

/**
 * JpaSpecificationExecutor:用来做动态查询的接口
 * Specification:是SpringDataJPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件。
 * JpaSpecificationExecutor接口下一共就5个接口方法
 */
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);// 统计总数
}

使用方法

  1. dao层集成JpaSpecificationExecutor
public interface UserJpaSpecificationExecutor extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}
  1. service层
 /**
     * 条件查询——单条件
     * Predicate 过滤条件
     * jpql:from User where username = '隔壁小王'
     * sql:select * from sys_user where username = '隔壁小王'
     */
    @Test
    public void test1(){
        Specification<User> spec = new Specification<User>() {
            /**
             * @param root 根对象。封装了查询条件的对象
             * @param criteriaQuery 定义了一个基本的查询。一般使用较少
             * @param criteriaBuilder 创建一个查询条件
             * @return Predicate:定义了查询条件
             */
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                Path<Object> username = root.get("username");
                Predicate predicate = criteriaBuilder.equal(username, "隔壁小王");
                return predicate;
            }
        };
        List<User> userList = userJpaSpecificationExecutor.findAll(spec);
        userList.forEach(System.out::println);
    }
	/**
     * 多条件查询二
     * jpql:from User where username like '%小王' and password = 'password' and age < 30
     * sql:select * from sys_user where username like '%小王' and password = 'password' and age < 30
     */
    @Test
    public void test3(){
        Specification<User> spec = (root, criteriaQuery, criteriaBuilder) -> {
            Predicate username = criteriaBuilder.like(root.get("username"), "%小王");
            Predicate password = criteriaBuilder.equal(root.get("password"), "password");
            Predicate age = criteriaBuilder.lt(root.get("age"), 30);
            Predicate predicate = criteriaBuilder.and(username, password);
            predicate = criteriaBuilder.and(predicate, age);
            return predicate;
        };
        List<User> userList = userJpaSpecificationExecutor.findAll(spec);
        userList.forEach(System.out::println);
    }

tips

  1. 对比:deleteAll() 和 deleteAllInBatch() 区别:
    • deleteAll()是删除全部,先findALL查找出来,再一条一条删除,最后提交事务
    • deleteAllInBatch()是删除全部,一条sql。显然:deleteAllInBatch()方法效率更高
    • 在进行批量删除操作时尽量使用JpaRepository自带的批量删除方法deleteInBatch()及deleteAllInBatch()
  2. getOne()与findById()方法总结与区别:
    • 1、getOne() 是延迟加载。findById() 是立马加载
    • 2、getOne()返回一个实体的引用,无结果会抛出异常。findById()返回一个Optional对象
    • 3、getOne()属于JpaRepository,底层调用getReference()方法,懒加载的模式,当调用对象时才会发送SQL
    • 4、findById()属于CrudRepository,底层调用了find()方法,当调用该方法的时就直接发送SQL

本文借鉴了CSDN博主「路面烧卖」的部分代码和文案,对博主表示非常感谢。
原文链接:https://blog.csdn.net/qq_36259143/article/details/120207737

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值