版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
SpringData實(shí)戰(zhàn)(簡(jiǎn)化數(shù)據(jù)庫(kù)訪問(wèn))目錄\h第一部分背景知識(shí)\h第1章SpringData項(xiàng)目\h1.1為Spring開(kāi)發(fā)人員提供的NoSQL數(shù)據(jù)訪問(wèn)功能\h1.2主題概述\h1.3領(lǐng)域\h1.4示例代碼\h1.4.1將源碼導(dǎo)入到IDE\h第2章Repository:便利的數(shù)據(jù)訪問(wèn)層\h2.1快速入門(mén)\h2.2定義查詢方法\h2.2.1查找查詢的策略\h2.2.2衍生查詢\h2.2.3分頁(yè)和排序\h2.3定義Repository\h2.3.1調(diào)整Repository接口\h2.3.2手動(dòng)實(shí)現(xiàn)Repository方法\h2.4IDE集成\h2.4.1IntelliIDEA\h第3章使用Querydsl實(shí)現(xiàn)類(lèi)型安全的查詢\h3.1Querydsl簡(jiǎn)介\h3.2生成查詢?cè)P蚛h3.2.1構(gòu)建系統(tǒng)集成\h3.2.2所支持的注解處理器\h3.2.3使用Querydsl對(duì)存儲(chǔ)進(jìn)行查詢\h3.3集成SpringDataRepository\h3.3.1執(zhí)行斷言\h3.3.2手動(dòng)實(shí)現(xiàn)Repository\h第二部分關(guān)系型數(shù)據(jù)庫(kù)\h第4章JPARepository\h4.1示例工程\h4.2傳統(tǒng)方式\h4.3啟動(dòng)示例代碼\h4.4使用SpringDataRepository\h4.4.1事務(wù)性\h4.4.2Repository與Querydsl集成\h第5章借助QuerydslSQL實(shí)現(xiàn)類(lèi)型安全的JDBC編程\h5.1示例工程與搭建過(guò)程\h5.1.1HyperSQL數(shù)據(jù)庫(kù)\h5.1.2Querydsl的SQL模塊\h5.1.3構(gòu)建系統(tǒng)集成\h5.1.4數(shù)據(jù)庫(kù)模式\h5.1.5示例工程的領(lǐng)域?qū)崿F(xiàn)\h5.2QueryDslJdbcTemplate\h5.3執(zhí)行查詢\h5.3.1Repository實(shí)現(xiàn)起步\h5.3.2查詢單個(gè)對(duì)象\h5.3.3OneToManyResultSetExtractor抽象類(lèi)\h5.3.4CustomerListExtractor實(shí)現(xiàn)\h5.3.5RowMapper的實(shí)現(xiàn)類(lèi)\h5.3.6查詢對(duì)象列表\h5.4插入、更新和刪除操作\h5.4.1使用SQLInsertClause進(jìn)行插入操作\h5.4.2使用SQLUpdateClause進(jìn)行更新操作\h5.4.3使用SQLDeleteClause進(jìn)行刪除行操作\h第三部分NoSQL\h第6章MongoDB:文檔存儲(chǔ)\h6.1MongoDB簡(jiǎn)介\h6.1.1設(shè)置MongoDB\h6.1.2使用MongoDBShell\h6.1.3MongoDBJava驅(qū)動(dòng)\h6.2使用Spring命名空間搭建基礎(chǔ)設(shè)施\h6.3映射模塊\h6.3.1領(lǐng)域模型\h6.3.2搭建映射的基礎(chǔ)設(shè)施\h6.3.3索引\h6.3.4自定義轉(zhuǎn)換\h6.4MongoTemplate\h6.5MongoRepository\h6.5.1搭建基礎(chǔ)設(shè)施\h6.5.2Repository詳解\h6.5.3MongoQuerydsl集成\h第7章Neo4j:圖數(shù)據(jù)庫(kù)\h7.1圖數(shù)據(jù)庫(kù)\h7.2Neo4j\h7.3SpringDataNeo4j概覽\h7.4將領(lǐng)域建模為圖\h7.5使用SpringDataNeo4j持久化領(lǐng)域?qū)ο骪h7.5.1Neo4jTemplate\h7.6組合發(fā)揮圖和Repository的威力\h7.6.1基本的圖Repository操作\h7.6.2衍生和基于注解的查找方法\h7.7示例領(lǐng)域模型中的高級(jí)圖用例\h7.7.1單個(gè)節(jié)點(diǎn)的多重角色\h7.7.2以產(chǎn)品分類(lèi)和標(biāo)簽為例講解圖中的索引\h7.7.3利用類(lèi)似的興趣(協(xié)同過(guò)濾)\h7.7.4推薦\h7.8事務(wù)、實(shí)體生命周期以及抓取策略\h7.9高級(jí)映射模型\h7.10使用Neo4j服務(wù)器\h7.11從這里繼續(xù)學(xué)習(xí)\h第8章Redis:鍵/值存儲(chǔ)\h8.1Redis概述\h8.1.1搭建Redis\h8.1.2使用RedisShell\h8.2連接到Redis\h8.3對(duì)象轉(zhuǎn)換\h8.4對(duì)象映射\h8.5原子級(jí)計(jì)數(shù)器\h8.6發(fā)布/訂閱功能\h8.6.1對(duì)信息進(jìn)行監(jiān)聽(tīng)和響應(yīng)\h8.6.2在Redis中使用Spring的緩存抽象\h第四部分快速應(yīng)用開(kāi)發(fā)\h第9章使用SpringRoo實(shí)現(xiàn)持久層\h9.1Roo簡(jiǎn)介\h9.2Roo的持久層\h9.3快速起步\h9.3.1借助命令行使用Roo\h9.3.2借助SpringToolSuite使用Roo\h9.4SpringRooJPARepository示例\h9.4.1創(chuàng)建工程\h9.4.2搭建JPA持久化\h9.4.3創(chuàng)建實(shí)體\h9.4.4定義Repository\h9.4.5創(chuàng)建Web層\h9.4.6運(yùn)行示例\h9.5SpringMongoDBJPARepository的例子\h9.5.1創(chuàng)建工程\h9.5.2搭建MongoDB持久化\h9.5.3創(chuàng)建實(shí)體\h9.5.4定義Repository\h9.5.5創(chuàng)建Web層\h9.5.6運(yùn)行示例\h第10章RESTRepository導(dǎo)出器\h10.1示例工程\h10.1.1與Rest導(dǎo)出器進(jìn)行交互\h10.1.2訪問(wèn)Product\h10.1.3訪問(wèn)Customer\h10.1.4訪問(wèn)Order\h第五部分大數(shù)據(jù)\h第11章SpringforApacheHadoop\h11.1Hadoop開(kāi)發(fā)面臨的挑戰(zhàn)\h11.2HelloWorld\h11.3揭秘HelloWorld\h11.4使用SpringforApacheHadoop的HelloWorld\h11.5在JVM中編寫(xiě)HDFS腳本\h11.6結(jié)合HDFS腳本與Job提交\h11.7Job調(diào)度\h11.7.1使用TaskScheduler調(diào)度MapReduceJob\h11.7.2使用Quartz調(diào)度MapReduceJob\h第12章使用Hadoop分析數(shù)據(jù)\h12.1使用Hive\h12.1.1HelloWorld\h12.1.2運(yùn)行Hive服務(wù)器\h12.1.3使用HiveThrift客戶端\h12.1.4使用HiveJDBC客戶端\h12.1.5使用Hive分析Apache日志文件\h12.2使用Pig\h12.2.1HelloWorld\h12.2.2運(yùn)行PigServer\h12.2.3控制運(yùn)行期腳本的執(zhí)行\(zhòng)h12.2.4在SpringIntegration數(shù)據(jù)管道中調(diào)用Pig腳本\h12.2.5使用Pig分析Apache日志文件\h12.3使用HBase\h12.3.1HelloWorld\h12.3.2使用HBaseJava客戶端\h第13章使用SpringBatch和SpringIntegration創(chuàng)建大數(shù)據(jù)管道\h13.1收集并將數(shù)據(jù)加載到HDFS\h13.1.1SpringIntegration介紹\h13.1.2復(fù)制日志文件\h13.1.3事件流\h13.1.4事件轉(zhuǎn)發(fā)\h13.1.5管理\h13.1.6SpringBatch簡(jiǎn)介\h13.1.7從數(shù)據(jù)庫(kù)中加載并處理數(shù)據(jù)\h13.2Hadoop工作流\h13.2.1SpringBatch對(duì)Hadoop的支持\h13.2.2將wordcount樣例改造為SpringBatch應(yīng)用\h13.2.3Hive和Pig的步驟\h13.3從HDFS導(dǎo)出數(shù)據(jù)\h13.3.1從HDFS到JDBC\h13.3.2從HDFS到MongoDB\h13.4收集并加載數(shù)據(jù)到Splunk\h第六部分?jǐn)?shù)據(jù)網(wǎng)格\h第14章分布式數(shù)據(jù)網(wǎng)格:GemFire\h14.1GemFire簡(jiǎn)介\h14.2緩存與域\h14.3如何獲取GemFire\h14.4通過(guò)SpringXML命名空間配置GemFire\h14.4.1緩存配置\h14.4.2域配置\h14.4.3緩存客戶端配置\h14.4.4緩存服務(wù)端配置\h14.4.5WAN配置\h14.4.6磁盤(pán)存儲(chǔ)配置\h14.5使用GemfireTemplate進(jìn)行數(shù)據(jù)訪問(wèn)\h14.6使用Repository\h14.6.1POJO映射\h14.6.2創(chuàng)建Repository\h14.6.3PDX序列化\h14.7支持持續(xù)查詢第一部分背景知識(shí)
第1章SpringData項(xiàng)目SpringData項(xiàng)目是在“SpringOne2010開(kāi)發(fā)者大會(huì)”上創(chuàng)建的,該項(xiàng)目起源于當(dāng)年早些時(shí)候RodJohnson(SpringSource)和EmilEifrem(NeoTechnologies)共同參與的一場(chǎng)黑客會(huì)議。他們?cè)噲D把Neo4j圖形數(shù)據(jù)庫(kù)整合到Spring框架中,并評(píng)估了各種不同的方式。這次會(huì)議最終為初始版本的SpringDataNeo4j模塊奠定了基礎(chǔ),這個(gè)新的SpringSource項(xiàng)目旨在迎合大眾對(duì)于NoSQL數(shù)據(jù)存儲(chǔ)日益增長(zhǎng)的興趣,而這種趨勢(shì)一直持續(xù)到了今天。從創(chuàng)立之初,Spring就為傳統(tǒng)的數(shù)據(jù)訪問(wèn)技術(shù)提供了完善的支持。不管是使用JDBC、Hibernate、JDO、TopLink還是iBatis作為持久化技術(shù),Spring都大大簡(jiǎn)化了數(shù)據(jù)訪問(wèn)層的實(shí)現(xiàn)。這種支持主要包括簡(jiǎn)化基礎(chǔ)配置、資源管理并將異常轉(zhuǎn)換成Spring的DataAccessExceptions。這種支持多年以來(lái)已經(jīng)逐漸成熟,最新版本的Spring也對(duì)這一層提供了很好的支持。過(guò)去涉及數(shù)據(jù)持久化時(shí),關(guān)系型數(shù)據(jù)庫(kù)是可供選擇的主要工具,所以Spring對(duì)傳統(tǒng)數(shù)據(jù)訪問(wèn)的支持只把關(guān)系數(shù)據(jù)庫(kù)作為唯一的目標(biāo)。但隨著NoSQL的問(wèn)世并成為工具箱中可行的替代方案,從支持開(kāi)發(fā)人員的角度來(lái)看就有了新的領(lǐng)域需要補(bǔ)充。另一方面,對(duì)于傳統(tǒng)關(guān)系型存儲(chǔ)的支持也還有許多需要改善的地方。這兩個(gè)方面是SpringData項(xiàng)目的主要驅(qū)動(dòng)力。SpringData包含NoSQL存儲(chǔ)的專(zhuān)有模塊以及為關(guān)系型數(shù)據(jù)庫(kù)提供更好支持的JPA和JDBC模塊。1.1為Spring開(kāi)發(fā)人員提供的NoSQL數(shù)據(jù)訪問(wèn)功能盡管用NoSQL這個(gè)術(shù)語(yǔ)統(tǒng)稱(chēng)一系列的新型數(shù)據(jù)存儲(chǔ),但所有的這些存儲(chǔ)都有不同的特性和使用場(chǎng)景。具有諷刺意味的是,正是這種缺失特性的特點(diǎn)(缺乏對(duì)運(yùn)行SQL查詢的支持)命名了這一系列數(shù)據(jù)庫(kù)。由于這些存儲(chǔ)的特征非常不同,所以它們的Java驅(qū)動(dòng)要使用完全不同的API才能充分發(fā)揮其特性和功能。如果試圖對(duì)這些差異進(jìn)行抽象的話,就會(huì)失去每種NoSQL數(shù)據(jù)存儲(chǔ)能帶來(lái)的收益。圖形數(shù)據(jù)庫(kù)應(yīng)該用來(lái)存儲(chǔ)高度關(guān)聯(lián)的數(shù)據(jù);文件數(shù)據(jù)庫(kù)應(yīng)該存儲(chǔ)樹(shù)狀以及聚合狀的數(shù)據(jù)結(jié)構(gòu);如果需要類(lèi)似緩存的功能和存取模式,那應(yīng)該選擇鍵/值(key/value)存儲(chǔ)。JavaEE(企業(yè)版)領(lǐng)域通過(guò)JPA提供了持久化API,這個(gè)API或許可以當(dāng)作NoSQL數(shù)據(jù)庫(kù)前端實(shí)現(xiàn)的候選方案。但是令人遺憾的是,規(guī)范的前兩句話已經(jīng)預(yù)示了這一點(diǎn)似乎不可能實(shí)現(xiàn):本文檔是關(guān)于在JavaEE和JavaSE中管理持久化和對(duì)象/關(guān)系映射的JavaAPI規(guī)范。這項(xiàng)成果的技術(shù)目標(biāo)是為Java應(yīng)用開(kāi)發(fā)人員提供一個(gè)對(duì)象/關(guān)系映射機(jī)制,借助它可以使用域模型來(lái)管理關(guān)系型數(shù)據(jù)庫(kù)。這一主題在規(guī)范的后面有清晰的體現(xiàn),它定義了與關(guān)系型持久化領(lǐng)域緊密關(guān)聯(lián)的概念和API。@Table注解對(duì)NoSQL數(shù)據(jù)庫(kù)而言沒(méi)有太大的意義,@Column或@JoinColumn也是如此。在MongoDB這樣的存儲(chǔ)中如何實(shí)現(xiàn)事務(wù)API呢?要知道在這些環(huán)境中并沒(méi)有提供跨多文檔操作的事務(wù)語(yǔ)義。因此在NoSQL存儲(chǔ)之上實(shí)現(xiàn)JPA層,最好采用一個(gè)基于配置文件的API。另一方面,所有NoSQL存儲(chǔ)提供的特殊功能(地理空間功能、map-reduce操作、圖形遍歷)都需要以專(zhuān)有的方式去實(shí)現(xiàn),因?yàn)镴PA并沒(méi)有為它們提供抽象。因此我們可能會(huì)以“兩邊不討好”(worst-of-both-world)的局面收?qǐng)觯河械牟糠挚梢酝ㄟ^(guò)JPA來(lái)實(shí)現(xiàn),另外還需要使用專(zhuān)有的特性來(lái)重新啟用與特定存儲(chǔ)相關(guān)的功能。上文排除了采用JPA作為這些存儲(chǔ)的抽象API的可能性。Spring生態(tài)系統(tǒng)中的各種項(xiàng)目為開(kāi)發(fā)人員帶來(lái)了高效率以及一致的編程模型,我們依然希望將其用于簡(jiǎn)化對(duì)NoSQL存儲(chǔ)的使用。為此,SpringData團(tuán)隊(duì)發(fā)布了如下使命宣言:針對(duì)NoSQL和關(guān)系型存儲(chǔ),SpringData提供了基于Spring的熟知且一致的編程模型,同時(shí)保留特定存儲(chǔ)的特性和功能。因此,我們決定采取略有不同的方法。不再試圖以單一的API將所有存儲(chǔ)抽象化。相反,SpringData項(xiàng)目對(duì)不同的存儲(chǔ)實(shí)現(xiàn)都提供一致的編程模型,并使用了在Spring框架中大家已熟悉的模式和抽象。這樣,在使用不同的存儲(chǔ)時(shí),會(huì)有一致的體驗(yàn)。1.2主題概述SpringData的核心目標(biāo)是:支持對(duì)所有的存儲(chǔ)進(jìn)行資源配置,從而實(shí)現(xiàn)對(duì)該存儲(chǔ)的訪問(wèn)。這種支持主要是通過(guò)XML命名空間和SpringJavaConfig的支持類(lèi)實(shí)現(xiàn)的,這可以使我們輕松地對(duì)Mongo數(shù)據(jù)庫(kù)、嵌入式Neo4j實(shí)例等建立訪問(wèn)。除此之外,它也集成了Spring的核心功能,如JMX。這意味著某些存儲(chǔ)可以通過(guò)原生API暴露統(tǒng)計(jì)數(shù)據(jù),這些數(shù)據(jù)將會(huì)由SpringData暴露給JMX。大部分的NoSQLJavaAPI并未支持將領(lǐng)域?qū)ο笥成涞酱鎯?chǔ)的數(shù)據(jù)抽象(MongoDB中的文件,Neo4j中的節(jié)點(diǎn)與關(guān)系)。因此,當(dāng)使用原生的Java驅(qū)動(dòng)程序進(jìn)行讀取和寫(xiě)入操作時(shí),通常需要編寫(xiě)大量的代碼來(lái)將數(shù)據(jù)映射到應(yīng)用程序的領(lǐng)域?qū)ο蟆K許pringData模塊最核心的部分是一個(gè)映射和轉(zhuǎn)換的API,用來(lái)獲取要持久化的領(lǐng)域類(lèi)中的元數(shù)據(jù),使得任意領(lǐng)域?qū)ο蠖伎梢赞D(zhuǎn)換成存儲(chǔ)用的數(shù)據(jù)類(lèi)型。在此基礎(chǔ)上,就如同著名的SpringJdbcTemplate、JmsTemplate等,我們也會(huì)看到以模板模式實(shí)現(xiàn)的API,其中包括RedisTemplate、MongoTemplate等?;蛟S你已經(jīng)知道,這些模板提供了讓我們可以執(zhí)行常用操作的輔助方法。例如:在一條語(yǔ)句中持久化一個(gè)對(duì)象的時(shí)候,能夠自動(dòng)進(jìn)行資源管理和異常處理。此外,還提供了回調(diào)接口的API,允許在資源管理和異常處理過(guò)程中使用存儲(chǔ)原生的API,以提高靈活性。這些功能給我們提供了一個(gè)工具箱,使得我們可以像使用傳統(tǒng)的數(shù)據(jù)庫(kù)那樣來(lái)實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)層。后面的章節(jié)將會(huì)詳細(xì)介紹這些功能。為了讓程序變得更簡(jiǎn)單,SpringData還在模板實(shí)現(xiàn)的基礎(chǔ)上提供了一個(gè)存儲(chǔ)(repository)抽象,這將減少數(shù)據(jù)訪問(wèn)對(duì)象在實(shí)現(xiàn)一個(gè)普通接口時(shí)去定義通用場(chǎng)景的代價(jià),如標(biāo)準(zhǔn)的CRUD(創(chuàng)建、讀取、更新、刪除)操作以及執(zhí)行存儲(chǔ)支持的查詢語(yǔ)句。事實(shí)上,這種抽象位于最頂層,而且它會(huì)盡可能在合理范圍內(nèi)將不同存儲(chǔ)API融合在一起。因此,存儲(chǔ)的操作將擁有許多共同點(diǎn)。這也是后面會(huì)有專(zhuān)門(mén)的章節(jié)(第2章)來(lái)介紹基本編程模型的原因。接著,我們來(lái)看一下用來(lái)展示這些特定存儲(chǔ)模塊功能所用的示例代碼和領(lǐng)域模型。1.3領(lǐng)域?yàn)榱苏f(shuō)明各種SpringData模塊的用法,我們會(huì)使用電子商務(wù)部門(mén)的示例領(lǐng)域(如圖1-1所示)。由于各種NoSQL數(shù)據(jù)存儲(chǔ)通常具有特定的功能和適用場(chǎng)景,在個(gè)別章節(jié)會(huì)對(duì)領(lǐng)域的實(shí)現(xiàn)方式做出一些調(diào)整,甚至只有它的部分實(shí)現(xiàn),這種做法不代表必須以一種特定的方法來(lái)實(shí)現(xiàn)領(lǐng)域,而是強(qiáng)調(diào)某些存儲(chǔ)應(yīng)該更適用于特定的應(yīng)用場(chǎng)景。在模型的核心,有客戶(customer),包含客戶的基本資料,如姓、名、電子郵箱地址、地址(一組包含街道、城市和國(guó)家的集合),還有由產(chǎn)品名稱(chēng)、描述、價(jià)格和其他屬性構(gòu)成的產(chǎn)品(product)。這些抽象是組成CRM(客戶關(guān)系管理系統(tǒng))和庫(kù)存系統(tǒng)的基礎(chǔ)。最重要的是客戶可以訂購(gòu)訂單(Order),訂單信息包含訂購(gòu)的客戶、郵寄和付款地址、訂購(gòu)時(shí)間、訂單狀態(tài)和一組商品明細(xì)。而這些商品明細(xì)又包含一個(gè)特定的產(chǎn)品、訂購(gòu)的數(shù)量和產(chǎn)品的價(jià)格。圖1-1領(lǐng)域模型1.4示例代碼本書(shū)的示例代碼可從GitHub(\h/SpringSource/spring-data-book)上獲取。它是一個(gè)Maven項(xiàng)目,包含每一章的模塊。另外,還需要在電腦中安裝Maven3或者一個(gè)能導(dǎo)入Maven項(xiàng)目的IDE,比如SpringToolSuite(STS)。從下面的操作中可以看到,取得示例代碼就如同復(fù)制版本庫(kù)一樣簡(jiǎn)單:現(xiàn)在可以在命令行中執(zhí)行Maven來(lái)構(gòu)建代碼:這樣Maven會(huì)解析依賴(lài)、編譯和測(cè)試代碼,執(zhí)行測(cè)試,最終打包模塊。1.4.1將源碼導(dǎo)入到IDESTS/Eclipse由于STS已經(jīng)配備了m2eclipse插件,所以可以在IDE中輕松使用Maven項(xiàng)目。如果已經(jīng)下載并安裝(詳情請(qǐng)見(jiàn)第3章)了STS,即可從File菜單選擇Import選項(xiàng),并在彈出的對(duì)話框中選擇ExistingMavenProjects,如圖1-2所示。圖1-2導(dǎo)入Maven項(xiàng)目到Eclipse(步驟1/2)在下一個(gè)窗口中,單擊Browse按鈕來(lái)選擇剛剛簽出的示例項(xiàng)目的文件夾。之后,在正下方的窗格中會(huì)列出并選中各個(gè)Maven模塊(如圖1-3所示)。單擊Finish按鈕進(jìn)行下一步,STS會(huì)將選中的Maven模塊導(dǎo)入到工作區(qū)。它將依照模塊根目錄下的pom.xml文件來(lái)解析所需的依賴(lài)和源文件夾。圖1-3將Maven項(xiàng)目導(dǎo)入到Eclispe(步驟2/2)最終會(huì)看到如圖1-4所示的包或者項(xiàng)目資源管理器。這時(shí)項(xiàng)目應(yīng)能成功編譯并且不包含紅色錯(cuò)誤標(biāo)記。圖1-4完成導(dǎo)入的EclipseProjectExplorer使用了Querydsl(詳見(jiàn)第5章)的項(xiàng)目可能會(huì)引發(fā)紅色的錯(cuò)誤標(biāo)記。原因是m2eclipse插件需要知道:在IDE構(gòu)建的生命周期中,哪個(gè)階段執(zhí)行Querydsl關(guān)聯(lián)的Maven插件??梢詮膍2e-querydsl擴(kuò)展更新站點(diǎn)來(lái)安裝這個(gè)插件,也可以在項(xiàng)目主頁(yè)上找最新的版本(\h/ilx/m2e-querydsl),復(fù)制最新版本的鏈接,并將它添加到可用的更新站點(diǎn)的列表中,如圖1-5所示。然后安裝在更新網(wǎng)站上發(fā)布了的功能,重新啟動(dòng)Eclipse,并更新Maven項(xiàng)目配置(在項(xiàng)目中單擊鼠標(biāo)右鍵,從彈出的快捷菜單中選擇Maven→UpdateProject),這樣就能去除Eclipse中的錯(cuò)誤標(biāo)記,并且成功地完成項(xiàng)目的編譯。圖1-5增加m2e-querydsl更新網(wǎng)站IntelliJIDEAIDEA可以直接打開(kāi)Maven項(xiàng)目而不需要其他額外的設(shè)置。選擇菜單中的OpenProject選項(xiàng)之后會(huì)彈出對(duì)話框(如圖1-6所示)。圖1-6將Maven項(xiàng)目導(dǎo)入到IDEA(步驟1/2)IDE會(huì)打開(kāi)項(xiàng)目并獲取所需的依賴(lài)。在下一個(gè)步驟(如圖1-7所示),它會(huì)探測(cè)已使用的框架(如Spring框架、JPA、WebApp等);可以使用彈出窗口的配置鏈接或者在事件日志中配置這些框架。圖1-7將Maven項(xiàng)目導(dǎo)入到IDEA(步驟2/2)這樣項(xiàng)目就可以使用了。此時(shí)可以看到“Project”視圖和“MavenProject”視圖,如圖1-8所示。然后便可以像往常一樣編譯項(xiàng)目了。圖1-8打開(kāi)SpringDataBook項(xiàng)目的IDEA接下來(lái),必須加入SpringDataJPA模塊的JPA支持以啟用finder方法以及版本庫(kù)的錯(cuò)誤檢查功能。只需要右鍵單擊該模塊并選擇“AddFrameworkSupport”項(xiàng),在彈出的對(duì)話框中勾選JavaEE持久化的支持并且選擇Hibernate提供的持久化支持,如圖1-9所示。接著它會(huì)生成一個(gè)持久化單元配置src/main/java/resources/META-INF/persistence.xml文件。圖1-9在SpringDataJPA模塊啟用JPA支持
第2章Repository:便利的數(shù)據(jù)訪問(wèn)層長(zhǎng)期以來(lái),實(shí)現(xiàn)應(yīng)用程序的數(shù)據(jù)訪問(wèn)層一直是件繁瑣的工作,因?yàn)槲覀兘?jīng)常需要編寫(xiě)大量的樣板式代碼,而且貧血(anemic)的領(lǐng)域類(lèi)并沒(méi)有按照真正面向?qū)ο蠡蝾I(lǐng)域驅(qū)動(dòng)方式來(lái)進(jìn)行設(shè)計(jì)。因此SpringDataRepository抽象的目標(biāo)就是大幅簡(jiǎn)化各種持久化存儲(chǔ)持久層的實(shí)現(xiàn)。我們將會(huì)使用SpringDataJPA模塊作為例子來(lái)討論Repository抽象的基本理念。對(duì)于其他類(lèi)型的存儲(chǔ),可以參考對(duì)應(yīng)的例子。2.1快速入門(mén)我們選取領(lǐng)域模型中的Customer領(lǐng)域類(lèi),它會(huì)被持久化到任意的存儲(chǔ)之中。這個(gè)類(lèi)應(yīng)該如示例2-1所示。示例2-1Customer領(lǐng)域類(lèi)傳統(tǒng)的實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)層的方式至少需要實(shí)現(xiàn)一個(gè)存儲(chǔ)類(lèi)(repositoryclass),這個(gè)類(lèi)會(huì)包含基本的CRUD(Create、Read、Update與Delete)方法以及通過(guò)限制條件來(lái)訪問(wèn)實(shí)體子集的查詢方法。SpringDataRepository的方式能夠避免大多數(shù)的代碼,只需為這個(gè)實(shí)體存儲(chǔ)聲明簡(jiǎn)單的接口定義即可,如示例2-2所示。示例2-2CustomerRepository接口定義正如你所見(jiàn),我們擴(kuò)展了SpringData的Repository接口,它是通用的標(biāo)識(shí)接口。它的主要職責(zé)是讓SpringData的基礎(chǔ)設(shè)施識(shí)別出所有用戶定義的SpringDataRepository。除此之外,它還會(huì)捕獲托管的領(lǐng)域類(lèi)以及實(shí)體的ID類(lèi)型,稍后這些功能會(huì)提供很大的便利性。為了能夠自動(dòng)發(fā)現(xiàn)所聲明的接口,可以使用存儲(chǔ)特定的XML命名空間中的<repositories/>元素(如示例2-3所示),或是在使用JavaConfig時(shí)借助相關(guān)的@Enable...Repositories注解(如示例2-4所示)。在示例中會(huì)使用JPA。我們只需將XML元素的base-package屬性配置為我們的根包(rootpackage),SpringData會(huì)掃描它來(lái)查找Repository接口。如果沒(méi)有給出更進(jìn)一步的配置,那么它只會(huì)簡(jiǎn)單地檢查包中帶有注解的類(lèi)。示例2-3使用XML激活SpringDataRepository示例2-4使用JavaConfig激活SpringDataRepositoryXML和JavaConfig配置都需要添加存儲(chǔ)專(zhuān)用的Bean聲明來(lái)進(jìn)行完善,如JPA的EntityManagerFactory以及DataSource等。對(duì)于其他形式的存儲(chǔ),我們只需使用對(duì)應(yīng)的命名空間元素或注解即可。例如,示例2-5所示的配置片段,將會(huì)找到SpringDataRepository并創(chuàng)建SpringBean,這些Bean實(shí)際上是由一組實(shí)現(xiàn)了所發(fā)現(xiàn)接口的代理所組成的。因此,現(xiàn)在可以繼續(xù)編寫(xiě)客戶端,通過(guò)Spring的自動(dòng)裝配就能訪問(wèn)這個(gè)Bean了。CustomerRepository接口建立之后,我們就可以繼續(xù)深入學(xué)習(xí)并添加一些易于聲明的查詢方法。常見(jiàn)的需求是通過(guò)電子郵件地址來(lái)獲取Customer。為了做到這一點(diǎn),我們添加合適的查詢方法,如示例2-6所示。示例2-5客戶端使用SpringDataRepository示例2-6聲明查詢方法命名空間元素將會(huì)在容器啟動(dòng)的時(shí)候掃描到這個(gè)接口并觸發(fā)SpringData的基礎(chǔ)設(shè)施為其創(chuàng)建SpringBean。基礎(chǔ)設(shè)施會(huì)探查接口中聲明的方法并確定方法調(diào)用時(shí)要執(zhí)行的查詢。如果只是這樣簡(jiǎn)單地定義方法的話,那么SpringData將會(huì)根據(jù)其名字衍生出一個(gè)查詢。在定義查詢方面還有其他的途徑可選,可以閱讀2.2小節(jié)“定義查詢方法”來(lái)了解更多信息。在示例2-6中,由于我們遵循了領(lǐng)域?qū)ο髮傩缘拿s定,因而查詢可以衍生得到。查詢方法名中的EmailAddress部分其實(shí)就對(duì)應(yīng)了Customer類(lèi)的emailAddress屬性,因此,在使用JPA模塊時(shí),SpringData會(huì)自動(dòng)為聲明的方法衍生出selectCfromCustomercwherec.emailAddress=?1。它還會(huì)檢查方法聲明中屬性引用的合法性,如果發(fā)現(xiàn)任何錯(cuò)誤則會(huì)在容器啟動(dòng)時(shí),出現(xiàn)啟動(dòng)失敗?,F(xiàn)在,客戶端可以很容易地執(zhí)行這個(gè)方法,給定的方法參數(shù)會(huì)綁定到根據(jù)方法名衍生出來(lái)的查詢之中并且執(zhí)行該查詢,如示例2-7所示。示例2-7執(zhí)行查詢方法2.2定義查詢方法2.2.1查找查詢的策略剛才看到的接口只聲明了一個(gè)簡(jiǎn)單的查詢方法。聲明的方法會(huì)被基礎(chǔ)設(shè)施探測(cè)到并進(jìn)行解析,最終衍生出與存儲(chǔ)相關(guān)的查詢。但是,隨著查詢變得更加復(fù)雜,方法名會(huì)變得很冗長(zhǎng),顯得很笨拙。對(duì)于更復(fù)雜的查詢,依靠方法解析器所支持的關(guān)鍵字就不夠了。因此,每種存儲(chǔ)模塊都提供了@Query注解,如示例2-8所示,它會(huì)接受存儲(chǔ)相關(guān)的查詢語(yǔ)言所支持的查詢字符串,從而允許查詢執(zhí)行時(shí)進(jìn)一步地定制化。示例2-8使用@Query注解手動(dòng)定義查詢?cè)谶@里,我們使用JPA作為例子并手動(dòng)定義了一個(gè)查詢,當(dāng)然這個(gè)查詢?cè)疽彩强梢酝ㄟ^(guò)衍生得到的。查詢甚至可以外部化配置到屬性文件中(它位于MATA-INF目錄下的$store-named-perties文件中),在這里$store是用于替換jpa、mongo以及neo4j等的占位符。key值必須要遵循$domainType.$methodName這樣的約定。因此,為了將我們已有的方法替換成外部配置的命名查詢,key將會(huì)是Customer.findByEmailAddress。如果是使用已命名查詢的話,那就不需要使用@Query注解了。2.2.2衍生查詢?nèi)缡纠?-9所示,查詢衍生機(jī)制內(nèi)置于SpringDataRepository的基礎(chǔ)設(shè)施之中,對(duì)基于Repository的實(shí)體來(lái)構(gòu)建限制性的查詢很有用處。我們會(huì)從方法中截取findBy、readBy以及getBy前綴并解析剩余的部分。一個(gè)基礎(chǔ)的用法是,基于實(shí)體的屬性來(lái)定義條件并使用And和Or將它們連接起來(lái)。示例2-9由方法名衍生查詢解析的實(shí)際結(jié)果依賴(lài)于我們所使用的數(shù)據(jù)存儲(chǔ)。這里也有一些需要注意的通用事項(xiàng)。表達(dá)式通常會(huì)是屬性的遍歷以及操作符,它們可以連接起來(lái)。如示例2-9所示的那樣,可以通過(guò)And以及Or來(lái)連接屬性表達(dá)式。除此之外,對(duì)于屬性表達(dá)式來(lái)說(shuō),還可以支持各種操作符,如Between、LessThan、GreaterThan以及Like。因?yàn)椴煌臄?shù)據(jù)存儲(chǔ)之間所支持的操作符有所區(qū)別,所以要看查閱每種存儲(chǔ)對(duì)應(yīng)的章節(jié)。屬性表達(dá)式屬性表達(dá)式可以直接引用所管理實(shí)體的屬性(如示例2-9中所示)。在查詢創(chuàng)建的時(shí)候,我們已經(jīng)確保所解析的屬性就是領(lǐng)域類(lèi)的屬性。但是,依然可以遍歷嵌套的屬性來(lái)定義限制條件。在前面我們可以看到,Customer的Address中具有ZipCode屬性。在這種情況下,如下這種方法名的查詢將會(huì)創(chuàng)建x.address.zipCode這樣的屬性遍歷。這個(gè)方案的算法首先會(huì)將整體(AddressZipCode)作為一個(gè)屬性進(jìn)行解析并檢查領(lǐng)域類(lèi)中是否具有該名稱(chēng)的屬性(第一個(gè)字母小寫(xiě))。如果它存在的話,就會(huì)使用該屬性。如果不存在,它會(huì)從右邊開(kāi)始將源信息按照“駝峰”命名的規(guī)則將其拆分為頭部和尾部,然后嘗試查找對(duì)應(yīng)的屬性(如AddressZip和Code)。如果按照這個(gè)頭部信息找到了屬性,那么我們將會(huì)使用尾部的信息繼續(xù)往下構(gòu)建樹(shù)形的信息。因?yàn)槭纠?,第一次的分割并不匹配,我們將分割點(diǎn)繼續(xù)左移(從“AddressZip、Code”移到“Address、ZipCode”)。盡管這在大多數(shù)的場(chǎng)景下都是可行的,但是在一定情況下算法可能會(huì)選擇錯(cuò)誤的屬性。假設(shè)Customer同時(shí)還有一個(gè)addressZip屬性。那么我們的算法將會(huì)在第一次分割的時(shí)候就完成了匹配,這實(shí)際上選擇了錯(cuò)誤的屬性,并且會(huì)導(dǎo)致最終的失?。ㄒ?yàn)閍ddressZip類(lèi)型可能并沒(méi)有code屬性)。為了解決這種模棱兩可的問(wèn)題,可以在方法名中使用下劃線(_)來(lái)手動(dòng)定義遍歷點(diǎn)。所以,我們的方法名最終看起來(lái)是這樣的:2.2.3分頁(yè)和排序如果查詢所返回的結(jié)果數(shù)量增長(zhǎng)很明顯,那么分塊訪問(wèn)數(shù)據(jù)就很有意義了。為了做到這一點(diǎn),SpringData提供了可與Repository一起使用的分頁(yè)API。要讀取哪一塊數(shù)據(jù)的定義隱藏在Pageable接口及其實(shí)現(xiàn)PageRequest之中。得到的分頁(yè)數(shù)據(jù)存放在Page中,它不僅包含了數(shù)據(jù)本身,還包含了元信息,這些信息包括它是不是第一頁(yè)或最后一頁(yè)以及一共有多少頁(yè)等。為了計(jì)算這個(gè)元數(shù)據(jù),除了初始的查詢外,我們需要觸發(fā)第二次查詢。借助于Repository,我們要使用分頁(yè)功能時(shí)只需添加一個(gè)Pageable方法參數(shù)即可。不像其他的參數(shù)那樣,這個(gè)參數(shù)是不與查詢綁定的,用來(lái)限制返回的結(jié)果集。一種可選的方案就是返回Page類(lèi)型,它會(huì)對(duì)結(jié)果集進(jìn)行限制,但是需要另外一次查詢來(lái)獲取元信息(如可用元素的總數(shù))。另一種可選的方案是使用List,它會(huì)避免額外的查詢,但是不會(huì)提供元數(shù)據(jù)。如果不需要分頁(yè)功能,只是想要排序,那么可以給方法簽名上添加Sort參數(shù),如示例2-10所示。示例2-10使用Pageable和Sort的查詢方法第一個(gè)方法允許傳遞Pageable實(shí)例到查詢方法中,從而為靜態(tài)定義的查詢動(dòng)態(tài)地增加分頁(yè)功能。排序的功能可以通過(guò)Sort參數(shù)顯式地傳遞給方法,也可以內(nèi)嵌到PageRequest值對(duì)象中,如示例2-11所示。示例2-11使用Pageable和Sort2.3定義Repository到目前為止,我們看到了帶有查詢方法的Repository接口,這些查詢有的是從方法名中衍生出來(lái)的,有的是手動(dòng)聲明的,這取決于SpringData為實(shí)際存儲(chǔ)類(lèi)型所提供的使用方式。為了衍生出這些查詢,我們必須擴(kuò)展SpringData的特定標(biāo)識(shí)接口:Repository。除了查詢以外,在你的Repository中還需要一些其他的功能:存儲(chǔ)對(duì)象,刪除對(duì)象,根據(jù)ID進(jìn)行查找,返回所有存儲(chǔ)的實(shí)體或按頁(yè)對(duì)它們進(jìn)行訪問(wèn)。通過(guò)Repository接口來(lái)暴露這些功能的最簡(jiǎn)單方式就是使用一個(gè)SpringData所提供的更為高級(jí)的Repository接口。Repository一個(gè)簡(jiǎn)單的標(biāo)識(shí)接口,允許SpringData的基礎(chǔ)設(shè)施獲取用戶定義的Repository。CrudRepository擴(kuò)展自Repository并添加了基本的持久化方法如對(duì)實(shí)體的保存、查找以及刪除。PagingAndSortingRepositories擴(kuò)展自CrudRepository并添加了按頁(yè)訪問(wèn)實(shí)體以及根據(jù)給定的條件(criteria)進(jìn)行排序的方法。假設(shè)我們想讓CustomerRepository暴露基本的CRUD方法,所需要做就是修改其聲明,如示例2-12所示。示例2-12暴露CRUD方法的CustomerRepositoryCrudRepository接口如示例2-13所示。它包括了保存單個(gè)實(shí)體以及多個(gè)Iterable實(shí)體的方法、獲取單個(gè)實(shí)體或所有實(shí)體的方法以及不同形式的delete(...)方法。示例2-13CrudRepository支持Repository方式的每個(gè)SpringData模塊都提供了這個(gè)接口的實(shí)現(xiàn)。因此,我們聲明的命名空間元素會(huì)觸發(fā)基礎(chǔ)設(shè)施,這些設(shè)施不僅會(huì)啟動(dòng)那些用于執(zhí)行查詢方法的合適代碼,同時(shí)還會(huì)使用一個(gè)通用Repository實(shí)現(xiàn)類(lèi)的實(shí)例來(lái)在背后執(zhí)行CrudRepository中所聲明的方法,最終會(huì)將save(...)、findAll()等方法的調(diào)用委托給該實(shí)例。PagingAndSortingRepository(如示例2-14所示)擴(kuò)展了CrudRepository并為通用的findAll(...)添加了處理Pageable和Sort實(shí)例的方法,從而能夠?qū)崿F(xiàn)逐頁(yè)訪問(wèn)實(shí)體。示例2-14PagingAndSortingRepository要將這些功能引入到CustomerRepository中,只需簡(jiǎn)單地?cái)U(kuò)展PagingAndSortingRepository來(lái)取代CrudRepository即可。2.3.1調(diào)整Repository接口正如我們?cè)谇懊嫠?jiàn),通過(guò)擴(kuò)展合適的SpringData接口,可以很容易地引入大量預(yù)先定義的功能。這種級(jí)別的粒度實(shí)際上是一種權(quán)衡,那就是如果為所有的查找方法、所有的保存方法等都定義單獨(dú)的接口,我們會(huì)暴露接口的數(shù)量(以及因此導(dǎo)致的復(fù)雜性)以及開(kāi)發(fā)人員使用的便利性之間的權(quán)衡。但是,可能會(huì)有這樣的場(chǎng)景,那就是只想暴露讀方法(CRUD中的R)或者只想在Repository接口中將刪除方法屏蔽掉。如今,SpringData允許定義個(gè)性化的基礎(chǔ)Repository,只需按照以下的步驟操作即可。1.創(chuàng)建一個(gè)接口,這個(gè)接口要么擴(kuò)展自Repository,要么添加@RepositoryDefinition注解。2.添加想要暴露的方法并確保它們與SpringData基礎(chǔ)Repository接口所提供的方法簽名相同。3.對(duì)于實(shí)體所對(duì)應(yīng)的接口聲明,要使用這個(gè)接口作為基礎(chǔ)接口。為了闡述這一點(diǎn),假設(shè)我們只想暴露接收Pageable的findAll(...)方法以及save方法。這個(gè)基礎(chǔ)接口看起來(lái)可能如示例2-15所示。示例2-15自定義基礎(chǔ)Repository接口需要注意的一點(diǎn)是我們?yōu)檫@個(gè)接口添加了一個(gè)額外的注解@NoRepositoryBean,從而確保SpringDataRepository的基礎(chǔ)設(shè)施不會(huì)試圖為其創(chuàng)建Bean的實(shí)例。讓CustomerRepository擴(kuò)展這個(gè)接口就能精確做到只暴露你所定義的API。接下來(lái)可以定義出各種基本的接口(如ReadOnlyRepository或SaveOnlyRepository)甚至組成它們的繼承體系,這取決于項(xiàng)目的需要。通常建議本地定義的CRUD方法在開(kāi)始的時(shí)候直接位于每個(gè)實(shí)體的具體Repository中,必要的話,再將它們要么轉(zhuǎn)移到SpringData提供的基礎(chǔ)Repository中,要么轉(zhuǎn)移到特制的Repository中。按照這種方式,可以保證隨著項(xiàng)目復(fù)雜性的增長(zhǎng),構(gòu)件(artifact)的數(shù)量能夠自然地增長(zhǎng)。2.3.2手動(dòng)實(shí)現(xiàn)Repository方法到目前為止,看到了兩種類(lèi)型的Repository方法:CRUD方法和查詢方法。每種類(lèi)型都是由SpringData的基礎(chǔ)設(shè)施實(shí)現(xiàn)的,要么通過(guò)背后的實(shí)現(xiàn)類(lèi),要么通過(guò)查詢執(zhí)行引擎。當(dāng)構(gòu)建應(yīng)用程序的時(shí)候,這兩種場(chǎng)景可能會(huì)覆蓋你所面臨的很大范圍的數(shù)據(jù)訪問(wèn)操作。但是,有些場(chǎng)景需要手動(dòng)實(shí)現(xiàn)代碼。現(xiàn)在,讓我們看一下如何做到這一點(diǎn)。我們開(kāi)始只實(shí)現(xiàn)那些需要手動(dòng)實(shí)現(xiàn)的功能并在實(shí)現(xiàn)類(lèi)中遵循一些命名的約定,如示例2-16所示。示例2-16為Repository實(shí)現(xiàn)自定義功能接口和實(shí)現(xiàn)類(lèi)均不需要了解SpringData的任何事情。它與使用Spring手動(dòng)實(shí)現(xiàn)代碼非常類(lèi)似。按照SpringData來(lái)看,這個(gè)代碼片段最有意思的地方在于實(shí)現(xiàn)類(lèi)的名字遵循了命名的約定,也就是在核心Repository接口(在我們的場(chǎng)景中就是CustomerRepository)的名字上加Impl后綴。同時(shí)需要注意,我們將接口和實(shí)現(xiàn)類(lèi)都設(shè)為包內(nèi)私有(packageprivate),從而阻止從包外訪問(wèn)它們。最后一步是修改初始Repository接口的聲明,使其擴(kuò)展剛剛引入的接口,如示例2-17所示。示例2-17在CustomerRepository中包含自定義功能現(xiàn)在,我們已經(jīng)將CustomerRepositoryCustom暴露的API引入到CustomerRepository之中了,這會(huì)使其成為Customer數(shù)據(jù)訪問(wèn)API的中心點(diǎn)。客戶端代碼現(xiàn)在就可以調(diào)用CustomerRepository.myCustomMethod(...)了。但是,這個(gè)實(shí)現(xiàn)類(lèi)會(huì)如何被發(fā)現(xiàn)并置于最終執(zhí)行的代理之中的呢?實(shí)際上,Repository的啟動(dòng)過(guò)程看起來(lái)是這樣的。1.發(fā)現(xiàn)repository接口(如CustomerRepository)。2.嘗試尋找一個(gè)Bean定義,這個(gè)Bean的名字為接口的小寫(xiě)形式并添加Impl后綴(如customerRepositoryImpl)。如果能夠找到,就使用它。3.如果沒(méi)有找到,我們會(huì)掃描尋找一個(gè)類(lèi),這個(gè)類(lèi)的名字為核心Repository接口的名字并添加Impl后綴(例如,在這個(gè)例子中CustomerRepositoryImpl會(huì)被找到)。如果找到了這樣的類(lèi),那么將其注冊(cè)為SpringBean并使用它。4.找到的自定義實(shí)現(xiàn)類(lèi)將會(huì)裝配到被發(fā)現(xiàn)接口的代理配置之中并且在方法調(diào)用時(shí)會(huì)作為潛在的目標(biāo)類(lèi)。這種機(jī)制可以很容易地為特定Repository實(shí)現(xiàn)自定義代碼。用于進(jìn)行實(shí)現(xiàn)查找的后綴可以在XML命名空間中或啟用Repository的注解屬性中(查看各種存儲(chǔ)相關(guān)的章節(jié)來(lái)了解更多)進(jìn)行個(gè)性化設(shè)置。參考文檔(\hhttps://bit.ly/VzYToo)中也包含了一些關(guān)于如何將自定義的行為應(yīng)用于多個(gè)Repositor的學(xué)習(xí)材料。2.4IDE集成在3.0版本中,Spring工具套件(SpringToolSuite,STS)提供了與SpringDataRepository抽象進(jìn)行集成的功能。STS為SpringData所提供的核心支持是查找方法的查詢衍生機(jī)制。它所能做到的第一件事就是在IDE中校驗(yàn)衍生查詢方法的正確性,這樣,不需要啟動(dòng)ApplicationContext就能立刻探測(cè)出方法名中引入的拼寫(xiě)錯(cuò)誤。
STS是一個(gè)特殊的Eclipse發(fā)布版本,它內(nèi)置了一些插件從而盡可能地便于進(jìn)行Spring應(yīng)用的構(gòu)建。這個(gè)工具可以在項(xiàng)目的站點(diǎn)上(\hhttp://www./sts)下載或者使用一般的Eclipse發(fā)布版本并通過(guò)STS更新站點(diǎn)進(jìn)行更新(基于Eclipse3.8(\h/release/TOOLS/update/e3.8)或Eclipse4.2(\h/release/TOOLS/update/e4.2))。如圖2-1所示,IDE檢測(cè)到Descrption是非法的,因?yàn)镻roduct類(lèi)中并沒(méi)有這樣的屬性。為了發(fā)現(xiàn)這些拼寫(xiě)錯(cuò)誤,它會(huì)分析Product領(lǐng)域類(lèi)(這些事情在啟動(dòng)SpringDataRepository時(shí)也會(huì)做)來(lái)獲取屬性并將方法名解析為屬性的遍歷樹(shù)。為了盡早避免這種類(lèi)型的拼寫(xiě)錯(cuò)誤,STS的SpringData輔助功能為屬性名、條件關(guān)鍵字(criteriakeyword)以及像And和Or這樣的連接符提供了代碼補(bǔ)全功能,如圖2-2所示。圖2-1SpringDataSTS對(duì)衍生查詢方法名進(jìn)行校驗(yàn)圖2-2對(duì)衍生查詢方法的屬性代碼補(bǔ)全提示Order類(lèi)中有一些你可能想要引用的屬性。假設(shè)我們要遍歷billingAddress屬性,Cmd+Space(或者在Windows中使用Ctrl+Space組合鍵)將會(huì)觸發(fā)嵌套屬性的遍歷,這樣將會(huì)提示出嵌套的屬性并根據(jù)此時(shí)所遍歷的屬性類(lèi)型提示所匹配的關(guān)鍵字(如圖2-3所示)。因此,String類(lèi)型的屬性將會(huì)多一個(gè)Like的提示。圖2-3嵌套屬性和關(guān)鍵字提示為了提供一些錦上添花的特性,SpringDataSTS會(huì)將Repository作為IDE導(dǎo)航中的一等公民,使其帶有眾所周知的SpringBean標(biāo)識(shí)。除此之外,導(dǎo)航中的Spring元素(SpringElements)節(jié)點(diǎn)將會(huì)包含一個(gè)專(zhuān)有的SpringDataRepositories節(jié)點(diǎn),用來(lái)放置應(yīng)用程序中所配置的所有Repository,如圖2-4所示。圖2-4在STS中,具備SpringData支持的Eclipse項(xiàng)目資源管理器可以看到,你能夠快速找到Repository接口并跟蹤它實(shí)際上來(lái)源于哪一個(gè)配置元素。2.4.1IntelliIDEA最后,啟用JPA支持后,IDEA提供了Repository查找方法的補(bǔ)全功能,這種補(bǔ)全涵蓋了衍生的屬性名以及可用的關(guān)鍵字,如圖2-5所示。圖2-5在IDEA編輯器中,查詢方法的補(bǔ)全功能
第3章使用Querydsl實(shí)現(xiàn)類(lèi)型安全的查詢編寫(xiě)訪問(wèn)數(shù)據(jù)的查詢通常會(huì)使用Java的字符串(String)來(lái)完成。對(duì)于JDBC來(lái)說(shuō),可供選擇的查詢語(yǔ)言就是SQL,而對(duì)于Hibernate/JPA來(lái)說(shuō)就是HQL/JPQL。使用簡(jiǎn)單的字符串來(lái)定義查詢的功能是很強(qiáng)大的,但也易于出錯(cuò),因?yàn)楹苋菀滓肫磳?xiě)錯(cuò)誤。除此之外,它與實(shí)際的查詢?cè)椿虻讓哟鎯?chǔ)很少有關(guān)聯(lián),所以列引用(在JDBC的場(chǎng)景下)或?qū)傩砸茫ㄔ贖QL/JPQL上下文中)在維護(hù)方面會(huì)成為一種負(fù)擔(dān),這是因?yàn)楸砘驅(qū)ο竽P偷淖兓⒉荒芎苋菀椎赜成涞讲樵冎?。Querydsl項(xiàng)目()試圖解決這樣的問(wèn)題,它提供了一個(gè)非常流暢的API來(lái)定義查詢。這個(gè)API衍生于實(shí)際的表或?qū)ο竽P?,但同時(shí)又是與存儲(chǔ)和模型高度無(wú)關(guān)的,所以它允許為各種存儲(chǔ)類(lèi)型創(chuàng)建和使用查詢API。它目前支持JPA、Hibernate、JDO、原生的JDBC、Lucene、HibernateSearch以及MongoDB。功能的多樣性是SpringData集成Querydsl的主要原因,因?yàn)镾pringData也集成了多種類(lèi)型的存儲(chǔ)。下面將會(huì)介紹Querydsl項(xiàng)目以及它的基本理念。在本書(shū)后面各種存儲(chǔ)類(lèi)型相關(guān)的章節(jié)中,我們還會(huì)介紹它對(duì)這些存儲(chǔ)的支持。3.1Querydsl簡(jiǎn)介當(dāng)使用Querydsl的時(shí)候,通常開(kāi)始會(huì)從領(lǐng)域類(lèi)中衍生出元模型。盡管這個(gè)類(lèi)庫(kù)也可以使用簡(jiǎn)單的字符串文本,但創(chuàng)建元模型會(huì)釋放出Querydsl的全部能量,尤其是在屬性的類(lèi)型安全以及關(guān)鍵字引用方面。衍生機(jī)制基于Java6的注解處理工具(AnnotationProcessingTool,APT),它能夠掛接到編譯器中并對(duì)源碼甚至編譯后的類(lèi)進(jìn)行處理。想了解更多信息的話,可參閱3.2小節(jié)“生成查詢模型”。開(kāi)始之前,需要定義一個(gè)領(lǐng)域類(lèi),如示例3-1所示。我們使用幾個(gè)原始類(lèi)型和非原始類(lèi)型的屬性來(lái)為Customer建模。示例3-1Customer領(lǐng)域類(lèi)注意我們?yōu)檫@個(gè)類(lèi)使用了@QueryEntity注解。這是默認(rèn)的注解,Querydsl注解處理器將會(huì)使用它來(lái)生成相關(guān)的查詢對(duì)象。在與特定的存儲(chǔ)集成使用的時(shí)候,APT處理器能夠識(shí)別出特定存儲(chǔ)的實(shí)體(如對(duì)于JPA所使用的就是@Entity)并使用它們來(lái)衍生查詢類(lèi)。在這個(gè)簡(jiǎn)介部分,我們不會(huì)與存儲(chǔ)一起工作,所以無(wú)法使用特定存儲(chǔ)的映射注解,只是使用@QueryEntity而已。生成的Querydsl查詢類(lèi)如示例3-2所示。示例3-2Querydsl生成的查詢類(lèi)可以在該模塊示例工程的target/generated-sources/queries目錄下找到這些類(lèi)。該類(lèi)暴露了公開(kāi)的Path屬性以及對(duì)其他查詢類(lèi)的引用(如QEmailAddress)。在定義斷言(predicate)的時(shí)候,IDE就能在代碼補(bǔ)全時(shí)列出所有可用的Path??梢允褂眠@些Path表達(dá)式來(lái)定義可重用的斷言,如示例3-3所示。示例3-3使用查詢類(lèi)來(lái)定義斷言將靜態(tài)的QCustomer.customer實(shí)例賦值給customer變量,這樣就能非常簡(jiǎn)潔地引用它的Path屬性??梢钥吹剑瑪嘌缘亩x非常簡(jiǎn)單整潔,而且──最重要的就是──類(lèi)型安全。變更領(lǐng)域類(lèi)將會(huì)重新生成查詢模型。變更所導(dǎo)致的屬性引用失效將會(huì)產(chǎn)生編譯錯(cuò)誤,從而提示我們要進(jìn)行修改的地方。對(duì)于每種Path類(lèi)型所能使用的方法也會(huì)考慮到Path的類(lèi)型(如like(…)方法就只能對(duì)String屬性有意義,因此只會(huì)提供給這種類(lèi)型使用)。因?yàn)閿嘌缘亩x非常簡(jiǎn)潔,所以它們能夠很容易地用在方法聲明的內(nèi)部。另一方面,我們可以很容易地以可重用的方式來(lái)定義斷言,定義原子性的斷言并通過(guò)像And或Or這樣的操作符將其連接起來(lái)以形成復(fù)雜的斷言(如示例3-4所示)。示例3-4連接原子性的斷言我們可以使用剛剛編寫(xiě)的斷言來(lái)為特定的存儲(chǔ)或者簡(jiǎn)單的集合來(lái)編寫(xiě)查詢。鑒于對(duì)特定存儲(chǔ)的查詢執(zhí)行主要會(huì)通過(guò)SpringDataRepository抽象來(lái)實(shí)現(xiàn)(見(jiàn)3.3小節(jié)“與SpringDataRepository集成”),所以作為例子,為了簡(jiǎn)單起見(jiàn),我們會(huì)使用這個(gè)特性來(lái)對(duì)集合進(jìn)行查詢。首先,構(gòu)建各種類(lèi)型的Product,這樣我們就有東西可以過(guò)濾了,如示例3-5所示。示例3-5構(gòu)建Product接下來(lái),我們可以使用Querydsl來(lái)對(duì)這個(gè)集合建立查詢,也就是對(duì)其建立某種類(lèi)型的過(guò)濾器(如示例3-6所示)。示例3-6使用Querydsl斷言過(guò)濾Product我們使用from(...)方法來(lái)建立Querydsl查詢,它是querydsl-collections模塊之中MiniAPI類(lèi)的一個(gè)靜態(tài)方法。我們將Product的查詢類(lèi)實(shí)例以及作為源的集合傳遞給它?,F(xiàn)在,我們就可以使用where(...)方法將斷言應(yīng)用于源列表之上并使用某一個(gè)list(...)方法(如示例3-7所示)執(zhí)行查詢。在我們的場(chǎng)景下,只是簡(jiǎn)單地返回那些匹配預(yù)先定義斷言的Product。將$.description傳遞給list(…)方法后,我們就能將結(jié)果投射(project)到Product的名稱(chēng)上,因此返回一個(gè)String的集合。示例3-7使用Querydsl斷言過(guò)濾Product(投射)借助于Querydsl,我們能夠以一種簡(jiǎn)潔和便利的方式來(lái)定義實(shí)體斷言。它們可以基于各種存儲(chǔ)以及Java類(lèi)的映射信息而產(chǎn)生。Querydsl的API以及它所支持的存儲(chǔ)方式使得我們可以產(chǎn)生斷言,并以此來(lái)定義查詢。相同的API也可以用于對(duì)Java集合進(jìn)行過(guò)濾。3.2生成查詢?cè)P驼缭谇拔闹兴?jiàn),Querydsl的核心構(gòu)件(artifact)就是查詢?cè)P皖?lèi)。這些類(lèi)是通過(guò)注解處理工具集(APT,AnnotationProcessingToolkit,見(jiàn)\h/en/jsr/detail?id=175)生成的,它是Java6中javacJava編譯器的一部分。APT有一種機(jī)制,能夠以編碼的方式探查已有的Java源代碼以查找特定的注解,然后再回過(guò)頭來(lái)調(diào)用函數(shù)以生成Java代碼。Querydsl使用了這種機(jī)制來(lái)提供特定的APT處理器實(shí)現(xiàn)類(lèi),這個(gè)類(lèi)可以用于探查注解。示例3-1使用了Querydsl特定的注解,如@QueryEntity以及@QueryEmbeddable。如果我們已經(jīng)擁有了某種存儲(chǔ)類(lèi)型的映射領(lǐng)域類(lèi),而這種存儲(chǔ)類(lèi)型是Querydsl所能夠支持的,那么生成元模型類(lèi)并不需要額外的過(guò)程。在這里的核心集成點(diǎn)在于需要將注解處理器傳遞給QuerydslAPT。通常處理器會(huì)在構(gòu)建過(guò)程中執(zhí)行。3.2.1構(gòu)建系統(tǒng)集成為了與Maven集成,Querydsl提供了maven-apt-plugin插件,借助它就能夠配置實(shí)際使用的處理器類(lèi)了。在示例3-8中,將process的目標(biāo)設(shè)置為generate-source階段,這樣所配置的處理器類(lèi)就能探查src/main/java目錄下的類(lèi)。如果想要為測(cè)試源碼包(src/test/java)中的類(lèi)生成元模型類(lèi)的話,需要在generate-test-sources階段執(zhí)行test-process目標(biāo)。示例3-8設(shè)置MavenAPT插件3.2.2所支持的注解處理器Querydsl自帶了各種APT處理器,以用于探查不同的注解并生成對(duì)應(yīng)的元模型類(lèi)。QuerydslAnnotationProcessor非常核心的注解處理器,會(huì)探查Querydsl的特定注解,如@QueryEntity和@QueryEmbeddable。JPAAnnotationProcessor用于探查javax.persistence注解,如@Entity以及@Embeddable。HibernateAnnotationProcessor類(lèi)似于JPA處理器,但是增加了對(duì)Hibernate注解的支持。JDOAnnotationProcessor探查JDO注解,如@PersistenceCapable和@EmbeddedOnly。MongoAnnotationProcessorSpringData的一個(gè)專(zhuān)用的處理器,會(huì)探查@Document注解。閱讀6.3小節(jié)“映射模塊”以了解更多信息。3.2.3使用Querydsl對(duì)存儲(chǔ)進(jìn)行查詢現(xiàn)在查詢類(lèi)已經(jīng)就緒了,讓我們看一下如何實(shí)際使用它們構(gòu)建與特定存儲(chǔ)相關(guān)的查詢。正如前面所提到的,Querydsl為各種存儲(chǔ)都提供了集成模塊,這些模塊以優(yōu)雅且一致的API來(lái)創(chuàng)建查詢對(duì)象、通過(guò)所生成的元模型類(lèi)進(jìn)行斷言定義并執(zhí)行最終的查詢。例如,JPA模塊提供了一個(gè)JPAQuery實(shí)現(xiàn)類(lèi),它會(huì)接受EntityManager并提供了API能夠?qū)崿F(xiàn)在執(zhí)行之前應(yīng)用斷言,如示例3-9所示。示例3-9使用QuerydslJPA模塊來(lái)查詢關(guān)系型存儲(chǔ)如果還記得示例3-6的話,就會(huì)發(fā)現(xiàn)這兩個(gè)代碼片段看上去區(qū)別并不大。實(shí)際上,唯一的區(qū)別在于我們采用JPAQuery作為基礎(chǔ),而前面的示例使用是集合包裝類(lèi)(CollectionWrapper)。為MongoDB存儲(chǔ)實(shí)現(xiàn)相同的場(chǎng)景同樣也沒(méi)有太大的變化,對(duì)于這一點(diǎn)你應(yīng)該不會(huì)感到太驚訝(如示例3-10所示)。示例3-10聯(lián)合使用QuerydslMongoDB模塊以及SpringDataMongoDB3.3集成SpringDataRepository如你所見(jiàn),使用Querydsl所執(zhí)行的查詢一般來(lái)說(shuō)包括3個(gè)主要的步驟。1.構(gòu)建存儲(chǔ)相關(guān)的查詢實(shí)例。2.在查詢上使用一些過(guò)濾斷言。3.執(zhí)行查詢實(shí)例,可能會(huì)對(duì)其使用投射。其中的兩個(gè)步驟可以視為樣板式的,因?yàn)樗鼈兺ǔ?huì)編寫(xiě)類(lèi)似的代碼。另一方面,SpringDataRepository會(huì)盡可能幫助用戶減少不必要代碼的數(shù)量,因此將Repository抽象與Querydsl集成在一起是很有意義的。3.3.1執(zhí)行斷言集成的核心在于QueryDslPredicateExecutor接口,它指定了用戶可以執(zhí)行Querydsl斷言的API,類(lèi)似于CrudRepository所提供的CRUD方法,如示例3-11所示。示例3-11QueryDslPredicateExecutor接口目前,SpringDataJPA和MongoDB模塊通過(guò)提供實(shí)現(xiàn)類(lèi)來(lái)實(shí)現(xiàn)示例3-11中的QueryDslPredicateExecutor接口以支持這個(gè)API。為了通過(guò)Repository接口暴露這個(gè)API,需要讓其擴(kuò)展QueryDslPredicateExecutor以及Repository(或其他可用的基礎(chǔ)接口),參見(jiàn)示例3-12。示例3-12CustomerRepository接口擴(kuò)展了QueryDslPredicateExecutor擴(kuò)展這個(gè)接口會(huì)有兩個(gè)重要的結(jié)果:首先(可能也是最重要的)就是它將API引入了進(jìn)來(lái)并且將其暴露給CustomerRepository的客戶端。其次,SpringDataRepository基礎(chǔ)設(shè)施將會(huì)探查每個(gè)Repository接口并判斷它是否擴(kuò)展了QueryDslPredicateExecutor。如果答案是肯定的并且Querydsl位于類(lèi)路徑之中,那么SpringData將會(huì)選擇一個(gè)特定的基類(lèi)支撐Repository代理,它一般會(huì)通過(guò)創(chuàng)建存儲(chǔ)相關(guān)的查詢實(shí)例、綁定給定的斷言、使用分頁(yè)并最終執(zhí)行查詢來(lái)實(shí)現(xiàn)這些API方法。3.3.2手動(dòng)實(shí)現(xiàn)Repository我們剛剛所看到的方式解決了為Repository所管理的領(lǐng)域類(lèi)進(jìn)行通用查詢的問(wèn)題。但是,無(wú)法通過(guò)這種機(jī)制執(zhí)行更新或刪除操作,或者管理特定存儲(chǔ)的查詢實(shí)例。這種場(chǎng)景可以很好地參與Repository抽象的特性,可以有選擇地實(shí)現(xiàn)那些需要手動(dòng)代碼的方法(參見(jiàn)2.3.2小節(jié)“手動(dòng)實(shí)現(xiàn)Repository方法”了解這個(gè)話題的更多細(xì)節(jié))。為了便于實(shí)現(xiàn)自定義的Repository擴(kuò)展,我們提供了特定存儲(chǔ)的基礎(chǔ)類(lèi)。要了解更多細(xì)節(jié),參見(jiàn)4.4.2小節(jié)“Repository與Querydsl集成”以及6.5.3小節(jié)“MongoQuerydsl集成”。
第二部分關(guān)系型數(shù)據(jù)庫(kù)
第4章JPARepositoryJava持久化API(JPA,JavaPersistenceAPI)是將Java對(duì)象持久化到關(guān)系型數(shù)據(jù)庫(kù)的標(biāo)準(zhǔn)方式。JPA包含了兩個(gè)部分:用于將類(lèi)匹配到關(guān)系型表的映射模塊以及訪問(wèn)對(duì)象、定義和執(zhí)行查詢等功能的EntityManagerAPI。JPA抽象了多種實(shí)現(xiàn),如Hibernate(\h)、EclipseLink(\h/eclipselink)、OpenJpa(\h)等。Spring框架一直以來(lái)就為簡(jiǎn)化JPA的存儲(chǔ)實(shí)現(xiàn)提供了良好的支持。它所提供的支持包括構(gòu)建EntityManager的輔助類(lèi)、集成Spring的事務(wù)抽象并將JPA的特定異常轉(zhuǎn)換為Spring的DataAccessException異常體系。SpringDataJPA模塊實(shí)現(xiàn)了SpringData通用的Repository抽象從而進(jìn)一步簡(jiǎn)化了存儲(chǔ)的實(shí)現(xiàn),這樣在大多數(shù)場(chǎng)景無(wú)需手動(dòng)實(shí)現(xiàn)存儲(chǔ)類(lèi)。要了解Repository抽象的簡(jiǎn)介信息,可以參考第2章。本章將會(huì)帶你了解這個(gè)模塊的通用構(gòu)建過(guò)程和特性。4.1示例工程本章的示例工程包含了3個(gè)包:com.oreilly.springdata.jpa這個(gè)基礎(chǔ)包以及core和order兩個(gè)子包。基礎(chǔ)包里面包含了一個(gè)Spring的JavaConfig類(lèi),它可以通過(guò)簡(jiǎn)單的Java類(lèi)而不是XML來(lái)配置Spring容器。其他的兩個(gè)包中包含了我們的領(lǐng)域類(lèi)以及Repository接口。正如其名字所示,core包中包含了對(duì)領(lǐng)域模型的基礎(chǔ)抽象:像AbstractEntity這種技術(shù)化的幫助類(lèi),同時(shí)也包含了像EmailAddress、Address、Customer以及Product這樣的領(lǐng)域概念。然后,還有order包,基于這些基礎(chǔ)的概念實(shí)現(xiàn)了訂單的概念。所以在這里可以看到Order以及OrderItem。在后面的段落中,我們會(huì)仔細(xì)地查看每個(gè)類(lèi),介紹其目的以及使用JPA映射注解將它們匹配到數(shù)據(jù)庫(kù)的方式。我們領(lǐng)域模型中所有實(shí)體的基礎(chǔ)類(lèi)就是AbstractEntity(如示例4-1所示)。它使用了@MappedSuperclass來(lái)表示它本身并不是受管理的實(shí)體類(lèi),而是會(huì)由其他的實(shí)體類(lèi)進(jìn)行擴(kuò)展。我們?cè)谶@里聲明了一個(gè)Long類(lèi)型的id并且告知持久化提供商自動(dòng)選擇最合適的策略來(lái)生成主鍵。除此之外,我們通過(guò)檢查id屬性實(shí)現(xiàn)了equals(…)和hashCode(…)方法,這樣一來(lái),具有相同id的同種類(lèi)型的實(shí)體會(huì)被視為相等。在這個(gè)類(lèi)中,包含了持久化實(shí)體的主要技術(shù)化信息,因此在具體的實(shí)體類(lèi)中就可以關(guān)注實(shí)際的領(lǐng)域?qū)傩?。示?-1AbstractEntity類(lèi)讓我們繼續(xù)看一下非常簡(jiǎn)單的Address領(lǐng)域類(lèi)。如示例4-2所示,它是一個(gè)帶有@Entity注解的普通類(lèi),并且包含了3個(gè)String屬性。因?yàn)樗鼈兌际腔A(chǔ)屬性,因此不需要添加額外的注解,持久化提供商會(huì)自動(dòng)將它們匹配到表的列上。如果需要自定義屬性要持久化的列名,可以使用@Column注解。示例4-2Address領(lǐng)域類(lèi)Address會(huì)被Customer實(shí)體所引用。Customer會(huì)包含一些其他的屬性(如原始類(lèi)型的firstname和lastname)。它們?cè)谟成渖吓c我們看到的Address類(lèi)似。每個(gè)Customer還會(huì)有一個(gè)電子郵件地址,這會(huì)通過(guò)EmailAddress類(lèi)體現(xiàn)(如示例4-3所示)。示例4-3EmailAddress領(lǐng)域類(lèi)這個(gè)類(lèi)是一個(gè)值對(duì)象(valueobject,\h/node/135),這個(gè)概念是EricEvans在《模型驅(qū)動(dòng)設(shè)計(jì)》[Evans03]一書(shū)中定義的。值對(duì)象通常用來(lái)表述領(lǐng)域的概念,可以簡(jiǎn)單地將其實(shí)現(xiàn)為原始類(lèi)型(在本例中就是字符串),但是值對(duì)象還允許在其內(nèi)部實(shí)現(xiàn)領(lǐng)域約束。電子郵件地址必須要遵循一定的格式,否則,就會(huì)出現(xiàn)不合法的郵件地址。所以,我們實(shí)際上會(huì)通過(guò)一些正則表達(dá)式實(shí)現(xiàn)格式檢查,這樣就能阻止實(shí)例化非法的EmailAddress。這就意味著,在處理這種類(lèi)型的實(shí)例時(shí),能夠確保有合法的電子郵件地址,所以我們不再需要專(zhuān)門(mén)的組件來(lái)校驗(yàn)它。在持久化映射方面,EmailAddress帶有@Embeddable注解,這會(huì)導(dǎo)致持久化供應(yīng)商會(huì)將其屬性放到包含它的類(lèi)所對(duì)應(yīng)的表中。在我們的場(chǎng)景下,這是一個(gè)列,我們將其定義為個(gè)性化的名字:email??梢钥吹?,我們需要為JPA持久化廠商提供一個(gè)空的構(gòu)造方法,這樣它才能夠通過(guò)反射實(shí)例化EmailAddress對(duì)象(見(jiàn)示例4-3)。這是一個(gè)很明顯的缺點(diǎn),因?yàn)闆](méi)有辦法讓emailAddress是final的或者斷言它是非空的。用于NoSQL實(shí)現(xiàn)的SpringData映射子系統(tǒng)并沒(méi)有將這一需求暴露給開(kāi)發(fā)人員。作為示例,可以查看6.3小節(jié)“映射模塊”來(lái)了解在MongoDB中如何對(duì)更為嚴(yán)格的值對(duì)象實(shí)現(xiàn)進(jìn)行建模。示例4-4Customer領(lǐng)域類(lèi)我們?cè)陔娮余]件地址上使用了@Column注解,以保證某一個(gè)電子郵件地址不能被多個(gè)客戶所使用,這樣就能根據(jù)電子郵件來(lái)唯一地查詢客戶了。最后,我們聲明Customer有一個(gè)Address的集合。這個(gè)屬性需要更多關(guān)注一下,因?yàn)槲覀冊(cè)谶@里定義了很多事情。首先,也是最基礎(chǔ)的,我們使用@OneToMany注解來(lái)指明一個(gè)Customer可以擁有多個(gè)Address。在這個(gè)注解中,我們將級(jí)聯(lián)類(lèi)型(cascade)設(shè)置成了CascadeType.ALL,并為Address啟用了子對(duì)象移除(orphanremoval)。這會(huì)產(chǎn)生多種影響。例如,當(dāng)我們持久化、更新或者刪除Customer時(shí),Address也會(huì)被持久化、更新或刪除。因此,我們不再需要在前端持久化Address實(shí)例了并且當(dāng)刪除Customer時(shí),也不用再關(guān)心刪除所有的Address了。持久化廠商會(huì)做到這一點(diǎn)。需要注意的一點(diǎn)是,這并不是數(shù)據(jù)庫(kù)級(jí)別的級(jí)聯(lián),而是你的JPA持久化廠商所管理的級(jí)聯(lián)。除此之外,將子對(duì)象移除設(shè)置為true,那么如果Address在集合中被移除了,它們也會(huì)在數(shù)據(jù)庫(kù)中被刪除。以上所有的結(jié)果將會(huì)導(dǎo)致Address的生命周期被Customer所控制,這種關(guān)系是一種經(jīng)典的組合,在領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(domain-drivendesign)術(shù)語(yǔ)中,Customer會(huì)被成為聚合根(aggregateroot,\h/wiki/Domain-drivendesign#Buildingblocks_of_DDD),因?yàn)樗粌H為自己也為其他實(shí)體控制持久化操作以及約束。最后,我們?cè)赼ddresses屬性上使用@JoinColumn,這會(huì)導(dǎo)致持久化廠商為Address對(duì)象后端所對(duì)應(yīng)的表添加另外一列。這一列會(huì)用來(lái)引用Customer,從而實(shí)現(xiàn)表關(guān)聯(lián)。如果我們遺漏了這個(gè)注解的話,持久化廠商將會(huì)創(chuàng)建一個(gè)專(zhuān)門(mén)的連接表。在包c(diǎn)ore中,最后一部分內(nèi)容是Product,如示例4-5所示。就像前面討論的其他類(lèi)一樣,它包含了多種基本屬性,所以無(wú)需添加注解就能使持久化廠商對(duì)它們產(chǎn)生映射。我們只是在定義name和price屬性時(shí),使用@Column注解,其目的是將其定義為強(qiáng)制性的屬性。除此之外,還添加了一個(gè)Map,它會(huì)用來(lái)存儲(chǔ)額外的屬性,不同的產(chǎn)品之間這些屬性可能是不同的。示例4-5Product領(lǐng)域類(lèi)對(duì)于構(gòu)建基本的客戶關(guān)系管理(CustomerRelationManagement,CRM)或庫(kù)存系統(tǒng)而言,我們已經(jīng)萬(wàn)事俱備了。接下來(lái),為了實(shí)現(xiàn)系統(tǒng)中Product的訂單,我們需要進(jìn)行一些抽象。首先,我們引入了LineItem,它會(huì)持有對(duì)Product的引用以及Product的數(shù)量和購(gòu)買(mǎi)的價(jià)格。匹配Product屬性時(shí),我們使用了@ManyToOne注解,它實(shí)際上會(huì)轉(zhuǎn)化成LineItem對(duì)應(yīng)表中的product_id列,這一列會(huì)用來(lái)指向Product,如示例4-6所示。示例4-6LineItem領(lǐng)域類(lèi)完成這個(gè)“拼圖游戲”的最后一塊就是Order實(shí)體了,它基本上就是一個(gè)指針,指向了Customer、投遞地址、賬單地址以及表示實(shí)際購(gòu)買(mǎi)的多個(gè)LineItem(如示例4-7所示)。LineItem的映射與前面看到的Customer和Address之間映射類(lèi)似。Order會(huì)自動(dòng)對(duì)LineItem實(shí)例進(jìn)行級(jí)聯(lián)持久化操作。因此,沒(méi)有必要單獨(dú)管理LineItem的持久化生命周期。其他的屬性都是已經(jīng)討論過(guò)的多對(duì)一關(guān)系。要注意的是,我們?yōu)镺rder定義了一個(gè)個(gè)性化的表名,因?yàn)樵诖蠖鄶?shù)的數(shù)據(jù)庫(kù)中,Order本身就是一個(gè)保留字,因此,所生成的建表SQL以及為查詢和數(shù)據(jù)操作生成的SQL在執(zhí)行時(shí)都會(huì)導(dǎo)致異常產(chǎn)生。示例4-7Order領(lǐng)域類(lèi)最后一個(gè)值得注意的方面是Order類(lèi)的構(gòu)造器使用了投遞地址和賬單地址的副本。這能夠保證方法傳遞進(jìn)來(lái)的Address實(shí)例如果發(fā)生變化的話,不會(huì)關(guān)聯(lián)影響到已經(jīng)存在的訂單。如果我們不創(chuàng)建這個(gè)副本的話,如果顧客稍后修改他的地址信息,那使用這個(gè)地址創(chuàng)建的訂單中對(duì)應(yīng)的地址也會(huì)隨之發(fā)生變化。4.2傳統(tǒng)方式在開(kāi)始之前,首先看一下SpringData如何為領(lǐng)域模型實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)層,并討論如何按照傳統(tǒng)的方式實(shí)現(xiàn)數(shù)據(jù)訪問(wèn)層。在示例項(xiàng)目中會(huì)找到示例的實(shí)現(xiàn)和客戶端都使用了一些額外的注解如@Profile(對(duì)于實(shí)現(xiàn))和@ActiveProfile(對(duì)于測(cè)試用例)。因?yàn)镾pringDataRepository方式會(huì)創(chuàng)建一個(gè)CustomerRepository實(shí)例,并且我們還有一個(gè)為手動(dòng)實(shí)現(xiàn)所創(chuàng)建的實(shí)例。因此,這里使用了Spring的Profile機(jī)制,只對(duì)單一的測(cè)試用例啟動(dòng)傳統(tǒng)的實(shí)現(xiàn)方式。我們并沒(méi)有在這里的實(shí)例代碼中展示這些注解,因?yàn)槿绻凑諅鹘y(tǒng)的方式來(lái)實(shí)現(xiàn)整個(gè)數(shù)據(jù)訪問(wèn)層的話,它們實(shí)際上并不會(huì)被用到。為了使用普通的JPA來(lái)持久化前述的實(shí)體,現(xiàn)在要?jiǎng)?chuàng)建一個(gè)接口以及針對(duì)我們存儲(chǔ)的實(shí)現(xiàn),如示例4-8所示。示例4-8為Customer定義的存儲(chǔ)接口這樣,我們定義了一個(gè)save(…)方法來(lái)存儲(chǔ)賬號(hào)以及根據(jù)電子郵件地址查找所有賬號(hào)的查詢方法。現(xiàn)在看一下如果基于普通JPA實(shí)現(xiàn)的話,存儲(chǔ)的實(shí)現(xiàn)類(lèi)會(huì)是什么樣子,如示例4-9所示。示例4-9對(duì)于Customer的傳統(tǒng)存儲(chǔ)實(shí)現(xiàn)實(shí)現(xiàn)類(lèi)中使用了JPA的EntityManager,因?yàn)樵O(shè)置了JPA的@PersistenceContext注解,因此它將會(huì)通過(guò)Spring容器注入進(jìn)來(lái)。這個(gè)類(lèi)設(shè)置了@Repository注解,因此會(huì)將JPA的異常轉(zhuǎn)換為Spring的DataAccessException異常體系。除此之外,我們使用了@Transactional注解,從而保證save(...)操作運(yùn)行在事務(wù)之中,并且還允許為findByEmailAddress(...)設(shè)置readOnly標(biāo)志(在類(lèi)級(jí)別)。這有助于在持久化廠商內(nèi)部以及數(shù)據(jù)庫(kù)級(jí)別對(duì)性能進(jìn)行優(yōu)化。因?yàn)槲覀儾幌胱尶蛻舳藖?lái)決定該調(diào)用EntityManager的merge(...)
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 二零二五年度臨時(shí)用工工作滿意度調(diào)查及改進(jìn)協(xié)議4篇
- 二零二五年度宿舍安全管理宿管員聘用協(xié)議范本3篇
- 二零二五年度ISO 22000食品安全管理體系認(rèn)證咨詢協(xié)議3篇
- 二零二五年度商業(yè)地產(chǎn)項(xiàng)目配套場(chǎng)地租賃服務(wù)協(xié)議2篇
- 二零二五年度外資企業(yè)外籍員工聘用協(xié)議范本3篇
- 2025年度文化旅游項(xiàng)目募集資金三方監(jiān)管合同4篇
- 2025年度豬圈建造與生物安全防護(hù)合同4篇
- 2025年度生物制藥研發(fā)合作協(xié)議
- 二零二五年度城市綠化用地承包合同范本4篇
- 2025年智能車(chē)輛識(shí)別一體機(jī)銷(xiāo)售與服務(wù)合同范本4篇
- 纖維增強(qiáng)復(fù)合材料 單向增強(qiáng)材料Ⅰ型-Ⅱ 型混合層間斷裂韌性的測(cè)定 編制說(shuō)明
- 習(xí)近平法治思想概論教學(xué)課件緒論
- 寵物會(huì)展策劃設(shè)計(jì)方案
- 孤殘兒童護(hù)理員(四級(jí))試題
- 梁湘潤(rùn)《子平基礎(chǔ)概要》簡(jiǎn)體版
- 醫(yī)院急診醫(yī)學(xué)小講課課件:急診呼吸衰竭的處理
- 腸梗阻導(dǎo)管在臨床中的使用及護(hù)理課件
- 調(diào)料廠工作管理制度
- 小學(xué)英語(yǔ)單詞匯總大全打印
- 衛(wèi)生健康系統(tǒng)安全生產(chǎn)隱患全面排查
- GB/T 15114-2023鋁合金壓鑄件
評(píng)論
0/150
提交評(píng)論