Spring Boot整合Spring Data JPA


1 Spring Boot整合Spring Data JPA环境的搭建和测试

1.1 创建项目配置pom文件

  1. Spring Boot整合Spring Data JPA时需要添加一下依赖包

    <dependencies>
    <!-- 导入springboot的启动器-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 导入mybatis的启动器 -->
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.2.0</version>
    </dependency>
    <!-- 导入springboot整合spring Data JPA的启动器-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <!-- mysql数据库 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- mysql的连接池 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>${druid.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
    </dependency>

  2. 该项目需要有一个父工程,该工程打包方式以pom形式打包,整合了测试所需要的的依赖包。

1.2 创建application.properties文件

注意:该文件这里主要配置数据库的连接,Jpa的一些属性

  1. 配置数据库的连接;
  2. 配置正向工程和sql语句的显示。
  3. 配置文件如下:

        # 数据库的连接
        spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
        spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8
        spring.datasource.username=root
        spring.datasource.password=123456
        # 配置数据库连接池
        spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    
        # Spring Data JPA的配置文件
        # 开启正向工程
        spring.jpa.hibernate.ddl-auto=update
        # 显示sql语句
        spring.jpa.show-sql=true  

1.3 创建实体类Users

        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 10:13
         * @Table(name = "t_users"):指明正向工程执行时在数据库中创建的数据表名称
         * @description:TODO
         */
        @Entity
        @Table(name = "t_users")
        public class Users {
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            @Column(name = "id")
            private Integer id;
            @Column(name = "name")
            private String name;
            @Column(name = "age")
            private Integer age;
            @Column(name = "address")
            private String address;

            public Integer getId() {
                return id;
            }

            public void setId(Integer id) {
                this.id = id;
            }

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }

            public Integer getAge() {
                return age;
            }

            public void setAge(Integer age) {
                this.age = age;
            }

            public String getAddress() {
                return address;
            }

            public void setAddress(String address) {
                this.address = address;
            }
        }

1.4 创建dao接口

    /**
     * @author fyzn12
     * @version 1.0
     * @date 2020/7/18 10:20
     * 参数一 T :当前需要映射的实体
     * 参数二 ID :当前映射的实体中的OID的类型
     * @description:TODO
     */
    public interface UsersRepository  extends JpaRepository<Users,Integer> {  

    }  

    注意:这里使用JpaRepository接口,里面的方法我们都不需要编写,可以直接调用  

1.5 编写启动器

        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 10:12
         * @description:TODO
         */
        @SpringBootApplication
        public class App {
            public static void main(String[] args) {
                SpringApplication.run(App.class,args);
            }
        }

1.6 编写测试方法

        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 10:22
         * @description:TODO
         */
        @RunWith(SpringJUnit4ClassRunner.class)
        @SpringBootTest(classes = App.class)
        public class UsersTest {
            @Autowired
            private UsersRepository usersRepository;
            @Test
            public void testSave(){
                Users users = new Users();
                users.setAddress("天津");
                users.setAge(20);
                users.setName("张三");
                this.usersRepository.save(users);
            }


        }  

1.7 运行测试方法效果图如下

1.8 整合需要注意的点

  • 依赖包
  • application.properties文件的编写


2 Spring Data JPA的提供的核心接口

  1. Repository接口
  2. CurdRepository接口
  3. PagingAndSortingRepository接口
  4. JpaRepository接口
  5. JPASpecificationExecutor接口


2.1 Repository接口的使用

该接口提供了两种方式

  • 提供了方法名称命名查询方式
  • 提供了基于@Query注解查询与更新

2.1.1 方法名称命名查询方式

1 创建接口dao
        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 11:01
         * @description:演示方法名称命名查询
         */
        public interface UsersRepositoryByName extends Repository<Users,Integer> {
            /**
             * 方法名称必须遵循驼峰式命名规则
             * 方法名的构成:关键字findBy+属性名称(首字母大写)+查询条件(首字母大写)
             *
             * */
            List<Users> findByName(String name);
            /**
            * 多条件查询
            * */
            List<Users> findByNameAndAge(String name,Integer age);
            /**
             * 模糊查询
             * 
             * */
            List<Users> findByNameLike(String name);


        } 
2 编写测试代码
        /**
         * 方法名称命名测试
         * */
        @Test
        public void testFinByName(){
            List<Users> list = this.usersRepositoryByName.findByName("张三");
            for (Users users:list) {
                System.out.println(users);
            }
        }
        /**
         * 方法名称命名测试
         * */
        @Test
        public void testFinByNameAndAge(){
            List<Users> list = this.usersRepositoryByName.findByNameAndAge("张三",20);
            for (Users users:list) {
                System.out.println(users);
            }
        }
        /**
         * 方法名称命名测试
         * */
        @Test
        public void testFinByNameLike(){
            List<Users> list = this.usersRepositoryByName.findByNameLike("张%");
            for (Users users:list) {
                System.out.println(users);
            }
        }  
3 运行结果显示
4 从上面图中可以看到SQL语句符合设定方法的查询条件,对方法名称命名查询做出以下总结
  • 方法名称必须遵循驼峰式命名规则
  • 方法名的构成:关键字findBy+属性名称(首字母大写)+查询条件(首字母大写)

2.1.2 基于@Query注解查询与更新

1. 创建dao接口
        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 15:53
         * @description:Repository的Query注解的使用
         */
        public interface UsersRepositoryQueryAnnotation extends Repository<Users,Integer> {
            /**
             * 使用HQL
             * 这里注意几点:在低版本的spring boot中在HQL语句后面不需要加上版本号,
             * 但是在Spring Boot2.x之后的版本在使用@Query注解时需要加上版本号
             * */
            @Query("from Users where name=?1")
            List<Users> list(String name);
            /**
             * 使用标准的SQL语句
             * */
            @Query(value="select * from t_users where name=?1",nativeQuery=true)
            List<Users> queryByNameUserSQL(String name);  

            /**
             * @Query注解用于更新操作
             * @Modifying:需要执行一个更新操作
             * */
            @Query("update Users set name =?1 where id=?1")
            @Modifying
            void updateUsersById(String name,Integer id);

        }  
2. 创建测试方法
             /**
             * @Query注解的测试1
             * */
            @Test
            public void testQuery(){
                List<Users> list = this.usersRepositoryQueryAnnotation.list("张三");
                for (Users users:list) {
                    System.out.println(users);
                }
            }
            /**
             * @Query注解的测试2
             * */
            @Test
            public void testQueryByNameSQL(){
                List<Users> list = this.usersRepositoryQueryAnnotation.queryByNameUserSQL("张三");
                for (Users users:list) {
                    System.out.println(users);
                }
            }  


            /**
             * @Query注解的用于更新测试1
             * */
            @Test
            public void testQueryUpdate(){
               this.usersRepositoryQueryAnnotation.updateUsersById("张三三",1);
            }  
3. 运行截图如下
4. 执行更新时遇到的bug和解决方案
  1. 首先看一下运行测试方法时遇到的问题

分析一下这个bug:在对数据库表进行更新删除操作时,要求操作必须在一个事务当中,而在我们的dao接口和测试类中,很明显我们并没有开启事务操作,在测试方法上面加上事务注解即可
5 总结:使用@Query注解能解决多条件查询时方法名的命名长度问题,但是得注意HQL语句会随着使用的Spring Boot的版本高低有所区别
  • @Test注解和@Transactional注解一起使用时,默认是自动回滚的,因此上面看到提示成功,但是数据库里的数据并没有修改
  • 在测试方法上面加上一个注解@Rollback

            /**
             * @Query注解的用于更新测试1
             * */
            @Test
            @Transactional
            @Rollback(false)
            public void testQueryUpdate(){
               this.usersRepositoryQueryAnnotation.updateUsersById("张三三",1);
            }  

2.2 CurdRepository接口的使用

2.2.1 CurdRepository接口的简单介绍

  • CurdRepository接口继承了Repository接口
  • Repository接口的所有方法他都可以使用
  • 作用:实现增删改查的操作
  • 该接口实现的方法如下图

1. 对CurdRepository的save方法源码进行解析如下图所示
  • 查看该源码知道,对一个事务进行保存和更新操作时,首先会进行判断数据库中是否存在这一条数据,如果存在,则判断此次事务为update操作,不存在则是insert操作,下面进行测试 ;
  • 编写dao接口如下

        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 17:56
         * @description:TODO
         */
        public interface UsersCurdRepository extends CrudRepository<Users,Integer> {
        }  
  • 编写测试save方法如下

     @Test
    public void testCurdRepositorySave(){
    
        Users users = new Users();
        users.setName("李四");
        users.setAge(22);
        users.setAddress("天津");
        this.usersCurdRepository.save(users);
    
    }  
  • 代码执行如下图所示

查看运行图可知,该save执行的insert操作。
2. 接下来测试update如下
  • 编写测试update方法如下

        @Test
        public void testCurdRepositoryUpdate(){
    
            /*
            * 这里得注意,对于CurdRepository的更新操作,也是save操作
            * */
            Users users = new Users();
            users.setId(2);
            users.setAddress("北京");
        }   
  • 方法测试运行图如下图所示

查看运行图,SQL语句执行过程中,先执行了一次select操作,判断数据库中是否存在该数据,第二次执行update操作,这个也是使用CurdRepository执行更新操作需要注意的一点。

2.3 PagingAndSortingRepository接口的使用

2.3.1 PagingAndSortingRepository接口的简单介绍

  • 该接口继承了CurdRepository接口
  • CurdRepository接口的方法该接口也可以使用
  • 该接口提供对查询所有数据进行排序的功能
  • 该接口提供对所有数据进行分页功能

如图所示

2.3.2 PagingAndSortingRepository接口中排序功能的测试

1. 编写dao接口
        /**
         * @author fyzn12
         * @version 1.0
         * @date 2020/7/18 18:30
         * @description:TODO
         */
        public interface UserPagingAndSortingRepository  extends PagingAndSortingRepository<Users,Integer>{
        }
2. 编写测试方法
        @Test
        public void testPaginAndSprtingRepositorySort(){
            //定义排序规则
            Order order = new Order(Direction.DESC,"id");
            /**
             * SpringBoot 2.2.1之后,Sort的构造方法变成私有了,不能通过构造方法实例化
             *
             * */
            //Sort sort = new Sort(order);
            //Sort对象封装了排序规则
            List<Users> list = (List<Users>)this.userPagingAndSortingRepository.findAll(Sort.by(order));
            for (Users users:list) {
                System.out.println(users);
            }

        }    
3. 测试方法执行如下

2.3.3 PagingAndSortingRepository接口中分页功能的演示

1. 编写dao接口接口和排序接口一样
2. 编写测试方法
        @Test
        public void testPaginAndSprtingRepositoryPage(){
            //Pageable封装了分页的参数:当前页,每页显示的条数
            //注意:当前页是从0开始(切记)
            //SpringBoot 2.2.1之后不能通过new PageRequest()获取对象
            Pageable pageable = PageRequest.of(0,1);
            Page<Users> page = this.userPagingAndSortingRepository.findAll(pageable);
            List<Users> list = page.getContent();

            System.out.println("总共有:"+page.getTotalElements()+"条数据");
            System.out.println("共有:"+page.getTotalPages()+"页");
            System.out.println("当前是第 "+1+"页");

            for (Users users:list) {
                System.out.println(users);
            }

        }    
3. 测试方法执行如下

2.4 JpaRepository接口的使用

2.4.1 JpaRepository接口的简单介绍

  • 该接口继承了PagingAndSortingRepository
  • 对继承的父接口的方法做了返回值的适配(也就是返回值不需要进行强制转化)
  • 该接口在使用中更加适用于实际开发

2.4.2 JpaRepository接口的功能与上面PagingAndSortingRepository接口的功能相同,唯一优化的就是对父接口的返回值做了适配,这里就不在继续介绍

2.5 JPASpecificationExecutor接口的使用

2.5.1 JpaSpecificationExecutor接口的简单介绍

  • 该接口主要提供了多条件查询的支持。并且可以在查询中添加分页与排序(PagingAndSortingRepository接口只能对所有数据进行排序和分页)
  • 该接口是单独出现的,并没有继承上面的四个接口,是完全独立的。一般开发中是使用该接口与JpaRepository一起使用。很少单独使用该接口
  • 该接口提供的方法

2.5.2 JpaSpecificationExecutor接口的单条件查询测试

1. 编写dao接口(下面测试接口相同将不再重写)
    /**
     * @author fyzn12
     * @version 1.0
     * @date 2020/7/18 19:41
     * @description:TODO
     */
    public interface UsersJPASpecificationExecutor extends JpaSpecificationExecutor<Users> , JpaRepository<Users,Integer> {
    }   
2. 编写测试方法
        /**
         * JpaSpecificationExecutor   单条件测试
         *
         * **/
        @Test
        public void testJpaSpecificationExecutor1(){
            /**
             * Specification:用于封装查询条件
             *
             * */
            Specification<Users>  spec = new Specification<Users>() {
                @Override
                public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    /**
                     * Predicate:封装了单个查询条件,一个Predicate就是一个查询条件
                     * Root<Users> root:查询对象属性的封装
                     * CriteriaQuery<?> criteriaQuery:封装了要执行的查询中的各个部分的信息如select、form、order by
                     * CriteriaBuilder criteriaBuilder:查询条件的构造器,定义不同的查询条件
                     * */
                    //如现在要查询where name = "张三"
                    /**
                     * 参数一:查询的条件属性
                     * 参数二:条件的值
                     *
                     * */
                    Predicate predicate = criteriaBuilder.equal(root.get("name"), "张三");
                    return predicate;
                }
            };
            List<Users> list = this.usersJPASpecificationExecutor.findAll(spec);
            for (Users users:list) {
                System.out.println(users);
            }

        }
3. 测试方法运行如下

2.5.3 JpaSpecificationExecutor接口的多条件查询测试

1. 编写dao接口
2. 编写测试方法
        /**
         * JpaSpecificationExecutor   多条件测试
         *
         * **/
        @Test
        public void testJpaSpecificationExecutor2(){
            /**
             * Specification:用于封装查询条件
             *
             * */
            Specification<Users>  spec = new Specification<Users>() {
                @Override
                public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    /**
                     * Predicate:封装了单个查询条件,一个Predicate就是一个查询条件
                     * Root<Users> root:查询对象属性的封装
                     * CriteriaQuery<?> criteriaQuery:封装了要执行的查询中的各个部分的信息如select、form、order by
                     * CriteriaBuilder criteriaBuilder:查询条件的构造器,定义不同的查询条件
                     * */
                    //如现在要查询where name = "张三" and age =20
                    /**
                     * 参数一:查询的条件属性
                     * 参数二:条件的值
                     *
                     * */
                    return criteriaBuilder.and(criteriaBuilder.equal(root.get("name"),"张三"),criteriaBuilder.equal(root.get("age"),20));
                }
            };
            List<Users> list = this.usersJPASpecificationExecutor.findAll(spec);
            for (Users users:list) {
                System.out.println(users);
            }

        }
        /**
         * JpaSpecificationExecutor   多条件测试
         *
         * **/
        @Test
        public void testJpaSpecificationExecutor3(){
            /**
             * Specification:用于封装查询条件
             *
             * */
            Specification<Users>  spec = new Specification<Users>() {
                @Override
                public Predicate toPredicate(Root<Users> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                    /**
                     * Predicate:封装了单个查询条件,一个Predicate就是一个查询条件
                     * Root<Users> root:查询对象属性的封装
                     * CriteriaQuery<?> criteriaQuery:封装了要执行的查询中的各个部分的信息如select、form、order by
                     * CriteriaBuilder criteriaBuilder:查询条件的构造器,定义不同的查询条件
                     * */
                    //如现在要查询where name = "张三" or age =20 or id=2
                    /**
                     * 参数一:查询的条件属性
                     * 参数二:条件的值
                     *
                     * */

                    return criteriaBuilder.or(criteriaBuilder.and(criteriaBuilder.equal(root.get("name"),"张三"),criteriaBuilder.equal(root.get("age"),20)),criteriaBuilder.equal(root.get("id"),2));
                }
            };
            List<Users> list = this.usersJPASpecificationExecutor.findAll(spec);
            for (Users users:list) {
                System.out.println(users);
            }

        }
3. 测试方法运行如下

2.5.4 JpaSpecificationExecutor接口的使用的总结

  1. 但条件查询时,注意每个参数的意义,在测试方法中做了详细的介绍,这里将不再继续介绍
  2. 多条件查询时需要注意criteriaBuilder对象的各个方法的使用对应于SQL语句中不同条件