Spring Data Jpa 入門學習

本文主要講解 springData Jpa 入門相關知識, 瞭解JPA規範與Jpa的實現,搭建springboot+dpringdata jpa環境實現基礎增刪改操做,適合新手學習,老鳥繞道~java

1. ORM 概論

ORM(Object-Relational Mapping)顧名思義就是表示對象關係映射。在面向對象的軟件開發中,咱們確定是須要和數據庫進行交互的,那麼這裏就存在一個問題如何將數據庫中的表與咱們代碼中的對象映射起來尼,咱們只要有一套程序可以作到創建對象與數據庫的關聯,操做對象就能夠直接操做數據庫數據,就能夠說這套程序實現了ORM對象關係映射mysql

簡單的說:ORM就是創建實體類和數據庫表之間的關係,從而達到操做實體類就至關於操做數據庫表的目的。web

目前市面上主流的ORM框架有Hibernate、Mybatis、Spring Data Jpa等,spring

  • 其中 Mybatis 框架是一個半自動的ORM框架,自己並非徹底面向對象的思想,可是得益於sql與代碼的解耦,能夠更靈活的操做sql與優化sql,可是同時也帶有複雜的映射文件,在國內目前仍是很是主流的。
  • Hibernate 是一款徹底開源的優秀的全自動ORM框架,實現了對JDBC的輕量級封裝,實現了JPA的一整套規範,它將數據庫與POJO創建了映射關係,讓開發人員實現了徹底面向對象的操做數據庫,框架幫咱們自動生成sql

2. JPA

  • JPA 的全稱是 Java Persistence API , 即 Java 持久化 API ,是 SUN 公司推出的一套基於ORM的規範,內部是由一系列的接口和抽象類構成。sql

  • JPA 經過 JDK 5.0 註解描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。數據庫

2.1 JPA優點

  1. 標準化

JPAJCP 組織發佈的 Java EE 標準之一,所以任何聲稱符合 JPA 標準的框架都遵循一樣的架構,提供相同的訪問 API ,這保證了基於 JPA 開發的企業應用可以通過少許的修改就可以在不一樣的 JPA 框架下運行。編程

  1. 容器級特性的支持

JPA 框架中支持大數據集、事務、併發等容器級事務,這使得 JPA 超越了簡單持久化框架的侷限,在企業應用發揮更大的做用。設計模式

  1. 簡單方便springboot

    JPA的主要目標之一就是提供更加簡單的編程模型:在JPA框架下建立實體和建立Java 類同樣簡單,沒有任何的約束和限制,只須要使用 javax.persistence.Entity 進行註釋, JPA 的框架和接口也都很是簡單,沒有太多特別的規則和設計模式的要求,開發者能夠很容易的掌握。JPA基於非侵入式原則設計,所以能夠很容易的和其它框架或者容器集成微信

  2. 查詢能力

    JPA的查詢語言是面向對象而非面向數據庫的,它以面向對象的天然語法構造查詢語句,能夠當作是 Hibernate HQL 的等價物。 JPA 定義了獨特的 JPQL(Java Persistence Query Language)JPQLEJB QL 的一種擴展,它是針對實體的一種查詢語言,操做對象是實體,而不是關係數據庫的表,並且可以支持批量更新和修改、 JOIN、GROUP BY、HAVING 等一般只有 SQL 纔可以提供的高級查詢特性,甚至還可以支持子查詢。

  3. 高級特性

JPA 中可以支持面向對象的高級特性,如類之間的繼承、多態和類之間的複雜關係,這樣的支持可以讓開發者最大限度的使用面向對象的模型設計企業應用,而不須要自行處理這些特性在關係數據庫的持久化。

2.2 JPA與hibernate的關係

  • JPA規範本質上就是一種ORM規範,注意不是ORM框架——由於JPA並未提供ORM實現,它只是制訂了一些規範,提供了一些編程的API接口,但具體實現則由服務廠商來提供實現。
    file

  • JPA和Hibernate的關係就像JDBC和JDBC驅動的關係,JPA是規範,Hibernate除了做爲ORM框架以外,它也是一種JPA實現。JPA怎麼取代Hibernate呢?JDBC規範能夠驅動底層數據庫嗎?答案是否認的,也就是說,若是使用JPA規範進行數據庫操做,底層須要hibernate做爲其實現類完成數據持久化工做。

3. Spring Data JPA

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

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

3.1 JPA 、Hibernate 與Spring Data Jpa

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

  • hibernate 是一套成熟的ORM框架,並且 Hibernate 實現了 JPA 規範,因此也能夠稱 hibernateJPA 的一種實現方式,咱們使用 JPAAPI 編程,意味着站在更高的角度上看待問題(面向接口編程)

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

3.2 五分鐘快速上手 Spring Data Jpa

3.2.1 構建開發環境

    1. 構建 springboot 腳手架構建初始環境,咱們不要 web 模塊,只須要 spring data jpamysql 便可

file

file

    1. 等待構建完成, spingboot 的強大在這裏體現了,爲咱們快速構建了開箱即用的環境,咱們在 application.yml 文件中添加咱們須要的配置信息
spring:
  datasource:
    url: jdbc:mysql:///db?serverTimezone=GMT
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update # 數據庫沒有表時自動構建,
    database: mysql # 指定數據庫類型
    generate-ddl: true # 自動生成
    show-sql: true # 現實sql到控制檯
    database-platform: org.hibernate.dialect.MySQL8Dialect # 數據庫方言 DataBbase枚舉內獲取

3.2.2 進行增刪改開發

    1. 建立映射實體類 DeptEntity , 這裏用來 lombok 插件省去了 get/set 方法,對於 @Column 註解是能夠放在 get 方法上的
@Entity
@Table(name = "dept", schema = "db", catalog = "")
@ToString
@Data
public class DeptEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "DEPTNO", nullable = false)
    private int deptno;

    // name : 數據庫中的列名  nullable  : 是否爲空  length:長度
    @Column(name = "DNAME", nullable = true, length = 14)
    private String dname;

    @Column(name = "LOC", nullable = true, length = 13)
    private String loc;

    @Column(name = "flag", nullable = true)
    private Integer flag;

    @Column(name = "type", nullable = true, length = 20)
    private String type;
    
}
    1. 編寫完實體後,而後添加相應的 Dao 層接口,須要繼承 JPA 的接口 JpaRepository , 代碼以下:
public interface DeptRepository extends JpaRepository<DeptEntity,Integer> {
    
}

咱們的 dao 層是一個接口,沒有具體的實現,在繼承的接口中已經定義了一些經常使用的方法供咱們使用,後面詳細介紹

    1. 編寫測試類方法進行驗證
@SpringBootTest
public class SpringJpaTest {
    @Autowired
    private DeptRepository deptRepository;

    @Test
    public void jpaTest(){
        List<DeptEntity> list = deptRepository.findAll();
        System.out.println(list);
    }
}
    1. 看到打印出查詢結果
Hibernate: select deptentity0_.deptno as deptno1_0_, deptentity0_.dname as dname2_0_, deptentity0_.flag as flag3_0_, deptentity0_.loc as loc4_0_, deptentity0_.type as type5_0_ from dept deptentity0_
[DeptEntity(deptno=10, dname=張三, loc=NEW YORK, flag=10, type=真實類型), DeptEntity(deptno=20, dname=RESEARCH, loc=DALLAS, flag=20, type=11), DeptEntity(deptno=50, dname=zonghebu, loc=CHICAGO, flag=null, type=null), DeptEntity(deptno=60, dname=??, loc=123, flag=null, type=22), DeptEntity(deptno=61, dname=??, loc=123, flag=null, type=null), DeptEntity(deptno=62, dname=??, loc=123, flag=null, type=null), DeptEntity(deptno=72, dname=??, loc=123, flag=null, type=null)]

同時能夠看到sql的語句,這是 Hibernate 幫咱們生成的,總的來講 Spring Data Jpa 的實現是依賴了 HibernateJpa 規範的實現,

    1. 相應的添加與查詢排序方法的測試所有以下:
@Test
    public void query(){
        Sort deptno = Sort.by(Sort.Direction.DESC,"deptno");
        List<DeptEntity> all = deptRepository.findAll(deptno);
        System.out.println(all);
    }

 @Test
    public void insert(){
        DeptEntity entity = new DeptEntity();
        entity.setDname("質量控制部門");
        entity.setFlag(1);
        entity.setType("test");
        //保存同時將保存結果返回
        DeptEntity save = deptRepository.save(entity);
        System.out.println(save);
    }

增長方法打印:

Hibernate: insert into dept (dname, flag, loc, type) values (?, ?, ?, ?)
DeptEntity(deptno=73, dname=質量控制部門, loc=null, flag=1, type=test)

其餘方法再也不贅述都基本類似,這些方法都是繼承自 JpaRepository 接口中

3.3 複雜查詢的實現

3.3.1 接口已定義的方法

在父接口中定義了一套經常使用的查詢, 相對比較簡單,以下:

Optional<T> findById(ID id);
Iterable<T> findAllById(Iterable<ID> ids);
List<T> findAll(Sort sort);
List<T> findAllById(Iterable<ID> ids);

3.3.2 jpql的查詢方式

  • jpql : jpa query language (jpq查詢語言)

  • 特色:

    語法或關鍵字和sql語句相似
    查詢的是類和類中的屬性

  • 須要將JPQL語句配置到接口方法上

    1.特有的查詢:須要在dao接口上配置方法
    2.在新添加的方法上,使用註解的形式配置jpql查詢語句
    3.註解 : @Query

  • 代碼實現以下:

在dao接口中自定義方法並添加註解

/**
     * 經過 deptno查詢
     * 使用@Query註解來編寫jpql 語句  是面向對象的操做
     * sql:  select * from dept where deptno = ?
     * jpql: select * from DeptEntity where deptno = ?1
     *
     * 在jpql中佔位符好指定索引,與參數列表對應,從1開始
     * @param id
     * @return
     */
    @Query(value=" from DeptEntity where deptno = ?1")
    DeptEntity queryDept(Integer id);

    /**
     * DML 操做, 須要加@Modifying 註解
     * 在多個條件時要注意佔位符下標的數字要和參數列表對應
     * 須要注意,DML 語句須要事務配置,須要加 @Transactional 註解,通常在業務層,而再也不數據層,
     * @param name
     * @param id
     */
    @Query(value="update DeptEntity set dname=?1 where deptno=?2")
    @Modifying
    void updateName(String name,Integer id);
// JAVA

測試方法代碼:

@Test
    public void queryDept(){
        DeptEntity entity = deptRepository.queryDept(10);
        System.out.println(entity);
    }

    /**
     * 這裏須要加  @Transactional 註解來管理事務
     */
    @Test
    @Transactional
    public void updateName(){
        deptRepository.updateName("測試",10);
    }

3.3.3 sql語句查詢方式

  • 特有的查詢:須要在dao接口上配置方法

  • 在新添加的方法上,使用註解的形式配置sql查詢語句

  • 註解 :

@Query

value :jpql語句 | sql語句
nativeQuery :false(使用jpql查詢) | true(使用本地查詢:sql查詢)   是否使用本地查詢

示例代碼:
dao接口代碼:

/**
     * 須要添加 nativeQuery 參數來設置是都是sql查詢, 默認是false ,是jpql查詢
     * @param num
     * @return
     */
    @Query(value="select * from dept where flag = ?",nativeQuery=true)
    List<DeptEntity> queryList(Integer num);

測試代碼:

@Test
    public void nativeQuery(){
        List<DeptEntity> list = deptRepository.queryList(10);
        System.out.println(list);
    }

3.3.4 方法名規則查詢

  • 是對jpql查詢,更加深刻一層的封裝

  • 咱們只須要按照SpringDataJpa提供的方法名稱規則定義方法,不須要再配置jpql語句,完成查詢

  • 規則:

    • findBy開頭: 表明查詢

      對象中屬性名稱(首字母大寫)

    • 含義:根據屬性名稱進行查詢

接口代碼:

/**
     * 方法名的約定:
     *      findBy : 查詢
     *            對象中的屬性名(首字母大寫) : 查詢的條件
     *            Dname
     *            * 默認狀況 : 使用 等於的方式查詢
     *                  特殊的查詢方式
     *
     *  findAllByDname   --   根據名稱查詢
     *
     *  再springdataJpa的運行階段
     *          會根據方法名稱進行解析  findBy    from  xxx(實體類)
     *                                      屬性名稱      where  dname =
     *
     *      1.findBy  + 屬性名稱 (根據屬性名稱進行完成匹配的查詢=)
     *      2.findBy  + 屬性名稱 + 「查詢方式(Like | isnull)」
     *          findAllByDnameLike
     *      3.多條件查詢
     *          findBy + 屬性名 + 「查詢方式」   + 「多條件的鏈接符(and|or)」  + 屬性名 + 「查詢方式」
     */
    List<DeptEntity> findAllByDname(String name);

    List<DeptEntity> findAllByDnameAndAndFlag(String name,Integer num);
    
    List<DeptEntity> findAllByDnameLike(String name);

測試代碼省略

  • 主要的一些規則對應以下:
    file

3.4 getOne,findOne以及findById的區別

這三個方法乍一看都是同樣的功能,都是經過主鍵返回一個實體,可是再實際使用中仍是有區別的,尤爲在一對一的關係中咱們若是直接用 getOne 查詢,會出現 下面錯誤:

org.hibernate. LazyInitializationException: could not initialize proxy - no Session

下面咱們簡單說一下這三個方法:

  • getOne

getOne是懶加載,須要在 springboot 增長這個配置: spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true ,yml中也是同樣配置。但這種方式不太友好,建議不要使用。
每次初始化一個實體的關聯就會建立一個臨時的 session 來加載,每一個臨時的 session 都會獲取一個臨時的數據庫鏈接,開啓一個新的事物。這就致使對底層鏈接池壓力很大,並且事物日誌也會被每次 flush .

設想一下:假如咱們查詢了一個分頁 list 每次查出1000條,這個實體有三個 lazy 關聯對象, 那麼,恭喜你,你至少須要建立 3000 個臨時 session+connection+transaction .

getOne 來自 JpaReposiroty 接口,對於傳入的標識則返回一個實體的引用;且取決於該方法的實現,可能會出現 EntityNotFoundException ,並會拒絕一些無效的標識;

  • findById

findById 來自 CrudRepository 接口,經過它的 id 返回一個實體;

findById返回一個Optional對象;

  • findOne

findOne 來自 QueryByExampleExecutor 接口, 返回一個經過 Example 匹配的實體或者 null

findOne返回一個Optional對象,能夠實現動態查詢

本文由AnonyStar 發佈,可轉載但需聲明原文出處。
仰慕「優雅編碼的藝術」 堅信熟能生巧,努力改變人生
歡迎關注微信公帳號 :coder簡碼 獲取更多優質文章
更多文章關注筆者博客 :IT簡碼

相關文章
相關標籤/搜索