SpringData JPA 接口查询

SpringData JPA 接口查询

1、SpringData JPA 核心接口

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

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

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

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

  5. JpaSpecificationExecutor:用来做负责查询的接口

    Specification:是 SpringData JPA提供的一个查询规范,要做复杂的查询,只需围绕这个规范来设置查询条件

SpringData JPA接口继承结构:

                Repository
                    ⬇
              CrudRepository
                    ⬇
        PagingAndSortingRepository             QueryByExampleExecutor
                    ⬇                                   ⬇
               JpaRepository                       JpaRepository                JpaSpecificationExecutor

2、Repository 接口

Repository 接口自带了如下两种查询方式:1)提供了方法名称命名查询方式、2)提供了@Query注解查询与更新

2.1、方法名称命名规则查询

SpringData 里面有一种很不错的操作,那就是在接口中定义方法而不用实现也不用注解就可以实现其查询操作,但是写方法名查询是有一套规范的,这里我在官方文档里面整理出来了分享给大家。【PS:方法名命名规则不能对其增加(insert)和修改(update)】

规则: 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

2.2、方法名查询的具体规则

1、规则描述:

按照 Spring Data 定义的规则,查询方法以 find | read | get 开头(比如 find、findBy、read、readBy、get、getBy),涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

如果方法的最后一个参数是 Sort 或者 Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。

2、举例说明:

例如:findByUserAddressZip()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,详细规则如下(此处假设该方法针对的域对象为 AccountInfo 类型):

  1. 先判断 userAddressZip (根据POJO规范,首字母变为小写)是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步
  2. 从右往左截取第一个大写字母开头的字符串(此处为 Zip),然后检查剩下的字符串是否为 AccountInfo 的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为 AccountInfo 的一个属性;
  3. 接着处理剩下部分(AddressZip )先判断 user 所对应的类型是否有 addressZip 属性,如果有,则表示该方法最终是根据 “AccountInfo.user.addressZip” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “AccountInfo.user.address.zip” 的值进行查询。

可能会存在一种特殊情况,比如 AccountInfo 包含一个 user 的属性,也有一个 userAddress 属性,此时会存在混淆。读者可以明确在属性之间加上 “_” 以显式表达意图,比如 “findByUser_AddressZip()” 或者 “findByUserAddress_Zip()”(强烈建议:无论是否存在混淆,都要在不同类层级之间加上"_" ,增加代码可读性

2.3、方法名查询实战案例

1、当查询条件为 null 时

举例说明如下:

  1. 实体定义:对于一个客户实体 User,包含字段:username、password,均是 String 类型。
  2. 查询方法定义:List<User> findByUsernameAndPassword(String username,String password);
  3. 使用时:dao.findByUsernameAndPassword(null, "123456");
  4. 后台生成SQL片断:where (user0_.username is null) and user0_.password=?
  5. 结论:当查询时传值是 null 时,数据库中只有该字段是 null 的记录才符合条件,并不是说忽略这个条件。也就是说,这种查询方式,只适合于明确查询条件必须传的业务,对于动态查询,这种简单查询是不能满足要求的。

2、排序功能

    /**
     * 假如你需要所有的数据而不需要按照条件查询,就要按照这样来写:
     * 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);

3、限定查询结果集大小

    /**
     * 说明:对表的全部数据根据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);

总结:这里的 FirstTop 意思相同都是最前面,query 也和 find 一样


4、计数

    /**
     * 计数 返回总数
     * countBy()等同于countAllBy()
     * SQL:
     *     select count(id) from sys_user where username = ?
     *     select count(id) from sys_user
     */
    Long countByUsername(String username);
    Long countAllBy();

5、删除

    /**
     * 说明:必须添加事务和回滚,这样根据条件找到几条就删几条
     * 对应的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);

2.4、使用@Query查询与更新

@Query:通过 JPQL 语句查询与更新(JPQL:通过 Hibernate 的 HQL 演变过来的。他和 HQL 语法及其相似。)

1、创建Repository接口:

/**
 * Repository接口:使用@Query注解查询JPQL
 * JPQL查询 和原生 SQL 查询,区别在于nativeQuery属性。
 * nativeQuery:默认的是false,false为JPQL查询。true为开启SQL查询。
 * 更新删除操作需要加上 @Modifying 注解(注意:不支持新增)
 */
public interface UserRepositoryJPQL extends Repository<User, Long> {

    /**
     * ? 展示位置参数绑定:?1
     * 必须使用?1形式,如果有多个参数以此类推,如: ?1,?2..
     * JPQL如果只输入?查询会报错,SQL查询不会
     */
    @Query("from User where username = ?1")
    List<User> queryUserByUsernameUseJPQL(String username);

    /**
     * : 展示名字参数绑定::username
     * 注意:使用 :username 绑定,必须在参数前加@Param("username")
     */
    @Query("from User where username like :username")
    List<User> queryUserByLikeUsernameUseJPQL(@Param("username")String username);

    @Query("from User where username = :username and age > :age")
    List<User> queryUserByUsernameAndAgeUserJPQL(@Param("username")String username, @Param("age")Integer age);

    @Query("update User set username = ?1 where id = ?2")
    @Modifying // 需要执行一个更新操作
    Integer updateUsernameById(String username,Long id);
}

2、测试类:

@SpringBootTest
public class UserRepositoryJPQLTests {

    @Autowired
    private UserRepositoryJPQL userRepositoryJPQL;

    /**
     * 测试@Query查询 JPQL
     */
    @Test
    public void test1(){
        List<User> listA = userRepositoryJPQL.queryUserByUsernameUseJPQL("王五");
        listA.forEach(System.out::println);
    }

    @Test
    public void test2(){
        List<User> list = userRepositoryJPQL.queryUserByLikeUsernameUseJPQL("%王%");
        list.forEach(System.out::println);
    }

    @Test
    public void test3(){
        List<User> list = userRepositoryJPQL.queryUserByUsernameAndAgeUserJPQL("隔壁小王", 15);
        list.forEach(System.out::println);
    }

    /**
     * 更新删除操作记得加上事务操作,不然会报错:
     * Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query
     */
    @Test
    @Transactional // @Transactional与@Test 一起使用时 事务是自动回滚的。
    @Rollback(false) // 取消自动回滚
    public void test4(){
        Integer integer = userRepositoryJPQL.updateUsernameById("隔壁小王", 5L);
        System.out.println("更新数据行数:" + integer);
    }

}

3、查看日志(更新操作)

Hibernate: 
    update
        sys_user 
    set
        username=? 
    where
        id=?
更新数据行数:1

@Query:通过原生 SQL 语句查询与更新

1、创建Repository接口:

/**
 * Repository接口:使用@Query注解查询SQL
 * JPQL查询 和 SQL查询,区别在于nativeQuery属性。
 * nativeQuery:默认的是false.表示不开启sql查询。是否对value中的语句做转义
 * 更新删除操作需要加上 @Modifying 注解(注意:不支持新增)
 */
public interface UserRepositorySQL extends Repository<User, Long> {

    /**
     * ? 展示位置参数绑定:? 或者 ?1
     */
    @Query(value = "select * from sys_user where username = ?1",nativeQuery=true)
    List<User> queryUserByUsernameUseSQL(String username);

    /**
     * 如果有多个参数以此类推,如: ?1,?2..或者 ? ?....
     * 如果只用?的话,方法中的参数必须按顺序填写,如果 ?1 则不用
     * JPQL查询如果只输入?查询会报错,SQL查询不会
     */
    @Query(value = "select * from sys_user where username = ?1 and age >= ?2",nativeQuery=true)
    List<User> queryUserByUsernameAndAgeUseSQL(String name, Integer age);

    /**
     * : 展示名字参数绑定::username
     * 注意:使用 :username 绑定,必须在参数前加@Param("username")
     */
    @Query(value = "select * from sys_user where username like :username",nativeQuery=true)
    List<User> queryUserByLikeUsernameUseSQL(@Param("username") String username);

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

2、测试代码

@SpringBootTest
public class UserRepositorySQLTests {

    @Autowired
    private UserRepositorySQL userRepositorySQL;

    /**
     * 测试@Query查询 SQL
     */
    @Test
    public void test1(){
        List<User> list = this.userRepositorySQL.queryUserByUsernameUseSQL("隔壁小王");
        list.forEach(System.out::println);
    }
    
    @Test
    public void test2(){
        List<User> list = this.userRepositorySQL.queryUserByUsernameAndAgeUseSQL("隔壁小王", 15);
        list.forEach(System.out::println);
    }

    @Test
    public void test3(){
        List<User> list = this.userRepositorySQL.queryUserByLikeUsernameUseSQL("%王%");
        list.forEach(System.out::println);
    }

    /**
     * 更新删除操作记得加上事务操作,不然会报错:
     * Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query
     */
    @Test
    @Transactional //@Transactional与@Test 一起使用时 事务是自动回滚的。
    @Rollback(false) //取消自动回滚
    public void test4(){
        Integer integer = userRepositorySQL.updateUsernameById("隔壁小王", 5L);
        System.out.println("更新数据行数:" + integer);
    }

}

3、查看日志

Hibernate: 
    update
        sys_user 
    set
        username = ? 
    where
        id = ?
更新数据行数:1

PS:从日志上看 JPQL 与 SQL 查询看不出什么区别,不过使用 SpringData 时推荐还是使用 JPQL 方式。


总结:

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

2.5、使用@NamedQuery查询

@NamedQuery 与 @NamedNativeQuery 都是定义查询的一种形式,@NamedQuery 使用的是JPQL,而 @NamedNativeQuery 使用的是原生SQL。这两种不常用,所以简单介绍一下。

1、使用 @NamedQuery、@NamedQueries 注解在实体类中定义命名查询(单个和多个命名查询)

@Data
@Entity
@Table(name = "sys_user")
@NamedQuery(name="User.findAllUserNamedQuery",query="from User") // name指定命名查询的名称,query属性指定命令查询的语句
@NamedQueries({                                                  // 定义多个
        @NamedQuery(name="User.findUserByUsernameNamedQuery",query="from User where username = ?1"),
        @NamedQuery(name="User.findUserByLikeUsernameNamedQuery",query="from User where username like :username")
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private Integer age;
}

2、使用 @NamedNativeQuery、@NamedNativeQueries 定义单个和多个命名查询(注意使用@NamedNativeQuery需要指定resultClass参数类型)

@Data
@Entity
@Table(name = "sys_user")
@NamedNativeQuery(name="User.findAllUserNamedNativeQuery",query="select * from sys_user", resultClass = User.class)
@NamedNativeQueries({
        @NamedNativeQuery(name="User.findUserByUsernameNamedNativeQuery",query="select * from sys_user where username = ?1", resultClass = User.class),
        @NamedNativeQuery(name="User.findUserByLikeUsernameNamedNativeQuery",query="select * from sys_user where username like :username", resultClass = User.class)
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private Integer age;
}

3、在Repository接口中声明方法

/**
 * Repository接口:
 * 在User.Java实体类使用@NamedQuery 与 @NamedNativeQuery 都是定义查询的一种形式,
 * @NamedQuery 使用的是JPQL,而 @NamedNativeQuery 使用的是原生SQL。
 * @NamedQuery 内的 name 就是本接口定义的方法名称
 */
public interface UserRepositoryNamedQuery extends Repository<User, Long> {
    /**
     * 使用@NamedQuery进行方法查询,方法名为@NamedQuery的name参数
     */
    List<User> findAllUserNamedQuery();
    List<User> findUserByUsernameNamedQuery(String name);
    List<User> findUserByLikeUsernameNamedQuery(String username);

    /**
     * 使用@NamedNativeQuery进行方法查询,方法名为@NamedQuery的name参数
     */
    List<User> findAllUserNamedNativeQuery();
    List<User> findUserByUsernameNamedNativeQuery(String name);
    List<User> findUserByLikeUsernameNamedNativeQuery(String username);
}

4、测试代码

package com.example.jpa;

import com.example.jpa.entity.User;
import com.example.jpa.repository.UserJpaRepository;
import com.example.jpa.repository.UserRepositoryNamedQuery;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;

@SpringBootTest
public class UserRepositoryNamedQueryTests {
    @Autowired
    private UserJpaRepository userJpaRepository;
    @BeforeEach
    public void initData(){
        // 保存多条数据
        User userA = new User();
        userA.setUsername("隔壁小王");
        userA.setPassword("password");
        userA.setAge(18);
        User userB = new User();
        userB.setUsername("赵小丽");
        userB.setPassword("666666");
        userB.setAge(21);
        User userC = new User();
        userC.setUsername("王小虎");
        userC.setPassword("123456");
        userC.setAge(25);
        this.userJpaRepository.saveAll(Arrays.asList(userA, userB, userC)).forEach(System.out::println);
    }

    @Autowired
    private UserRepositoryNamedQuery userRepositoryNamedQuery;

    @Test
    public void testNamedQuery(){
        System.out.println(userRepositoryNamedQuery.findAllUserNamedQuery());
        System.out.println(userRepositoryNamedQuery.findUserByUsernameNamedQuery("隔壁小王"));
        System.out.println(userRepositoryNamedQuery.findUserByLikeUsernameNamedQuery("%王%"));
    }

    @Test
    public void testNamedNativeQuery(){
        System.out.println(userRepositoryNamedQuery.findAllUserNamedNativeQuery());
        System.out.println(userRepositoryNamedQuery.findUserByUsernameNamedNativeQuery("隔壁小王"));
        System.out.println(userRepositoryNamedQuery.findUserByLikeUsernameNamedNativeQuery("%王%"));
    }
}

5、查看日志

// 省略数据初始化日志
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_
[User(id=1, username=隔壁小王, password=password, age=18), User(id=2, username=赵小丽, password=666666, age=21), User(id=3, username=王小虎, password=123456, age=25)]
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username=?
[User(id=1, username=隔壁小王, password=password, age=18)]
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username like ?
[User(id=1, username=隔壁小王, password=password, age=18), User(id=3, username=王小虎, password=123456, age=25)]
// 省略数据初始化日志
Hibernate: 
    select
        * 
    from
        sys_user
[User(id=1, username=隔壁小王, password=password, age=18), User(id=2, username=赵小丽, password=666666, age=21), User(id=3, username=王小虎, password=123456, age=25)]
Hibernate: 
    select
        * 
    from
        sys_user 
    where
        username = ?
[User(id=1, username=隔壁小王, password=password, age=18)]
Hibernate: 
    select
        * 
    from
        sys_user 
    where
        username like ?
[User(id=1, username=隔壁小王, password=password, age=18), User(id=3, username=王小虎, password=123456, age=25)]

PS:

  1. @NamedQuery、@NamedNativeQuery注解也可以使用<named-query>、<named-native-query />标签来替代写在orm.xml中。
  2. @NamedNativeQuery还可以与@SqlResultSetMapping(@EntityResult、@ConstructorResult、@ColumnResult、@FieldResult)注解配置使用,指定映射。
  3. @NamedQueries,@NamedNativeQueries、@SqlResultSetMappings用装多个@NamedQuery、@NamedNativeQuery、@SqlResultSetMapping
  4. 我们一般不推荐使用@NamedQuery、@NamedNativeQuery,而使用下面的@Query注解。

3、CrudRepository 接口

CrudRepository 接口主要是完成一些增删改查的操作。CrudRepository 继承了 Repository 。查看 CrudRepository 源码,该接口提供了11个常用操作方法

@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(); // 删除所有 (后台执行时,一条一条删除)
}

1、创建Repository层接口,并且继承 CrudRepository 接口

/**
 * CrudRepository 接口
 * 主要是完成一些增删改查的操作。注意:CrudRepository接口继承了Repository接口
 */
public interface UserCrudRepository extends CrudRepository<User,Long> {
}

2、测试代码

package com.example.jpa;

import com.example.jpa.entity.User;
import com.example.jpa.repository.UserCrudRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import javax.transaction.Transactional;
import java.util.Arrays;
import java.util.Optional;

@SpringBootTest
public class UserCrudRepositoryTests {

    @Autowired
    private UserCrudRepository userCrudRepository;

    /**
     * 数据保存:
     *  1.添加单条数据
     *  2.批量添加数据
     */
    @Test
    public void testSave(){
        // 保存单个数据
        User user = new User();
        user.setUsername("隔壁小王");
        user.setPassword("password");
        user.setAge(18);
        User save = this.userCrudRepository.save(user);
        System.out.println(save);

        // 保存多条数据
        User userA = new User();
        userA.setUsername("赵小丽");
        userA.setPassword("666666");
        userA.setAge(21);
        User userB = new User();
        userB.setUsername("王小虎");
        userB.setPassword("123456");
        userB.setAge(25);
        Iterable<User> saveAll = this.userCrudRepository.saveAll(Arrays.asList(userA, userB));
        saveAll.forEach(System.out::println);
    }


    /**
     * 数据查询:
     *  1.根据ID查询单条数据
     *  2.查询全部数据
     *  3.根据多个id查询多条数据
     *  4.根据ID判断数据是否存在
     *  5.查询该表(实体类)有多少数据
     */
    @Test
    public void testFind(){
        // 1.根据ID查询单条数据
        Optional<User> user = this.userCrudRepository.findById(1L);
        System.out.println(user.get());
        System.err.println("--------------华丽的分割线-------------------");
        // 2.查询全部数据
        Iterable<User> userIterableA = this.userCrudRepository.findAll();
        userIterableA.forEach(System.out::println);
        System.err.println("--------------华丽的分割线-------------------");
        // 3.根据多个id查询多条数据
        Iterable<User> userIterableB = this.userCrudRepository.findAllById(Arrays.asList(1L, 2L));
        userIterableB.forEach(System.out::println);
        System.err.println("--------------华丽的分割线-------------------");
        // 4.根据ID判断数据是否存在
        System.out.println(this.userCrudRepository.existsById(1L));
        System.err.println("--------------华丽的分割线-------------------");
        // 5.查询该表(实体类)有多少数据
        System.out.println(this.userCrudRepository.count());
    }

    /**
     * 更新数据方式一: save()方法
     */
    @Test
    public void testUpdate1(){
        // 持久化状态的
        User user = this.userCrudRepository.findById(1L).get();
        user.setUsername("王小红");
        this.userCrudRepository.save(user);
    }
    /**
     * 更新数据方式二: 设置对象属性自动更新数据库
     * 【持久态对象的属性被修改后,,在数据库事物提交的时候,会更新数据库】
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testUpdate2(){
        // 持久化状态,当前方法加了事物,SpringDataJPA在事物完成的时候,会自动提交修改
        User user = this.userCrudRepository.findById(1L).get();
        user.setUsername("王小小");
    }
    
    /**
     * 删除数据
     *  1.根据id删除
     *  2.根据实体类删除单个数据
     *  3.根据实体类数组删除多调数据
     *  4.删除全部数据
     */
    @Test
    public void testDelete(){
        // 1.根据id删除
        this.userCrudRepository.deleteById(1L);
        // 2.根据实体类删除单个数据
        User user = this.userCrudRepository.findById(2L).get();
        this.userCrudRepository.delete(user);
        // 3.根据实体类数组删除多调数据
        this.userCrudRepository.deleteAll(this.userCrudRepository.findAllById(Arrays.asList(1L, 2L)));
        // 4.删除全部数据
        this.userCrudRepository.deleteAll();
    }
    
}

查看日志:数据保存 testSave()

Hibernate: 
    insert 
    into
        sys_user
        (age, password, username) 
    values
        (?, ?, ?)
User(id=2, username=隔壁小王, password=password, age=18)
Hibernate: 
    insert 
    into
        sys_user
        (age, password, username) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        sys_user
        (age, password, username) 
    values
        (?, ?, ?)
User(id=3, username=赵小丽, password=666666, age=21)
User(id=4, username=王小虎, password=123456, age=25)

查看日志:数据查询 testFind()

Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.age as age2_1_0_,
        user0_.password as password3_1_0_,
        user0_.username as username4_1_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
User(id=1, username=admin, password=password, age=18)
--------------华丽的分割线-------------------
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_
User(id=1, username=admin, password=password, age=18)
User(id=2, username=隔壁小王, password=password, age=18)
User(id=3, username=赵小丽, password=666666, age=21)
User(id=4, username=王小虎, password=123456, age=25)
--------------华丽的分割线-------------------
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.id in (
            ? , ?
        )
User(id=1, username=admin, password=password, age=18)
User(id=2, username=隔壁小王, password=password, age=18)
--------------华丽的分割线-------------------
Hibernate: 
    select
        count(*) as col_0_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
true
--------------华丽的分割线-------------------
Hibernate: 
    select
        count(*) as col_0_0_ 
    from
        sys_user user0_
4

查看日志:数据更新方式一 testUpdate1(),可以发现查询了2次Select(第二次select是save()方法查询的)

Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.age as age2_1_0_,
        user0_.password as password3_1_0_,
        user0_.username as username4_1_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.age as age2_1_0_,
        user0_.password as password3_1_0_,
        user0_.username as username4_1_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        sys_user 
    set
        age=?,
        password=?,
        username=? 
    where
        id=?

查看日志:数据更新方式二 testUpdate2(),可以发现只查询了1次Select

Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.age as age2_1_0_,
        user0_.password as password3_1_0_,
        user0_.username as username4_1_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
Hibernate: 
    update
        sys_user 
    set
        age=?,
        password=?,
        username=? 
    where
        id=?

4、PagingAndSortingRepository 接口

该接口提供了两个方法,实现了分页和排序的功能了,该接口继承了 CrudRepository 接口。查看 PagingAndSortingRepository 接口源码:

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

1、创建Repository层接口,并且继承 PagingAndSortingRepository 接口

/**
 * PagingAndSortingRepository 接口:
 * 该接口提供了分页与排序的操作,注意:该接口继承了CrudRepository接口
 */
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {
}

2、测试代码

package com.example.jpa;

import com.example.jpa.entity.User;
import com.example.jpa.repository.UserPagingAndSortingRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

@SpringBootTest
public class UserPagingAndSortingTests {

    @Autowired
    private UserPagingAndSortingRepository userPagingAndSortingRepository;

    /**
     * 分页操作
     */
    @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);
    }

    /**
     * 排序操作:
     *  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);
    }

    /**
     * 分页 + 排序 处理
     *
     */
    @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);
    }
}

查看日志:分页操作 test1()

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ limit ?
Hibernate: 
    select
        count(user0_.id) as col_0_0_ 
    from
        sys_user user0_
总记录数: 4
当前第几页: 1
总页数: 2
当前页面的 List: [User(id=1, username=王小小, password=password, age=18), User(id=2, username=隔壁小王, password=password, age=18)]
当前页面的记录数: 2
User(id=1, username=王小小, password=password, age=18)
User(id=2, username=隔壁小王, password=password, age=18)

查看日志:排序操作 test2()

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    order by
        user0_.id desc
User(id=4, username=王小虎, password=123456, age=25)
User(id=3, username=赵小丽, password=666666, age=21)
User(id=2, username=隔壁小王, password=password, age=18)
User(id=1, username=王小小, password=password, age=18)
--------------华丽的分割线-------------------
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    order by
        user0_.age desc,
        user0_.id asc
User(id=4, username=王小虎, password=123456, age=25)
User(id=3, username=赵小丽, password=666666, age=21)
User(id=1, username=王小小, password=password, age=18)
User(id=2, username=隔壁小王, password=password, age=18)

查看日志:分页 + 排序操作 test3()

Hibernate: 
    select
        user0_.id as id1_0_,
        user0_.age as age2_0_,
        user0_.password as password3_0_,
        user0_.username as username4_0_ 
    from
        sys_user user0_ 
    order by
        user0_.age desc,
        user0_.id asc limit ?
Hibernate: 
    select
        count(user0_.id) as col_0_0_ 
    from
        sys_user user0_
总记录数: 3
当前第几页: 1
总页数: 2
当前页面的记录数: 2
当前页面的 List: [User(id=6, username=王小虎, password=123456, age=25), User(id=5, username=赵小丽, password=666666, age=21)]
--------------华丽的分割线-------------------
User(id=6, username=王小虎, password=123456, age=25)
User(id=5, username=赵小丽, password=666666, age=21)

5、JpaRepository 接口

该接口继承了PagingAndSortingRepository。是我们开发时使用的最多的接口。其特点是可以帮助我们将其他接口的方法的返回值做适配处理。可以使得我们在开发时更方便的使用这些方法。同时也继承QueryByExampleExecutor接口,这是个用“实例”进行查询的接口,后续再写文章详细说明。

查看 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);// 根据实例查询并排序
}

1、创建Repository层接口,并且继承 JpaRepository 接口

/**
 * JpaRepository 接口
 * 该接口继承了PagingAndSortingRepository。是我们开发时使用的最多的接口。
 * 接口其特点是可以帮助我们将其他接口的方法的返回值做适配处理。可以使得我们在开发时更方便的使用这些方法。
 */
public interface UserJpaRepository extends JpaRepository<User,Long> {
}

2、测试代码

package com.example.jpa;

import com.example.jpa.entity.User;
import com.example.jpa.repository.UserJpaRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Sort;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@SpringBootTest
public class UserJpaRepositoryTests {

    @Autowired
    private UserJpaRepository userJpaRepository;

    /**
     * 这几个方法跟之前的接口内的方法虽然一样,但是最返回值做了自适应
     * 返回值都由 Iterable 变成了 List
     */
    @Test
    public void test1(){
        List<User> usersA = this.userJpaRepository.findAll();
        usersA.forEach(System.out::println);
        System.err.println("--------------华丽的分割线-------------------");
        List<User> usersB = this.userJpaRepository.findAll(Sort.by("age"));
        usersB.forEach(System.out::println);
        System.err.println("--------------华丽的分割线-------------------");
        List<User> usersC = this.userJpaRepository.findAllById(Arrays.asList(1L, 2L));
        usersC.forEach(System.out::println);

        System.err.println("--------------华丽的分割线-------------------");
        User user = new User();
        user.setUsername("隔壁小王");
        user.setPassword("password");
        user.setAge(18);
        List<User> users = this.userJpaRepository.saveAll(Arrays.asList(user));
        System.out.println(users);
    }

    /**
     * JpaRepository 接口内还新增了一方法
     * flush():强制写入数据库
     * saveAndFlush:保存并且立马写入数据库(不需要等事务走完就写入数据库)
     */
    @Test
    public void test2(){
        User user = new User();
        user.setUsername("隔壁小王");
        user.setPassword("password");
        user.setAge(18);
        // 立马写入数据库
        User saveAndFlush = this.userJpaRepository.saveAndFlush(user);
        System.out.println(saveAndFlush);
    }

    /**
     * 批量删除方法:deleteInBatch(Iterable<T> var1) 和 deleteAllInBatch()
     * 之前CrudRepository 接口内也有删除方法:delete、deleteById、deleteAll
     * 对比:deleteAll() 和 deleteAllInBatch() 区别:
     *     deleteAll()是删除全部,先findALL查找出来,再一条一条删除,最后提交事务
     *     deleteAllInBatch()是删除全部,一条sql。显然:deleteAllInBatch()方法效率更高
     *
     * 分别执行批量删除方法,查看日志对比.可以得出结论:
     *     在进行批量删除操作时尽量使用JpaRepository自带的批量删除方法deleteInBatch()及deleteAllInBatch()
     *
     * 可参考:https://jingyan.baidu.com/article/597a06431601fb312b52431c.html
     */
    @Test
    public void test3(){
        // 不推荐使用
        this.userJpaRepository.deleteAll();
    }
    @Test
    public void test4(){
        // 推荐使用
        this.userJpaRepository.deleteAllInBatch();
    }

    /**
     * getOne()与findById()方法总结与区别:
     *  1、getOne() 是延迟加载。findById() 是立马加载
     *  2、getOne()返回一个实体的引用,无结果会抛出异常。findById()返回一个Optional对象
     *  3、getOne()属于JpaRepository,底层调用getReference()方法,懒加载的模式,当调用对象时才会发送SQL
     *  4、findById()属于CrudRepository,底层调用了find()方法,当调用该方法的时就直接发送SQL
     */
    @Test
    public void test5(){
        // getOne()返回一个实体的引用,无结果会抛出异常
        User user = this.userJpaRepository.getOne(1L);
        System.out.println(user);
        // findById()返回一个Optional对象,如果Optional为空get()会抛异常
        Optional<User> optional = this.userJpaRepository.findById(1L);
        System.out.println(optional.orElse(null));
    }
}

查看日志:查询操作 test1() 方法

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_
User(id=1, username=王小小, password=password, age=18)
--------------华丽的分割线-------------------
User(id=2, username=隔壁小王, password=password, age=18)
User(id=3, username=赵小丽, password=666666, age=21)
User(id=4, username=王小虎, password=123456, age=25)
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    order by
        user0_.age asc
User(id=1, username=王小小, password=password, age=18)
User(id=2, username=隔壁小王, password=password, age=18)
User(id=3, username=赵小丽, password=666666, age=21)
User(id=4, username=王小虎, password=123456, age=25)
--------------华丽的分割线-------------------
Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.id in (
            ? , ?
        )
User(id=1, username=王小小, password=password, age=18)
User(id=2, username=隔壁小王, password=password, age=18)
--------------华丽的分割线-------------------
Hibernate: 
    insert 
    into
        sys_user
        (age, password, username) 
    values
        (?, ?, ?)
[User(id=5, username=隔壁小王, password=password, age=18)]

查看日志:新增操作 test2() 方法

Hibernate: 
    insert 
    into
        sys_user
        (age, password, username) 
    values
        (?, ?, ?)
User(id=6, username=隔壁小王, password=password, age=18)

查看日志:批量删除操作 test3() 方法

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_
Hibernate: 
    delete 
    from
        sys_user 
    where
        id=?
Hibernate: 
    delete 
    from
        sys_user 
    where
        id=?
Hibernate: 
    delete 
    from
        sys_user 
    where
        id=?
...

查看日志:批量删除操作 test4() 方法(推荐使用)

Hibernate: 
    delete 
    from
        sys_user

总结对比:deleteAll() 和 deleteAllInBatch() 方法的区别:

  1. deleteAll():是删除全部,先findAll()查找出来,再一条一条删除,最后提交事务
  2. deleteAllInBatch():是删除全部,不过一条sql。显然:deleteAllInBatch() 方法效率更高
  3. 在进行批量删除操作时尽量使用JpaRepository自带的批量删除方法:deleteInBatch() 及 deleteAllInBatch()
  4. 可参考:https://jingyan.baidu.com/article/597a06431601fb312b52431c.html

查看日志:test5(),getOne() 可能会报错:org.hibernate.LazyInitializationException: could not initialize proxy XXX - no Session

org.hibernate.LazyInitializationException: could not initialize proxy [com.example.jpa.entity.User#4] - no Session

	at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:170)
	at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:310)
	at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
	at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
	at com.example.jpa.entity.User$HibernateProxy$7rZgySgi.toString(Unknown Source)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at com.example.jpa.UserJpaRepositoryTests.test5(UserJpaRepositoryTests.java:95)

原因:没有开启懒加载。解决方案如下:问题解决参考:https://www.gaoyaxuan.net/blog/229.html

方案一:在出问题的实体类上加:@Proxy(lazy = false)

// 默认懒加载,false为急加载
@Proxy(lazy = false)
public class User {}

方案二:、application.properties 文件中开启延迟加载的配置

# 开启延迟加载,这个配置的意思就是在没有事务的情况下允许懒加载
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

6、JpaSpecificationExecutor 接口

JpaSpecificationExecutor 接口不能单独使用,需要配合着 JPA 的其他接口一起使用

该接口主要是提供了多条件查询(动态查询),并且可以在查询中添加排序与分页。查看 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);// 统计总数
}

Specification 是我们传入进去的查询参数,实际上它是一个接口,并且只有一个方法:

public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

1、创建Repository层接口,除了继承JpaSpecificationExecutor,还需要继承 JPA 其他接口

/**
 * JpaSpecificationExecutor 接口
 * 该接口主要是提供了多条件查询的支持,并且可以在查询中添加排序与分页。
 * 注意:JpaSpecificationExecutor不能单独使用,需要配合着jpa中的其他接口一起使用
 */
public interface UserJpaSpecificationExecutor extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}

2、下面测试类实战操作,先准备测试数据

    @Autowired
    private UserJpaRepository userJpaRepository;
    @BeforeEach
    public void initData(){
        // 保存多条数据
        User userA = new User();
        userA.setUsername("隔壁小王");
        userA.setPassword("password");
        userA.setAge(18);
        User userB = new User();
        userB.setUsername("赵小丽");
        userB.setPassword("666666");
        userB.setAge(21);
        User userC = new User();
        userC.setUsername("王小虎");
        userC.setPassword("123456");
        userC.setAge(25);
        this.userJpaRepository.saveAll(Arrays.asList(userA, userB, userC)).forEach(System.out::println);
    }

1、条件查询

package com.example.jpa;

import com.example.jpa.entity.User;
import com.example.jpa.repository.UserJpaSpecificationExecutor;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@SpringBootTest
public class UserSpecificationExecutorTests {

    @Autowired
    private UserJpaSpecificationExecutor userJpaSpecificationExecutor;

    /**
     * 条件查询——单条件
     * 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 = '隔壁小王' and password = 'password' and age < 30
     * sql:select * from sys_user where username = '隔壁小王' and password = 'password' and age < 30
     */
    @Test
    public void test2(){
        Specification<User> spec = (root, criteriaQuery, criteriaBuilder) -> {
            Predicate username = criteriaBuilder.equal(root.get("username"), "隔壁小王");
            Predicate password = criteriaBuilder.equal(root.get("password"), "password");
            Predicate age = criteriaBuilder.lt(root.get("age"), 30);
            List<Predicate> list = Arrays.asList(username, password, age);
            return criteriaBuilder.and(list.toArray(new 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);
    }
}

查看日志:test1()

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username=?
User(id=1, username=隔壁小王, password=password, age=18)

查看日志:test2()

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username=? 
        and user0_.password=? 
        and user0_.age<30
User(id=1, username=隔壁小王, password=password, age=18)

查看日志:test3()

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        (
            user0_.username like ?
        ) 
        and user0_.password=? 
        and user0_.age<30
User(id=1, username=隔壁小王, password=password, age=18)

2、分页查询

    /**
     * 分页操作
     */
    @Test
    public void testPageable(){
        Specification<User> spec = (root, criteriaQuery, criteriaBuilder) -> {
            Predicate username = criteriaBuilder.equal(root.get("username"), "隔壁小王");
            Predicate password = criteriaBuilder.equal(root.get("password"), "password");
            return criteriaBuilder.and(username, password);
        };
        //分页
        Pageable pageable = PageRequest.of(0, 2);
        Page<User> page = this.userJpaSpecificationExecutor.findAll(spec, 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);
    }

查看日志:

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username=? 
        and user0_.password=? limit ?
总记录数: 1
当前第几页: 1
总页数: 1
当前页面的 List: [User(id=1, username=隔壁小王, password=password, age=18)]
当前页面的记录数: 1
User(id=1, username=隔壁小王, password=password, age=18)

3、排序查询

    /**
     * 排序操作
     */
    @Test
    public void testSort(){
        Specification<User> spec = (root, criteriaQuery, criteriaBuilder) -> {
            Predicate username = criteriaBuilder.equal(root.get("username"), "隔壁小王");
            Predicate password = criteriaBuilder.equal(root.get("password"), "password");
            return criteriaBuilder.and(username, password);
        };
        // 排序
        Sort sort = Sort.by(Sort.Direction.DESC,"id");
        List<User> users = this.userJpaSpecificationExecutor.findAll(spec, sort);
        users.forEach(System.out::println);
    }

查看日志:

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username=? 
        and user0_.password=? 
    order by
        user0_.id desc
User(id=1, username=隔壁小王, password=password, age=18)

4、分页 + 排序查询

    /**
     * 分页+排序 操作
     */
    @Test
    public void testPageableAndSort(){
        Specification<User> spec = (root, criteriaQuery, criteriaBuilder) -> {
            Predicate username = criteriaBuilder.equal(root.get("username"), "隔壁小王");
            Predicate password = criteriaBuilder.equal(root.get("password"), "password");
            return criteriaBuilder.and(username, password);
        };
        // 排序
        Sort sort = Sort.by(Sort.Direction.DESC,"id");
        // 分页
        Pageable pageable = PageRequest.of(0, 2, sort);
        Page<User> page = this.userJpaSpecificationExecutor.findAll(spec, 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);
    }

查看日志:

Hibernate: 
    select
        user0_.id as id1_1_,
        user0_.age as age2_1_,
        user0_.password as password3_1_,
        user0_.username as username4_1_ 
    from
        sys_user user0_ 
    where
        user0_.username=? 
        and user0_.password=? 
    order by
        user0_.id desc limit ?
总记录数: 1
当前第几页: 1
总页数: 1
当前页面的 List: [User(id=1, username=隔壁小王, password=password, age=18)]
当前页面的记录数: 1
User(id=1, username=隔壁小王, password=password, age=18)

5、多表查询(使用 单向多对一 模式)

/**
 * 多方外键表
 * 单向多对一模式
 */
@Data
@Entity
@Table(name = "sys_address")
public class UserAddress {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "address_id")
    private Long addressId;
    private String country;
    private String city;

    @ManyToOne
    @JoinColumn(name = "user_id", referencedColumnName = "id")
    private User user;
}

/**
 * 一方主键表
 */
@Data
@Entity
@Table(name = "sys_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private Integer age;
}

测试代码:

    /**
     * Join代表链接查询,通过root对象获取
     * 创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
     * JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
     * 使用多表关联后,Join 就相当有了 root 的功能,可以join.get("对象属性名称")
     * 注意!注意!注意:
     *     Root:代表的是UserAddress表,所以通过root.get()只能获取Many表的属性或字段.
     *     join:代表的是One表,虽然是关联查询,但它只代表关联的One表,只能获取One的属性
     *     如果root/join获取自己表以外的属性或字段会报如下错:
     *     Unable to locate Attribute  with the the given name [categoryType] on this ManagedType[XXX]
     *     如果有第三张表关联使用join.join()往下传递即可。
     */
    @Test
    public void testFindJoinTable(){
        // 1.初始化数据
        User user = new User();
        user.setUsername("老王");
        user.setPassword("000000");
        user.setAge(19);
        userJpaSpecificationExecutor.save(user);
        UserAddress userAddressA = new UserAddress();
        userAddressA.setCountry("中国");
        userAddressA.setCity("广州");
        userAddressA.setUser(user);
        UserAddress userAddressB = new UserAddress();
        userAddressB.setCountry("中国");
        userAddressB.setCity("深圳");
        userAddressB.setUser(user);
        userAddressJpaSpecificationExecutor.saveAll(Arrays.asList(userAddressA,userAddressB));

        // 2.联表复杂查询
        Specification<UserAddress> spec = (root, criteriaQuery, criteriaBuilder) -> {
            Join<UserAddress, User> join = root.join("user", JoinType.INNER);
            Path<String> username = join.get("username");
            Path<Object> city = root.get("city");
            // Path<Object> city 由于Path没有指定类型,所以criteriaBuilder.like(city.as(String.class)这里指定了as(String.class)
            return criteriaBuilder.like(city.as(String.class), "广州");
        };
        userAddressJpaSpecificationExecutor.findAll(spec).forEach(System.out::println);
    }

查看日志:

Hibernate: 
    insert 
    into
        sys_user
        (age, password, username) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        sys_address
        (city, country, user_id) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        sys_address
        (city, country, user_id) 
    values
        (?, ?, ?)
Hibernate: 
    select
        useraddres0_.address_id as address_1_0_,
        useraddres0_.city as city2_0_,
        useraddres0_.country as country3_0_,
        useraddres0_.user_id as user_id4_0_ 
    from
        sys_address useraddres0_ 
    inner join
        sys_user user1_ 
            on useraddres0_.user_id=user1_.id 
    where
        useraddres0_.city like ?
Hibernate: 
    select
        user0_.id as id1_1_0_,
        user0_.age as age2_1_0_,
        user0_.password as password3_1_0_,
        user0_.username as username4_1_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
UserAddress(addressId=1, country=中国, city=广州, user=User(id=1, username=老王, password=000000, age=19))

PS 注意(重点):

可以发现 Specification 重写方法里面的rootcriteriaQuerybuilder都已经被JPA赋值好了,我们只需要关注Predicate的构建,也就是说在调用JpaSpecificationExecutor接口内的findXXX方法只能完成where条件的构建,而不能实现select后面属性的选择和groupBy的构建。

5、投影和分组查询

虽然JpaSpecificationExecutor无法实现select后面属性的选择和groupBy的构建,不过可以直接使用Criteria方式来完成。

1、创建接口实现代码类(为了简便没有写接口了,直接写了类)(主要关注@PersistenceContext注入EntityManager对象)

package com.example.jpa.repository;

import com.example.jpa.entity.User;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Tuple;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.util.List;

@Repository
public class UserCriteriaRepository {
    @PersistenceContext
    private EntityManager entityManager;

    /**
     * 投影查询 — criteriaBuilder.createTupleQuery()
     * sql:select username, age from sys_user
     * 注意:
     *   使用:返回元组(Tuple)的查询
     */
    public List<Tuple> testFindAllMultiSelect(){
        // 1.CriteriaBuilder 安全查询创建工厂
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        // 2.CriteriaQuery 安全查询主语句
        CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
        // 3.Root 定义查询的From子句中能出现的类型
        Root<User> from = criteriaQuery.from(User.class);

        // 需要给字段取别名,否则无法通过tuple.get(field)获取数据
        criteriaQuery.multiselect(from.get("username").alias("username"), from.get("age").alias("age"));
        TypedQuery<Tuple> query = entityManager.createQuery(criteriaQuery);
        List<Tuple> list = query.getResultList();
        return list;
    }

    /**
     * 分组查询
     * sql:select username,count(username),max(age),min(age),sum(age),avg(age) from sys_user group by username
     */
    public List<Tuple> testFindGroupBy() {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
        Root<User> from = criteriaQuery.from(User.class);

        // .alias("name") 取别名
        criteriaQuery.multiselect(
                from.get("username").alias("username"),
                criteriaBuilder.count(from.get("username")).alias("count"),
                criteriaBuilder.max(from.get("age")).alias("max"),
                criteriaBuilder.min(from.get("age")).alias("min"),
                criteriaBuilder.sum(from.get("age")).alias("sum"),
                criteriaBuilder.avg(from.get("age")).alias("avg"));
        criteriaQuery.groupBy(from.get("username"));
        // criteriaQuery.having(criteriaBuilder.disjunction());

        List<Tuple> tupleList = entityManager.createQuery(criteriaQuery).getResultList();
        return tupleList;
    }
}

2、测试代码:

package com.example.jpa;

import com.example.jpa.entity.User;
import com.example.jpa.repository.UserCriteriaRepository;
import com.example.jpa.repository.UserJpaRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.persistence.Tuple;
import java.util.Arrays;
import java.util.List;

@SpringBootTest
public class UserCriteriaRepositoryTests {

    @Autowired
    private UserJpaRepository userJpaRepository;
    @BeforeEach
    public void initData(){
        // 保存多条数据
        User userA = new User();
        userA.setUsername("隔壁小王");
        userA.setPassword("password");
        userA.setAge(18);
        User userB = new User();
        userB.setUsername("赵小丽");
        userB.setPassword("666666");
        userB.setAge(21);
        User userC = new User();
        userC.setUsername("王小虎");
        userC.setPassword("123456");
        userC.setAge(25);
        this.userJpaRepository.saveAll(Arrays.asList(userA, userB, userC)).forEach(System.out::println);
    }

    @Autowired
    private UserCriteriaRepository userCriteriaRepository;

    @Test
    public void testFindAllMultiSelect(){
        List<Tuple> tuples = userCriteriaRepository.testFindAllMultiSelect();
        // name = list.get(0).get(0); age = list.get(0).get(1)
        tuples.forEach(x-> System.out.println(x.get("username")+"、"+x.get("age")));
    }

    @Test
    public void testFindGroupBy(){
        List<Tuple> tuples = userCriteriaRepository.testFindGroupBy();
        tuples.forEach(x-> System.out.println(x.get("username") + "、" +
                        x.get("count") + "、" +
                        x.get("max") + "、" +
                        x.get("min") + "、" +
                        x.get("sum") + "、" +
                        x.get("avg"))
                );
    }
}

3、查看日志:投影查询

Hibernate: 
    select
        user0_.username as col_0_0_,
        user0_.age as col_1_0_ 
    from
        sys_user user0_
隔壁小王、18
赵小丽、21
王小虎、25

4、查看日志:分组查询

Hibernate: 
    select
        user0_.username as col_0_0_,
        count(user0_.username) as col_1_0_,
        max(user0_.age) as col_2_0_,
        min(user0_.age) as col_3_0_,
        sum(user0_.age) as col_4_0_,
        avg(user0_.age) as col_5_0_ 
    from
        sys_user user0_ 
    group by
        user0_.username
王小虎、1、25、25、25、25.0
赵小丽、1、21、21、21、21.0
隔壁小王、1、18、18、18、18.0

所以如果想投影查询和分组查询的话还得使用Criteria方式来查询。

一般的复杂或者连表复杂查询使用CriteriaBuilder、CriteriaQuery、Root 来完成查询。如果不需要构建Selection、groupBy时,也可以只构建Predicate,然后使用JPA的findAll()方法即可。

7、Repository 自定义接口

有时JPA接口提供的方法并不满足我们的需求,这时我们可以自定义Repository来扩展。主要通过@PersistenceContext 获取 EntityManager 对象来实现

1、定义一个普通接口

/**
 * 自定义Repository接口
 */
public interface UserCustomRepository {
    User findUserById(Long id);
}

2、创建接口实现类(使用 @PersistenceContext 注入 EntityManager 对象)

public class UserCustomRepositoryImpl implements UserCustomRepository {
    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public User findUserById(Long id) {
        return entityManager.find(User.class,id);
    }
}

3、使用接口

/**
 * 用户自定义Repository接口讲解
 */
public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User>, UserCustomRepository{
}

4、测试代码

@SpringBootTest
public class UserCustomRepositoryTests {
    @Autowired
    private UserDao userDao;
    @Test
    public void testFind(){
        System.out.println(userDao.findUserById(6L));
    }
}

5、查看日志

Hibernate: 
    select
        user0_.id as id1_0_0_,
        user0_.age as age2_0_0_,
        user0_.password as password3_0_0_,
        user0_.username as username4_0_0_ 
    from
        sys_user user0_ 
    where
        user0_.id=?
User(id=2, username=王小虎, password=123456, age=25)
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值