第10章 通過Spring和JDBC征服數(shù)據(jù)庫(kù)
本章內(nèi)容:
定義Spring對(duì)數(shù)據(jù)訪問的支持
配置數(shù)據(jù)庫(kù)資源
使用Spring的JDBC模版
在掌握了Spring容器的核心知識(shí)之后纯命,是時(shí)候?qū)⑺趯?shí)際應(yīng)用中進(jìn)行使用了谦屑。數(shù)據(jù)持久化是一 個(gè)非常不錯(cuò)的起點(diǎn)炼幔,因?yàn)閹缀跛械钠髽I(yè)級(jí)應(yīng)用程序中都存在這樣的需求鸟款。我們可能都處理 過數(shù)據(jù)庫(kù)訪問功能囤躁,在實(shí)際的工作中也發(fā)現(xiàn)數(shù)據(jù)訪問有一些不足之處窃这。我們必須初始化數(shù)據(jù) 訪問框架疚脐、打開連接溪烤、處理各種異常和關(guān)閉連接。如果上述操作出現(xiàn)任何問題闷叉,都有可能損壞 或刪除珍貴的企業(yè)數(shù)據(jù)擦俐。如果你還未曾經(jīng)歷過因未妥善處理數(shù)據(jù)訪問而帶來的嚴(yán)重后果,那 我要提醒你這絕對(duì)不是什么好事情握侧。
做事要追求盡善盡美蚯瞧,所以我們選擇了Spring。Spring自帶了一組數(shù)據(jù)訪問框架品擎,集成了多種數(shù) 據(jù)訪問技術(shù)埋合。不管你是直接通過JDBC還是像Hibernate這樣的對(duì)象關(guān)系映射(object-relational mapping,ORM)框架實(shí)現(xiàn)數(shù)據(jù)持久化孽查,Spring都能夠幫你消除持久化代碼中那些單調(diào)枯燥的數(shù) 據(jù)訪問邏輯饥悴。我們可以依賴Spring來處理底層的數(shù)據(jù)訪問,這樣就可以專注于應(yīng)用程序中數(shù)據(jù) 的管理了盲再。
10.1 Spring的數(shù)據(jù)訪問哲學(xué)
從前面的幾章可以看出西设,Spring的目標(biāo)之一就是允許我們?cè)陂_發(fā)應(yīng)用程序時(shí),能夠遵循面向?qū)?象(OO)原則中的“針對(duì)接口編程”答朋。Spring對(duì)數(shù)據(jù)訪問的支持也不例外贷揽。
像很多應(yīng)用程序一樣,Spittr應(yīng)用需要從某種類型的數(shù)據(jù)庫(kù)中讀取和寫入數(shù)據(jù)梦碗。為了避免持久 化的邏輯分散到應(yīng)用的各個(gè)組件中禽绪,最好將數(shù)據(jù)訪問的功能放到一個(gè)或多個(gè)專注于此項(xiàng)任務(wù) 的組件中。這樣的組件通常稱為數(shù)據(jù)訪問對(duì)象(data access object洪规,DAO)或Repository印屁。
為了避免應(yīng)用與特定的數(shù)據(jù)訪問策略耦合在一起,編寫良好的Repository應(yīng)該以接口的方式暴 露功能斩例。圖10.1展現(xiàn)了設(shè)計(jì)數(shù)據(jù)訪問層的合理方式雄人。
服務(wù)對(duì)象 ——> repository接口 ——> repository實(shí)現(xiàn)
服務(wù)對(duì)象本身并不會(huì)處理數(shù)據(jù)訪問,而是將數(shù)據(jù)訪問委托給Repository念赶。 Repository接口確保其與服務(wù)對(duì)象的松耦合
服務(wù)對(duì)象通過接口來訪問Repository础钠。這樣做會(huì)有幾個(gè)好處。第一叉谜,它使得服務(wù)對(duì)象 易于測(cè)試旗吁,因?yàn)樗鼈儾辉倥c特定的數(shù)據(jù)訪問實(shí)現(xiàn)綁定在一起。
實(shí)際上停局,你可以為這些數(shù)據(jù)訪問 接口創(chuàng)建mock實(shí)現(xiàn)很钓,這樣無需連接數(shù)據(jù)庫(kù)就能測(cè)試服務(wù)對(duì)象,而且會(huì)顯著提升單元測(cè)試的效 率并排除因數(shù)據(jù)不一致所造成的測(cè)試失敗董栽。
此外履怯,數(shù)據(jù)訪問層是以持久化技術(shù)無關(guān)的方式來進(jìn)行訪問的。持久化方式的選擇獨(dú)立于 Repository裆泳,同時(shí)只有數(shù)據(jù)訪問相關(guān)的方法才通過接口進(jìn)行暴露叹洲。這可以實(shí)現(xiàn)靈活的設(shè)計(jì),并 且切換持久化框架對(duì)應(yīng)用程序其他部分所帶來的影響最小工禾。如果將數(shù)據(jù)訪問層的實(shí)現(xiàn)細(xì)節(jié)滲 透到應(yīng)用程序的其他部分中运提,那么整個(gè)應(yīng)用程序?qū)⑴c數(shù)據(jù)訪問層耦合在一起,從而導(dǎo)致僵化 的設(shè)計(jì)闻葵。
接口與Spring:如果在閱讀了上面幾段文字之后民泵,你能感受到我傾向于將持久層隱藏在接口之 后,那很高興我的目的達(dá)到了槽畔。我相信接口是實(shí)現(xiàn)松耦合代碼的關(guān)鍵栈妆,并且應(yīng)將其用于應(yīng)用程序的各個(gè)層,而不僅僅是持久化層。還要說明一點(diǎn)鳞尔,盡管Spring鼓勵(lì)使用接口嬉橙,但這并不是強(qiáng) 制的——你可以使用Spring將bean(DAO或其他類型)直接裝配到另一個(gè)bean的某個(gè)屬性中,而不需要一定通過接口注入寥假。
為了將數(shù)據(jù)訪問層與應(yīng)用程序的其他部分隔離開來市框,Spring采用的方式之一就是提供統(tǒng)一的 異常體系,這個(gè)異常體系用在了它支持的所有持久化方案中糕韧。
10.1.1 了解Spring的數(shù)據(jù)訪問異常體系
這里有一個(gè)關(guān)于跳傘運(yùn)動(dòng)員的經(jīng)典笑話枫振,這個(gè)運(yùn)動(dòng)員被風(fēng)吹離正常路線后降落在樹上并高高 地掛在那里。后來萤彩,有人路過粪滤,跳傘運(yùn)動(dòng)員就問他自己在什么地方。過路人回答說:“你在離地 大約20尺的空中雀扶≌刃。”跳傘運(yùn)動(dòng)員說:“你一定是個(gè)軟件分析師∨挛猓”過路人回應(yīng)說“你說對(duì)了窍侧。你是 怎么知道的呢?”“因?yàn)槟愀艺f的話百分百正確转绷,但絲毫用處都沒有伟件。”
__ 軟件分析師表示不背鍋
這個(gè)故事已經(jīng)聽過很多遍了议经,每次過路人的職業(yè)或國(guó)籍都會(huì)有所不同斧账。但是這個(gè)故事使我想 起了JDBC中的SQLException。如果你曾經(jīng)編寫過JDBC代碼(不使用Spring)煞肾,你肯定會(huì)意識(shí) 到如果不強(qiáng)制捕獲SQLException的話咧织,幾乎無法使用JDBC做任何事情。SQLException表 示在嘗試訪問數(shù)據(jù)庫(kù)的時(shí)出現(xiàn)了問題籍救,但是這個(gè)異常卻沒有告訴你哪里出錯(cuò)了以及如何進(jìn)行 處理习绢。
可能導(dǎo)致拋出SQLException的常見問題包括:
應(yīng)用程序無法連接數(shù)據(jù)庫(kù);
要執(zhí)行的查詢存在語法錯(cuò)誤蝙昙;
查詢中所使用的表和/或列不存在闪萄;
試圖插入或更新的數(shù)據(jù)違反了數(shù)據(jù)庫(kù)約束。
SQLException的問題在于捕獲到它的時(shí)候該如何處理奇颠。事實(shí)上败去,能夠觸發(fā)SQLException 的問題通常是不能在catch代碼塊中解決的。大多數(shù)拋出SQLException的情況表明發(fā)生了致 命性錯(cuò)誤烈拒。如果應(yīng)用程序不能連接到數(shù)據(jù)庫(kù)圆裕,這通常意味著應(yīng)用不能繼續(xù)使用了广鳍。類似地,如果查詢時(shí)出現(xiàn)了錯(cuò)誤吓妆,那在運(yùn)行時(shí)基本上也是無能為力赊时。
如果無法從SQLException中恢復(fù),那為什么我們還要強(qiáng)制捕獲它呢耿战?
即使對(duì)某些SQLException有處理方案蛋叼,我們還是要捕獲SQLException并查看其屬性才能獲知問題根源的更多信息焊傅。這是因?yàn)镾QLException被視為處理數(shù)據(jù)訪問所有問題的通用異常剂陡。對(duì)于所有的數(shù)據(jù)訪問問題都會(huì)拋出SQLException,而不是對(duì)每種可能的問題都會(huì)有不同的異常類型狐胎。
一些持久化框架提供了相對(duì)豐富的異常體系鸭栖。例如,Hibernate提供了二十個(gè)左右的異常握巢,分別 對(duì)應(yīng)于特定的數(shù)據(jù)訪問問題晕鹊。這樣就可以針對(duì)想處理的異常編寫catch代碼塊。
即便如此暴浦,Hibernate的異常是其本身所特有的溅话。正如前面所言,我們想將特定的持久化機(jī)制獨(dú) 立于數(shù)據(jù)訪問層歌焦。如果拋出了Hibernate所特有的異常飞几,那我們對(duì)Hibernate的使用將會(huì)滲透到應(yīng)用程序的其他部分。如果不這樣做的話独撇,我們就得捕獲持久化平臺(tái)的異常屑墨,然后將其作為平臺(tái) 無關(guān)的異常再次拋出。
一方面纷铣,JDBC的異常體系過于簡(jiǎn)單了——實(shí)際上卵史,它算不上一個(gè)體系。另一方面搜立,Hibernate的異常體系是其本身所獨(dú)有的以躯。我們需要的數(shù)據(jù)訪問異常要具有描述性而且又與特定的持久化框架無關(guān)。
__Hibernate 現(xiàn)在已經(jīng)被棄用了
Spring所提供的平臺(tái)無關(guān)的持久化異常
Spring JDBC提供的數(shù)據(jù)訪問異常體系解決了以上的兩個(gè)問題啄踊。不同于JDBC忧设,Spring提供了多 個(gè)數(shù)據(jù)訪問異常,分別描述了它們拋出時(shí)所對(duì)應(yīng)的問題社痛。表10.1對(duì)比了Spring的部分?jǐn)?shù)據(jù)訪問 異常以及JDBC所提供的異常见转。
從表中可以看出,Spring為讀取和寫入數(shù)據(jù)庫(kù)的幾乎所有錯(cuò)誤都提供了異常蒜哀。Spring的數(shù)據(jù)訪 問異常要比表10.1所列的還要多斩箫。(在此沒有列出所有的異常吏砂,因?yàn)槲也幌胱孞DBC顯得太寒 酸。)
JDBC的異常 | Spring的數(shù)據(jù)訪問異常 |
---|---|
BatchUpdateException DataTruncation SQLException | BadSqlGrammarException CannotAcquireLockException CannotSerializeTransactionException CannotGetJdbcConnectionException CleanupFailureDataAccessException ConcurrencyFailureExceptionBatchUpdateException DataAccessException DataRetrievalFailureException DataAccessResourceFailureException DataIntegrityViolationException ....... |
少 | 多 |
盡管Spring的異常體系比JDBC簡(jiǎn)單的SQLException豐富得多乘客,但它并沒有與特定的持久化方式相關(guān)聯(lián)狐血。這意味著我們可以使用Spring拋出一致的異常,而不用關(guān)心所選擇的持久化方案易核。這有助于我們將所選擇持久化機(jī)制與數(shù)據(jù)訪問層隔離開來匈织。
看!不用寫catch代碼塊
表10.1中沒有體現(xiàn)出來的一點(diǎn)就是這些異常都繼承自DataAccessException牡直。DataAccessException的特殊之處在于它是一個(gè)非檢查型異常缀匕。換句話說,沒有必要捕獲Spring所拋出的數(shù)據(jù)訪問異常(當(dāng)然碰逸,如果你想捕獲的話也是完全可以的)乡小。
DataAccessException只是Sping處理檢查型異常和非檢查型異常哲學(xué)的一個(gè)范例。Spring認(rèn)為觸發(fā)異常的很多問題是不能在catch代碼塊中修復(fù)的饵史。Spring使用了非檢查型異常满钟,而不是強(qiáng)制開發(fā)人員編寫catch代碼塊(里面經(jīng)常是空的)。這把是否要捕獲異常的權(quán)力留給了開發(fā)人員胳喷。
為了利用Spring的數(shù)據(jù)訪問異常湃番,我們必須使用Spring所支持的數(shù)據(jù)訪問模板。讓我們看一下Spring的模板是如何簡(jiǎn)化數(shù)據(jù)訪問的吭露。
【】這類異常僅僅是提示吠撮,并沒有捕獲,catch內(nèi)容需要自己來寫奴饮,好像是即使有異常仍然放行纬向,什么的
10.1.2 數(shù)據(jù)訪問模板化
如果以前有搭乘飛機(jī)旅行的經(jīng)歷,你肯定會(huì)覺得旅行中很重要的一件事就是將行李從一個(gè)地方搬運(yùn)到另一個(gè)地方戴卜。這個(gè)過程包含多個(gè)步驟逾条。當(dāng)你到達(dá)機(jī)場(chǎng)時(shí),第一站是到柜臺(tái)辦理行李托運(yùn)投剥。然后保安人員對(duì)其進(jìn)行安檢以確保安全师脂。之后行李將通過行李車轉(zhuǎn)送到飛機(jī)上。如果你需要中途轉(zhuǎn)機(jī)江锨,行李也要進(jìn)行中轉(zhuǎn)吃警。當(dāng)你到達(dá)目的地的時(shí)候,行李需要從飛機(jī)上取下來并放到傳送帶上啄育。最后酌心,你到行李認(rèn)領(lǐng)區(qū)將其取回。
盡管在這個(gè)過程中包含多個(gè)步驟挑豌,但是涉及到旅客的只有幾個(gè)安券。承運(yùn)人負(fù)責(zé)推動(dòng)整個(gè)流程墩崩。你只會(huì)在必要的時(shí)候進(jìn)行參與,其余的過程不必關(guān)心侯勉。這反映了一個(gè)強(qiáng)大的設(shè)計(jì)模式:模板方法模式鹦筹。
【】設(shè)計(jì)模式:模板方法模式 就是子類繼承接口和抽象類,只負(fù)責(zé)實(shí)現(xiàn)具體時(shí)機(jī)具體動(dòng)作需要做的個(gè)性業(yè)務(wù)邏輯代碼址貌。通用部分與調(diào)用時(shí)機(jī)都由抽象類來負(fù)責(zé)铐拐。你只要存和去行李,其他的不用管练对,感覺這個(gè)比喻不恰當(dāng)
模板方法定義過程的主要框架遍蟋。在我們的示例中,整個(gè)過程是將行李從出發(fā)地運(yùn)送到目的地锹淌。過程本身是固定不變的匿值。處理行李過程中的每個(gè)事件都會(huì)以同樣的方式進(jìn)行:托運(yùn)檢查赠制、運(yùn)送到飛機(jī)上等等赂摆。在這個(gè)過程中的某些步驟是固定的——這些步驟每次都是一樣的。比如當(dāng)飛機(jī)到達(dá)目的地后钟些,所有的行李被取下來并通過傳送帶運(yùn)到取行李處烟号。
在某些特定的步驟上,處理過程會(huì)將其工作委派給子類來完成一些特定實(shí)現(xiàn)的細(xì)節(jié)政恍。這是過程中變化的部分汪拥。例如,處理行李是從乘客在柜臺(tái)托運(yùn)行李開始的篙耗。這部分的處理往往是在最開始的時(shí)候進(jìn)行迫筑,所以它在處理過程中的順序是固定的。由于每位乘客的行李登記都不一樣宗弯,所以這個(gè)過程的實(shí)現(xiàn)是由旅客決定的脯燃。按照軟件方面的術(shù)語來講,模板方法將過程中與特定實(shí)現(xiàn)相關(guān)的部分委托給接口蒙保,而這個(gè)接口的不同實(shí)現(xiàn)定義了過程中的具體行為辕棚。
這也是Spring在數(shù)據(jù)訪問中所使用的模式。不管我們使用什么樣的技術(shù)邓厕,都需要一些特定的數(shù)據(jù)訪問步驟逝嚎。例如,我們都需要獲取一個(gè)到數(shù)據(jù)存儲(chǔ)的連接并在處理完成后釋放資源详恼。這都是在數(shù)據(jù)訪問處理過程中的固定步驟补君,但是每種數(shù)據(jù)訪問方法又會(huì)有些不同,我們會(huì)查詢不同的對(duì)象或以不同的方式更新數(shù)據(jù)昧互,這都是數(shù)據(jù)訪問過程中變化的部分挽铁。
Spring將數(shù)據(jù)訪問過程中固定的和可變的部分明確劃分為兩個(gè)不同的類:模板(template)和回調(diào)(callback)她紫。模板管理過程中固定的部分,而回調(diào)處理自定義的數(shù)據(jù)訪問代碼屿储。圖展現(xiàn)了這兩個(gè)類的職責(zé)贿讹。
repository模板 repository回調(diào)
1、準(zhǔn)備資源
2够掠、開始事務(wù) ——> 3民褂、在事務(wù)中執(zhí)行
5、提交/回滾事務(wù) <—— 4疯潭、返回?cái)?shù)據(jù)
6赊堪、關(guān)閉資源和處理錯(cuò)誤
如圖所示,Spring的模板類處理數(shù)據(jù)訪問的固定部分——事務(wù)控制竖哩、管理資源以及處理異常哭廉。同時(shí),應(yīng)用程序相關(guān)的數(shù)據(jù)訪問——語句相叁、綁定參數(shù)以及整理結(jié)果集——在回調(diào)的實(shí)現(xiàn)中處理遵绰。事實(shí)證明,這是一個(gè)優(yōu)雅的架構(gòu)增淹,因?yàn)槟阒恍桕P(guān)心自己的數(shù)據(jù)訪問邏輯即可椿访。
針對(duì)不同的持久化平臺(tái),Spring提供了多個(gè)可選的模板虑润。如果直接使用JDBC成玫,那你可以選擇JdbcTemplate。如果你希望使用對(duì)象關(guān)系映射框架拳喻,那HibernateTemplate或JpaTemplate可能會(huì)更適合你哭当。表列出了Spring所提供的所有數(shù)據(jù)訪問模板及其用途。
模板類(org.springframework.*) | 用 途 |
---|---|
jdbc.core.JdbcTemplate | JDBC連接 |
jdbc.core.namedparam.NamedParameterJdbcTemplate | 支持命名參數(shù)的JDBC連接 |
orm.hibernate3.HibernateTemplate | Hibernate 3.x以上的Session |
orm.ibatis.SqlMapClientTemplate | iBATIS SqlMap客戶端 |
orm.jdo.JdoTemplate | Java數(shù)據(jù)對(duì)象(Java Data Object)實(shí)現(xiàn) |
orm.jpa.JpaTemplate | Java持久化API的實(shí)體管理器 |
Spring為多種持久化框架提供了支持冗澈,這里沒有那么多的篇幅在本章對(duì)其進(jìn)行一一介紹钦勘。因此,我會(huì)關(guān)注于我認(rèn)為最為實(shí)用的持久化方案渗柿,這也是讀者最可能用到的个盆。
在本章中,我們將會(huì)從基礎(chǔ)的JDBC訪問開始朵栖,因?yàn)檫@是從數(shù)據(jù)庫(kù)中讀取和寫入數(shù)據(jù)的最基本方式颊亮。在第11章中,我們將會(huì)了解Hibernate和JPA陨溅,這是最流行的基于POJO的ORM方案终惑。我們會(huì)在第12章結(jié)束Spring持久化的話題,在這一章中门扇,將會(huì)看到Spring Data項(xiàng)目是如何讓Spring支持無模式數(shù)據(jù)的雹有。
但首先要說明的是Spring所支持的大多數(shù)持久化功能都依賴于數(shù)據(jù)源偿渡。因此,在聲明模板和Repository之前,我們需要在Spring中配置一個(gè)數(shù)據(jù)源用來連接數(shù)據(jù)庫(kù)。
10.2 配置數(shù)據(jù)源
無論選擇Spring的哪種數(shù)據(jù)訪問方式剪廉,你都需要配置一個(gè)數(shù)據(jù)源的引用。Spring提供了在Spring上下文中配置數(shù)據(jù)源bean的多種方式适揉,包括:
通過JDBC驅(qū)動(dòng)程序定義的數(shù)據(jù)源;
通過JNDI查找的數(shù)據(jù)源煤惩;
連接池的數(shù)據(jù)源嫉嘀。
對(duì)于即將發(fā)布到生產(chǎn)環(huán)境中的應(yīng)用程序,我建議使用從連接池獲取連接的數(shù)據(jù)源魄揉。如果可能的話剪侮,我傾向于通過應(yīng)用服務(wù)器的JNDI來獲取數(shù)據(jù)源。請(qǐng)記住這一點(diǎn)洛退,讓我們首先看一下如何配置Spring從JNDI中獲取數(shù)據(jù)源瓣俯。
10.2.1 使用JNDI數(shù)據(jù)源
Spring應(yīng)用程序經(jīng)常部署在Java EE應(yīng)用服務(wù)器中,如WebSphere不狮、JBoss或甚至像Tomcat這樣的Web容器中降铸。這些服務(wù)器允許你配置通過JNDI獲取數(shù)據(jù)源。這種配置的好處在于數(shù)據(jù)源完全可以在應(yīng)用程序之外進(jìn)行管理摇零,這樣應(yīng)用程序只需在訪問數(shù)據(jù)庫(kù)的時(shí)候查找數(shù)據(jù)源就可以了。
另外桶蝎,在應(yīng)用服務(wù)器中管理的數(shù)據(jù)源通常以池的方式組織驻仅,從而具備更好的性能,并且還支持系統(tǒng)管理員對(duì)其進(jìn)行熱切換登渣。
利用Spring噪服,我們可以像使用Spring bean那樣配置JNDI中數(shù)據(jù)源的引用并將其裝配到需要的類中。位于jee命名空間下的元素可以用于檢索JNDI中的任何對(duì)象(包括數(shù)據(jù)源)并將其作為Spring的bean胜茧。例如粘优,如果應(yīng)用程序的數(shù)據(jù)源配置在JNDI中,我們可以使用元素將其裝配到Spring中呻顽,如下所示:
如果想使用Java配置的話雹顺,那我們可以借助JndiObjectFactoryBean從JNDI中查找DataSource:
顯然,通過Java配置獲取JNDI bean要更為復(fù)雜廊遍。大多數(shù)情況下嬉愧,Java配置要比XML配置簡(jiǎn)單,但是這一次我們需要寫更多的Java代碼喉前。但是没酣,很容易就能夠看出Java代碼中與XML相對(duì)應(yīng)的配置王财,Java配置的內(nèi)容其實(shí)也不算多。
10.2.2 使用數(shù)據(jù)源連接池
如果你不能從JNDI中查找數(shù)據(jù)源裕便,那么下一個(gè)選擇就是直接在Spring中配置數(shù)據(jù)源連接池绒净。盡管Spring并沒有提供數(shù)據(jù)源連接池實(shí)現(xiàn),但是我們有多項(xiàng)可用的方案偿衰,包括如下開源的實(shí)現(xiàn):
Apache Commons DBCP (http://jakarta.apache.org/commons/dbcp)疯溺;
BoneCP (http://jolbox.com/) 哎垦。
Java配置的話囱嫩,連接池形式的DataSourcebean可以聲明如下:
/**
* 配置數(shù)據(jù)源 注入到Spring中
* @return
* @throws SQLException
*/
@Bean
public DataSource dataSource() throws SQLException {
// BasicDataSource ds = new BasicDataSource();
DruidDataSource ds = new DruidDataSource(); // 將讀取出來的數(shù)據(jù)設(shè)置到 DruidDataSource 中
ds.setDriverClassName(druidSettings.getJdbcDriverClassName());
ds.setUrl(druidSettings.getDruidUrl());
ds.setUsername(druidSettings.getUsername());
ds.setPassword(druidSettings.getPassword());
ds.setInitialSize(druidSettings.getInitialSize());
ds.setMinIdle(druidSettings.getMinIdle());
ds.setMaxActive(druidSettings.getMaxActive());
ds.setTimeBetweenEvictionRunsMillis(druidSettings.getTimeBetweenEvictionRunsMillis());
ds.setMinEvictableIdleTimeMillis(druidSettings.getMinEvictableIdleTimeMillis());
ds.setValidationQuery(druidSettings.getValidationQuery());
ds.setTestWhileIdle(druidSettings.isTestWhileIdle());
ds.setTestOnBorrow(druidSettings.isTestOnBorrow());
ds.setTestOnReturn(druidSettings.isTestOnReturn());
ds.setPoolPreparedStatements(druidSettings.isPoolPreparedStatements());
ds.setMaxPoolPreparedStatementPerConnectionSize(druidSettings.getMaxPoolPreparedStatementPerConnectionSize());
ds.setFilters(druidSettings.getFilters());
ds.setConnectionProperties(druidSettings.getConnectionProperties());
return ds;
}
前四個(gè)屬性是配置BasicDataSource所必需的。屬性driverClassName指定了JDBC驅(qū)動(dòng)類的全限定類名漏设。在這里我們配置的是H2數(shù)據(jù)庫(kù)的數(shù)據(jù)源墨闲。屬性u(píng)rl用于設(shè)置數(shù)據(jù)庫(kù)的JDBCURL。最后郑口,username和password用于在連接數(shù)據(jù)庫(kù)時(shí)進(jìn)行認(rèn)證鸳碧。
以上四個(gè)基本屬性定義了BasicDataSource的連接信息。除此以外犬性,還有多個(gè)配置數(shù)據(jù)源連接池的屬性瞻离。表10.3列出了DBCP BasicDataSource最有用的一些池配置屬性
BasicDataSource的池配置屬性
池配置屬性 | 所指定的內(nèi)容 |
---|---|
initialSize | 池啟動(dòng)時(shí)創(chuàng)建的連接數(shù)量 |
maxActive | 同一時(shí)間可從池中分配的最多連接數(shù)。如果設(shè)置為0乒裆,表示無限制 |
maxIdle | 池里不會(huì)被釋放的最多空閑連接數(shù)套利。如果設(shè)置為0,表示無限 |
minIdle | 在不創(chuàng)建新連接的情況下鹤耍,池中保持空閑的最小連接數(shù) |
maxWait | 在拋出異常之前肉迫,池等待連接回收的最大時(shí)間(當(dāng)沒有可用連接時(shí))。如果設(shè)置為-1稿黄,表示無限等待 |
minEvictableIdleTimeMillis | 連接在池中保持空閑而不被回收的最大時(shí)間 |
poolPreparedStatements | 是否對(duì)預(yù)處理語句(prepared statement)進(jìn)行池管理(布爾值) |
maxOpenPreparedStatements | 在同一時(shí)間能夠從語句池中分配的預(yù)處理語句(prepared statement)的最大數(shù)量喊衫。如果設(shè)置為0,表示無限制 |
10.2.3 基于JDBC驅(qū)動(dòng)的數(shù)據(jù)源
在Spring中杆怕,通過JDBC驅(qū)動(dòng)定義數(shù)據(jù)源是最簡(jiǎn)單的配置方式族购。Spring提供了三個(gè)這樣的數(shù)據(jù)源類(均位于org.springframework.jdbc.datasource包中)供選擇:
DriverManagerDataSource:在每個(gè)連接請(qǐng)求時(shí)都會(huì)返回一個(gè)新建的連接。與DBCP的BasicDataSource不同陵珍,由DriverManagerDataSource提供的連接并沒有進(jìn)行池化管理寝杖;
SimpleDriverDataSource:與DriverManagerDataSource的工作方式類似,但是它直接使用JDBC驅(qū)動(dòng)撑教,來解決在特定環(huán)境下的類加載問題朝墩,這樣的環(huán)境包括OSGi容器;
SingleConnectionDataSource:在每個(gè)連接請(qǐng)求時(shí)都會(huì)返回同一個(gè)的連接。盡管SingleConnectionDataSource不是嚴(yán)格意義上的連接池?cái)?shù)據(jù)源收苏,但是你可以將其視為只有一個(gè)連接的池亿卤。
以上這些數(shù)據(jù)源的配置與DBCPBasicDataSource的配置類似。例如鹿霸,如下就是配置DriverManagerDataSource的方法:
與具備池功能的數(shù)據(jù)源相比排吴,唯一的區(qū)別在于這些數(shù)據(jù)源bean都沒有提供連接池功能,所以沒有可配置的池相關(guān)的屬性懦鼠。
盡管這些數(shù)據(jù)源對(duì)于小應(yīng)用或開發(fā)環(huán)境來說是不錯(cuò)的钻哩,但是要將其用于生產(chǎn)環(huán)境,你還是需要慎重考慮肛冶。因?yàn)镾ingleConnectionDataSource有且只有一個(gè)數(shù)據(jù)庫(kù)連接街氢,所以不適合用于多線程的應(yīng)用程序,最好只在測(cè)試的時(shí)候使用睦袖。而DriverManagerDataSource和SimpleDriverDataSource盡管支持多線程珊肃,但是在每次請(qǐng)求連接的時(shí)候都會(huì)創(chuàng)建新連接,這是以性能為代價(jià)的馅笙。鑒于以上的這些限制伦乔,我強(qiáng)烈建議應(yīng)該使用數(shù)據(jù)源連接池。
10.2.4 使用嵌入式的數(shù)據(jù)源
除此之外董习,還有一個(gè)數(shù)據(jù)源是我想對(duì)讀者介紹的:嵌入式數(shù)據(jù)庫(kù)(embedded database)烈和。嵌入式數(shù)據(jù)庫(kù)作為應(yīng)用的一部分運(yùn)行,而不是應(yīng)用連接的獨(dú)立數(shù)據(jù)庫(kù)服務(wù)器皿淋。盡管在生產(chǎn)環(huán)境的設(shè)置中招刹,它并沒有太大的用處,但是對(duì)于開發(fā)和測(cè)試來講沥匈,嵌入式數(shù)據(jù)庫(kù)都是很好的可選方案蔗喂。 這是因?yàn)槊看沃貑?yīng)用或運(yùn)行測(cè)試的時(shí)候,都能夠重新填充測(cè)試數(shù)據(jù)高帖。
H2數(shù)據(jù)庫(kù)比較坑,就不學(xué)了畦粮,現(xiàn)在用的也少
10.2.5 使用profile選擇數(shù)據(jù)源
多種在Spring中配置數(shù)據(jù)源的方法散址,我相信你已經(jīng)找到了一兩種適合你的應(yīng)用程序的配置方式。實(shí)際上宣赔,我們很可能面臨這樣一種需求预麸,那就是在某種環(huán)境下需要其中一種數(shù)據(jù)源,而在另外的環(huán)境中需要不同的數(shù)據(jù)源儒将。
例如吏祸,對(duì)于開發(fā)期來說,元素是很合適的钩蚊,而在QA環(huán)境中贡翘,你可能希望使用DBCP的BasicDataSource蹈矮,在生產(chǎn)部署環(huán)境下,可能需要使用鸣驱。
對(duì)于hap說來是用幾個(gè)profile來分開配置sit uat prd下的環(huán)境泛鸟,打包時(shí)選擇不同的環(huán)境,本地啟動(dòng)時(shí)需要配置
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<profile.env>dev</profile.env>
</properties>
</profile>
<profile>
<id>sit</id>
<activation/>
<properties>
<profile.env>sit</profile.env>
</properties>
</profile>
<profile>
<id>uat</id>
<activation/>
<properties>
<profile.env>uat</profile.env>
</properties>
</profile>
</profiles>
10.3 在Spring中使用JDBC
持久化技術(shù)有很多種踊东,而Hibernate北滥、iBATIS和JPA只是其中的幾種而已。盡管如此闸翅,還是有很多的應(yīng)用程序使用最古老的方式將Java對(duì)象保存到數(shù)據(jù)庫(kù)中:他們自食其力再芋。不,等等坚冀,這是他們掙錢的途徑济赎。這種久經(jīng)考驗(yàn)并證明行之有效的持久化方法就是古老的JDBC。
為什么不采用它呢遗菠?JDBC不要求我們掌握其他框架的查詢語言联喘。它是建立在SQL之上的,而SQL本身就是數(shù)據(jù)訪問語言辙纬。此外豁遭,與其他的技術(shù)相比,使用JDBC能夠更好地對(duì)數(shù)據(jù)訪問的性能進(jìn)行調(diào)優(yōu)贺拣。JDBC允許你使用數(shù)據(jù)庫(kù)的所有特性蓖谢,而這是其他框架不鼓勵(lì)甚至禁止的。
再者譬涡,相對(duì)于持久層框架闪幽,JDBC能夠讓我們?cè)诟偷膶哟紊咸幚頂?shù)據(jù),我們可以完全控制應(yīng)用程序如何讀取和管理數(shù)據(jù)涡匀,包括訪問和管理數(shù)據(jù)庫(kù)中單獨(dú)的列盯腌。這種細(xì)粒度的數(shù)據(jù)訪問方式在很多應(yīng)用程序中是很方便的。例如在報(bào)表應(yīng)用中陨瘩,如果將數(shù)據(jù)組織為對(duì)象腕够,而接下來唯一要做的就是將其解包為原始數(shù)據(jù),那就沒有太大意義了舌劳。
但是JDBC也不是十全十美的帚湘。雖然JDBC具有強(qiáng)大、靈活和其他一些優(yōu)點(diǎn)甚淡,但也有其不足之處大诸。
10.3.1 應(yīng)對(duì)失控的JDBC代碼
如果使用JDBC所提供的直接操作數(shù)據(jù)庫(kù)的API,你需要負(fù)責(zé)處理與數(shù)據(jù)庫(kù)訪問相關(guān)的所有事情,其中包含管理數(shù)據(jù)庫(kù)資源和處理異常资柔。如果你曾經(jīng)使用JDBC往數(shù)據(jù)庫(kù)中插入數(shù)據(jù)焙贷,那如下代碼對(duì)你應(yīng)該并不陌生:
private static final String SQL_INSERT_SPITTER = "insert into spitter (username, password, first_name) values (?, ?, ?)";
private DataSource dataSource;
?
public void addSpitter(Spitter spitter) {
Connection conn = null;
PreparedStatement stmt = null;
?
try {
/**
* 1、獲取連接
* 2建邓、創(chuàng)建語句
* 3盈厘、綁定參數(shù)
* 4、執(zhí)行語句
*/
conn = dataSource.getConnection();
stmt = conn.prepareStatement(SQL_INSERT_SPITTER);
stmt.setString(1, spitter.getUsername());
stmt.setString(2, spitter.getPassword());
stmt.setString(3, spitter.getFirstName());
stmt.execute();
} catch (SQLException e) {
e.printStackTrace();
// 處理異常
} finally {
try {
// 清理資源
if (stmt !=null) {
stmt.close();
}
if (conn == null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
?
}
傳統(tǒng)的JDBC來更新數(shù)據(jù)庫(kù)中Spitter表的一行官边。查詢 省略沸手。。注簿。契吉。
10.3.2 使用JDBC模板
Spring的JDBC框架承擔(dān)了資源管理和異常處理的工作,從而簡(jiǎn)化了JDBC代碼诡渴,讓我們只需編寫從數(shù)據(jù)庫(kù)讀寫數(shù)據(jù)的必需代碼捐晶。
正如前面小節(jié)所介紹過的,Spring將數(shù)據(jù)訪問的樣板代碼抽象到模板類之中妄辩。Spring為JDBC提供了三個(gè)模板類供選擇:
JdbcTemplate:最基本的Spring JDBC模板惑灵,這個(gè)模板支持簡(jiǎn)單的JDBC數(shù)據(jù)庫(kù)訪問功能以及基于索引參數(shù)的查詢;
NamedParameterJdbcTemplate:使用該模板類執(zhí)行查詢時(shí)可以將值以命名參數(shù)的形式綁定到SQL中眼耀,而不是使用簡(jiǎn)單的索引參數(shù)英支;
SimpleJdbcTemplate:該模板類利用Java 5的一些特性如自動(dòng)裝箱、泛型以及可變參數(shù)列表來簡(jiǎn)化JDBC模板的使用哮伟。
以前干花,在選擇哪一個(gè)JDBC模板的時(shí)候,我們需要仔細(xì)權(quán)衡楞黄。但是從Spring 3.1開始池凄,做這個(gè)決定變得容易多了。SimpleJdbcTemplate已經(jīng)被廢棄了鬼廓,其Java 5的特性被轉(zhuǎn)移到了JdbcTemplate中肿仑,并且只有在你需要使用命名參數(shù)的時(shí)候,才需要使用NamedParameterJdbcTemplate碎税。這樣的話柏副,對(duì)于大多數(shù)的JDBC任務(wù)來
說,JdbcTemplate就是最好的可選方案蚣录,這也是本小節(jié)中所關(guān)注的方案。
使用JdbcTemplate來插入數(shù)據(jù)
為了讓JdbcTemplate正常工作眷篇,只需要為其設(shè)置DataSource就可以了萎河,這使得在Spring中配置JdbcTemplate非常容易,如下面的@Bean方法所示:
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
DataSource是通過構(gòu)造器參數(shù)注入進(jìn)來的,將jdbcTemplate裝配到Repository中并使用它來訪問數(shù)據(jù)庫(kù)
@Repository
public class JdbcSpitterRepository implements SpittleRepository {
?
private JdbcOperations jdbcOperations;
@Inject
public JdbcSpitterRepository(JdbcOperations jdbcOperations) {
this.jdbcOperations = jdbcOperations;
}
}
在這里虐杯,JdbcSpitterRepository類上使用了@Repository注解玛歌,這表明它將會(huì)在組件掃描的時(shí)候自動(dòng)創(chuàng)建。它的構(gòu)造器上使用了@Inject注解擎椰,因此在創(chuàng)建的時(shí)候支子,會(huì)自動(dòng)獲得一個(gè)JdbcOperations對(duì)象。JdbcOperations是一個(gè)接口达舒,定義了JdbcTemplate所實(shí)現(xiàn)的操作值朋。通過注入JdbcOperations,而不是具體的JdbcTemplate巩搏,能夠保證 JdbcSpitterRepository通過JdbcOperations接口達(dá)到與JdbcTemplate保持松耦合昨登。
public Spitter save(Spitter spitter) {
jdbcTemplate.update(
"insert into Spitter (username, password, first_name, last_name, email)" +
" values (?, ?, ?, ?, ?)",
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName(),
spitter.getLastName(),
spitter.getEmail());
return spitter;
}
?
@Override
public void save(Spitter spitter) {
jdbcOperations.update(SQL_INSERT_SPITTER,
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName());
?
}
在JdbcTemplate中使用Java 8的Lambda表達(dá)式
public Spitter findById(Long id) {
return jdbcOperations.queryForObject(
"select id, username,password,first_name, last_name, email from spitter where id=? ",
(rs, rowNum) -> {return new Spitter(rs.getLong("id"),
rs.getString("username"),
rs.getString("passsword"),
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("email"));
},
id);
}
此處查詢過程中遇到個(gè)小小的問題,也是糾結(jié)很久贯底,報(bào)錯(cuò)信息大概是
Request processing failed; nested exception is java.lang.IllegalArgumentExce
參數(shù)異常丰辣,不能轉(zhuǎn)化為spitter等
我解決方式從數(shù)據(jù)映射開始,看是否查出結(jié)果禽捆,是否可以從結(jié)果中取出值笙什,是否可以轉(zhuǎn)成spitter,[圖片上傳失敗...(image-5b69e1-1557068358284)]
并沒有問題,此時(shí)才明白了是沒有返回json格式胚想,是springmvc的問題琐凭,有添加了兩個(gè)fastjson的jar包就可以了,對(duì)自己也是呵呵一笑
private static final String SQL_INSERT_SPITTER = "insert into spitter (username, password, first_name, last_name, email) values (?, ?, ?, ?, ?)";
private static final String SQL_UPDATE_SPITTER = "update spitter set username = ?, password = ?, first_name = ?, last_name = ?, email = ?" +
"where id = ?";
private static final String SQL_INSERT_SPITTER_PARAM = "insert into spitter (username, password, first_name, last_name, email)" +
" values (:username, :password, :first_name, :last_name, :email)";
?
private static final String SQL_UPDATE_SPITTER_ROLE = "update spitter set ROLE_USER = ?, enabled = ?" +
"where id = ?";
?
public void addSpitter(Spitter spitter) {
// 綁定參數(shù)
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("username", spitter.getUsername());
paramMap.put("password", spitter.getPassword());
paramMap.put("first_name", spitter.getFirstName());
paramMap.put("last_name", spitter.getLastName());
paramMap.put("email", spitter.getEmail());
jdbcOperations.update(SQL_INSERT_SPITTER_PARAM, paramMap);
?
KeyHolder keyHolder = new GeneratedKeyHolder();
//jdbcTemplate.update(new PreparedStatementCreator() {
// @Override
// public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
// PreparedStatement ps = (PreparedStatement) connection.prepareStatement(SQL_INSERT_SPITTER, Statement.RETURN_GENERATED_KEYS);
// ps.setObject(1, spitter.getUsername());
// ps.setObject(2, spitter.getPassword());
// ps.setObject(3, spitter.getFirstName());
// ps.setObject(4, spitter.getLastName());
// ps.setObject(5, spitter.getEmail());
// return ps;
// }
//}, keyHolder);
?
paramMap.put("role_name", "ROLE_USER");
paramMap.put("enabled", "true");
jdbcOperations.update(SQL_UPDATE_SPITTER_ROLE, paramMap);
// jdbcOperations.update(SQL_UPDATE_SPITTER_ROLE, "ROLE_USER", "true", keyHolder.getKey().intValue());
}
?
?
這個(gè)版本的addSpitter()比前一版本的代碼要長(zhǎng)一些顿仇。這是因?yàn)槊麉?shù)是通過java.util.Map來進(jìn)行綁定的淘正。不過,每行代碼都關(guān)注于往數(shù)據(jù)庫(kù)中插入Spitter對(duì)象臼闻。這個(gè)方法的核心功能并不會(huì)被資源管理或異常處理這樣的代碼所充斥鸿吆。
這個(gè)例子,沒有執(zhí)行成功述呐,而選擇了傳統(tǒng)的惩淳?方式執(zhí)行的,注釋部分為可執(zhí)行代碼
10.4 小結(jié)
數(shù)據(jù)是應(yīng)用程序的血液乓搬。有些數(shù)據(jù)中心論者甚至主張數(shù)據(jù)即應(yīng)用思犁。鑒于數(shù)據(jù)的重要地位,以健壯进肯、簡(jiǎn)單和清晰的方式開發(fā)應(yīng)用程序的數(shù)據(jù)訪問部分就顯得舉足輕重了激蹲。 在Java中,JDBC是與關(guān)系型數(shù)據(jù)庫(kù)交互的最基本方式江掩。但是按照規(guī)范学辱,JDBC有些太笨重了乘瓤。 Spring能夠解除我們使用JDBC中的大多數(shù)痛苦,包括消除樣板式代碼策泣、簡(jiǎn)化JDBC異常處理衙傀,你所需要做的僅僅是關(guān)注要執(zhí)行的SQL語句。 在本章中萨咕,我們學(xué)習(xí)了Spring對(duì)數(shù)據(jù)持久化的支持统抬,以及Spring為JDBC所提供的基于模板的抽象,它能夠極大地簡(jiǎn)化JDBC的使用危队。
雜談
這章雖然相對(duì)基礎(chǔ)聪建,沒有精髓地方?jīng)]有列出,比如說batchupdate等
補(bǔ)充知識(shí)
查詢多條數(shù)據(jù)
public List<Spitter> findByUsername(String username) {
?
List<Spitter> spitters = this.jdbcTemplate.query(
"select id, username,password,first_name, last_name, email from spitter where username=? ",
(rs, rowNum) -> {return new Spitter(rs.getLong("id"),
rs.getString("username"),
rs.getString("password"),
rs.getString("first_name"),
rs.getString("last_name"),
rs.getString("email"));
},
username);
return spitters;
}
jdbc批量操作
public int[] batchUpdate(final List<Spitter> spitters) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Spitter spitter : spitters) {
Object[] values = new Object[] {
spitter.getUsername(),
spitter.getPassword(),
spitter.getFirstName(),
spitter.getLastName(),
spitter.getEmail()};
batch.add(values);
}
String SQL_INSERT_SPITTER = "insert into spitter (username, password, first_name, last_name, email) values (?, ?, ?, ?, ?)";
?
int[] updateCounts = jdbcTemplate.batchUpdate(
SQL_INSERT_SPITTER,
batch);
return updateCounts;
}
mybatis的批量操作
<insert id="batchInsert" parameterType="java.util.List">
insert into words_result(id,wc,wordBg,wordEd, wordsName, wp, alter_native,text_id,date_time)
values
<foreach collection="list" item="item" index="index" separator=",">
(#{item.id,jdbcType=INTEGER},
#{item.wc,jdbcType=VARCHAR},
#{item.wordBg,jdbcType=VARCHAR},
#{item.wordEd,jdbcType=VARCHAR},
#{item.wordsName,jdbcType=VARCHAR},
#{item.wp,jdbcType=VARCHAR},
#{item.alterNative,jdbcType=VARCHAR},
#{item.textId,jdbcType=INTEGER},
#{item.dateTime})
</foreach>
</insert>
?
<update id="batchUpdate" parameterType="java.util.Map">
?
<foreach collection="list" separator=";" item="item">
update words_result set
?
<if test="item.wc != null">
wc = #{item.wc,jdbcType=VARCHAR},
</if>
<if test="item.wordBg != null">
wordBg = #{item.wordBg,jdbcType=VARCHAR},
</if>
<if test="item.wordEd != null">
wordEd = #{item.wordEd,jdbcType=VARCHAR},
</if>
<if test="item.wordsName != null">
wordsName = #{item.wordsName,jdbcType=VARCHAR},
</if>
<if test="item.wp != null">
wp = #{item.wp,jdbcType=VARCHAR},
</if>
<if test="item.alterNative != null">
alter_native = #{item.alterNative,jdbcType=VARCHAR},
</if>
<if test="item.textId != null">
text_id = #{item.textId,jdbcType=INTEGER},
</if>
<if test="item.dateTime != null">
update_date = #{item.dateTime,jdbcType=VARCHAR}
</if>
where id = #{item.id,jdbcType=INTEGER}
</foreach>
</update>
spring security 配置權(quán)限
/**
* 從數(shù)據(jù)庫(kù)中讀取用戶和權(quán)限信息
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws AuthenticationException {
System.out.println("加載Security交掏。妆偏。。讀取權(quán)限");
// 啟用內(nèi)存用戶儲(chǔ)存
/* auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("user").password("1").roles("USER").and()
.withUser("admin").password("1").roles("USER","ADMIN");*/
try {
auth.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select username, password, enabled from spitter where username = ?")
.authoritiesByUsernameQuery("select username, ROLE_USER from spitter where username = ?");
} catch (Exception e) {
e.printStackTrace();
}
}
? ——2019/05/05 于成都完成