第11章 使用對(duì)象-關(guān)系映射持久化數(shù)據(jù)
本章內(nèi)容:
使用Spring和Hibernate
借助上下文Session茉继,編寫(xiě)不依賴于Spring的Repository
通過(guò)Spring使用JPA
借助Spring Data實(shí)現(xiàn)自動(dòng)化的JPA Repository
在數(shù)據(jù)持久化的世界中瞄崇,JDBC就像自行車(chē)。對(duì)于份內(nèi)的工作,它能很好地完成并且在一些特定的場(chǎng)景下表現(xiàn)出色咆瘟。但隨著應(yīng)用程序變得越來(lái)越復(fù)雜温鸽,對(duì)持久化的需求也變得更復(fù)雜。我們需要將對(duì)象的屬性映射到數(shù)據(jù)庫(kù)的列上秉版,并且需要自動(dòng)生成語(yǔ)句和查詢贤重,這樣我們就能從無(wú)休止的問(wèn)號(hào)字符串中解脫出來(lái)。此外清焕,我們還需要一些更復(fù)雜的特性:
延遲加載(Lazy loading):隨著我們的對(duì)象關(guān)系變得越來(lái)越復(fù)雜并蝗,有時(shí)候我們并不希望立即獲取完整的對(duì)象間關(guān)系。舉一個(gè)典型的例子秸妥,假設(shè)我們?cè)诓樵円唤MPurchaseOrder對(duì)象滚停,而每個(gè)對(duì)象中都包含一個(gè)LineItem對(duì)象集合。如果我們只關(guān)心PurchaseOrder的屬性粥惧,那查詢出LineItem的數(shù)據(jù)就毫無(wú)意義键畴。而且這可能是開(kāi)銷(xiāo)很大的操作。延遲加載允許我們只在需要的時(shí)候獲取數(shù)據(jù)突雪。
預(yù)先抓绕鹛琛(Eager fetching):這與延遲加載是相對(duì)的。借助于預(yù)先抓取咏删,我們可以使用一個(gè)查詢獲取完整的關(guān)聯(lián)對(duì)象惹想。如果我們需要PurchaseOrder及其關(guān)聯(lián)的LineItem對(duì)象,預(yù)先抓取的功能可以在一個(gè)操作中將它們?nèi)繌臄?shù)據(jù)庫(kù)中取出來(lái)督函,節(jié)省了多次查詢的成本嘀粱。
級(jí)聯(lián)(Cascading):有時(shí),更改數(shù)據(jù)庫(kù)中的表會(huì)同時(shí)修改其他表侨核〔菽拢回到我們訂購(gòu)單的例子中,當(dāng)刪除Order對(duì)象時(shí)搓译,我們希望同時(shí)在數(shù)據(jù)庫(kù)中刪除關(guān)聯(lián)的LineItem悲柱。
一些可用的框架提供了這樣的服務(wù),這些服務(wù)的通用名稱是對(duì)象/關(guān)系映射(object-relational mapping些己,ORM)豌鸡。在持久層使用ORM工具嘿般,可以節(jié)省數(shù)千行的代碼和大量的開(kāi)發(fā)時(shí)間。ORM工具能夠把你的注意力從容易出錯(cuò)的SQL代碼轉(zhuǎn)向如何實(shí)現(xiàn)應(yīng)用程序的真正需求涯冠。 Spring對(duì)多個(gè)持久化框架都提供了支持炉奴,包括Hibernate、iBATIS蛇更、Java數(shù)據(jù)對(duì)象(Java Data Objects瞻赶,JDO)以及Java持久化API(Java Persistence API,JPA)派任。與Spring對(duì)JDBC的支持那樣砸逊,Spring對(duì)ORM框架的支持提供了與這些框架的集成點(diǎn)以及一些附加的服務(wù):
支持集成Spring聲明式事務(wù);
透明的異常處理掌逛;
線程安全的师逸、輕量級(jí)的模板類;
DAO支持類豆混;
資源管理篓像。
本章沒(méi)有足夠的篇幅介紹Spring支持的全部ORM框架。其實(shí)這并不會(huì)有什么問(wèn)題皿伺,因?yàn)镾pring對(duì)不同ORM解決方案的支持是很相似的员辩。一旦掌握了Spring對(duì)某種ORM框架的支持后,你可以輕松地切換到另一個(gè)框架心傀。
在本章中屈暗,我們將會(huì)看到Spring如何與最常用的兩種ORM方案集成:Hibernate和JPA拆讯。同時(shí)還會(huì)通過(guò)Spring Data JPA了解一下Spring Data項(xiàng)目脂男。借助這種方式,我們不僅可以學(xué)習(xí)到如何借助Spring Data JPA移除JPA Repository中的樣板式代碼种呐,還能為下一章的如何將Spring Data用于無(wú)模式的存儲(chǔ)打下基礎(chǔ)宰翅。 讓我們先來(lái)看看Spring是如何為Hibernate提供支持的。
11.1 在Spring中集成Hibernate
Hibernate是在開(kāi)發(fā)者社區(qū)很流行的開(kāi)源持久化框架爽室。它不僅提供了基本的對(duì)象關(guān)系映射汁讼,還提供了ORM工具所應(yīng)具有的所有復(fù)雜功能,比如緩存阔墩、延遲加載嘿架、預(yù)先抓取以及分布式緩存。 在本章中啸箫,我們會(huì)關(guān)注Spring如何與Hibernate集成耸彪,而不會(huì)涉及太多Hibernate使用時(shí)的復(fù)雜細(xì)節(jié)。如果你需要了解更多關(guān)于Hibernate如何使用的知識(shí)忘苛,我推薦你閱讀Christian Bauer蝉娜、GavinKing和Gary Gregory撰寫(xiě)的《Java Persistence with Hibernate唱较,Second Edition》(Manning,2014召川,www.manning.com/bauer3/)或訪問(wèn)Hibernate的網(wǎng)站http://www.hibernate.org南缓。
關(guān)于HIbernate,我想說(shuō)荧呐,這是學(xué)生課本中的基礎(chǔ)框架知識(shí)汉形,在工作中基本被淘汰了,對(duì)于讀者瀏覽一下便好了倍阐。
使用Hibernate所需的主要接口是org.hibernate.Session获雕。Session接口提供了基本的數(shù)據(jù)訪問(wèn)功能,如保存收捣、更新届案、刪除以及從數(shù)據(jù)庫(kù)加載對(duì)象的功能。通過(guò)Hibernate的Session接口罢艾,應(yīng)用程序的Repository能夠滿足所有的持久化需求楣颠。
獲取Hibernate Session對(duì)象的標(biāo)準(zhǔn)方式是借助于Hibernate SessionFactory接口的實(shí)現(xiàn)類。除了一些其他的任務(wù)咐蚯,SessionFactory主要負(fù)責(zé)Hibernate Session的打開(kāi)童漩、關(guān)閉以及管理。
在Spring中春锋,我們要通過(guò)Spring的某一個(gè)Hibernate Session工廠bean來(lái)獲取HibernateSessionFactory矫膨。從3.1版本開(kāi)始,Spring提供了三個(gè)Session工廠bean供我們選擇:
org.springframework.orm.hibernate3.LocalSessionFactoryBean
org.springframework.orm.hibernate3.annotation.AnnotationSessionFactory
org.springframework.orm.hibernate4.LocalSessionFactoryBean
這些Session工廠bean都是Spring FactoryBean接口的實(shí)現(xiàn)期奔,它們會(huì)產(chǎn)生一個(gè)HibernateSessionFactory侧馅,它能夠裝配進(jìn)任何SessionFactory類型的屬性中。這樣的話呐萌,就能在應(yīng)用的Spring上下文中馁痴,與其他的bean一起配置Hibernate Session工廠。
至于選擇使用哪一個(gè)Session工廠肺孤,這取決于使用哪個(gè)版本的Hibernate以及你使用XML還是使用注解來(lái)定義對(duì)象-數(shù)據(jù)庫(kù)之間的映射關(guān)系罗晕。如果你使用Hibernate 3.2或更高版本(直到Hibernate 4.0,但不包含這個(gè)版本)并且使用XML定義映射的話赠堵,那么你需要定義Spring的org.springframework.orm.hibernate3包中的LocalSessionFactoryBean:
@Repository
public class ProductDaoImpl implements ProductDao {
// class body here...
}
<beans>
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
在配置LocalSessionFactoryBean時(shí)小渊,我們使用了三個(gè)屬性。屬性dataSource裝配了一個(gè)DataSource bean的引用茫叭。屬性mappingResources列出了一個(gè)或多個(gè)的Hibernate映射文件酬屉,在這些文件中定義了應(yīng)用程序的持久化策略。最后杂靶,hibernateProperties屬性配置了Hibernate如何進(jìn)行操作的細(xì)節(jié)梆惯。在本示例中酱鸭,我們配置Hibernate使用H2數(shù)據(jù)庫(kù)并且要按照H2Dialect來(lái)構(gòu)建SQL。
如果你更傾向于使用注解的方式來(lái)定義持久化垛吗,并且你還沒(méi)有使用Hibernate 4的話凹髓,那么需要使用AnnotationSessionFactoryBean來(lái)代替LocalSessionFactoryBean:
如果你使用Hibernate 4的話,那么就應(yīng)該使用org.springframework.orm.hibernate4中的LocalSessionFactoryBean怯屉。盡管它與Hibernate 3包中的LocalSessionFactoryBean使用了相同的名稱蔚舀,但是Spring 3.1新引入的這個(gè)Session工廠類似于Hibernate 3中LocalSessionFactoryBean和 AnnotationSessionFactoryBean的結(jié)合體。它有很多相同的屬性锨络,能夠支持基于XML的映射和基于注解的映射赌躺。如下的代碼展現(xiàn)了如何對(duì)它進(jìn)行配置,使其支持基于注解的映射: 在這兩個(gè)配置中羡儿,dataSource和hibernateProperties屬性都聲明了從哪里獲取數(shù)據(jù)庫(kù)連接以及要使用哪一種數(shù)據(jù)庫(kù)礼患。這里不再列出Hibernate配置文件,而是使用packagesToScan屬性告訴Spring掃描一個(gè)或多個(gè)包以查找域類掠归,這些類通過(guò)注解的方式表明要使用Hibernate進(jìn)行持久化缅叠,這些類可以使用的注解包括JPA的@Entity 或@MappedSuperclass以及Hibernate的@Entity。
如果愿意的話虏冻,你還可以使用annotatedClasses屬性來(lái)將應(yīng)用程序中所有的持久化類以全限定名的方式明確列出:
annotatedClasses屬性對(duì)于準(zhǔn)確指定少量的域類是不錯(cuò)的選擇肤粱。如果你有很多的域類并且不想將其全部列出,又或者你想自由地添加或移除域類而不想修改Spring配置的話厨相,那使用packagesToScan屬性是更合適的领曼。
在Spring應(yīng)用上下文中配置完Hibernate的Session工廠bean后,那我們就可以創(chuàng)建自己的Repository類了蛮穿。
省略以下內(nèi)容.........
11.2 Spring與Java持久化API
Java持久化API(Java Persistence API庶骄,JPA)誕生在EJB 2實(shí)體Bean的廢墟之上,并成為下一代Java持久化標(biāo)準(zhǔn)绪撵。JPA是基于POJO的持久化機(jī)制瓢姻,它從Hibernate和Java數(shù)據(jù)對(duì)象(Java Data Object祝蝠,JDO)上借鑒了很多理念并加入了Java 5注解的特性音诈。
在Spring 2.0版本中,Spring首次集成了JPA的功能绎狭。具有諷刺意味的是细溅,很多人批評(píng)(或贊賞)Spring顛覆了EJB。但是儡嘶,當(dāng)Spring支持JPA后喇聊,很多開(kāi)發(fā)人員都推薦在基于Spring的應(yīng)用程序中使用JPA實(shí)現(xiàn)持久化。實(shí)際上蹦狂,有些人還將Spring-JPA的組合稱為POJO開(kāi)發(fā)的夢(mèng)之隊(duì)誓篱。
在Spring中使用JPA的第一步是要在Spring應(yīng)用上下文中將實(shí)體管理器工廠(entity manager factory)按照bean的形式來(lái)進(jìn)行配置朋贬。
11.2.1 配置實(shí)體管理器工廠
簡(jiǎn)單來(lái)講,基于JPA的應(yīng)用程序需要使用EntityManagerFactory的實(shí)現(xiàn)類來(lái)獲取EntityManager實(shí)例窜骄。JPA定義了兩種類型的實(shí)體管理器:
應(yīng)用程序管理類型(Application-managed):當(dāng)應(yīng)用程序向?qū)嶓w管理器工廠直接請(qǐng)求實(shí)體管理器時(shí)锦募,工廠會(huì)創(chuàng)建一個(gè)實(shí)體管理器。在這種模式下邻遏,程序要負(fù)責(zé)打開(kāi)或關(guān)閉實(shí)體管理器并在事務(wù)中對(duì)其進(jìn)行控制糠亩。這種方式的實(shí)體管理器適合于不運(yùn)行在Java EE容器中的獨(dú)立應(yīng)用程序。
容器管理類型(Container-managed):實(shí)體管理器由Java EE創(chuàng)建和管理准验。應(yīng)用程序根本不與實(shí)體管理器工廠打交道赎线。相反,實(shí)體管理器直接通過(guò)注入或JNDI來(lái)獲取糊饱。容器負(fù)責(zé)配置實(shí)體管理器工廠垂寥。這種類型的實(shí)體管理器最適用于Java EE容器,在這種情況下會(huì)希望在persistence.xml指定的JPA配置之外保持一些自己對(duì)JPA的控制另锋。
以上的兩種實(shí)體管理器實(shí)現(xiàn)了同一個(gè)EntityManager接口矫废。關(guān)鍵的區(qū)別不在于EntityManager本身,而是在于EntityManager的創(chuàng)建和管理方式砰蠢。應(yīng)用程序管理類型的EntityManager是由EntityManagerFactory創(chuàng)建的蓖扑,而后者是通過(guò)PersistenceProvider的createEntityManagerFactory()方法得到的。與此相對(duì)台舱,容器管理類型的Entity ManagerFactory是通過(guò)PersistenceProvider的createContainerEntityManager Factory()方法獲得的律杠。
這對(duì)想使用JPA的Spring開(kāi)發(fā)者來(lái)說(shuō)又意味著什么呢?其實(shí)這并沒(méi)太大的關(guān)系竞惋。不管你希望使用哪種EntityManagerFactory柜去,Spring都會(huì)負(fù)責(zé)管理EntityManager。如果你使用的是應(yīng)用程序管理類型的實(shí)體管理器拆宛,Spring承擔(dān)了應(yīng)用程序的角色并以透明的方式處理EntityManager嗓奢。在容器管理的場(chǎng)景下,Spring會(huì)擔(dān)當(dāng)容器的角色浑厚。
這兩種實(shí)體管理器工廠分別由對(duì)應(yīng)的Spring工廠Bean創(chuàng)建:
LocalEntityManagerFactoryBean生成應(yīng)用程序管理類型的EntityManagerFactory股耽;
LocalContainerEntityManagerFactoryBean生成容器管理類型的EntityManagerFactory。
需要說(shuō)明的是钳幅,選擇應(yīng)用程序管理類型的還是容器管理類型的EntityManager Factory物蝙,對(duì)于基于Spring的應(yīng)用程序來(lái)講是完全透明的。當(dāng)組合使用Spring和JPA時(shí)敢艰,處理EntityManagerFactory的復(fù)雜細(xì)節(jié)被隱藏了起來(lái)诬乞,數(shù)據(jù)訪問(wèn)代碼只需關(guān)注它們的真正目標(biāo)即可,也就是數(shù)據(jù)訪問(wèn)。
應(yīng)用程序管理類型和容器管理類型的實(shí)體管理器工廠之間唯一值得關(guān)注的區(qū)別是在Spring應(yīng)用上下文中如何進(jìn)行配置震嫉。讓我們先看看如何在Spring中配置應(yīng)用程序管理類型的LocalEntityManagerFactoryBean森瘪,然后再看看如何配置容器管理類型的LocalContainerEntityManagerFactoryBean。
配置應(yīng)用程序管理類型的JPA對(duì)于應(yīng)用程序管理類型的實(shí)體管理器工廠來(lái)說(shuō)票堵,它絕大部分配置信息來(lái)源于一個(gè)名為 persistence.xml的配置文件柜砾。這個(gè)文件必須位于類路徑下的META-INF目錄下。
persistence.xml的作用在于定義一個(gè)或多個(gè)持久化單元换衬。持久化單元是同一個(gè)數(shù)據(jù)源下的一個(gè)或多個(gè)持久化類痰驱。簡(jiǎn)單來(lái)講,persistence.xml列出了一個(gè)或多個(gè)的持久化類以及一些其他的配置如數(shù)據(jù)源和基于XML的配置文件瞳浦。如下是一個(gè)典型的persistence.xml文件担映,它是用于Spittr應(yīng)用程序的:
<persistence
xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
<persistence-unit name="spitterPU">
<class>com.web.spittr.Spitter</class>
<class>com.web.spittr.Spittle</class>
<properties>
<property name="toplink.jdbc.url" value="jdbc:hsqldb:hsql://localhost/spitter" />
<property name="toplink.jdbc.user" value="sa" />
<property name="toplink.jdbc.password" value="123456" />
</properties>
</persistence-unit>
</persistence>
HSQLDB(HyperSQL DataBase)是一個(gè)開(kāi)放源代碼的JAVA數(shù)據(jù)庫(kù),其具有標(biāo)準(zhǔn)的SQL語(yǔ)法和JAVA接口叫潦,它可以自由使用和分發(fā)蝇完,非常簡(jiǎn)潔和快速的
容器管理的JPA:
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabase(Database.MYSQL);
adapter.setShowSql(true);
adapter.setGenerateDdl(false);
adapter.setGenerateDdl(false);
adapter.setDatabasePlatform("org.hibernate.dialect.MYSQLDialect");
return adapter;
}
?
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(DataSource dataSource,
JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
emfb.setDataSource(dataSource);
emfb.setJpaVendorAdapter(jpaVendorAdapter);
// emfb.setPackagesToScan("com.web.spittr.data");
return emfb;
}
11.2.2 編寫(xiě)基于JPA的Repository
import com.web.spittr.Spittle;
import com.web.spittr.data.SpittleRepository;
?
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import java.util.List;
?
/**
* @description:
* @author: Shenshuaihu
* @version: 1.0
* @data: 2019-05-06 10:56
*/
public class JpaSpitterRepository implements SpittleRepository {
?
@PersistenceUnit
private EntityManagerFactory emf;
?
@Override
public List<Spitter> findByUsername(String username) {
return (List<Spitter>) emf.createEntityManager().find(Spitter.class, username);
}
?
@Override
public Spitter findById(Long id) {
return emf.createEntityManager().find(Spitter.class, id);
}
?
}
11.3 借助Spring Data實(shí)現(xiàn)自動(dòng)化的JPA Repository
借助Spring Data,以接口定義的方式創(chuàng)建Repository
import com.web.spittr.Spitter;
import org.springframework.data.jpa.repository.JpaRepository;
?
public interface SpitterReposity extends JpaRepository<Spitter, Long> {
?
Spitter findByUsername(String username);
}
編寫(xiě)Spring Data JPA Repository的關(guān)鍵在于要從一組接口中挑選一個(gè)進(jìn)行擴(kuò)展矗蕊。這里短蜕,SpitterRepository擴(kuò)展了Spring Data JPA的JpaRepository(稍后,我會(huì)介紹幾個(gè)其他的接口)傻咖。
通過(guò)這種方式朋魔,JpaRepository進(jìn)行了參數(shù)化,所以它就能知道這是一個(gè)用來(lái)持久化Spitter對(duì)象的Repository卿操,并且Spitter的ID類型為L(zhǎng)ong警检。另外,它還會(huì)繼承18個(gè)執(zhí)行持久化操作的通用方法害淤,如保存Spitter扇雕、刪除Spitter以及根據(jù)ID查詢Spitter。
此時(shí)窥摄,你可能會(huì)想下一步就該編寫(xiě)一個(gè)類實(shí)現(xiàn)SpitterRepository和它的18個(gè)方法了镶奉。如果真的是這樣的話,那本章就會(huì)變得乏味無(wú)聊了崭放。其實(shí)哨苛,我們根本不需要編寫(xiě)SpitterRepository的任何實(shí)現(xiàn)類,相反莹菱,我們讓Spring Data來(lái)為我們做這件事請(qǐng)移国。我們所需要做的就是對(duì)它提出要求。
為了要求Spring Data創(chuàng)建SpitterRepository的實(shí)現(xiàn)道伟,我們需要在Spring配置中添加一個(gè)元素。
使用Java配置的話,那就不需要使用jpa:repositories元素了蜜徽,而是要在Java配置類上添加@EnableJpaRepositories注解祝懂。如下就是一個(gè)Java配置類,它使用了@EnableJpaRepositories注解拘鞋,并且會(huì)掃描com.web.spittr包:
@Configuration
@EnableJpaRepositories("com.web.spittr")
public class SpringDataJpaConfig {
......
}
讓我們回到SpitterRepository接口砚蓬,它擴(kuò)展自JpaRepository,而JpaRepository又?jǐn)U展自Repository標(biāo)記接口(雖然是間接的)盆色。因此灰蛙,SpitterRepository就傳遞性地?cái)U(kuò)展了Repository接口,也就是Repository掃描時(shí)所要查找的接口隔躲。當(dāng)Spring Data找到它后摩梧,就會(huì)創(chuàng)建SpitterRepository的實(shí)現(xiàn)類,其中包含了繼承自JpaRepository宣旱、PagingAndSortingRepository和CrudRepository的18個(gè)方法仅父。
很重要的一點(diǎn)在于Repository的實(shí)現(xiàn)類是在應(yīng)用啟動(dòng)的時(shí)候生成的,也就是Spring的應(yīng)用上下文創(chuàng)建的時(shí)候浑吟。它并不是在構(gòu)建時(shí)通過(guò)代碼生成技術(shù)產(chǎn)生的笙纤,也不是接口方法調(diào)用時(shí)才創(chuàng)建的。
很漂亮的技術(shù)组力,對(duì)吧省容?
Spring Data JPA很棒的一點(diǎn)在于它能為Spitter對(duì)象提供18個(gè)便利的方法來(lái)進(jìn)行通用的JPA操作,而無(wú)需你編寫(xiě)任何持久化代碼燎字。但是蓉冈,如果你的需求超過(guò)了它所提供的這18個(gè)方法的話,該怎么辦呢轩触?幸好寞酿,Spring Data JPA提供了幾種方式來(lái)為Repository添加自定義的方法。讓我們看一下如何為Spring Data JPA編寫(xiě)自定義的查詢方法脱柱。
11.3.1 定義查詢方法
現(xiàn)在伐弹,SpitterRepository需要完成的一項(xiàng)功能是根據(jù)給定的username查找Spitter對(duì)象。比如榨为,我們將SpitterRepository接口修改為如下所示的樣子:
public interface SpitterRepositoy extends JpaRepository<Spitter, Long> {
Spitter findByUsername(String username);
}
這個(gè)新的findByUserName()非常簡(jiǎn)單惨好,但是足以滿足我們的需求。現(xiàn)在随闺,該如何讓SpringData JPA提供這個(gè)方法的實(shí)現(xiàn)呢日川?
實(shí)際上,我們并不需要實(shí)現(xiàn)findByUsername()矩乐。方法簽名已經(jīng)告訴Spring Data JPA足夠的信息來(lái)創(chuàng)建這個(gè)方法的實(shí)現(xiàn)了龄句。
當(dāng)創(chuàng)建Repository實(shí)現(xiàn)的時(shí)候回论,Spring Data會(huì)檢查Repository接口的所有方法,解析方法的名稱分歇,并基于被持久化的對(duì)象來(lái)試圖推測(cè)方法的目的傀蓉。本質(zhì)上,Spring Data定義了一組小型的領(lǐng)域特定語(yǔ)言(domain-specific language 职抡,DSL)葬燎,在這里,持久化的細(xì)節(jié)都是通過(guò)Repository方法的簽名來(lái)描述的缚甩。
Spring Data能夠知道這個(gè)方法是要查找Spitter的谱净,因?yàn)槲覀兪褂肧pitter對(duì)JpaRepository進(jìn)行了參數(shù)化。方法名findByUsername確定該方法需要根據(jù)username屬性相匹配來(lái)查找Spitter擅威,而username是作為參數(shù)傳遞到方法中來(lái)的壕探。另外,因?yàn)樵诜椒ê灻卸x了該方法要返回一個(gè)Spitter對(duì)象裕寨,而不是一個(gè)集合浩蓉,因此它只會(huì)查找一個(gè) username屬性匹配的Spitter。
findByUsername()方法非常簡(jiǎn)單宾袜,但是Spring Data也能處理更加有意思的方法名稱捻艳。
Repository方法是由一個(gè)動(dòng)詞、一個(gè)可選的主題(Subject)庆猫、關(guān)鍵詞By以及一個(gè)斷言所組成认轨。
在findByUsername()這個(gè)樣例中,動(dòng)詞是find月培,斷言是Username嘁字,主題并沒(méi)有指定,暗含的主題是Spitter杉畜。
作為編寫(xiě)Repository方法名稱的樣例纪蜒,我們參照名為readSpitterByFirstnameOrLastname()的方法,看一下方法中的各個(gè)部分是如何映射的此叠。圖11.1展現(xiàn)了這個(gè)方法是如何拆分的纯续。
我們可以看到,這里的動(dòng)詞是read灭袁,與之前樣例中的find有所差別猬错。Spring Data允許在方法名中使用四種動(dòng)詞:get、read茸歧、find和count倦炒。其中,動(dòng)詞get软瞎、read和find是同義的逢唤,這三個(gè)動(dòng)詞對(duì)應(yīng)的Repository方法都會(huì)查詢數(shù)據(jù)并返回對(duì)象拉讯。而動(dòng)詞count則會(huì)返回匹配對(duì)象的數(shù)量,而不是對(duì)象本身智玻。
readSpittersByFirstNameOrderByLastNameAsc
read | Spitters | ByFirstNameOrderByLastNameAsc
查詢動(dòng)詞 | 主題 | 斷言
Repository方法的命名遵循一種模式遂唧,有助于Spring Data生成針對(duì)數(shù)據(jù)庫(kù)的查詢
Repository方法的主題是可選的芙代。它的主要目的是讓你在命名方法的時(shí)候吊奢,有更多的靈活性。 如果你更愿意將方法稱為readSpittersByFirstnameOrLastname()而不是readByFirstnameOrLastname()的話纹烹,那么你盡可以這么做页滚。
對(duì)于大部分場(chǎng)景來(lái)說(shuō),主題會(huì)被省略掉铺呵。readSpittersByFirstnameOrLastname()與readPuppiesByFirstnameOrLastname()并沒(méi)有什么差別裹驰,它們與readThoseThingsWeWantByFirstnameOrLastname()同樣沒(méi)有什么區(qū)別。要查詢的對(duì)象類型是通 過(guò)如何參數(shù)化JpaRepository接口來(lái)確定的片挂,而不是方法名稱中的主題幻林。
在省略主題的時(shí)候,有一種例外情況音念。如果主題的名稱以Distinct開(kāi)頭的話沪饺,那么在生成查詢的時(shí)候會(huì)確保所返回結(jié)果集中不包含重復(fù)記錄。
斷言是方法名稱中最為有意思的部分闷愤,它指定了限制結(jié)果集的屬性整葡。
在readByFirstnameOrLastname()這個(gè)樣例中,會(huì)通過(guò)firstname屬性或lastname屬性的值來(lái)限制結(jié)果讥脐。
在斷言中遭居,會(huì)有一個(gè)或多個(gè)限制結(jié)果的條件。每個(gè)條件必須引用一個(gè)屬性旬渠,并且還可以指定一種比較操作俱萍。如果省略比較操作符的話,那么這暗指是一種相等比較操作告丢。不過(guò)枪蘑,我們也可以選擇其他的比較操作,包括如下的種類:
IsAfter芋齿、After腥寇、IsGreaterThan、GreaterThan
IsGreaterThanEqual觅捆、GreaterThanEqual
IsBefore赦役、Before、IsLessThan栅炒、LessThan
IsLessThanEqual掂摔、LessThanEqual
IsBetween术羔、Between
IsNull、Null
IsNotNull乙漓、NotNull
IsIn级历、In
IsNotIn、NotIn
IsStartingWith叭披、StartingWith寥殖、StartsWith
IsEndingWith、EndingWith涩蜘、EndsWith
IsContaining嚼贡、Containing、Contains
IsLike同诫、Like
IsNotLike粤策、NotLike
IsTrue、True
IsFalse误窖、False
Is叮盘、Equals
IsNot、Not
要對(duì)比的屬性值就是方法的參數(shù)霹俺。完整的方法簽名如下所示:
List<Spitter> readSpittersByFirstNameOrLastNameOrderByLastNameAsc(String firstName, String lastName);
要處理String類型的屬性時(shí)柔吼,條件中可能還會(huì)包含IgnoringCase或IgnoresCase,這樣在執(zhí)行對(duì)比的時(shí)候就會(huì)不再考慮字符是大寫(xiě)還是小寫(xiě)吭服。例如嚷堡,要在firstname和lastname屬性上忽略大小寫(xiě),那么可以將方法簽名改成如下的形式:
需要注意艇棕,IgnoringCase和IgnoresCase是同義的蝌戒,你可以隨意挑選一個(gè)最合適的。作為IgnoringCase/IgnoresCase的替代方案沼琉,我們還可以在所有條件的后面添加AllIgnoringCase或AllIgnoresCase北苟,這樣它就會(huì)忽略所有條件的大小寫(xiě):
沒(méi)有找到提示,可能被廢棄了
注意打瘪,參數(shù)的名稱是無(wú)關(guān)緊要的友鼻,但是它們的順序必須要與方法名稱中的操作符相匹配。
最后闺骚,我們還可以在方法名稱的結(jié)尾處添加OrderBy彩扔,實(shí)現(xiàn)結(jié)果集排序。例如僻爽,我們可以按照l(shuí)astname屬性升序排列結(jié)果集:
List<Spitter> readSpittersByFirstNameOrLastNameOrderByLastNameAsc(String firstName, String lastName);
如果要根據(jù)多個(gè)屬性排序的話虫碉,只需將其依序添加到OrderBy中即可。例如胸梆,下面的樣例中敦捧,首先會(huì)根據(jù)lastname升序排列须板,然后根據(jù)firstname屬性降序排列:
List<Spitter> readSpittersByFirstNameOrderByFirstNameAscLastNameDesc(String firstName, String lastName);
可以看到,條件部分是通過(guò)And或者Or進(jìn)行分割的兢卵。
List<Pet> findPetsByBreedIn(List<String> breed)
int countProductsByDiscontinuedTrue()
List<Order> findByShippingDateBetween(Date start, Date end)
我們只是初步體驗(yàn)了所能聲明的方法種類习瑰,Spring Data JPA會(huì)為我們實(shí)現(xiàn)這些方法。現(xiàn)在秽荤,我們只需知道通過(guò)使用屬性名和關(guān)鍵字構(gòu)建Repository方法簽名甜奄,就能讓Spring Data JPA生成方法實(shí)現(xiàn),完成幾乎所有能夠想象到的查詢王滤。
不過(guò)贺嫂,Spring Data這個(gè)小型的DSL依舊有其局限性滓鸠,有時(shí)候通過(guò)方法名稱表達(dá)預(yù)期的查詢很煩瑣雁乡,甚至無(wú)法實(shí)現(xiàn)。如果遇到這種情形的話糜俗,Spring Data能夠讓我們通過(guò)@Query注解來(lái)解決問(wèn)題踱稍。
11.3.2 聲明自定義查詢
假設(shè)我們想要?jiǎng)?chuàng)建一個(gè)Repository方法,用來(lái)查找E-mail地址是Gmail郵箱的Spitter悠抹。有一種方式就是定義一個(gè)findByEmailLike()方法珠月,然后每次想查找Gmail用戶的時(shí)候就將“%gmail.com”傳遞進(jìn)來(lái)。不過(guò)楔敌,更好的方案是定義一個(gè)更加便利的findAllGmailSpitters()方法啤挎,這樣的話,就不用將Email地址的一部分傳遞進(jìn)來(lái)了:
List<Spitter> findAllByGEmail();
不過(guò)卵凑,這個(gè)方法并不符合Spring Data的方法命名約定庆聘。當(dāng)Spring Data試圖生成這個(gè)方法的實(shí)現(xiàn)時(shí),無(wú)法將方法名的內(nèi)容與Spitter元模型進(jìn)行匹配勺卢,因此會(huì)拋出異常伙判。 如果所需的數(shù)據(jù)無(wú)法通過(guò)方法名稱進(jìn)行恰當(dāng)?shù)孛枋觯敲次覀兛梢允褂聾Query注解黑忱,為Spring Data提供要執(zhí)行的查詢宴抚。對(duì)于findAllGmailSpitters()方法,我們可以按照如下的方式來(lái)使用@Query注解:
@Query("select s from spitter s where email like '%gmail.com'")
List<Spitter> findAllByGEmail();
我們依然不需要編寫(xiě)findAllGmailSpitters()方法的實(shí)現(xiàn)甫煞,只需提供查詢即可菇曲,讓SpringData JPA知道如何實(shí)現(xiàn)這個(gè)方法。 可以看到抚吠,當(dāng)使用方法命名約定很難表達(dá)預(yù)期的查詢時(shí)常潮,@Query注解能夠發(fā)揮作用。如果按照命名約定埃跷,方法的名稱特別長(zhǎng)的時(shí)候蕊玷,也可以使用這個(gè)注解邮利。例如,考慮如下的查詢方法: 這真的是一個(gè)方法的名稱垃帅!我不得不在返回類型后將其斷開(kāi)延届,這樣才能適應(yīng)本書(shū)頁(yè)面的寬度。
我承認(rèn)這是一個(gè)有點(diǎn)牽強(qiáng)的例子贸诚。但在現(xiàn)實(shí)世界中方庭,確實(shí)存在這樣的需求,使用Repository方法所執(zhí)行的查詢會(huì)得到一個(gè)很長(zhǎng)的方法名酱固。在這種情況下械念,你最好使用一個(gè)較短的方法名,并使用@Query來(lái)指定該方法要如何查詢數(shù)據(jù)庫(kù)运悲。 對(duì)于Spring Data JPA的接口來(lái)說(shuō)龄减,@Query是一種添加自定義查詢的便利方式。但是班眯,它僅限于單個(gè)JPA查詢希停。
混合自定義的功能 省略
11.4 小結(jié)
對(duì)于很多應(yīng)用來(lái)講,關(guān)系型數(shù)據(jù)庫(kù)是主流的數(shù)據(jù)存儲(chǔ)形式署隘,并且這種情況已經(jīng)持續(xù)了很多年宠能。使用JDBC并且將對(duì)象映射為數(shù)據(jù)庫(kù)表是很煩瑣乏味的事情,像Hibernate和JPA這樣的ORM方案能夠讓我們以更加聲明式的模型實(shí)現(xiàn)數(shù)據(jù)持久化磁餐。盡管Spring沒(méi)有為ORM提供直接的支持违崇,但是它能夠與多種流行的ORM方案集成,包括Hibernate與Java持久化API诊霹。
在本章中羞延,我們看到了如何在Spring應(yīng)用中使用Hibernate的上下文Session,這樣我們的Repository就能包含很少甚至不包含Spring相關(guān)的代碼畅哑。與之類似肴楷,我們還看到了如何將EntityManagerFactory或EntityManager注入到Repository實(shí)現(xiàn)中,從而實(shí)現(xiàn)不依賴于Spring的JPA Repository荠呐。
我們稍后初步了解了Spring Data赛蔫,在這個(gè)過(guò)程中,只需聲明JPA Repository接口即可泥张,讓SpringData JPA在運(yùn)行時(shí)自動(dòng)生成這些接口的實(shí)現(xiàn)呵恢。當(dāng)我們需要的Repository方法超出了Spring DataJPA所提供的功能時(shí),可以借助@Query注解以及編寫(xiě)自定義的Repository方法來(lái)實(shí)現(xiàn)媚创。
但是渗钉,對(duì)于Spring Data的整體功能來(lái)說(shuō),我們只是接觸到了皮毛。在下一章中鳄橘,我們將會(huì)更加深入地學(xué)習(xí)Spring Data的方法命名DSL声离,以及Spring Data如何為關(guān)系型數(shù)據(jù)庫(kù)以外的領(lǐng)域帶來(lái)幫助。也就是說(shuō):我們將會(huì)看到Spring Data如何支持新興的NoSQL數(shù)據(jù)庫(kù)瘫怜,這些數(shù)據(jù)庫(kù)在最近幾年變得越來(lái)越流行术徊。
補(bǔ)充說(shuō)明
作者本章內(nèi)容粗略的講述了JPA相關(guān)的數(shù)據(jù)庫(kù)操作拒炎,對(duì)于我很遺憾的是沒(méi)有跑起來(lái)邑狸,
WARN : org.springframework.web.context.support.AnnotationConfigWebApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'emf' defined in class path resource [com/web/spittr/config/database/SpringDataJpaConfig.class]: Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/context/index/CandidateComponentsIndexLoader
ERROR: org.springframework.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'emf' defined in class path resource [com/web/spittr/config/database/SpringDataJpaConfig.class]: Initialization of bean failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/context/index/CandidateComponentsIndexLoader
一直不能解決問(wèn)題馒铃,只怪是使用的Java配置沒(méi)有選擇xml或者是springboot簡(jiǎn)化的方式砾肺,不過(guò)一些查詢方式還是聯(lián)系了一下,在springboot中整合jpa是相當(dāng)簡(jiǎn)單的膛薛,之前看廖師兄的視頻大多都是jpa寂屏,但現(xiàn)在常用的還是mybatis词顾,因?yàn)榘胱詣?dòng)的炸裆,發(fā)雜的SQL可以獨(dú)自自己寫(xiě)垃它,不會(huì)被框架限制。
2019/5/7晚于成都