SpringDataJpa学习

Spring Data Jpa 是应⽤于Dao层的⼀个框架,简化数据库开发的,作⽤和Mybatis框架⼀样,但是在使 ⽤⽅式和底层机制是有所不同的。最明显的⼀个特点,Spring Data Jpa 开发Dao的时候,很多场景我们 连sql语句都不需要开发。由Spring出品。


概述

  • 什么是Spring Data Jpa

    Spring Data JPA 是 Spring 基于JPA 规范的基础上封装的⼀套 JPA 应⽤框架,可使开发者⽤极简的 代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常⽤功能!学习并使⽤ Spring Data JPA 可以极⼤提⾼开发效率。

    说明:Spring Data JPA 极⼤简化了数据访问层代码。

    如何简化呢? 使⽤了Spring Data JPA,我们Dao层中只需要写接⼝,不需要写实现类,就⾃动具有 了增删改查、分⻚查询等⽅法。

    使⽤Spring Data JPA 很多场景下不需要我们⾃⼰写sql语句

  • Spring Data家族

Spring Data JPA,JPA规范和Hibernate之间的关系

Spring Data JPA 是 Spring 提供的⼀个封装了JPA 操作的框架,⽽ JPA 仅仅是规范,单独使⽤规范⽆法 具体做什么,那么Spring Data JPA 、 JPA规范 以及 Hibernate (JPA 规范的⼀种实现)之间的关系是什 么?如下图:

JPA 是⼀套规范,内部是由接⼝和抽象类组成的,Hiberanate 是⼀套成熟的 ORM 框架,⽽且 Hiberanate 实现了 JPA 规范,所以可以称 Hiberanate 为 JPA 的⼀种实现⽅式,我们使⽤ JPA 的 API 编 程,意味着站在更⾼的⻆度去看待问题(⾯向接⼝编程)。

Spring Data JPA 是 Spring 提供的⼀套对 JPA 操作更加⾼级的封装,是在 JPA 规范下的专⻔⽤来进⾏数 据持久化的解决⽅案。

应⽤

  • 需求:使⽤ Spring Data JPA 完成对 tb_resume 表(简历表)的Dao 层操作(增删改查,排序, 分⻚等)

  • 数据表设计

开发步骤梳理

  • 构建工程

    • 创建⼯程导⼊坐标(Java框架于我们⽽⾔就是⼀堆jar)

    • 配置 Spring 的配置⽂件(配置指定框架执⾏的细节)

    • 编写实体类 Resume,使⽤ JPA 注解配置映射关系

    • 编写⼀个符合 Spring Data JPA 的 Dao 层接⼝(ResumeDao接⼝)

Spring Data JPA 开发实现

  • maven

    <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
            <!--spring-data-jpa相关jar包-->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-jpa</artifactId>
                <version>2.3.1.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>javax.el</groupId>
                <artifactId>javax.el-api</artifactId>
                <version>3.0.1-b04</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.web</groupId>
                <artifactId>javax.el</artifactId>
                <version>2.2.6</version>
            </dependency>
            <!--spring相关jar包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>5.3.20</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.7</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.20</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>5.3.20</version>
            </dependency>
            <!--spring对orm框架的支持包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>5.3.20</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>5.3.20</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>5.3.20</version>
            </dependency>
            <!--hibernate相关jar包-->
            <dependency>
                <groupId>org.hibernate.orm</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>6.1.4.Final</version>
            </dependency>
            <!--hibernate对jpa的实现-->
            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-entitymanager</artifactId>
                <version>5.6.7.Final</version>
            </dependency>
            <dependency>
                <groupId>org.hibernate.validator</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>6.0.20.Final</version>
            </dependency>
            <!--mysql-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.28</version>
            </dependency>
            <!--druid连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.6</version>
            </dependency>
            <!--test-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.3.20</version>
            </dependency>
        </dependencies>
  • 配置 Spring 的配置⽂件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:jpa="http://www.springframework.org/schema/data/jpa"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tool"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/data/jpa
           http://www.springframework.org/schema/data/jpa/spring-jpa.xsd 
              http://www.springframework.org/schema/tool 
              http://www.springframework.org/schema/tool/spring-tool.xsd">
    ​
        <!--对Spring和SpringDataJpa进行配置-->
    ​
        <!--1 创建数据库连接池druid-->
        <!--引⼊外部资源⽂件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
        <!--第三⽅jar中的bean定义在xml中-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    ​
        <!--2 配置jpa非常重要的一个对象,entityManagerFactory
        entityManager类似于mybatis中的SqlSession
        entityManagerFactory类似于Mybatis中的SqlSessionFactory-->
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <!--配置数据源-->
            <property name="dataSource" ref="dataSource"/>
            <!--配置包扫描-->
            <property name="packagesToScan" value="com.yun.demo.pojo"/>
            <!--指定jpa具体实现,也就是hibernate-->
            <property name="persistenceProvider">
                <bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
            </property>
            <!--指定jpa方言,不同的jpa方言实现对于类似于beginTransaction等细节实现起来是不一样的,
                                                        所以传入jpaDialect具体的实现类-->
            <property name="jpaDialect">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean>
            </property>
            <!--配置具体provider,hibernate框架的执行细节-->
            <property name="jpaVendorAdapter">
                <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                    <!--定义hibernate框架的一些细节-->
                    <!--配置数据库表是否自动创建(因为我们会建⽴pojo和数据表之间的映射关系
                            程序启动时,如果数据表还没有创建,是否要程序给创建⼀下)-->
                    <property name="generateDdl" value="false"/>
                    <!--指定数据库的类型(hibernate本身是个dao层框架,可以⽀持多种数据库类型
                                                的,这⾥就指定本次使⽤的什么数据库)-->
                    <property name="database" value="MYSQL"/>
                    <!--配置数据库的方言(hiberante可以帮助我们拼装sql语句,但是不同的数据库sql
                                            语法是不同的,所以需要我们注⼊具体的数据库⽅⾔)-->
                    <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
                    <!--是否显示sql(操作数据库时,是否打印sql)-->
                    <property name="showSql" value="true"/>
                </bean>
            </property>
        </bean>
    ​
        <!--3 引用上面创建的entityManagerFactory
                <jpa:repositories> 配置jpa的dao层细节
                base-package:指定dao层接⼝所在包-->
        <jpa:repositories base-package="com.yun.demo.dao" entity-manager-factory-ref="entityManagerFactory"
                          transaction-manager-ref="transactionManager"/>
    ​
        <!--4 事务管理器配置 (jdbcTemplate/mybatis 使⽤的是DataSourceTransactionManager
                                                jpa规范:JpaTransactionManager)-->
        <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
            <property name="entityManagerFactory" ref="entityManagerFactory"/>
        </bean>
        <!--5 声明式事务配置-->
    ​
        <!--6 配置spring包扫描-->
        <context:component-scan base-package="com.yun.demo"/>
    ​
    </beans>
  • resume实体

    /**
     * @author: Shaoyun_Wang
     * @create: 2022/11/24 16:46
     * @Description: 简历实体(在类中要使用注解建立实体类和数据表之间的映射关系以及属性和字段的映射关系)
     * 1,实体类和数据表映射关系
     * @Entity
     * @Table
     * 2,实体类属性和表字段的映射关系
     * @Id 标识主键
     * @GeneratedValue 标识主键的生成车策略
     * @Column 建立属性和字段映射
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @Entity
    @Table(name = "tb_resume")
    public class Resume {
    ​
        /**
         * GenerationType.SEQUENCE:依靠序列来产⽣主键 Oracle
         * GenerationType.IDENTITY:依赖数据库中主键⾃增功能 Mysql
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Long id;
    ​
        @Column(name = "address")
        private String address;
    ​
        @Column(name = "name")
        private String name;
    ​
        @Column(name = "phone")
        private String phone;
    ​
    }
  • ResumeDao 接⼝

    /**
     * @author: Shaoyun_Wang
     * @create: 2022/11/24 17:23
     * @Description:
     * 一个符合SpringDATaJpa要求的Dao层接口是需要继承 JpaRepository 和 JpaSpecificationExecutor
     * JpaRepository<操作的实体类类型,主键类型>封装了基本的CRUD操作
     * JpaSpecificationExecutor<操作的实体类类型>封装了复杂的查询(分⻚、排序等)
     */
    public interface ResumeDao extends JpaRepository<Resume, Long>, JpaSpecificationExecutor<Resume> {
    }
  • 调用继承的接口中的方法

    /**
     * @author: Shaoyun_Wang
     * @create: 2022/11/24 19:53
     * @Description: 调用继承的接口中的方法
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
    public class ResumeDaoTest {
    ​
        @Autowired
        private ResumeDao dao;
    ​
        //selectall
        @Test
        public void testFindAll(){
            List<Resume> all = dao.findAll();
            all.forEach(System.out::println);
    ​
        }
    ​
        //delete
        @Test
        public void testDeleteById(){
         dao.deleteById(1l);
    ​
        }
    ​
        //create&update
        @Test
        public void testSave(){
            //新增和更新都是用save(),没有主键信息的就是新增,有主键信息的就是更新
            Resume resume = new Resume();
            resume.setId(4l);
            resume.setAddress("西京");
            resume.setName("平安");
            resume.setPhone("1000010");
            Resume save = dao.save(resume);
            System.out.println(save);
        }
    ​
        //selectone
        @Test
        public void testFindOne(){
            Resume resume = new Resume();
            resume.setId(1l);
            Example<Resume> of = Example.of(resume);
            Resume resume1 = dao.findOne(of).get();
            System.out.println(resume1);
        }
    ​
        //selectone
        @Test
        public void testFindById(){
            Resume resume = dao.findById(1l).get();
            System.out.println(resume);
    ​
        }
    ​
    }
  • 引入Jpql语句进行查询===>jpql语句类似sql,只不过sql操作的是数据库表和字段,jpql操作的是对象和属性

    public interface ResumeDao extends JpaRepository<Resume, Long>, JpaSpecificationExecutor<Resume> {
    ​
        @Query("from Resume where id=?1")
        public Resume getByJpql(Long id);
    ​
        @Query("from Resume where id=?1 and name=?2")
        public List<Resume> getByJpqlList(Long id,String name);
    }
    ​
    @Test
    public void testjpqlLsit(){
        List<Resume> list = dao.getByJpqlList(1l, "刘二");
        list.forEach(System.out::println);
    }
    ​
    @Test
    public void testjpql(){
        Resume byJpql = dao.getByJpql(1l);
        System.out.println(byJpql);
    }
  • 引入原生的sql语句

    /**
     * 使用原生sql语句查询,需要将nativeQuery属性设置为true
     * @param name
     * @param address
     * @return
     */
    @Query(value = "select * from tb_resume where name like ?1 and address like ?2",nativeQuery = true)
    public List<Resume> getBySql(String name,String address);
    ​
    @Test
    public void testgetBySql(){
        List<Resume> bySql = dao.getBySql("刘%", "北%");
        bySql.forEach(System.out::println);
    }
  • 可以在接口中自定义方法,而且不必引入jpql或者sql语句,这种方法叫做方法命名规则查询

    /**
     * 方法命名规则
     * @param name
     * @return 按照name模糊查询,方法名以findBy开头
     *                         -属性名首字母大写,
     *                         -查询方式(模糊查询,等价查询),如果不写成查询方式,默认等价查询
     */
    public List<Resume> findByNameLike(String name);
    ​
    ​
    @Test
    public void testMethodName() {
        List<Resume> byNameLike = dao.findByNameLike("刘%");
        byNameLike.forEach(System.out::println);
    }
  • 动态查询==>service层传入dao层的条件不确定,把service拿到条件封装成一个对象传递给dao层,这个对象就叫做Specification(对条件的封装)

    • Optional<T> findOne(@Nullable Specification<T> var1); ==> 根据条件查询单个对象

    • List<T> findAll(@Nullable Specification<T> var1); ==> 根据条件查询所有对象

    • Page<T> findAll(@Nullable Specification<T> var1, Pageable var2); ==> 根据条件查询并且分页

    • List<T> findAll(@Nullable Specification<T> var1, Sort var2); ==> 根据条件查询并进行排序

    • long count(@Nullable Specification<T> var1); ==> 根据条件统计

    /**
     * 动态查询单个对象
     * toPredicate->动态组装查询条件
     * Root -> 获取需要查询的对象属性
     * CriteriaQuery ->
     * CriteriaBuilder -> 构建查询条件,内部封装了一揽子查询条件,如模糊查询,,,
     */
    @Test
    public void testSpecification(){
        Specification<Resume> specification = new Specification<Resume>() {
            @Override
            public Predicate toPredicate(Root<Resume> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                //获取到name以及address模糊属性
                Path<Object> name = root.get("name");
                Path<Object> address = root.get("address");
                //条件1 使用CriteriaBuilder针对name属性构建条件,以精准查询为例
                Predicate predicate1 = criteriaBuilder.equal(name, "刘二");
                //条件2 address以”北开头“ 模糊查询
                Predicate predicate2 = criteriaBuilder.like(address.as(String.class),"北%")
                //组合两个条件
                Predicate predicate = criteriaBuilder.and(predicate1,predicate2)
                return predicate;
            }
        };
        Resume resume = dao.findOne(specification).get();
        System.out.println(resume);
    }
  • 排序 注意:springboot2.2.1(含)以上的版本Sort已经不能再实例化了,构造方法已经是私有的了! 可以改用Sort.by获得Sort对象

    @Test
    public void testSort(){
        Sort sort = Sort.by(Sort.Direction.DESC,"name");
        List<Resume> all = dao.findAll(sort);
        all.forEach(System.out::println);
    }
  • 分页

  • @Test
    public void testLimit(){
        Pageable pageable = PageRequest.of(0,2);
        Page<Resume> all = dao.findAll(pageable);
        all.getContent().forEach(System.out::println);
        System.out.println(all.getTotalPages());
        System.out.println(all.getContent());
    }
  • Springboot整合JPA

    • 添加Spring Data JPA依赖启动器。在项目的pom.xml文件中添加Spring Data JPA依赖启动器

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-jpa</artifactId>
      </dependency>
    • 编写ORM实体类

      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      @Entity
      @Table(name = "tb_resume")
      public class Resume {
      ​
          /**
           * GenerationType.SEQUENCE:依靠序列来产⽣主键 Oracle
           * GenerationType.IDENTITY:依赖数据库中主键⾃增功能 Mysql
           */
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          @Column(name = "id")
          private Long id;
      ​
          @Column(name = "address")
          private String address;
      ​
          @Column(name = "name")
          private String name;
      ​
          @Column(name = "phone")
          private String phone;
      ​
      }
    • 编写Repository接口

      public interface ResumeDao extends JpaRepository<Resume, Long>{...}
    • 测试

      @Autowired
      private ResumeDao dao;
      ​
      @Test
      public void selectResume () {
          Optional<Comment> optional = dao.findById(1l);
          if(optional.isPresent()){
              System.out.println(optional.get());
          }
          System.out.println();
      }
      ​