說到咱們的web開發架構分層中,持久層是相對底層也是相對穩定的一層,奠基好根基後,咱們才能專一於業務邏輯和視圖開發。而自從ORM思想蔓延開來後,全自動ORM的Hibernate和半自動ORM的MyBatis幾乎壟斷了持久層(固然還有不少公司或者大牛本身封裝的框架,不過相對佔小部分),是發展過程當中比較主流的兩款持久層框架。前段時間也關注了不少有關領域驅動設計的內容,感受對前面的傳統架構分層衝擊較大,尤爲是業務邏輯層、持久層、實體ORM那塊,引入了許多新概練,一時間也遇到了不少困惑,網上搜索資料發現領域驅動其實由來已久,目前也應用不少,可是想要徹底掌握,並非一件容易事。固然本文跟領域驅動並沒有直接關聯,如今的問題是在面試題「Hibernate和MyBatis的區別」背景下,咱們在持久層還有第三種典型選擇嗎,實際上是有的,那就是本文的主角,Spring Data Jpa。 html
提及Jpa,其實它並非一個新概念,更不是說有了Spring Data Jpa纔出現,它是Java Persistence API的簡稱,中文名Java持久層API,它是一種規範,例如Hibernate框架即實現了這種規範,Spring Data Jpa中便集成了Hibernate的模塊。Spring Data,看名字很容易知道又是Spring系列的,除了Spring MVC在web層的成功,在持久層這塊Spring也想拿下,大有想一統江湖的勢頭。另外去深刻關注Spring Data內容,還會發現,不只僅是RDBMS,還有Spring Data Redis、Spring Data Mongodb等等...本文內容主要是針對關係型數據庫,本人在使用過程當中,最看好的仍是其在通用持久化方面的簡易封裝,基於層層的泛型繼承,咱們能夠省略大量的簡單增刪改查方法的編碼,另外提供的經過特定格式方法命名簡化方法定義過程也很特別和好用。下面就基於Spring Data編寫一個單獨的簡單持久層實例來展示其使用過程。 java
Eclipse + MySql + Mavenmysql
基於傳統幾大框架的開發目前已經相對成熟不少了,可是就實際工做開發環境中,筆者最強烈的感覺有一點,配置!配置文件實在太多了!特別是多工程組合集成的時候,漫天飛的XML和properties真是讓人頭大。因此建議如今學習過程當中必定要儘可能搞懂配置中每段配置語句的含義,哪一個參數有什麼做用,這樣進入實際開發中纔不會一時間無章可循。本文中配置儘可能給出註釋來闡述含義。web
在eclipse新建一個普通maven項目,quickstart類型,項目結構大體以下面試
pom.xml依賴以下spring
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!-- Spring 系列 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.9.1.RELEASE</version> </dependency> <!-- Hibernate系列 --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>4.3.11.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>4.3.11.Final</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> </dependencies>
第一步、配置文件(固然實際開發中,咱們不會將配置這樣集中在一個文件中,同時數據源配置等關鍵參數每每會經過properties文件去設置而後引入)sql
<?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:jpa="http://www.springframework.org/schema/data/jpa" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- Spring的bean掃描機制,會根據bean註解例如@Service等去實例化相應bean --> <context:component-scan base-package="com.sdj"></context:component-scan> <!-- 這句代碼是告訴jpa咱們的持久層接口都在哪些包下面 --> <jpa:repositories base-package="com.sdj.repository"/> <!-- 這裏使用dbcp配置數據源,能實現鏈接池功能 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://192.168.0.100:3306/sdj"/> <property name="username" value="root"/> <property name="password" value="abc123"/> </bean> <!-- 實體管理器工廠配置,關聯數據源,指定實體類所在包等等 --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan" value="com.sdj.domain"/> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="database" value="MYSQL"/> <property name="generateDdl" value="false"/> <property name="showSql" value="true"/> </bean> </property> <property name="jpaProperties"> <props> <prop key="hibernate.hbm2ddl.auto">none</prop> <!-- 若是想要自動生成數據表,這裏的配置是關鍵 --> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <!-- <prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop> --> <prop key="hibernate.connection.charSet">UTF-8</prop> <prop key="hibernate.format_sql">true</prop> </props> </property> </bean> <!--配置事務管理器--> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> <!--啓用事務註解來管理事務--> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
上面Spring配置文件中,實體管理器工廠配置是比較複雜的部分,下面具體到參數逐個介紹數據庫
dataSource,指定數據源apache
packagesToScan,與前面的component-scan相似,這裏也是一種掃描機制,不過前面是掃描bean,這裏既然是實體管理器,不難理解是掃描實體類,即指定實體類所在的包,這裏爲com.sdj.domain架構
jpaVendorAdapter,這裏對應Jpa持久化實現廠商Hibernate,同時指定其專用特性,包括以下
database,指定數據庫,這裏爲MYSQL
generateDdl,是否自動生成DDL,這裏爲false
showSql,是否在控制檯打印SQL語句,這點在調試時比較有用,能看到具體發送了哪些SQL
jpaProperties,jpa屬性設置,有以下
hibernate.hbm2ddl.auto,根據須要能夠設置爲validate | update | create | create-drop,固然也能夠設置爲none,設置的時候要當心,使用不到會有丟失數據的危險,例如這裏若是咱們想要根據實體類自動生成數據表,能夠設置爲update,不用的話這裏設置爲none
hibernate.dialect,指定數據庫方言,這裏爲MySql數據庫類型的
hibernate.connection.charSet,指定連接字符編碼,解決亂碼問題
hibernate.format_sql,前面指定控制檯會打印SQL,這裏是指定將其格式化打印,更加清晰美觀一點
第二步、實體類Person
package com.sdj.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="TB_PERSON") public class Person { private Integer id; //主鍵 private String name; //姓名 private String gender; //性別 private String addr; //地址 @Id @GeneratedValue(strategy=GenerationType.AUTO) public Integer getId() { return id; } @Column(name="NAME") public String getName() { return name; } public String getGender() { return gender; } public String getAddr() { return addr; } public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setGender(String gender) { this.gender = gender; } public void setAddr(String addr) { this.addr = addr; } }
若是仔細觀察實體中系列註解,會發現其來源是hibernate-jpa,這也是前面提到的hibernate實現jpa規範內容。經常使用註解解釋以下
@Entity,指定該類爲一個數據庫映射實體類、
@Table,指定與該實體類對應的數據表
@Id和@Column,都是爲實體類屬性關聯數據表字段,區別是Id是對應主鍵字段,另外還能夠指定其對應字段名(不指定默認與屬性名一致)、長度等等...若是不加這兩個註解也是會以屬性名默認關聯到數據庫,若是不想關聯能夠加上下面的@Transient
@GeneratedValue,指定主鍵生成策略
@Transient,表名該屬性並不是數據庫表的字段映射
@OneToMany、@ManyToOne、@ManyToMany等,均爲關聯映射註解系列,用於指定關聯關係,一對多、多對一等等
另外,@Id、@Column、@Transient等註解每每是加在屬性的get方法上。
第三步、持久層接口PersonRepository
package com.sdj.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import com.sdj.domain.Person; public interface PersonRepository extends JpaRepository<Person, Integer> { List<Person> findByName(String name); }
咱們發現這裏持久層代碼反而是最簡潔的,咱們的注意點以下:
1.在這個針對Person實體的dao接口中咱們並未定義常規通用的那些增刪改查等方法,只定義了一個特定的根據姓名查找人的方法,同時繼承了一個JpaRepository接口。
2.無論繼承接口也好,自定義方法也好,終究是接口,可是這裏咱們連實現類也沒有。
暫時先不走到業務邏輯Service層,一二三步走完,咱們這個持久層程序已經能夠運行了,下面咱們編寫測試方法來看看。
package com.test; import java.util.List; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.sdj.domain.Person; import com.sdj.repository.PersonRepository; public class TestSDJ { @Test public void testDB() { @SuppressWarnings("resource") ApplicationContext context = new ClassPathXmlApplicationContext("application-root.xml"); PersonRepository bean = (PersonRepository) context.getBean("personRepository"); List<Person> list = bean.findAll(); System.out.println(list); } }
運行測試類能夠看到控制檯輸出以下結果
首先是格式化打印出了SQL語句,能夠清楚看出來是查詢數據表全部記錄,下面則是輸出集合內容,這樣一來咱們成功查出了表中數據。
疑問點以下:
1.首先前面咱們定義PersonRepository是一個接口,而且沒有實現類,更沒有bean註解,那麼經過Spring咱們爲何能拿到這樣一個(接口名首字母小寫)名字的bean,這個bean又具體是什麼?
2.咱們的PersonRepository是一個接口,明沒有這樣的findAll()方法來查詢表中全部記錄,有人可能會很快想到其繼承了CrudRepository接口,那麼這個方法又是怎麼實現的?
JpaRepository這個接口是Spring Data提供的核心接口系列繼承鏈中的一環,主要有以下四個
Repository
public interface Repository<T, ID extends Serializable> { }
這是頂層接口,也是一個空接口,後面定義的泛型T對應咱們的實體類,ID對應實體類中主鍵字段對應屬性的類型,好比本文是數字主鍵類型Integer,這裏即對應Integer。
@NoRepositoryBean public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { <S extends T> S save(S entity); T findOne(ID id); Iterable<T> findAll(); ... }
CrudRepository繼承Repository接口,CRUD你們應該都不陌生,增長(Create)、讀取查詢(Read)、更新(Update)和刪除(Delete),這裏即新增了增刪改查等通用方法的定義。
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { ... }
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> { ...... }
而後PagingAndSortingRepository繼承CrudRepository,同時新增分頁和排序等相關方法
最後就是文中的JpaRepository繼承PagingAndSortingRepository,同時定義了系列經常使用方法。
不知不覺,咱們能夠看到JpaRepository這裏已經基本涵蓋了咱們基礎操做的相關方法集合了,例如測試類中的查找全部記錄方法。可是問題沒完,方法再多,終究是接口,既然是接口,你這些方法沒有實現的話咱們仍是沒法使用,可是咱們在測試中已經發現成功了,那麼它是怎麼實現的呢。
咱們來關注一下JpaRepository的實現類SimpleJpaRepository,源代碼類聲明段落以下
@Repository @Transactional(readOnly = true) public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> { ...... }
能夠發現,這個類中已經實現了前面四環的定義方法,終於齊集五環。
@Repository,Spring系列bean註解之一,告訴系統這是一個持久層的bean示例;
@NoRepositoryBean,與之相反,使用該註解標明,此接口不是一個Repository Bean,例如前面的JpaRepository等,都用上了該註解,可是咱們自定義的PersonRepository並無加,同時前面配置文件中 <jpa:repositories base-package="com.sdj.repository"/> ,隨後Spring Data會自動幫咱們實現該接口,並實例出bean,bean名字爲接口名首字母小寫後的字符串。
下面咱們在原先的測試類中加入以下代碼
ApplicationContext context = new ClassPathXmlApplicationContext("application-root.xml"); String[] beanNames = context.getBeanDefinitionNames(); for(String beanName:beanNames) { System.out.println(beanName); } ...
從新運行測試類,咱們除了能看到先前的輸出信息,在前面還會看到以下輸出
這一行行的都是Spring容器中現有的bean示例名稱,其中就有剛剛說到的"personRepository",因此咱們才能根據這個名稱拿到該bean示例。
而後咱們在Service層就能夠注入持久層bean去組合業務邏輯操做了,經過@Autowired注入,同時不要忘記Service類上的@Service註解。
@Service public class PersonServiceImpl implements PersonService { @Autowired PersonRepository personRepository; ..... }
這樣一來,咱們發如今常規的基礎操做範圍內,包括增刪改查、分頁查詢、排序等等,咱們不用編寫一個方法,也不用寫一條SQL語句,Spring Data Jpa都幫咱們封裝好了。但這只是一部份內容,例如前面接口中咱們不是定義了一個findByName(),有人可能會說了,難不成這也能幫咱們自動實現?就算能,那我再findByGenger()?到底能不能,這也引出了下面要說的內容。
在前面的測試類查詢方法改爲以下:
List<Person> list = bean.findByName("小明");
運行測試方法,控制檯輸出以下
看SQL語句發現的確是根據name姓名去查的,也成功查到告終果。
你們都知道增刪改查,一個查字一片天,簡單查也好,花樣查也好,它都是咱們持久層不可缺乏的部分。
除了前面提到了Spring Data對常規持久層操做的封裝,它另外還提供了一種經過格式化命名建立查詢的途徑,使得咱們在建立查詢方法的時候有更多簡單的實現方式和選擇。
這裏的格式具體體現示例以下:
查詢方法命名都是findBy****這樣的開頭,後面跟字段或者字段加關鍵字的組合
好比findByName等,至關於SQL:where name= ?都是規範的駝峯式命名。
好比findByNameAndGender,至關於SQL:where name= ? and gender = ?
這裏的and就是一個keyword關鍵字,相似的還有許多,能夠參考官方文檔連接點擊查看以下相關內容
也就是說符合上述命名規範的自定義方法,Spring Data一樣會幫助咱們去實現這些方法,這樣一來又方便了許多。
可是若是個人命名不符合規範,我是否必定要實現這個接口並重寫相關方法,這樣其實也是可行的,不過Spring Data還提供了@Query方法註解,供咱們自定義操做語句去實現它。例如上面的findByName方法,相似的咱們在接口中新建一個任意方法名的以下方法:
@Query("from Person p where p.name = (:name)")
List<Person> abc(@Param("name")String name);
而後在測試類中引用該方法,能實現與findByName相同的查詢效果。
這裏方法名給了個adc,同時方法上面註解定義了查詢語句,用過Hibernate的HQL語句的應該比較熟悉,這不由讓人想起,當初Hibernate用的人用起來都說好啊好啊,面向對象思惟啊,全自動啊,一句SQL都不用寫啊,真牛逼啊!而後全是HQL.....
Spring Data還有不少特性,若是有興趣也能夠繼續深刻學習一下。
目前來看,除了主流的MyBatis、Hibernate,Spring Data Jpa也有很多公司在使用,並且Spring Boot系列中基於Spring Data的數據訪問也有使用到,畢竟Spring系列。我的比較喜歡Spring Data Jpa的點在數據庫通用操做的封裝,以及這些便利命名方法,這使得咱們在業務邏輯相對簡單的狀況下,能節省不少代碼和時間。可是問題是咱們大多時候咱們要攻克去專一的每每是那些複雜的業務邏輯,而在這點上Spring Data Jpa並沒有明顯優點,莫非這就是在知乎上搜素"Spring Data怎麼樣"連話題都搜不出來的緣由....同時高封裝會不會引起低可控,如同之前用Hibernate,它自動幫助咱們發送SQL,可是簡便的同時不會有像MyBatis那樣看到本身寫SQL的透明度來得直觀,同時SQL優化等東西彷佛沒那麼好掌控,這些應該是項目技術選型初始大體都會考慮到的一些問題吧,效率、性能、對開發人羣的總體上手難度等等。總的來講,根據應用場景作出最適合項目的選擇纔是關鍵吧。