我的架構夢:(十一)Spring Data JPA 的應用(基於案例)

一、Spring Data JPA 概述

1、什麼是 Spring Data JPA

Spring Data JPA 是應用於Dao層的一個框架,簡化數據庫開發的,作用和MyBatis框架一樣,但是在使用方式和底層機制是有所不同的。最明顯的一個特點,Spring Data JPA 開發Dao的時候,很多場景我們連sql語句都不需要開發。由Spring出品。

在這裏插入圖片描述

Spring Data JPASpring 基於JPA 規範的基礎上封裝的一套 JPA 應用框架,可使開發者用極簡的代碼即可實現對數據庫的訪問和操作。它提供了包括增刪改查等在內的常用功能!學習並使用 Spring Data JPA 可以極大提高開發效率。

說明Spring Data JPA 極大簡化了數據訪問層代碼。

如何簡化呢?使用了Spring Data JPA,我們Dao層中只需要寫接口,不需要寫實現類,就自動具有
了增刪改查、分⻚查詢等方法。

使用Spring Data JPA 很多場景下不需要我們自己寫sql語句。

2、Spring Data 家族

在這裏插入圖片描述

二、Spring Data JPA,JPA規範和Hibernate之間的關係

Spring Data JPASpring 提供的一個封裝了JPA 操作的框架,而 JPA 僅僅是規範,單獨使用規範無法 具體做什麼,那麼Spring Data JPAJPA規範以及Hibernate (JPA 規範的一種實現)之間的關係是什麼?

在這裏插入圖片描述

JPA 是一套規範,內部是由接口和抽象類組成的。

Hiberanate 是一套成熟的 ORM 框架,而且 Hiberanate 實現了 JPA 規範,所以可以稱 HiberanateJPA 的一種實現方式,我們使用 JPAAPI 編 程,意味着站在更高的⻆度去看待問題(面向接口編程)。

Spring Data JPASpring 提供的一套對 JPA 操作更加高級的封裝,是在 JPA 規範下的專⻔用來進行數 據持久化的解決方案。

三、Spring Data JPA 的應用

  • 需求:使用 Spring Data JPA 完成對 tb_resume 表(簡歷表)的Dao 層操作(增刪改查,排序,
    分⻚等)

  • 數據表設計

    在這裏插入圖片描述

  • 初始化Sql語句

    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for tb_resume 
    -- ----------------------------
    DROP TABLE IF EXISTS `tb_resume`; CREATE TABLE `tb_resume` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
    `address` varchar(255) DEFAULT NULL, 
    `name` varchar(255) DEFAULT NULL, 
    `phone` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of tb_resume
    -- ----------------------------
    BEGIN;
    INSERT INTO `tb_resume` VALUES (1, '北京', '張三', '131000000');
    INSERT INTO `tb_resume` VALUES (2, '上海', '李四', '151000000');
    INSERT INTO `tb_resume` VALUES (3, '廣州', '王五', '153000000');
    COMMIT;
    
    SET FOREIGN_KEY_CHECKS = 1;
    

1、Spring Data JPA 開發步驟梳理

  • 構建工程

    創建工程導入依賴(Java框架於我們而言就是一堆jar)
    配置 Spring 的配置文件(配置指定框架執行的細節)
    編寫實體類 Resume,使用 JPA 註解配置映射關係
    編寫一個符合 Spring Data JPADao 層接口(ResumeDao接口)

  • 操作 ResumeDao 接口對象完成 Dao 層開發

2、 Spring Data JPA 開發實現

2.1 導入依賴

<dependencies>
    <!--單元測試jar-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <!--spring-data-jpa 需要引入的jar,start-->
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>2.1.8.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-data-jpa 需要引入的jar,end-->

    <!--spring 相關jar,start-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>

    <!--spring對orm框架的支持包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <!--spring 相關jar,end-->

    <!--hibernate相關jar包,start-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <!--hibernate對jpa的實現jar-->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.4.0.Final</version>
    </dependency>
    <!--hibernate相關jar包,end-->

    <!--mysql 數據庫驅動jar-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.19</version>
    </dependency>
    <!--druid連接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>
    <!--spring-test-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
        <scope>provided</scope>
    </dependency>

</dependencies>

2.2 配置 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"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/data/jpa
        https://www.springframework.org/schema/data/jpa/spring-jpa.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"/>
        <!--配置包掃描(pojo實體類所在的包)-->
        <property name="packagesToScan" value="com.riemann.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,hibearnte框架的執行細節-->
        <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.riemann.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、聲明式事務配置-->
    <!--
        <tx:annotation-driven/>
    -->

    <!--6、配置spring包掃描-->
    <context:component-scan base-package="com.riemann"/>

</beans>

2.3 編寫實體類 Resume,使用 JPA 註解配置映射關係

/**
 * 簡歷實體類(在類中要使用註解建立實體類和數據表之間的映射關係以及屬性和字段的映射關係)
 * 1、實體類和數據表映射關係
 * @Entity
 * @Table
 * 2、實體類屬性和表字段的映射關係
 * @Id 標識主鍵
 * @GeneratedValue 標識主鍵的生成策略
 * @Column 建立屬性和字段映射
 */
@Data
@Entity
@Table(name = "tb_resume")
public class Resume {

    /**
     * 生成策略經常使用的兩種:
     * GenerationType.IDENTITY:依賴數據庫中主鍵自增功能  Mysql
     * GenerationType.SEQUENCE:依靠序列來產生主鍵     Oracle
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    @Column(name = "address")
    private String address;

    @Column(name = "phone")
    private String phone;

}

2.4 編寫 ResumeDao 接口

/**
 * 一個符合SpringDataJpa要求的Dao層接口是需要繼承JpaRepository和JpaSpecificationExecutor
 *
 * JpaRepository<操作的實體類類型,主鍵類型>
 *      封裝了基本的CRUD操作
 *
 * JpaSpecificationExecutor<操作的實體類類型>
 *      封裝了複雜的查詢(分頁、排序等)
 *
 */
public interface ResumeDao extends JpaRepository<Resume, Long>, JpaSpecificationExecutor<Resume> {

    @Query("from Resume where id=?1 and name=?2")
    List<Resume> findByJpql(Long id, String name);

    /**
     * 使用原生sql語句查詢,需要將nativeQuery屬性設置爲true,默認爲false(jpql)
     * @param name
     * @param address
     * @return
     */
    @Query(value = "select * from tb_resume where name like ?1 and address like ?2", nativeQuery = true)
    List<Resume> findBySql(String name, String address);

    /**
     * 方法命名規則查詢
     * 按照name模糊查詢(like)
     * 方法名以findBy開頭
     *     -屬性名(首字母大寫)
     *     -查詢方式(模糊查詢、等價查詢),如果不寫查詢方式,默認等價查詢
     */
    List<Resume> findByNameLikeAndAddress(String name, String address);
    
}

2.5 操作 ResumeDao 接口完成 Dao 層開發(客戶端測試)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class ResumeDaoTest {

    // 要測試IOC哪個對象注入即可
    @Autowired
    private ResumeDao resumeDao;

    /**
     * dao層接口調用,分成兩塊:
     * 1、基礎的增刪改查
     * 2、專門針對查詢的詳細分析使用
     */

    @Test
    public void testFindById() {
        // 早期的版本 dao.findOne(id);

        /*
            select resume0_.id as id1_0_0_,
                resume0_.address as address2_0_0_, resume0_.name as name3_0_0_,
                 resume0_.phone as phone4_0_0_ from tb_resume resume0_ where resume0_.id=?
         */

        Optional<Resume> optional = resumeDao.findById(1L);
        Resume resume = optional.get();
        System.out.println(resume);
    }

    @Test
    public void testFindOne(){
        Resume resume = new Resume();
        resume.setId(1l);
        resume.setName("張三");
        Example<Resume> example = Example.of(resume);
        Optional<Resume> one = resumeDao.findOne(example);
        Resume resume1 = one.get();
        System.out.println(resume1);
    }

    @Test
    public void testSave(){
        // 新增和更新都使用save方法,通過傳入的對象的主鍵有無來區分,沒有主鍵信息那就是新增,有主鍵信息就是更新
        Resume resume = new Resume();
        resume.setId(5l);
        resume.setName("趙六六");
        resume.setAddress("成都");
        resume.setPhone("132000000");
        Resume save = resumeDao.save(resume);
        System.out.println(save);

    }

    @Test
    public void testDelete(){
        resumeDao.deleteById(5l);
    }

    @Test
    public void testFindAll(){
        List<Resume> list = resumeDao.findAll();
        System.out.println(list);
    }

    @Test
    public void testSort(){
        Sort sort = new Sort(Sort.Direction.DESC,"id");
        List<Resume> list = resumeDao.findAll(sort);
        for (int i = 0; i < list.size(); i++) {
            Resume resume =  list.get(i);
            System.out.println(resume);
        }
    }

    @Test
    public void testPage(){
        /**
         * 第一個參數:當前查詢的頁數,從0開始
         * 第二個參數:每頁查詢的數量
         */
        Pageable pageable  = PageRequest.of(0,2);
        Page<Resume> all = resumeDao.findAll(pageable);
        System.out.println(all);
    }



    /**
     * ========================針對查詢的使用進行分析=======================
     * 方式一:調用繼承的接口中的方法  findOne(),findById()
     * 方式二:可以引入jpql(jpa查詢語言)語句進行查詢 (=====>>>> jpql 語句類似於sql,只不過sql操作的是數據表和字段,jpql操作的是對象和屬性,比如 from Resume where id=xx) hql
     * 方式三:可以引入原生的sql語句
     * 方式四:可以在接口中自定義方法,而且不必引入jpql或者sql語句,這種方式叫做方法命名規則查詢,也就是說定義的接口方法名是按照一定規則形成的,那麼框架就能夠理解我們的意圖
     * 方式五:動態查詢
     *       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);
     *
     *      interface Specification<T>
     *              toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);用來封裝查詢條件的
     *                  Root:根屬性(查詢所需要的任何屬性都可以從根對象中獲取)
     *                  CriteriaQuery 自定義查詢方式 用不上
     *                  CriteriaBuilder 查詢構造器,封裝了很多的查詢條件(like = 等)
     *
     *
     */

    @Test
    public void testJpql(){
        List<Resume> list = resumeDao.findByJpql(1l, "張三");
        System.out.println(list);
    }

    @Test
    public void testSql(){
        List<Resume> list = resumeDao.findBySql("李%", "上海%");
        System.out.println(list);
    }

    @Test
    public void testMethodName(){
        List<Resume> list = resumeDao.findByNameLikeAndAddress("李%", "上海");
        System.out.println(list);
    }


    // 動態查詢,查詢單個對象
    @Test
    public void testSpecfication() {
        /**
         * 動態條件封裝
         * 匿名內部類
         *
         * toPredicate:動態組裝查詢條件
         *
         *      藉助於兩個參數完成條件拼裝,select * from tb_resume where name='張三'
         *      Root: 獲取需要查詢的對象屬性
         *      CriteriaBuilder:構建查詢條件,內部封裝了很多查詢條件(模糊查詢,精準查詢)
         *
         *      需求:根據name(指定爲"張三")查詢簡歷
         */
        Specification specification = new Specification() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
                // 獲取到name屬性
                Path<Object> name = root.get("name");

                // 使用CriteriaBuilder針對name屬性構建條件(精準查詢)
                Predicate predicate = criteriaBuilder.equal(name, "張三");
                return predicate;
            }
        };
        Optional<Resume> optional = resumeDao.findOne(specification);
        Resume resume = optional.get();
        System.out.println(resume);
    }

    @Test
    public void testSpecficationMultiCon() {
        /**
         * 需求:根據name(指定爲"張三")並且,address 以"北"開頭(模糊匹配),查詢簡歷
         */
        Specification<Resume> specification = new Specification<Resume>() {
            @Override
            public Predicate toPredicate(Root<Resume> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                // 獲取到name屬性
                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 and = criteriaBuilder.and(predicate1, predicate2);

                return and;
            }
        };

        Optional<Resume> optional = resumeDao.findOne(specification);
        Resume resume = optional.get();
        System.out.println(resume);
    }

}

四、代碼倉庫

https://github.com/riemannChow/perseverance/tree/master/middleware-example/spring-data-jpa-example

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章