通過Spring和JDBC征服數(shù)據(jù)庫(kù)

10章 通過SpringJDBC征服數(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):

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.3Spring中使用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 于成都完成

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末盅弛,一起剝皮案震驚了整個(gè)濱河市钱骂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挪鹏,老刑警劉巖见秽,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異讨盒,居然都是意外死亡解取,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門返顺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禀苦,“玉大人,你說我怎么就攤上這事遂鹊≌穹Γ” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵秉扑,是天一觀的道長(zhǎng)慧邮。 經(jīng)常有香客問我,道長(zhǎng)舟陆,這世上最難降的妖魔是什么误澳? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮秦躯,結(jié)果婚禮上忆谓,老公的妹妹穿的比我還像新娘。我一直安慰自己踱承,他們只是感情好陪毡,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布米母。 她就那樣靜靜地躺著,像睡著了一般毡琉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妙色,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天桅滋,我揣著相機(jī)與錄音,去河邊找鬼身辨。 笑死丐谋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的煌珊。 我是一名探鬼主播号俐,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼定庵!你這毒婦竟也來了吏饿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蔬浙,失蹤者是張志新(化名)和其女友劉穎猪落,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畴博,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笨忌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俱病。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片官疲。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亮隙,靈堂內(nèi)的尸體忽然破棺而出途凫,到底是詐尸還是另有隱情,我是刑警寧澤咱揍,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布颖榜,位于F島的核電站,受9級(jí)特大地震影響煤裙,放射性物質(zhì)發(fā)生泄漏掩完。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一硼砰、第九天 我趴在偏房一處隱蔽的房頂上張望且蓬。 院中可真熱鬧,春花似錦题翰、人聲如沸恶阴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冯事。三九已至焦匈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昵仅,已是汗流浹背缓熟。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摔笤,地道東北人够滑。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像吕世,于是被迫代替她去往敵國(guó)和親彰触。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容