Springboot2(51)集成jpa

源碼地址

springboot2教程系列

Spring Data JPA 與 MyBatis簡單對比

Spring Data JPA是Spring Data的子模塊。使用Spring Data,使得基於“repositories”概念的JPA實現更簡單和容易。Spring Data JPA的目標是大大簡化數據訪問層代碼的編碼。作爲使用者,我們只需要編寫自己的repository接口,接口中包含一些個性化的查詢方法,Spring Data JPA將自動實現查詢方法。
JPA默認使用hibernate作爲ORM實現,所以,一般使用Spring Data JPA即會使用hibernate。我們再看看hibernate的官方概念,Hibernate是一個開放源代碼的對象關係映射框架,它對JDBC進行了非常輕量級的對象封裝,它將POJO與數據庫表建立映射關係,是一個全自動的orm框架,hibernate可以自動生成SQL語句,自動執行,使得Java程序員可以隨心所欲的使用對象編程思維來操縱數據庫。

MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。

這樣看,Spring Data JPA與MyBatis對比,起始也就是hibernate與MyBatis對比。所以,我們直接來比較後兩者。

從基本概念和框架目標上看,兩個框架差別還是很大的。hibernate是一個自動化更強、更高級的框架,畢竟在java代碼層面上,省去了絕大部分sql編寫,取而代之的是用面向對象的方式操作關係型數據庫的數據。而MyBatis則是一個能夠靈活編寫sql語句,並將sql的入參和查詢結果映射成POJOs的一個持久層框架。所以,從表面上看,hibernate能方便、自動化更強,而MyBatis 在Sql語句編寫方面則更靈活自由。

但這只是從使用層面上看兩者的區別,並未涉及的本質。但如果看問題,值看淺層次、表象問題的話,就不能理解技術本質,也不能發揮技術的最多效用。所以,如果更上一個抽象層次去看,對於數據的操作,hibernate是面向對象的,而MyBatis是面向關係的。當然,用hibernate也可以寫出面向關係代碼和系統,但卻得不到面向關係的各種好處,最大的便是編寫sql的靈活性,同時也失去面向對象意義和好處——一句話,不倫不類。那麼,面向對象和關係型模型有什麼不同,體現在哪裏呢?實際上兩者要面對的領域和要解決的問題是根本不同的:面向對象致力於解決計算機邏輯問題,而關係模型致力於解決數據的高效存取問題。我們不妨對比一下面向對象的概念原則和關係型數據庫的不同之處:

  1. 面向對象考慮的是對象的整個生命週期包括在對象的創建、持久化、狀態的改變和行爲等,對象的持久化只是對象的一種狀態,而面向關係型數據庫的概念則更關注數據的高效存儲和讀取;
  2. 面向對象更強調對象狀態的封裝性,對象封裝自己的狀態(或數據)不允許外部對象隨意修改,只暴露一些合法的行爲方法供外部對象調用;而關係型數據庫則是開放的,可以供用戶隨意讀取和修改關係,並可以和其他表任意的關聯(只要sql正確允許的情況下);
  3. 面向對象試圖爲動態的世界建模,他要描述的是世界的過程和規律,進而適應發展和變化,面向對象總是在變化中處理各種各樣的變化。而關係型模型爲靜態世界建模,它通過數據快照記錄了世界在某一時候的狀態,它是靜態的。

從上面兩者基本概念和思想的對比來看,可以得出結論hibernate和MyBatis兩個框架的側重點完全不同。所以我們就兩個框架選擇上,就需要根據不同的項目需求選擇不同的框架。在框架的使用中,也要考慮考慮框架的優勢和劣勢,揚長避短,發揮出框架的最大效用,才能真正的提高項目研發效率、完成項目的目標。但相反,如果使用Spring Data JPA和hibernate等ORM的框架而沒有以面向對象思想和方法去分析和設計系統,而是抱怨框架不能靈活操作sql查詢數據,那就是想讓狗去幫你拿耗子了。

那麼,話題再說回來,使用兩個框架時候的時候,也要注意最佳的步驟和流程。下面我們來分別討論一下,hibernate的一般使用步驟如下:

  1. 分析、抽象和歸納出系統中的業務概念,並梳理出各個業務概念之間的關係——創建概念模型
  2. 根據概念模型,進一步細化設計系統中的對象類以及類的依賴關係——創建設計模型
  3. 將設計好的類映射到數據庫的表和字段配置好
  4. hibernate可以根據配置信息自動生成數據庫表,這個時候也可以集中精力去梳理一下表關係,看看錶結構是否合理,並適當調整一下類和表的映射關係,重新生成表結構

完成以上步驟,基本上完成了體統中主要的業務概念類和表結構的設計工作,只是完成表結構設計的出發點事如何持久化系統的對象,同時兼顧數據庫表、字段、字段類型、表的關聯關係的合理性和合規性,而不是單純表設計。這兩者思考和關注點還是有很大差別的。另外,需要說明一點,這只是使用hibernate的最通用步驟,實際操作過程中還是需要根據具體項目情況來安排。

而MyBatis對於面向對象的概念強調比較少,更適用於靈活的對數據進行增、刪、改、查,所以在系統分析和設計過程中,要最大的發揮MyBatis的效用的話,一般使用步驟則與hibernate有所區別:

  1. 綜合整個系統分析出系統需要存儲的數據項目,並畫出E-R關係圖,設計表結構
  2. 根據上一步設計的表結構,創建數據庫、表
  3. 編寫MyBatis的SQL 映射文件、Pojos以及數據庫操作對應的接口方法

這樣看來MyBatis更適合於面向關係(或面向數據、或面向過程)的系統設計方法,這樣的系統一般稱爲“事務腳步”系統(事務腳步(Transaction Script) 出自Martin Fowler 2004年所著的企業應用架構模式(Patterns of Enterprise Application Architecture))。而hibernate(也可以說Spring Data JPA)更適合於構建領域模型類的系統。當然,我們也不能說MyBatis無法構建領域模型驅動的系統,而hibernate無法構建事務腳步系統。只是用MyBatis構建領域模型要做更多、更髒、更累的工作;而用hibernate構建一個事務腳本系統有些大材小用,數據的查詢反而沒那麼靈活。

綜合上面所有描述和對比,我們對這兩個框架的本質區別應該有所瞭解了。我們瞭解了這些區別,可以幫助我們選擇更合適的框架,同時,也可以利用不同的框架,讓他們去做更合適事,這也是所謂的物盡其用吧,更不至於我們“爲物所役”。

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

添加配置

spring:
  messages:
    basename: i18n/Messages,i18n/Pages
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource    # 配置當前要使用的數據源的操作類型
    driver-class-name: org.gjt.mm.mysql.Driver      # 配置MySQL的驅動程序類
    url: jdbc:mysql://47.106.106.53:3306/mybatis?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8           # 數據庫連接地址
    username: root                                  # 數據庫用戶名
    password: Rojao@123

# JPA 相關配置
spring.jpa.database-platform: org.hibernate.dialect.MySQL5InnoDBDialect 
spring.jpa.database: mysql
# 顯示後臺處理的SQL語句
spring.jpa.show-sql: true
# 自動檢查實體和數據庫表是否一致,如果不一致則會進行更新數據庫表
spring.jpa.hibernate.ddl-auto: create

spring.jpa.show-sql=true 配置在日誌中打印出執行的 SQL 語句信息。

spring.jpa.hibernate.ddl-auto=create 配置指明在程序啓動的時候要刪除並且創建實體類對應的表。這個參數很危險,因爲他會把對應的表刪除掉然後重建。所以千萬不要在生成環境中使用。只有在測試環境中,一開始初始化數據庫結構的時候才能使用一次。

  • create:每次加載hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因(一般只會在第一次創建時使用)

  • create-drop:每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除

  • update:最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器後,表結構是不會被馬上建立起來的,是要等應用第一次運行起來後纔會

  • validate:每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值

spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 。在 SrpingBoot 2.0 版本中,Hibernate 創建數據表的時候,默認的數據庫存儲引擎選擇的是 MyISAM (之前好像是 InnoDB,這點比較詭異)。這個參數是在建表的時候,將默認的存儲引擎切換爲 InnoDB 用的。

建立第一個數據實體類

@Data
@Entity
@Table(name = "bus_receiver")
public class BusReceiverEntity {
    //主鍵
    @Id
    @Column(name="id")
    private Long id;

    //姓名
    @Column(name="name",length = 32)
    private String name;
    //區域
    @Column(name="region_code")
    private String regionCode;
    //地址
    @Column(name="address")
    private String address;
    //地址英文名字
    @Column(name="enname")
    private String enName;
    //家庭成員
    @Column(name="memberfamily")
    private int memberFamily;
    //創建時間
    @Column(name="create_date")
    private Date createDate;

}

其中:

  1. @Entity 是一個必選的註解,聲明這個類對應了一個數據庫表。
  2. @Table(name = “region_code”) 是一個可選的註解。聲明瞭數據庫實體對應的表信息。包括表名稱、索引信息等。這裏聲明這個實體類對應的表名是 AUTH_USER。如果沒有指定,則表名和實體的名稱保持一致。
  3. @Id 註解聲明瞭實體唯一標識對應的屬性。
  4. @Column(length = 32) 用來聲明實體屬性的表字段的定義。默認的實體每個屬性都對應了表的一個字段。字段的名稱默認和屬性名稱保持一致(並不一定相等)。字段的類型根據實體屬性類型自動推斷。這裏主要是聲明瞭字符字段的長度。如果不這麼聲明,則系統會採用 255 作爲該字段的長度
 * 常用註解解釋:
 * @Entity 用來標示這個類是實體類
 * @Table  實體類與數據表的映射,通過name確定在表名(默認類名爲表名)
 * @Id         主鍵註解,表示該字段爲主鍵
 * @GeneratedValue 定義主鍵規則,默認AUTO
 * @Column 類屬性與表字段映射註解,其中可以設置在字段名,長度等信息
 * @ManyToOne  多對一,可以設置數據加載方式等 默認加載方式是EAGER 就是使用left join
 * @OneToMany  一對多 默認加載方式是 LAZY 懶加載
 * @JoinColumn 與*對*配合使用,用來設置外鍵名等信息
 * @Basic  實體類中會默認爲每一個屬性加上這個註解,表示與數據表存在關聯,
 *              沒有使用Column註解的類屬性會以屬性名作爲字段名,駝峯命名需要轉爲_
 * @Temporal 對於Date屬性的格式化註解,有 TIME,DATE,TIMESTAMP 幾個選擇
 * @Transient 若存在不想與數據表映射的屬性,則需要加上該註解

實現一個持久層服務

在 Spring Data JPA 的世界裏,實現一個持久層的服務是一個非常簡單的事情。以上面的 UserDO 實體對象爲例,我們要實現一個增加、刪除、修改、查詢功能的持久層服務,那麼我只需要聲明一個接口,這個接口繼承
org.springframework.data.repository.Repository<T, ID> 接口或者他的子接口就行。這裏爲了功能的完備,我們繼承了 org.springframework.data.jpa.repository.JpaRepository<T, ID> 接口。其中 T 是數據庫實體類,ID 是數據庫實體類的主鍵。
然後再簡單的在這個接口上增加一個 @Repository 註解就結束了。

@Repository
public interface BusReceiverDao  extends JpaRepository<BusReceiverEntity,Long> {

}

已經擁有下下面的功能

img

Dao 保存實體功能

img

Dao 保存實體刪除功能

img

Dao 查詢實體功能

該Dao成繼承了JpaRepository接口,指定了需要操作的實體對象和實體對象的主鍵類型,通過查看JpaRepository接口源碼可以看到,裏面已經封裝了創建(save)、更新(save)、刪除(delete)、查詢(findAll、findOne)等基本操作的函數,使用起來非常方便了,但是還是會存在一些複雜的sql,spring-data-jpa還提供了一個非常方便的方式,通過實體屬性來命名方法,它會根據命名來創建sql查詢相關數據,對應更加複雜的語句,還可以用直接寫sql來完成,具體例子如上所示。

根據屬性來查詢

根據屬性來查詢

如果想要根據實體的某個屬性來進行查詢我們可以在 Dao 接口中進行接口聲明。例如,如果我們想根據實體的 name 這個屬性來進行查詢,只需要

BusReceiverEntity findByName(String name);

這種方式非常強大,不經能夠支持單個屬性,還能支持多個屬性組合。例如如果我們想查找賬號和密碼同時滿足查詢條件的接口。那麼我們在 Dao 接口中聲明

BusReceiverEntity findByNameAndAddress(String name,String address);

這個語句結構可以用下面的表來說明

img

自定義查詢

如果上述的情況還無法滿足需要。那麼我們就可以通過通過 import org.springframework.data.jpa.repository.Query註解來解決這個問題。

@Query("SELECT r FROM BusReceiverEntity r where r.name = ?1")
BusReceiverEntity findByName(String name);*/

這裏是用 PQL 的語法來定義一個查詢。 (注意是HQL,不是SQL語句)

如果你習慣編寫 SQL 語句來完成查詢,還可以在用下面的方式實現

@Query(nativeQuery = true, value = "SELECT * FROM AUTH_USER WHERE name = :name1  OR name = :name2 ")
List<UserDO> findSQL(@Param("name1") String name1, @Param("name2") String name2);

這裏在 @Query 註解中增加一個 nativeQuery = true 的屬性,就可以採用原生 SQL 語句的方式來編寫查詢。

@Param註解注入參數,:引入參數,需要指定參數名)

修改語句的自定義使用

@Query@Modifying同時使用用來自定義數據更改操作的SQL,@Transactional負責提交事務

 @Transactional
 @Query(value = "update BusReceiverEntity p set p.name = ?1")
 @Modifying
 Integer updateName(String name);

添加二級緩存

引入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-jcache -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-jcache</artifactId>
    <version>5.4.3.Final</version>
</dependency>

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.5.2</version>
</dependency>

此處需要注意,如果高版本的 SpringBoot,我們需要使用org.hibernate:hibernate-jcache,而不是org.hibernate:hibernate-ehcache。否則啓動異常

配置

ehcache配置

這裏需要注意的是,百度裏面的文章大部分都是告訴大家怎麼配置ehcache2.x,但是ehcache的2.x和3.x完全就兩碼事情了 ,這裏使用ehcache3.x配置

<?xml version="1.0" encoding="UTF-8"?>
<eh:config
        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
        xmlns:eh='http://www.ehcache.org/v3'
        xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.3.xsd">
    <!--指定緩存目錄-->
    <eh:persistence directory="${java.io.tmpdir}/cfa-cache-data"/>

    <!--緩存模板-->
    <eh:cache-template name="default">
        <eh:expiry>
            <eh:ttl unit="seconds">600</eh:ttl>
        </eh:expiry>
        <eh:resources>
            <!--堆內內存可以放2000個條目,超出部分堆外100MB-->
            <eh:heap unit="entries">2000</eh:heap>
            <eh:offheap unit="MB">100</eh:offheap>
        </eh:resources>
    </eh:cache-template>

    <!--實際的緩存區間,繼承了default緩存模板,cfa 完全使用模板默認-->
    <eh:cache alias="cfa" uses-template="default">

    </eh:cache>

    <!--下面兩個繼承了default緩存模板,但覆蓋了緩存的過期時間-->
    <eh:cache alias="authority" uses-template="default">
        <eh:expiry>
            <eh:ttl unit="hours">1</eh:ttl>
        </eh:expiry>
    </eh:cache>

    <eh:cache alias="lapp_service" uses-template="default">
        <eh:expiry>
            <eh:ttl unit="hours">24</eh:ttl>
        </eh:expiry>
    </eh:cache>
</eh:config>

SpringBoot配置

#開啓二級緩存
spring.jpa.properties.hibernate.cache.use_second_level_cache: true
spring.jpa.properties.cache.use_query_cache: true
spring.jpa.properties.cache.provider_configuration_file_resource_path: ehcache.xml
#指定緩存provider
spring.jpa.properties.hibernate.cache.region.factory_class: org.hibernate.cache.jcache.internal.JCacheRegionFactory
spring.jpa.open-in-view: false
spring.jpa.properties.javax.persistence.sharedCache.mode: ENABLE_SELECTIVE

啓用註解

@SpringBootApplication
@EnableCaching
public class JpaApplication {
	
	public static void main(String[] args){
        SpringApplication app = new SpringApplication(JpaApplication.class);
       // app.setWebEnvironment(false);
        app.run(args);
    }

}

應用緩存

@Entity
@Table(name = "bus_receiver")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class BusReceiverEntity {
 ....
}

在使用 jpa 的 findById方法進行查詢和 save進行更新數據的時候,hibernate 就會使用到緩存了 .

根據屬性來查詢是不使用緩存

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