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的靈活性,同時也失去面向對象意義和好處——一句話,不倫不類。那麼,面向對象和關係型模型有什麼不同,體現在哪裏呢?實際上兩者要面對的領域和要解決的問題是根本不同的:面向對象致力於解決計算機邏輯問題,而關係模型致力於解決數據的高效存取問題。我們不妨對比一下面向對象的概念原則和關係型數據庫的不同之處:
- 面向對象考慮的是對象的整個生命週期包括在對象的創建、持久化、狀態的改變和行爲等,對象的持久化只是對象的一種狀態,而面向關係型數據庫的概念則更關注數據的高效存儲和讀取;
- 面向對象更強調對象狀態的封裝性,對象封裝自己的狀態(或數據)不允許外部對象隨意修改,只暴露一些合法的行爲方法供外部對象調用;而關係型數據庫則是開放的,可以供用戶隨意讀取和修改關係,並可以和其他表任意的關聯(只要sql正確允許的情況下);
- 面向對象試圖爲動態的世界建模,他要描述的是世界的過程和規律,進而適應發展和變化,面向對象總是在變化中處理各種各樣的變化。而關係型模型爲靜態世界建模,它通過數據快照記錄了世界在某一時候的狀態,它是靜態的。
從上面兩者基本概念和思想的對比來看,可以得出結論hibernate和MyBatis兩個框架的側重點完全不同。所以我們就兩個框架選擇上,就需要根據不同的項目需求選擇不同的框架。在框架的使用中,也要考慮考慮框架的優勢和劣勢,揚長避短,發揮出框架的最大效用,才能真正的提高項目研發效率、完成項目的目標。但相反,如果使用Spring Data JPA和hibernate等ORM的框架而沒有以面向對象思想和方法去分析和設計系統,而是抱怨框架不能靈活操作sql查詢數據,那就是想讓狗去幫你拿耗子了。
那麼,話題再說回來,使用兩個框架時候的時候,也要注意最佳的步驟和流程。下面我們來分別討論一下,hibernate的一般使用步驟如下:
- 分析、抽象和歸納出系統中的業務概念,並梳理出各個業務概念之間的關係——創建概念模型
- 根據概念模型,進一步細化設計系統中的對象類以及類的依賴關係——創建設計模型
- 將設計好的類映射到數據庫的表和字段配置好
- hibernate可以根據配置信息自動生成數據庫表,這個時候也可以集中精力去梳理一下表關係,看看錶結構是否合理,並適當調整一下類和表的映射關係,重新生成表結構
完成以上步驟,基本上完成了體統中主要的業務概念類和表結構的設計工作,只是完成表結構設計的出發點事如何持久化系統的對象,同時兼顧數據庫表、字段、字段類型、表的關聯關係的合理性和合規性,而不是單純表設計。這兩者思考和關注點還是有很大差別的。另外,需要說明一點,這只是使用hibernate的最通用步驟,實際操作過程中還是需要根據具體項目情況來安排。
而MyBatis對於面向對象的概念強調比較少,更適用於靈活的對數據進行增、刪、改、查,所以在系統分析和設計過程中,要最大的發揮MyBatis的效用的話,一般使用步驟則與hibernate有所區別:
- 綜合整個系統分析出系統需要存儲的數據項目,並畫出E-R關係圖,設計表結構
- 根據上一步設計的表結構,創建數據庫、表
- 編寫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;
}
其中:
- @Entity 是一個必選的註解,聲明這個類對應了一個數據庫表。
- @Table(name = “region_code”) 是一個可選的註解。聲明瞭數據庫實體對應的表信息。包括表名稱、索引信息等。這裏聲明這個實體類對應的表名是 AUTH_USER。如果沒有指定,則表名和實體的名稱保持一致。
- @Id 註解聲明瞭實體唯一標識對應的屬性。
- @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> {
}
已經擁有下下面的功能
Dao 保存實體功能
Dao 保存實體刪除功能
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);
這個語句結構可以用下面的表來說明
自定義查詢
如果上述的情況還無法滿足需要。那麼我們就可以通過通過 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 就會使用到緩存了 .
根據屬性來查詢
是不使用緩存