1. 程式人生 > >Spring Data JPA入門及深入

Spring Data JPA入門及深入

一:Spring Data JPA簡介

  Spring Data JPA 是 Spring 基於 ORM 框架、JPA 規範的基礎上封裝的一套JPA應用框架,可使開發者用極簡的程式碼即可實現對資料庫的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴充套件!學習並使用 Spring Data JPA 可以極大提高開發效率!

  Spring Data JPA 讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴於它來實現,在實際的工作工程中,推薦使用Spring Data JPA + ORM(如:hibernate)完成操作,這樣在切換不同的ORM框架時提供了極大的方便,同時也使資料庫層操作更加簡單,方便解耦

1:Spring Data JPA與JPA和hibernate三者關係

  我在接下面的文章中都對它們三者進行擴充套件及應用,以及三者的封裝關係及呼叫關係,我下面也會以一張圖說明,如果此時有對JPA還一竅不通的可以參考我之前的一篇關於JPA文章的介紹

  關係:其實看三者框架中,JPA只是一種規範,內部都是由介面和抽象類構建的;hibernate它是我們最初使用的一套由ORM思想構建的成熟框架,但是這個框架內部又實現了一套JPA的規範(實現了JPA規範定義的介面),所有也可以稱hibernate為JPA的一種實現方式我們使用JPA的API程式設計,意味著站在更高的角度上看待問題(面向介面程式設計);Spring Data JPA它是Spring家族提供的,對JPA規範有一套更高階的封裝,是在JPA規範下專門用來進行資料持久化的解決方案。

   其實規範是死的,但是實現廠商是有很多的,這裡我對hibernate的實現商介紹,如其它的實現廠商大家可以自行去理解,因為規範在這,實現類可以更好別的,面向介面程式設計。

二:SpringDataJPA快速入門(完成簡單CRUD)

1:環境搭建及簡單查詢

-- 刪除庫
-- drop database demo_jpa;
-- 建立庫
create database if not exists demo_jpa charset gbk collate gbk_chinese_ci;
-- 使用庫
use demo_jpa;
-- 建立表
create table if not exists student(
    sid int primary key auto_increment,     -- 主鍵id
    sname varchar(10) not null,             -- 姓名
    sage tinyint unsigned default 22,        -- 年齡
    smoney decimal(6,1),                    -- 零花錢
    saddress varchar(20)                    -- 住址
)charset gbk collate gbk_chinese_ci;
insert into student values
(1,"螞蟻小哥",23,8888.8,"安徽大別山"),
(2,"王二麻",22,7777.8,"安徽大別山"),
(3,"李小二",23,6666.8,"安徽大別山"),
(4,"霍元甲",23,5555.8,null),
(5,"葉問",22,4444.8,"安徽大別山"),
(6,"李連杰",23,3333.8,"安徽大別山"),
(7,"馬克思",20,2222.8,"安徽大別山");
MySQL簡單建表語句   重要【後面都參照這個建表語句進行】
<dependencies>
        <!--單元測試座標  4.12為最穩定-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!--Spring核心座標  注:匯入此座標也同時依賴匯入了一些其它jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring對事務管理座標-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring整合ORM框架的必須座標 如工廠/事務等交由Spring管理-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring單元測試座標-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!--Spring Data JPA 核心座標-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.10.4.RELEASE</version>
        </dependency>
        <!--匯入AOP切入點表示式解析包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>

        <!--Hibernate核心座標-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.4.10.Final</version>
        </dependency>
        <!--hibernate對持久層的操作座標-->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.10.Final</version>
        </dependency>

        <!--這下面的2個el座標是使用Spring data jpa 必須匯入的,不匯入則報el異常-->
        <dependency>
            <groupId>javax.el</groupId>
            <artifactId>javax.el-api</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.web</groupId>
            <artifactId>javax.el</artifactId>
            <version>2.2.4</version>
        </dependency>

        <!--C3P0連線池座標-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <!--MySQL驅動座標-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

        <!--JAXB API是java EE 的API,因此在java SE 9.0 中不再包含這個 Jar 包。
            java 9 中引入了模組的概念,預設情況下,Java SE中將不再包含java EE 的Jar包
            而在 java 6/7 / 8 時關於這個API 都是捆綁在一起的
            丟擲:java.lang.ClassNotFoundException: javax.xml.bind.JAXBException異常加下面4個座標
            -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
    </dependencies>
pom.xml座標匯入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jpa="http://www.springframework.org/schema/data/jpa"
       xmlns:task="http://www.springframework.org/schema/task"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/data/jpa
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

    <!--配置註解掃描-->
    <context:component-scan base-package="cn.xw"></context:component-scan>

    <!--配置C3P0連線池-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/demo_jpa"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123"></property>
    </bean>

    <!--建立EntityManagerFactory交給Spring管理,讓Spring生成EntityManager實體管理器操作JDBC-->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <!--配置一個連線池,後期獲取連線的Connection連線物件-->
        <property name="dataSource" ref="dataSource"></property>
        <!--配置要掃描的包,因為ORM操作是基於實體類的-->
        <property name="packagesToScan" value="cn.xw.domain"></property>
        <!--配置JPA的實現廠家 實現了JPA的一系列規範-->
        <property name="persistenceProvider">
            <bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
        </property>
        <!--JPA供應商介面卡 因為我上面使用的是hibernate,所有介面卡也選擇hibernate-->
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <!--指定當前操作的資料庫型別 必須大寫,底層是一個列舉類-->
                <property name="database" value="MYSQL"></property>
                <!--是否自動建立資料庫表-->
                <property name="generateDdl" value="false"></property>
                <!--是否在執行的時候 在控制檯列印操作的SQL語句-->
                <property name="showSql" value="true"></property>
                <!--指定資料庫方言:支援的語法,如Oracle和Mysql語法略有不同 org.hibernate.dialect下面的類就是支援的語法-->
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"></property>
                <!--設定是否準備事務休眠會話的底層JDBC連線,即是否將特定於事務的隔離級別和/或事務的只讀標誌應用於底層JDBC連線。-->
                <property name="prepareConnection" value="false"></property>
            </bean>
        </property>
        <!--JPA方言:高階特性 我下面配置了hibernate對JPA的高階特性 如一級/二級快取等功能-->
        <property name="jpaDialect">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean>
        </property>
        <!--注入JPA的配置資訊
            載入JPA的基本配置資訊和JPA的實現方式(hibernate)的配置資訊
            hibernate.hbm2ddl.auto:自動建立資料庫表
                create:每次讀取配置檔案都會建立資料庫表
                update:有則不做操作,沒有則建立資料庫表 -->
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <!--配置事務管理器  不同的事務管理器有不同的類 如我們當初使用這個事務管理器DataSourceTransactionManager-->
    <bean id="jpaTransactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <!--把配置好的EntityManagerFactory物件交由Spring內部的事務管理器-->
        <property name="entityManagerFactory" ref="entityManagerFactory"></property>
        <!--因為entityManagerFactory內部設定資料庫連線了  所有後面不用設定-->
        <!--<property name="dataSource" ref="dataSource"></property>-->
    </bean>

    <!--配置tx事務-->
    <tx:advice id="txAdvice" transaction-manager="jpaTransactionManager">
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="insert*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="update*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="delete*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
            <!--如果命名規範直接使用下面2行控制事務-->
            <!--<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>-->
            <!--<tx:method name="*" propagation="REQUIRED" read-only="false"/>-->
        </tx:attributes>
    </tx:advice>

    <!--配置AOP切面-->
    <aop:config>
        <!--在日常業務中配置事務處理的都是Service層,因為這裡是案例講解,所有我直接在測試類進行
            所有我把事務配置這,也方便後期拷貝配置到真專案中-->
        <aop:pointcut id="pt1" expression="execution(* cn.xw.service.impl.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

    <!--整合SpringDataJPA-->
    <!--base-package:指定持久層介面
        entity-manager-factory-ref:引用其實體管理器工廠
        transaction-manager-ref:引用事務    -->
    <jpa:repositories base-package="cn.xw.dao" entity-manager-factory-ref="entityManagerFactory"
                      transaction-manager-ref="jpaTransactionManager"></jpa:repositories>
</beans>
applicationContext.xml 配置檔案(重要)
@Entity
@Table(name = "student")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "sid")
    private Integer id;
    @Column(name = "sname")
    private String name;
    @Column(name = "sage")
    private Integer age;
    @Column(name = "smoney")
    private Double money;
    @Column(name = "saddress")
    private String address;
    //下面get/set/構造/toString都省略
    //注意:我上面的都使用包裝類,切記要使用包裝類,
    // 原因可能資料庫某個欄位查詢出來的值為空 null    
}
Student實體類及對映關係
//@Repository("studentDao") 這裡不用加入IOC容器 Spring預設幫我們注入
public interface StudentDao extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {
    /*
    JpaRepository<T,ID>:T:當前表的型別  ID:當前表主鍵欄位型別 
        功能:用來完成基本的CRUD操作 ,因為內部定義了很多增刪改查操作
    JpaSpecificationExecutor<T>:T:當前表的型別
        功能:用來完成複雜的查詢等一些操作
    */
}
StudentDao介面
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Client {
    //注入資料
    @Autowired
    @Qualifier(value = "studentDao")
    private StudentDao sd;

    @Test
    public void test() {
        //查詢id為4學生
        Student student = sd.findOne(4);
        System.out.println("開始列印:" + student);
        //開始列印:Student{id=4, name='霍元甲', age=23, money=5555.8, address='null'}
    }
}
測試類

2:SpringDataJPA簡單單表介面方法查詢圖 

3:針對上面圖的一些方法示例

  在針對上面方法進行操作的時候,我們的Dao介面必須繼承JpaRepository<T,ID>和JpaSpecificationExecutor<T>(後面複雜查詢使用),大家也可以去研究一下CrudRepository類,這個類在這裡就不說了,JpaRespository類間接實現了它

①:Repository:最頂層的介面,是一個空的介面,目的是為了統一所有Repository的型別,且能讓元件掃描的時候自動識別。
②:CrudRepository :是Repository的子介面,提供CRUD的功能
③:PagingAndSortingRepository:是CrudRepository的子介面,新增分頁和排序的功能
④:JpaRepository:是PagingAndSortingRepository的子介面,增加了一些實用的功能,比如:批量操作等。
⑤:JpaSpecificationExecutor:用來做負責查詢的介面
⑥:Specification:是Spring Data JPA提供的一個查詢規範,要做複雜的查詢,只需圍繞這個規範來設定查詢條件
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Client {
    //注入資料
    @Autowired
    @Qualifier(value = "studentDao")
    private StudentDao sd;

    /****************************查詢操作*****************************/
    @Test   //查詢並排序
    public void findtestA() {
        //查詢全部 先對age排序後在進行id排序
        Sort sort = new Sort(Sort.Direction.DESC, "age", "id");
        List<Student> students = sd.findAll(sort);
        //列印省略
    }

    @Test   //查詢並分頁
    public void findtestB() {
        //查詢分頁  page是第幾頁 size 每頁個數  當前是第2頁查詢3個   相當limit 6,3
        Pageable pageable = new PageRequest(2, 3);
        Page<Student> page = sd.findAll(pageable);
        System.out.println("當前頁:"+page.getNumber());
        System.out.println("每頁顯示條目數:"+page.getSize());
        System.out.println("總頁數:"+page.getTotalPages());
        System.out.println("結果集總數量:"+page.getTotalElements());
        System.out.println("是否是首頁:"+page.isFirst());
        System.out.println("是否是尾頁:"+page.isLast());
        System.out.println("是否有上一頁:"+page.hasPrevious());
        System.out.println("是否有下一頁:"+page.hasNext());
        System.out.println("結果集:"+page.getContent());
        /*
            當前頁:2
            每頁顯示條目數:3
            總頁數:3
            結果集總數量:7
            是否是首頁:false
            是否是尾頁:true
            是否有上一頁:true
            是否有下一頁:false
            結果集:[Student{id=7, name='馬克思', age=20, money=2222.8, address='安徽大別山'}]
         */
        //總結:以後做分頁操作變容易了呀
    }

    @Test   //查詢指定學生
    public void findtesC() {
        //建立一個集合   List就是一個Iterable可迭代容器物件,因為實現了Iterable介面
        List<Integer> list = new ArrayList<Integer>() {{
            add(1);add(5);add(3);
        }};
        List<Student> students = sd.findAll(list);
        //列印省略
    }

    /****************************更新操作*****************************/
    @Test   //更新多個學生 更新學生全部地址為 安徽合肥
    @Transactional
    @Rollback(value = false)
    public void savetestA() {
        //建立2個集合  第一個集合查詢全部資料,然後把集合裡物件地址改為新的放入新集合上,後面迭代更新
        List<Student> list = sd.findAll();
        System.out.println(list.size());
        List<Student> newlist = sd.findAll();
        for (int i = 0; i < list.size(); i++) {
            Student student = list.get(i);
            student.setAddress("安徽合肥");
            System.out.println(student);
            newlist.add(student);
        }
        List<Student> save = sd.save(newlist);
        //列印略
    }

    /****************************刪除操作*****************************/
    @Test   //刪除指定學生
    public void deletetest() {
        List<Integer> list = new ArrayList<Integer>() {{
            add(1);add(5);add(3);
        }};
        List<Student> all = sd.findAll(list);
        sd.deleteInBatch(all);
    }
    /**
     *  刪除方法介紹:
     *  void deleteAll():底層會一條一條刪除
     *  void delete(Iterable<? extends T> entities):底層會一條一條刪除
     *  void deleteAllInBatch():底層會生成一個帶or語句刪除
     *  void deleteInBatch(Iterable<T> entities):底層會生成一個帶or語句刪除
     *  如:Hibernate: delete from student where sid=? or sid=? or sid=?
     */
}
針對圖上面的一些方法操作

提取注意點: