經(jīng)典Java面試題收集(三)

這部分主要是開源Java EE框架方面的內(nèi)容,包括Hibernate、MyBatis擎宝、Spring、Spring MVC等浑玛,由于Struts 2已經(jīng)是明日黃花绍申,在這里就不討論Struts 2的面試題。此外顾彰,這篇文章還對(duì)企業(yè)應(yīng)用架構(gòu)极阅、大型網(wǎng)站架構(gòu)和應(yīng)用服務(wù)器優(yōu)化等內(nèi)容進(jìn)行了簡(jiǎn)單的探討,這些內(nèi)容相信對(duì)面試會(huì)很有幫助涨享。

126筋搏、什么是ORM?

答:對(duì)象關(guān)系映射(Object-Relational Mapping灰伟,簡(jiǎn)稱ORM)是一種為了解決程序的面向?qū)ο竽P团c數(shù)據(jù)庫(kù)的關(guān)系模型互不匹配問題的技術(shù)拆又;簡(jiǎn)單的說儒旬,ORM是通過使用描述對(duì)象和數(shù)據(jù)庫(kù)之間映射的元數(shù)據(jù)(在Java中可以用XML或者是注解)栏账,將程序中的對(duì)象自動(dòng)持久化到關(guān)系數(shù)據(jù)庫(kù)中或者將關(guān)系數(shù)據(jù)庫(kù)表中的行轉(zhuǎn)換成Java對(duì)象帖族,其本質(zhì)上就是將數(shù)據(jù)從一種形式轉(zhuǎn)換到另外一種形式。

127挡爵、持久層設(shè)計(jì)要考慮的問題有哪些竖般?你用過的持久層框架有哪些?

答:所謂"持久"就是將數(shù)據(jù)保存到可掉電式存儲(chǔ)設(shè)備中以便今后使用茶鹃,簡(jiǎn)單的說涣雕,就是將內(nèi)存中的數(shù)據(jù)保存到關(guān)系型數(shù)據(jù)庫(kù)、文件系統(tǒng)闭翩、消息隊(duì)列等提供持久化支持的設(shè)備中挣郭。持久層就是系統(tǒng)中專注于實(shí)現(xiàn)數(shù)據(jù)持久化的相對(duì)獨(dú)立的層面。

持久層設(shè)計(jì)的目標(biāo)包括:

  • 數(shù)據(jù)存儲(chǔ)邏輯的分離疗韵,提供抽象化的數(shù)據(jù)訪問接口兑障。
  • 數(shù)據(jù)訪問底層實(shí)現(xiàn)的分離,可以在不修改代碼的情況下切換底層實(shí)現(xiàn)蕉汪。
  • 資源管理和調(diào)度的分離流译,在數(shù)據(jù)訪問層實(shí)現(xiàn)統(tǒng)一的資源調(diào)度(如緩存機(jī)制)。
  • 數(shù)據(jù)抽象者疤,提供更面向?qū)ο蟮臄?shù)據(jù)操作福澡。

持久層框架有:

  • Hibernate
  • MyBatis
  • TopLink
  • Guzz
  • jOOQ
  • Spring Data
  • ActiveJDBC

128、Hibernate中SessionFactory是線程安全的嗎驹马?Session是線程安全的嗎(兩個(gè)線程能夠共享同一個(gè)Session嗎)革砸?

答:SessionFactory對(duì)應(yīng)Hibernate的一個(gè)數(shù)據(jù)存儲(chǔ)的概念,它是線程安全的糯累,可以被多個(gè)線程并發(fā)訪問算利。SessionFactory一般只會(huì)在啟動(dòng)的時(shí)候構(gòu)建。對(duì)于應(yīng)用程序寇蚊,最好將SessionFactory通過單例模式進(jìn)行封裝以便于訪問笔时。Session是一個(gè)輕量級(jí)非線程安全的對(duì)象(線程間不能共享session),它表示與數(shù)據(jù)庫(kù)進(jìn)行交互的一個(gè)工作單元仗岸。Session是由SessionFactory創(chuàng)建的允耿,在任務(wù)完成之后它會(huì)被關(guān)閉。Session是持久層服務(wù)對(duì)外提供的主要接口扒怖。Session會(huì)延遲獲取數(shù)據(jù)庫(kù)連接(也就是在需要的時(shí)候才會(huì)獲冉衔)。為了避免創(chuàng)建太多的session盗痒,可以使用ThreadLocal將session和當(dāng)前線程綁定在一起蚂蕴,這樣可以讓同一個(gè)線程獲得的總是同一個(gè)session低散。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。

129骡楼、Hibernate中Session的load和get方法的區(qū)別是什么熔号?

答:主要有以下三項(xiàng)區(qū)別:
① 如果沒有找到符合條件的記錄,get方法返回null鸟整,load方法拋出異常引镊。
② get方法直接返回實(shí)體類對(duì)象,load方法返回實(shí)體類對(duì)象的代理篮条。
③ 在Hibernate 3之前弟头,get方法只在一級(jí)緩存中進(jìn)行數(shù)據(jù)查找,如果沒有找到對(duì)應(yīng)的數(shù)據(jù)則越過二級(jí)緩存涉茧,直接發(fā)出SQL語句完成數(shù)據(jù)讀雀昂蕖;load方法則可以從二級(jí)緩存中獲取數(shù)據(jù)伴栓;從Hibernate 3開始伦连,get方法不再是對(duì)二級(jí)緩存只寫不讀,它也是可以訪問二級(jí)緩存的挣饥。

說明:對(duì)于load()方法Hibernate認(rèn)為該數(shù)據(jù)在數(shù)據(jù)庫(kù)中一定存在可以放心的使用代理來實(shí)現(xiàn)延遲加載除师,如果沒有數(shù)據(jù)就拋出異常,而通過get()方法獲取的數(shù)據(jù)可以不存在扔枫。

130汛聚、Session的save()、update()短荐、merge()倚舀、lock()、saveOrUpdate()和persist()方法分別是做什么的忍宋?有什么區(qū)別痕貌?

答:Hibernate的對(duì)象有三種狀態(tài):瞬時(shí)態(tài)(transient)、持久態(tài)(persistent)和游離態(tài)(detached)糠排,如第135題中的圖所示舵稠。瞬時(shí)態(tài)的實(shí)例可以通過調(diào)用save()、persist()或者saveOrUpdate()方法變成持久態(tài)入宦;游離態(tài)的實(shí)例可以通過調(diào)用 update()哺徊、saveOrUpdate()、lock()或者replicate()變成持久態(tài)乾闰。save()和persist()將會(huì)引發(fā)SQL的INSERT語句落追,而update()或merge()會(huì)引發(fā)UPDATE語句。save()和update()的區(qū)別在于一個(gè)是將瞬時(shí)態(tài)對(duì)象變成持久態(tài)涯肩,一個(gè)是將游離態(tài)對(duì)象變?yōu)槌志脩B(tài)轿钠。merge()方法可以完成save()和update()方法的功能巢钓,它的意圖是將新的狀態(tài)合并到已有的持久化對(duì)象上或創(chuàng)建新的持久化對(duì)象。對(duì)于persist()方法疗垛,按照官方文檔的說明:① persist()方法把一個(gè)瞬時(shí)態(tài)的實(shí)例持久化症汹,但是并不保證標(biāo)識(shí)符被立刻填入到持久化實(shí)例中,標(biāo)識(shí)符的填入可能被推遲到flush的時(shí)間继谚;② persist()方法保證當(dāng)它在一個(gè)事務(wù)外部被調(diào)用的時(shí)候并不觸發(fā)一個(gè)INSERT語句烈菌,當(dāng)需要封裝一個(gè)長(zhǎng)會(huì)話流程的時(shí)候阵幸,persist()方法是很有必要的花履;③ save()方法不保證第②條,它要返回標(biāo)識(shí)符挚赊,所以它會(huì)立即執(zhí)行INSERT語句诡壁,不管是在事務(wù)內(nèi)部還是外部。至于lock()方法和update()方法的區(qū)別荠割,update()方法是把一個(gè)已經(jīng)更改過的脫管狀態(tài)的對(duì)象變成持久狀態(tài)妹卿;lock()方法是把一個(gè)沒有更改過的脫管狀態(tài)的對(duì)象變成持久狀態(tài)。

131蔑鹦、闡述Session加載實(shí)體對(duì)象的過程夺克。

答:Session加載實(shí)體對(duì)象的步驟是:
① Session在調(diào)用數(shù)據(jù)庫(kù)查詢功能之前,首先會(huì)在一級(jí)緩存中通過實(shí)體類型和主鍵進(jìn)行查找嚎朽,如果一級(jí)緩存查找命中且數(shù)據(jù)狀態(tài)合法铺纽,則直接返回;
② 如果一級(jí)緩存沒有命中哟忍,接下來Session會(huì)在當(dāng)前NonExists記錄(相當(dāng)于一個(gè)查詢黑名單狡门,如果出現(xiàn)重復(fù)的無效查詢可以迅速做出判斷,從而提升性能)中進(jìn)行查找锅很,如果NonExists中存在同樣的查詢條件其馏,則返回null;
③ 如果一級(jí)緩存查詢失敗則查詢二級(jí)緩存爆安,如果二級(jí)緩存命中則直接返回叛复;
④ 如果之前的查詢都未命中,則發(fā)出SQL語句扔仓,如果查詢未發(fā)現(xiàn)對(duì)應(yīng)記錄則將此次查詢添加到Session的NonExists中加以記錄褐奥,并返回null;
⑤ 根據(jù)映射配置和SQL語句得到ResultSet当辐,并創(chuàng)建對(duì)應(yīng)的實(shí)體對(duì)象抖僵;
⑥ 將對(duì)象納入Session(一級(jí)緩存)的管理;
⑦ 如果有對(duì)應(yīng)的攔截器缘揪,則執(zhí)行攔截器的onLoad方法耍群;
⑧ 如果開啟并設(shè)置了要使用二級(jí)緩存义桂,則將數(shù)據(jù)對(duì)象納入二級(jí)緩存;
⑨ 返回?cái)?shù)據(jù)對(duì)象蹈垢。

132慷吊、Query接口的list方法和iterate方法有什么區(qū)別?

答:
① list()方法無法利用一級(jí)緩存和二級(jí)緩存(對(duì)緩存只寫不讀)曹抬,它只能在開啟查詢緩存的前提下使用查詢緩存溉瓶;iterate()方法可以充分利用緩存,如果目標(biāo)數(shù)據(jù)只讀或者讀取頻繁谤民,使用iterate()方法可以減少性能開銷堰酿。
② list()方法不會(huì)引起N+1查詢問題,而iterate()方法可能引起N+1查詢問題

說明:關(guān)于N+1查詢問題张足,可以參考CSDN上的一篇文章《什么是N+1查詢》

133触创、Hibernate如何實(shí)現(xiàn)分頁查詢?

答:通過Hibernate實(shí)現(xiàn)分頁查詢为牍,開發(fā)人員只需要提供HQL語句(調(diào)用Session的createQuery()方法)或查詢條件(調(diào)用Session的createCriteria()方法)哼绑、設(shè)置查詢起始行數(shù)(調(diào)用Query或Criteria接口的setFirstResult()方法)和最大查詢行數(shù)(調(diào)用Query或Criteria接口的setMaxResults()方法),并調(diào)用Query或Criteria接口的list()方法碉咆,Hibernate會(huì)自動(dòng)生成分頁查詢的SQL語句抖韩。

134、鎖機(jī)制有什么用疫铜?簡(jiǎn)述Hibernate的悲觀鎖和樂觀鎖機(jī)制茂浮。

答:有些業(yè)務(wù)邏輯在執(zhí)行過程中要求對(duì)數(shù)據(jù)進(jìn)行排他性的訪問,于是需要通過一些機(jī)制保證在此過程中數(shù)據(jù)被鎖住不會(huì)被外界修改块攒,這就是所謂的鎖機(jī)制励稳。
Hibernate支持悲觀鎖和樂觀鎖兩種鎖機(jī)制。悲觀鎖囱井,顧名思義悲觀的認(rèn)為在數(shù)據(jù)處理過程中極有可能存在修改數(shù)據(jù)的并發(fā)事務(wù)(包括本系統(tǒng)的其他事務(wù)或來自外部系統(tǒng)的事務(wù))驹尼,于是將處理的數(shù)據(jù)設(shè)置為鎖定狀態(tài)。悲觀鎖必須依賴數(shù)據(jù)庫(kù)本身的鎖機(jī)制才能真正保證數(shù)據(jù)訪問的排他性庞呕,關(guān)于數(shù)據(jù)庫(kù)的鎖機(jī)制和事務(wù)隔離級(jí)別在《經(jīng)典java面試題收集》中已經(jīng)討論過了新翎。樂觀鎖,顧名思義住练,對(duì)并發(fā)事務(wù)持樂觀態(tài)度(認(rèn)為對(duì)數(shù)據(jù)的并發(fā)操作不會(huì)經(jīng)常性的發(fā)生)地啰,通過更加寬松的鎖機(jī)制來解決由于悲觀鎖排他性的數(shù)據(jù)訪問對(duì)系統(tǒng)性能造成的嚴(yán)重影響。最常見的樂觀鎖是通過數(shù)據(jù)版本標(biāo)識(shí)來實(shí)現(xiàn)的讲逛,讀取數(shù)據(jù)時(shí)獲得數(shù)據(jù)的版本號(hào)亏吝,更新數(shù)據(jù)時(shí)將此版本號(hào)加1,然后和數(shù)據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本號(hào)進(jìn)行比較盏混,如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫(kù)中此記錄的當(dāng)前版本號(hào)則更新數(shù)據(jù)蔚鸥,否則認(rèn)為是過期數(shù)據(jù)無法更新惜论。Hibernate中通過Session的get()和load()方法從數(shù)據(jù)庫(kù)中加載對(duì)象時(shí)可以通過參數(shù)指定使用悲觀鎖;而樂觀鎖可以通過給實(shí)體類加整型的版本字段再通過XML或@Version注解進(jìn)行配置止喷。

提示:使用樂觀鎖會(huì)增加了一個(gè)版本字段馆类,很明顯這需要額外的空間來存儲(chǔ)這個(gè)版本字段,浪費(fèi)了空間弹谁,但是樂觀鎖會(huì)讓系統(tǒng)具有更好的并發(fā)性乾巧,這是對(duì)時(shí)間的節(jié)省。因此樂觀鎖也是典型的空間換時(shí)間的策略预愤。

135沟于、闡述實(shí)體對(duì)象的三種狀態(tài)以及轉(zhuǎn)換關(guān)系。

答:最新的Hibernate文檔中為Hibernate對(duì)象定義了四種狀態(tài)(原來是三種狀態(tài)鳖粟,面試的時(shí)候基本上問的也是三種狀態(tài))社裆,分別是:瞬時(shí)態(tài)(new, or transient)、持久態(tài)(managed, or persistent)向图、游狀態(tài)(detached)和移除態(tài)(removed,以前Hibernate文檔中定義的三種狀態(tài)中沒有移除態(tài))标沪,如下圖所示榄攀,就以前的Hibernate文檔中移除態(tài)被視為是瞬時(shí)態(tài)。

  • 瞬時(shí)態(tài):當(dāng)new一個(gè)實(shí)體對(duì)象后金句,這個(gè)對(duì)象處于瞬時(shí)態(tài)檩赢,即這個(gè)對(duì)象只是一個(gè)保存臨時(shí)數(shù)據(jù)的內(nèi)存區(qū)域,如果沒有變量引用這個(gè)對(duì)象违寞,則會(huì)被JVM的垃圾回收機(jī)制回收贞瞒。這個(gè)對(duì)象所保存的數(shù)據(jù)與數(shù)據(jù)庫(kù)沒有任何關(guān)系,除非通過Session的save()趁曼、saveOrUpdate()军浆、persist()、merge()方法把瞬時(shí)態(tài)對(duì)象與數(shù)據(jù)庫(kù)關(guān)聯(lián)挡闰,并把數(shù)據(jù)插入或者更新到數(shù)據(jù)庫(kù)乒融,這個(gè)對(duì)象才轉(zhuǎn)換為持久態(tài)對(duì)象。
  • 持久態(tài):持久態(tài)對(duì)象的實(shí)例在數(shù)據(jù)庫(kù)中有對(duì)應(yīng)的記錄摄悯,并擁有一個(gè)持久化標(biāo)識(shí)(ID)赞季。對(duì)持久態(tài)對(duì)象進(jìn)行delete操作后,數(shù)據(jù)庫(kù)中對(duì)應(yīng)的記錄將被刪除奢驯,那么持久態(tài)對(duì)象與數(shù)據(jù)庫(kù)記錄不再存在對(duì)應(yīng)關(guān)系申钩,持久態(tài)對(duì)象變成移除態(tài)(可以視為瞬時(shí)態(tài))。持久態(tài)對(duì)象被修改變更后瘪阁,不會(huì)馬上同步到數(shù)據(jù)庫(kù)撒遣,直到數(shù)據(jù)庫(kù)事務(wù)提交断盛。
  • 游離態(tài):當(dāng)Session進(jìn)行了close()、clear()愉舔、evict()或flush()后钢猛,實(shí)體對(duì)象從持久態(tài)變成游離態(tài),對(duì)象雖然擁有持久和與數(shù)據(jù)庫(kù)對(duì)應(yīng)記錄一致的標(biāo)識(shí)值轩缤,但是因?yàn)閷?duì)象已經(jīng)從會(huì)話中清除掉命迈,對(duì)象不在持久化管理之內(nèi),所以處于游離態(tài)(也叫脫管態(tài))火的。游離態(tài)的對(duì)象與臨時(shí)狀態(tài)對(duì)象是十分相似的壶愤,只是它還含有持久化標(biāo)識(shí)。

提示:關(guān)于這個(gè)問題馏鹤,在Hibernate的官方文檔中有更為詳細(xì)的解讀征椒。

136、如何理解Hibernate的延遲加載機(jī)制湃累?在實(shí)際應(yīng)用中勃救,延遲加載與Session關(guān)閉的矛盾是如何處理的?

答:延遲加載就是并不是在讀取的時(shí)候就把數(shù)據(jù)加載進(jìn)來治力,而是等到使用時(shí)再加載蒙秒。Hibernate使用了虛擬代理機(jī)制實(shí)現(xiàn)延遲加載,我們使用Session的load()方法加載數(shù)據(jù)或者一對(duì)多關(guān)聯(lián)映射在使用延遲加載的情況下從一的一方加載多的一方宵统,得到的都是虛擬代理晕讲,簡(jiǎn)單的說返回給用戶的并不是實(shí)體本身,而是實(shí)體對(duì)象的代理马澈。代理對(duì)象在用戶調(diào)用getter方法時(shí)才會(huì)去數(shù)據(jù)庫(kù)加載數(shù)據(jù)瓢省。但加載數(shù)據(jù)就需要數(shù)據(jù)庫(kù)連接。而當(dāng)我們把會(huì)話關(guān)閉時(shí)痊班,數(shù)據(jù)庫(kù)連接就同時(shí)關(guān)閉了勤婚。

延遲加載與session關(guān)閉的矛盾一般可以這樣處理:
① 關(guān)閉延遲加載特性。這種方式操作起來比較簡(jiǎn)單辩块,因?yàn)镠ibernate的延遲加載特性是可以通過映射文件或者注解進(jìn)行配置的蛔六,但這種解決方案存在明顯的缺陷。首先废亭,出現(xiàn)"no session or session was closed"通常說明系統(tǒng)中已經(jīng)存在主外鍵關(guān)聯(lián)誓篱,如果去掉延遲加載的話呼巴,每次查詢的開銷都會(huì)變得很大。
② 在session關(guān)閉之前先獲取需要查詢的數(shù)據(jù),可以使用工具方法Hibernate.isInitialized()判斷對(duì)象是否被加載绢涡,如果沒有被加載則可以使用Hibernate.initialize()方法加載對(duì)象以蕴。
③ 使用攔截器或過濾器延長(zhǎng)Session的生命周期直到視圖獲得數(shù)據(jù)。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種做法。

137宁玫、舉一個(gè)多對(duì)多關(guān)聯(lián)的例子,并說明如何實(shí)現(xiàn)多對(duì)多關(guān)聯(lián)映射柑晒。

答:例如:商品和訂單欧瘪、學(xué)生和課程都是典型的多對(duì)多關(guān)系〕自蓿可以在實(shí)體類上通過@ManyToMany注解配置多對(duì)多關(guān)聯(lián)或者通過映射文件中的和標(biāo)簽配置多對(duì)多關(guān)聯(lián)佛掖,但是實(shí)際項(xiàng)目開發(fā)中,很多時(shí)候都是將多對(duì)多關(guān)聯(lián)映射轉(zhuǎn)換成兩個(gè)多對(duì)一關(guān)聯(lián)映射來實(shí)現(xiàn)的涌庭。

138芥被、談一下你對(duì)繼承映射的理解。

答:繼承關(guān)系的映射策略有三種:
① 每個(gè)繼承結(jié)構(gòu)一張表(table per class hierarchy)坐榆,不管多少個(gè)子類都用一張表拴魄。
② 每個(gè)子類一張表(table per subclass),公共信息放一張表席镀,特有信息放單獨(dú)的表匹中。
③ 每個(gè)具體類一張表(table per concrete class),有多少個(gè)子類就有多少?gòu)埍怼?br> 第一種方式屬于單表策略愉昆,其優(yōu)點(diǎn)在于查詢子類對(duì)象的時(shí)候無需表連接职员,查詢速度快,適合多態(tài)查詢跛溉;缺點(diǎn)是可能導(dǎo)致表很大。后兩種方式屬于多表策略扮授,其優(yōu)點(diǎn)在于數(shù)據(jù)存儲(chǔ)緊湊芳室,其缺點(diǎn)是需要進(jìn)行連接查詢,不適合多態(tài)查詢刹勃。

139堪侯、簡(jiǎn)述Hibernate常見優(yōu)化策略。

答:這個(gè)問題應(yīng)當(dāng)挑自己使用過的優(yōu)化策略回答荔仁,常用的有:
① 制定合理的緩存策略(二級(jí)緩存伍宦、查詢緩存)。
② 采用合理的Session管理機(jī)制乏梁。
③ 盡量使用延遲加載特性次洼。
④ 設(shè)定合理的批處理參數(shù)。
⑤ 如果可以遇骑,選用UUID作為主鍵生成器卖毁。
⑥ 如果可以,選用基于版本號(hào)的樂觀鎖替代悲觀鎖落萎。
⑦ 在開發(fā)過程中, 開啟hibernate.show_sql選項(xiàng)查看生成的SQL亥啦,從而了解底層的狀況炭剪;開發(fā)完成后關(guān)閉此選項(xiàng)。
⑧ 考慮數(shù)據(jù)庫(kù)本身的優(yōu)化翔脱,合理的索引奴拦、恰當(dāng)?shù)臄?shù)據(jù)分區(qū)策略等都會(huì)對(duì)持久層的性能帶來可觀的提升,但這些需要專業(yè)的DBA(數(shù)據(jù)庫(kù)管理員)提供支持届吁。

140错妖、談一談Hibernate的一級(jí)緩存、二級(jí)緩存和查詢緩存瓷产。

答:Hibernate的Session提供了一級(jí)緩存的功能站玄,默認(rèn)總是有效的,當(dāng)應(yīng)用程序保存持久化實(shí)體濒旦、修改持久化實(shí)體時(shí)株旷,Session并不會(huì)立即把這種改變提交到數(shù)據(jù)庫(kù),而是緩存在當(dāng)前的Session中尔邓,除非顯示調(diào)用了Session的flush()方法或通過close()方法關(guān)閉Session晾剖。通過一級(jí)緩存,可以減少程序與數(shù)據(jù)庫(kù)的交互梯嗽,從而提高數(shù)據(jù)庫(kù)訪問性能齿尽。
SessionFactory級(jí)別的二級(jí)緩存是全局性的,所有的Session可以共享這個(gè)二級(jí)緩存灯节。不過二級(jí)緩存默認(rèn)是關(guān)閉的循头,需要顯示開啟并指定需要使用哪種二級(jí)緩存實(shí)現(xiàn)類(可以使用第三方提供的實(shí)現(xiàn))。一旦開啟了二級(jí)緩存并設(shè)置了需要使用二級(jí)緩存的實(shí)體類炎疆,SessionFactory就會(huì)緩存訪問過的該實(shí)體類的每個(gè)對(duì)象卡骂,除非緩存的數(shù)據(jù)超出了指定的緩存空間。
一級(jí)緩存和二級(jí)緩存都是對(duì)整個(gè)實(shí)體進(jìn)行緩存形入,不會(huì)緩存普通屬性全跨,如果希望對(duì)普通屬性進(jìn)行緩存,可以使用查詢緩存亿遂。查詢緩存是將HQL或SQL語句以及它們的查詢結(jié)果作為鍵值對(duì)進(jìn)行緩存浓若,對(duì)于同樣的查詢可以直接從緩存中獲取數(shù)據(jù)。查詢緩存默認(rèn)也是關(guān)閉的蛇数,需要顯示開啟挪钓。

141、Hibernate中DetachedCriteria類是做什么的苞慢?

答:DetachedCriteria和Criteria的用法基本上是一致的诵原,但Criteria是由Session的createCriteria()方法創(chuàng)建的,也就意味著離開創(chuàng)建它的Session,Criteria就無法使用了绍赛。DetachedCriteria不需要Session就可以創(chuàng)建(使用DetachedCriteria.forClass()方法創(chuàng)建)蔓纠,所以通常也稱其為離線的Criteria,在需要進(jìn)行查詢操作的時(shí)候再和Session綁定(調(diào)用其getExecutableCriteria(Session)方法)吗蚌,這也就意味著一個(gè)DetachedCriteria可以在需要的時(shí)候和不同的Session進(jìn)行綁定腿倚。

142、@OneToMany注解的mappedBy屬性有什么作用蚯妇?

答:@OneToMany用來配置一對(duì)多關(guān)聯(lián)映射敷燎,但通常情況下,一對(duì)多關(guān)聯(lián)映射都由多的一方來維護(hù)關(guān)聯(lián)關(guān)系箩言,例如學(xué)生和班級(jí)硬贯,應(yīng)該在學(xué)生類中添加班級(jí)屬性來維持學(xué)生和班級(jí)的關(guān)聯(lián)關(guān)系(在數(shù)據(jù)庫(kù)中是由學(xué)生表中的外鍵班級(jí)編號(hào)來維護(hù)學(xué)生表和班級(jí)表的多對(duì)一關(guān)系),如果要使用雙向關(guān)聯(lián)陨收,在班級(jí)類中添加一個(gè)容器屬性來存放學(xué)生饭豹,并使用@OneToMany注解進(jìn)行映射,此時(shí)mappedBy屬性就非常重要务漩。如果使用XML進(jìn)行配置拄衰,可以用<set>標(biāo)簽的inverse="true"設(shè)置來達(dá)到同樣的效果。

143饵骨、MyBatis中使用#和$書寫占位符有什么區(qū)別翘悉?

答:#將傳入的數(shù)據(jù)都當(dāng)成一個(gè)字符串,會(huì)對(duì)傳入的數(shù)據(jù)自動(dòng)加上引號(hào)居触;$將傳入的數(shù)據(jù)直接顯示生成在SQL中妖混。注意:使用$占位符可能會(huì)導(dǎo)致SQL注射攻擊,能用#的地方就不要使用$轮洋,寫order by子句的時(shí)候應(yīng)該用$而不是#源葫。

144、解釋一下MyBatis中命名空間(namespace)的作用砖瞧。

答:在大型項(xiàng)目中,可能存在大量的SQL語句嚷狞,這時(shí)候?yàn)槊總€(gè)SQL語句起一個(gè)唯一的標(biāo)識(shí)(ID)就變得并不容易了块促。為了解決這個(gè)問題,在MyBatis中床未,可以為每個(gè)映射文件起一個(gè)唯一的命名空間竭翠,這樣定義在這個(gè)映射文件中的每個(gè)SQL語句就成了定義在這個(gè)命名空間中的一個(gè)ID。只要我們能夠保證每個(gè)命名空間中這個(gè)ID是唯一的薇搁,即使在不同映射文件中的語句ID相同斋扰,也不會(huì)再產(chǎn)生沖突了。

145、MyBatis中的動(dòng)態(tài)SQL是什么意思传货?

答:對(duì)于一些復(fù)雜的查詢屎鳍,我們可能會(huì)指定多個(gè)查詢條件,但是這些條件可能存在也可能不存在问裕,例如在58同城上面找房子逮壁,我們可能會(huì)指定面積、樓層和所在位置來查找房源粮宛,也可能會(huì)指定面積窥淆、價(jià)格、戶型和所在位置來查找房源巍杈,此時(shí)就需要根據(jù)用戶指定的條件動(dòng)態(tài)生成SQL語句忧饭。如果不使用持久層框架我們可能需要自己拼裝SQL語句,還好MyBatis提供了動(dòng)態(tài)SQL的功能來解決這個(gè)問題筷畦。MyBatis中用于實(shí)現(xiàn)動(dòng)態(tài)SQL的元素主要有:

  • if
  • choose / when / otherwise
  • trim
  • where
  • set
  • foreach

下面是映射文件的片段词裤。

<select id="foo" parameterType="Blog" resultType="Blog">
    select * from t_blog where 1 = 1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="content != null">
        and content = #{content}
    </if>
    <if test="owner != null">
        and owner = #{owner}
    </if>
</select>

當(dāng)然也可以像下面這些書寫。

<select id="foo" parameterType="Blog" resultType="Blog">
    select * from t_blog where 1 = 1 
    <choose>
        <when test="title != null">
            and title = #{title}
        </when>
        <when test="content != null">
            and content = #{content}
        </when>
        <otherwise>
            and owner = "owner1"
        </otherwise>
    </choose>
</select>

再看看下面這個(gè)例子汁咏。

<select id="bar" resultType="Blog">
    select * from t_blog where id in
    <foreach collection="array" index="index" 
        item="item" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

146亚斋、什么是IoC和DI?DI是如何實(shí)現(xiàn)的攘滩?

答:IoC叫控制反轉(zhuǎn)帅刊,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入漂问,是對(duì)IoC更簡(jiǎn)單的詮釋赖瞒。控制反轉(zhuǎn)是把傳統(tǒng)上由程序代碼直接操控的對(duì)象的調(diào)用權(quán)交給容器蚤假,通過容器來實(shí)現(xiàn)對(duì)象組件的裝配和管理栏饮。所謂的"控制反轉(zhuǎn)"就是對(duì)組件對(duì)象控制權(quán)的轉(zhuǎn)移,從程序代碼本身轉(zhuǎn)移到了外部容器磷仰,由容器來創(chuàng)建對(duì)象并管理對(duì)象之間的依賴關(guān)系袍嬉。IoC體現(xiàn)了好萊塢原則 - "Don’t call me, we will call you"。依賴注入的基本原則是應(yīng)用組件不應(yīng)該負(fù)責(zé)查找資源或者其他依賴的協(xié)作對(duì)象灶平。配置對(duì)象的工作應(yīng)該由容器負(fù)責(zé)伺通,查找資源的邏輯應(yīng)該從應(yīng)用組件的代碼中抽取出來,交給容器來完成逢享。DI是對(duì)IoC更準(zhǔn)確的描述罐监,即組件之間的依賴關(guān)系由容器在運(yùn)行期決定,形象的來說瞒爬,即由容器動(dòng)態(tài)的將某種依賴關(guān)系注入到組件之中弓柱。

舉個(gè)例子:一個(gè)類A需要用到接口B中的方法沟堡,那么就需要為類A和接口B建立關(guān)聯(lián)或依賴關(guān)系,最原始的方法是在類A中創(chuàng)建一個(gè)接口B的實(shí)現(xiàn)類C的實(shí)例矢空,但這種方法需要開發(fā)人員自行維護(hù)二者的依賴關(guān)系航罗,也就是說當(dāng)依賴關(guān)系發(fā)生變動(dòng)的時(shí)候需要修改代碼并重新構(gòu)建整個(gè)系統(tǒng)。如果通過一個(gè)容器來管理這些對(duì)象以及對(duì)象的依賴關(guān)系妇多,則只需要在類A中定義好用于關(guān)聯(lián)接口B的方法(構(gòu)造器或setter方法)伤哺,將類A和接口B的實(shí)現(xiàn)類C放入容器中,通過對(duì)容器的配置來實(shí)現(xiàn)二者的關(guān)聯(lián)者祖。

依賴注入可以通過setter方法注入(設(shè)值注入)立莉、構(gòu)造器注入和接口注入三種方式來實(shí)現(xiàn),Spring支持setter注入和構(gòu)造器注入七问,通常使用構(gòu)造器注入來注入必須的依賴關(guān)系蜓耻,對(duì)于可選的依賴關(guān)系,則setter注入是更好的選擇械巡,setter注入需要類提供無參構(gòu)造器或者無參的靜態(tài)工廠方法來創(chuàng)建對(duì)象刹淌。

147、Spring中Bean的作用域有哪些讥耗?

答:在Spring的早期版本中有勾,僅有兩個(gè)作用域:singleton和prototype,前者表示Bean以單例的方式存在古程;后者表示每次從容器中調(diào)用Bean時(shí)蔼卡,都會(huì)返回一個(gè)新的實(shí)例,prototype通常翻譯為原型挣磨。

補(bǔ)充:設(shè)計(jì)模式中的創(chuàng)建型模式中也有一個(gè)原型模式雇逞,原型模式也是一個(gè)常用的模式,例如做一個(gè)室內(nèi)設(shè)計(jì)軟件茁裙,所有的素材都在工具箱中塘砸,而每次從工具箱中取出的都是素材對(duì)象的一個(gè)原型,可以通過對(duì)象克隆來實(shí)現(xiàn)原型模式晤锥。

Spring 2.x中針對(duì)WebApplicationContext新增了3個(gè)作用域掉蔬,分別是:request(每次HTTP請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的Bean)、session(同一個(gè)HttpSession共享同一個(gè)Bean矾瘾,不同的HttpSession使用不同的Bean)和globalSession(同一個(gè)全局Session共享一個(gè)Bean)眉踱。

說明:?jiǎn)卫J胶驮湍J蕉际侵匾脑O(shè)計(jì)模式。一般情況下霜威,無狀態(tài)或狀態(tài)不可變的類適合使用單例模式。在傳統(tǒng)開發(fā)中册烈,由于DAO持有Connection這個(gè)非線程安全對(duì)象因而沒有使用單例模式戈泼;但在Spring環(huán)境下婿禽,所有DAO類對(duì)可以采用單例模式,因?yàn)镾pring利用AOP和Java API中的ThreadLocal對(duì)非線程安全的對(duì)象進(jìn)行了特殊處理大猛。

ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路扭倾。ThreadLocal,顧名思義是線程的一個(gè)本地化對(duì)象挽绩,當(dāng)工作于多線程中的對(duì)象使用ThreadLocal維護(hù)變量時(shí)膛壹,ThreadLocal為每個(gè)使用該變量的線程分配一個(gè)獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立的改變自己的副本唉堪,而不影響其他線程所對(duì)應(yīng)的副本模聋。從線程的角度看,這個(gè)變量就像是線程的本地變量唠亚。

ThreadLocal類非常簡(jiǎn)單好用链方,只有四個(gè)方法,能用上的也就是下面三個(gè)方法:

  • void set(T value):設(shè)置當(dāng)前線程的線程局部變量的值灶搜。
  • T get():獲得當(dāng)前線程所對(duì)應(yīng)的線程局部變量的值祟蚀。
  • void remove():刪除當(dāng)前線程中線程局部變量的值。

ThreadLocal是如何做到為每一個(gè)線程維護(hù)一份獨(dú)立的變量副本的呢割卖?在ThreadLocal類中有一個(gè)Map前酿,鍵為線程對(duì)象,值是其線程對(duì)應(yīng)的變量的副本鹏溯,自己要模擬實(shí)現(xiàn)一個(gè)ThreadLocal類其實(shí)并不困難罢维,代碼如下所示:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyThreadLocal<T> {
    private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());

    public void set(T newValue) {
        map.put(Thread.currentThread(), newValue);
    }

    public T get() {
        return map.get(Thread.currentThread());
    }

    public void remove() {
        map.remove(Thread.currentThread());
    }
}

148、解釋一下什么叫AOP(面向切面編程)剿涮?

答:AOP(Aspect-Oriented Programming)指一種程序設(shè)計(jì)范型言津,該范型以一種稱為切面(aspect)的語言構(gòu)造為基礎(chǔ),切面是一種新的模塊化機(jī)制取试,用來描述分散在對(duì)象悬槽、類或方法中的橫切關(guān)注點(diǎn)(crosscutting concern)。

149瞬浓、你是如何理解"橫切關(guān)注"這個(gè)概念的初婆?

答:"橫切關(guān)注"是會(huì)影響到整個(gè)應(yīng)用程序的關(guān)注功能,它跟正常的業(yè)務(wù)邏輯是正交的猿棉,沒有必然的聯(lián)系磅叛,但是幾乎所有的業(yè)務(wù)邏輯都會(huì)涉及到這些關(guān)注功能。通常萨赁,事務(wù)弊琴、日志、安全性等關(guān)注就是應(yīng)用中的橫切關(guān)注功能杖爽。

150敲董、你如何理解AOP中的連接點(diǎn)(Joinpoint)紫皇、切點(diǎn)(Pointcut)、增強(qiáng)(Advice)腋寨、引介(Introduction)聪铺、織入(Weaving)、切面(Aspect)這些概念萄窜?

答:
a. 連接點(diǎn)(Joinpoint):程序執(zhí)行的某個(gè)特定位置(如:某個(gè)方法調(diào)用前铃剔、調(diào)用后,方法拋出異常后)查刻。一個(gè)類或一段程序代碼擁有一些具有邊界性質(zhì)的特定點(diǎn)键兜,這些代碼中的特定點(diǎn)就是連接點(diǎn)。Spring僅支持方法的連接點(diǎn)赖阻。
b. 切點(diǎn)(Pointcut):如果連接點(diǎn)相當(dāng)于數(shù)據(jù)中的記錄蝶押,那么切點(diǎn)相當(dāng)于查詢條件,一個(gè)切點(diǎn)可以匹配多個(gè)連接點(diǎn)火欧。Spring AOP的規(guī)則解析引擎負(fù)責(zé)解析切點(diǎn)所設(shè)定的查詢條件棋电,找到對(duì)應(yīng)的連接點(diǎn)。
c. 增強(qiáng)(Advice):增強(qiáng)是織入到目標(biāo)類連接點(diǎn)上的一段程序代碼苇侵。Spring提供的增強(qiáng)接口都是帶方位名的赶盔,如:BeforeAdvice、AfterReturningAdvice榆浓、ThrowsAdvice等于未。很多資料上將增強(qiáng)譯為“通知”,這明顯是個(gè)詞不達(dá)意的翻譯陡鹃,讓很多程序員困惑了許久烘浦。

說明: Advice在國(guó)內(nèi)的很多書面資料中都被翻譯成"通知",但是很顯然這個(gè)翻譯無法表達(dá)其本質(zhì)萍鲸,有少量的讀物上將這個(gè)詞翻譯為"增強(qiáng)"闷叉,這個(gè)翻譯是對(duì)Advice較為準(zhǔn)確的詮釋,我們通過AOP將橫切關(guān)注功能加到原有的業(yè)務(wù)邏輯上脊阴,這就是對(duì)原有業(yè)務(wù)邏輯的一種增強(qiáng)握侧,這種增強(qiáng)可以是前置增強(qiáng)、后置增強(qiáng)嘿期、返回后增強(qiáng)品擎、拋異常時(shí)增強(qiáng)和包圍型增強(qiáng)。

d. 引介(Introduction):引介是一種特殊的增強(qiáng)备徐,它為類添加一些屬性和方法萄传。這樣宙地,即使一個(gè)業(yè)務(wù)類原本沒有實(shí)現(xiàn)某個(gè)接口龄句,通過引介功能阅酪,可以動(dòng)態(tài)的為該業(yè)務(wù)類添加接口的實(shí)現(xiàn)邏輯棍鳖,讓業(yè)務(wù)類成為這個(gè)接口的實(shí)現(xiàn)類。
e. 織入(Weaving):織入是將增強(qiáng)添加到目標(biāo)類具體連接點(diǎn)上的過程答朋,AOP有三種織入方式:①編譯期織入:需要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器棠笑,在裝載類的時(shí)候?qū)︻愡M(jìn)行增強(qiáng)梦碗;③運(yùn)行時(shí)織入:在運(yùn)行時(shí)為目標(biāo)類生成代理實(shí)現(xiàn)增強(qiáng)。Spring采用了動(dòng)態(tài)代理的方式實(shí)現(xiàn)了運(yùn)行時(shí)織入蓖救,而AspectJ采用了編譯期織入和裝載期織入的方式洪规。
f. 切面(Aspect):切面是由切點(diǎn)和增強(qiáng)(引介)組成的,它包括了對(duì)橫切關(guān)注功能的定義循捺,也包括了對(duì)連接點(diǎn)的定義斩例。

補(bǔ)充:代理模式是GoF提出的23種設(shè)計(jì)模式中最為經(jīng)典的模式之一,代理模式是對(duì)象的結(jié)構(gòu)模式从橘,它給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象念赶,并由代理對(duì)象控制對(duì)原對(duì)象的引用。簡(jiǎn)單的說恰力,代理對(duì)象可以完成比原對(duì)象更多的職責(zé)叉谜,當(dāng)需要為原對(duì)象添加橫切關(guān)注功能時(shí),就可以使用原對(duì)象的代理對(duì)象踩萎。我們?cè)诖蜷_Office系列的Word文檔時(shí)停局,如果文檔中有插圖,當(dāng)文檔剛加載時(shí)香府,文檔中的插圖都只是一個(gè)虛框占位符董栽,等用戶真正翻到某頁要查看該圖片時(shí),才會(huì)真正加載這張圖企孩,這其實(shí)就是對(duì)代理模式的使用锭碳,代替真正圖片的虛框就是一個(gè)虛擬代理;Hibernate的load方法也是返回一個(gè)虛擬代理對(duì)象柠硕,等用戶真正需要訪問對(duì)象的屬性時(shí)工禾,才向數(shù)據(jù)庫(kù)發(fā)出SQL語句獲得真實(shí)對(duì)象。

下面用一個(gè)找槍手代考的例子演示代理模式的使用:

/**
 * 參考人員接口
 * @author nnngu
 *
 */
public interface Candidate {

    /**
     * 答題
     */
    public void answerTheQuestions();
}
/**
 * 懶學(xué)生
 * @author nnngu
 *
 */
public class LazyStudent implements Candidate {
    private String name;        // 姓名

    public LazyStudent(String name) {
        this.name = name;
    }

    @Override
    public void answerTheQuestions() {
        // 懶學(xué)生只能寫出自己的名字不會(huì)答題
        System.out.println("姓名: " + name);
    }

}
/**
 * 槍手
 * @author nnngu
 *
 */
public class Gunman implements Candidate {
    private Candidate target;   // 被代理對(duì)象

    public Gunman(Candidate target) {
        this.target = target;
    }

    @Override
    public void answerTheQuestions() {
        // 槍手要寫上代考的學(xué)生的姓名
        target.answerTheQuestions();
        // 槍手要幫助懶學(xué)生答題并交卷
        System.out.println("奮筆疾書正確答案");
        System.out.println("交卷");
    }

}
public class ProxyTest1 {

    public static void main(String[] args) {
        Candidate c = new Gunman(new LazyStudent("小明"));
        c.answerTheQuestions();
    }
}

說明:從JDK 1.3開始蝗柔,Java提供了動(dòng)態(tài)代理技術(shù)闻葵,允許開發(fā)者在運(yùn)行時(shí)創(chuàng)建接口的代理實(shí)例,主要包括Proxy類和InvocationHandler接口癣丧。下面的例子使用動(dòng)態(tài)代理為ArrayList編寫一個(gè)代理槽畔,在添加和刪除元素時(shí),在控制臺(tái)打印添加或刪除的元素以及ArrayList的大行脖唷:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

public class ListProxy<T> implements InvocationHandler {
    private List<T> target;

    public ListProxy(List<T> target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        Object retVal = null;
        System.out.println("[" + method.getName() + ": " + args[0] + "]");
        retVal = method.invoke(target, args);
        System.out.println("[size=" + target.size() + "]");
        return retVal;
    }

}
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

public class ProxyTest2 {

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        Class<?> clazz = list.getClass();
        ListProxy<String> myProxy = new ListProxy<String>(list);
        List<String> newList = (List<String>) 
                Proxy.newProxyInstance(clazz.getClassLoader(), 
                clazz.getInterfaces(), myProxy);
        newList.add("apple");
        newList.add("banana");
        newList.add("orange");
        newList.remove("banana");
    }
}

說明:使用Java的動(dòng)態(tài)代理有一個(gè)局限性就是代理的類必須要實(shí)現(xiàn)接口厢钧,雖然面向接口編程是每個(gè)優(yōu)秀的Java程序員都知道的規(guī)則鳞尔,但現(xiàn)實(shí)往往不盡如人意,對(duì)于沒有實(shí)現(xiàn)接口的類如何為其生成代理呢早直?繼承寥假!繼承是最經(jīng)典的擴(kuò)展已有代碼能力的手段,雖然繼承常常被初學(xué)者濫用霞扬,但繼承也常常被進(jìn)階的程序員忽視糕韧。CGLib采用非常底層的字節(jié)碼生成技術(shù),通過為一個(gè)類創(chuàng)建子類來生成代理喻圃,它彌補(bǔ)了Java動(dòng)態(tài)代理的不足萤彩,因此Spring中動(dòng)態(tài)代理和CGLib都是創(chuàng)建代理的重要手段,對(duì)于實(shí)現(xiàn)了接口的類就用JDK動(dòng)態(tài)代理為其生成代理類斧拍,而沒有實(shí)現(xiàn)接口的類就用CGLib通過繼承的方式為其創(chuàng)建代理雀扶。

151、Spring中自動(dòng)裝配的方式有哪些肆汹?

答:

  • no:不進(jìn)行自動(dòng)裝配愚墓,手動(dòng)設(shè)置Bean的依賴關(guān)系。
  • byName:根據(jù)Bean的名字進(jìn)行自動(dòng)裝配县踢。
  • byType:根據(jù)Bean的類型進(jìn)行自動(dòng)裝配转绷。
  • constructor:類似于byType,不過是應(yīng)用于構(gòu)造器的參數(shù)硼啤,如果正好有一個(gè)Bean與構(gòu)造器的參數(shù)類型相同則可以自動(dòng)裝配议经,否則會(huì)導(dǎo)致錯(cuò)誤。
  • autodetect:如果有默認(rèn)的構(gòu)造器谴返,則通過constructor的方式進(jìn)行自動(dòng)裝配煞肾,否則使用byType的方式進(jìn)行自動(dòng)裝配。

說明:自動(dòng)裝配沒有自定義裝配方式那么精確嗓袱,而且不能自動(dòng)裝配簡(jiǎn)單屬性(基本類型籍救、字符串等),在使用時(shí)應(yīng)注意渠抹。

152蝙昙、Spring中如何使用注解來配置Bean?有哪些相關(guān)的注解梧却?

答:首先需要在Spring配置文件中增加如下配置:

<context:component-scan base-package="com.nnngu"/>

然后可以用@Component奇颠、@Controller、@Service放航、@Repository注解來標(biāo)注需要由Spring IoC容器進(jìn)行對(duì)象托管的類烈拒。這幾個(gè)注解沒有本質(zhì)區(qū)別,只不過@Controller通常用于控制器,@Service通常用于業(yè)務(wù)邏輯類荆几,@Repository通常用于倉(cāng)儲(chǔ)類(例如我們的DAO實(shí)現(xiàn)類)吓妆,普通的類用@Component來標(biāo)注。

153吨铸、Spring支持的事務(wù)管理類型有哪些行拢?你在項(xiàng)目中使用哪種方式?

答:Spring支持編程式事務(wù)管理和聲明式事務(wù)管理诞吱。許多Spring框架的用戶選擇聲明式事務(wù)管理剂陡,因?yàn)檫@種方式和應(yīng)用程序的關(guān)聯(lián)較少,因此更加符合輕量級(jí)容器的概念狐胎。聲明式事務(wù)管理要優(yōu)于編程式事務(wù)管理,盡管在靈活性方面它弱于編程式事務(wù)管理歌馍,因?yàn)榫幊淌绞聞?wù)允許你通過代碼控制業(yè)務(wù)握巢。

事務(wù)分為全局事務(wù)和局部事務(wù)。全局事務(wù)由應(yīng)用服務(wù)器管理松却,需要底層服務(wù)器JTA支持(如WebLogic暴浦、WildFly等)。局部事務(wù)和底層采用的持久化方案有關(guān)晓锻,例如使用JDBC進(jìn)行持久化時(shí)歌焦,需要使用Connetion對(duì)象來操作事務(wù);而采用Hibernate進(jìn)行持久化時(shí)砚哆,需要使用Session對(duì)象來操作事務(wù)独撇。

Spring提供了如下所示的事務(wù)管理器。

事務(wù)管理器實(shí)現(xiàn)類 目標(biāo)對(duì)象
DataSourceTransactionManager 注入DataSource
HibernateTransactionManager 注入SessionFactory
JdoTransactionManager 管理JDO事務(wù)
JtaTransactionManager 使用JTA管理事務(wù)
PersistenceBrokerTransactionManager 管理Apache的OJB事務(wù)

這些事務(wù)的父接口都是PlatformTransactionManager躁锁。Spring的事務(wù)管理機(jī)制是一種典型的策略模式纷铣,PlatformTransactionManager代表事務(wù)管理接口,該接口定義了三個(gè)方法战转,該接口并不知道底層如何管理事務(wù)搜立,但是它的實(shí)現(xiàn)類必須提供getTransaction()方法(開啟事務(wù))、commit()方法(提交事務(wù))槐秧、rollback()方法(回滾事務(wù))的多態(tài)實(shí)現(xiàn)啄踊,這樣就可以用不同的實(shí)現(xiàn)類代表不同的事務(wù)管理策略。使用JTA全局事務(wù)策略時(shí)刁标,需要底層應(yīng)用服務(wù)器支持颠通,而不同的應(yīng)用服務(wù)器所提供的JTA全局事務(wù)可能存在細(xì)節(jié)上的差異,因此實(shí)際配置全局事務(wù)管理器是可能需要使用JtaTransactionManager的子類命雀,如:WebLogicJtaTransactionManager(Oracle的WebLogic服務(wù)器提供)蒜哀、UowJtaTransactionManager(IBM的WebSphere服務(wù)器提供)等。

編程式事務(wù)管理如下所示。

<?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:p="http://www.springframework.org/schema/p"
    xmlns:p="http://www.springframework.org/schema/context"
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

     <context:component-scan base-package="com.nnngu"/>

     <bean id="propertyConfig"
         class="org.springframework.beans.factory.config.
  PropertyPlaceholderConfigurer">
         <property name="location">
             <value>jdbc.properties</value>
         </property>
     </bean>

     <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
         <property name="driverClassName">
             <value>${db.driver}</value>
         </property>
         <property name="url">
             <value>${db.url}</value>
         </property>
         <property name="username">
             <value>${db.username}</value>
         </property>
         <property name="password">
             <value>${db.password}</value>
         </property>
     </bean>

     <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>

     <!-- JDBC事務(wù)管理器 -->
     <bean id="transactionManager"
         class="org.springframework.jdbc.datasource.
       DataSourceTransactionManager" scope="singleton">
         <property name="dataSource">
             <ref bean="dataSource" />
         </property>
     </bean>

     <!-- 聲明事務(wù)模板 -->
     <bean id="transactionTemplate"
         class="org.springframework.transaction.support.
   TransactionTemplate">
         <property name="transactionManager">
             <ref bean="transactionManager" />
         </property>
     </bean>

</beans>
package com.nnngu.dao.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import com.nnngu.dao.EmpDao;
import com.nnngu.entity.Emp;

@Repository
public class EmpDaoImpl implements EmpDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public boolean save(Emp emp) {
        String sql = "insert into emp values (?,?,?)";
        return jdbcTemplate.update(sql, emp.getId(), emp.getName(), emp.getBirthday()) == 1;
    }

}
package com.nnngu.biz.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.nnngu.biz.EmpService;
import com.nnngu.dao.EmpDao;
import com.nnngu.entity.Emp;

@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private TransactionTemplate txTemplate;
    @Autowired
    private EmpDao empDao;

    @Override
    public void addEmp(final Emp emp) {
        txTemplate.execute(new TransactionCallbackWithoutResult() {

            @Override
            protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
                empDao.save(emp);
            }
        });
    }


}

聲明式事務(wù)如下所示撵儿,以Spring整合Hibernate 3為例乘客,包括完整的DAO和業(yè)務(wù)邏輯代碼。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-3.2.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">

    <!-- 配置由Spring IoC容器托管的對(duì)象對(duì)應(yīng)的被注解的類所在的包 -->
    <context:component-scan base-package="com.nnngu" />

    <!-- 配置通過自動(dòng)生成代理實(shí)現(xiàn)AOP功能 -->
    <aop:aspectj-autoproxy />

    <!-- 配置數(shù)據(jù)庫(kù)連接池 (DBCP) -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <!-- 配置驅(qū)動(dòng)程序類 -->
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <!-- 配置連接數(shù)據(jù)庫(kù)的URL -->
        <property name="url" value="jdbc:mysql://localhost:3306/myweb" />
        <!-- 配置訪問數(shù)據(jù)庫(kù)的用戶名 -->
        <property name="username" value="root" />
        <!-- 配置訪問數(shù)據(jù)庫(kù)的口令 -->
        <property name="password" value="1" />
        <!-- 配置最大連接數(shù) -->
        <property name="maxActive" value="150" />
        <!-- 配置最小空閑連接數(shù) -->
        <property name="minIdle" value="5" />
        <!-- 配置最大空閑連接數(shù) -->
        <property name="maxIdle" value="20" />
        <!-- 配置初始連接數(shù) -->
        <property name="initialSize" value="10" />
        <!-- 配置連接被泄露時(shí)是否生成日志 -->
        <property name="logAbandoned" value="true" />
        <!-- 配置是否刪除超時(shí)連接 -->
        <property name="removeAbandoned" value="true" />
        <!-- 配置刪除超時(shí)連接的超時(shí)門限值(以秒為單位) -->
        <property name="removeAbandonedTimeout" value="120" />
        <!-- 配置超時(shí)等待時(shí)間(以毫秒為單位) -->
        <property name="maxWait" value="5000" />
        <!-- 配置空閑連接回收器線程運(yùn)行的時(shí)間間隔(以毫秒為單位) -->
        <property name="timeBetweenEvictionRunsMillis" value="300000" />
        <!-- 配置連接空閑多長(zhǎng)時(shí)間后(以毫秒為單位)被斷開連接 -->
        <property name="minEvictableIdleTimeMillis" value="60000" />
    </bean>

    <!-- 配置Spring提供的支持注解ORM映射的Hibernate會(huì)話工廠 -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <!-- 通過setter注入數(shù)據(jù)源屬性 -->
        <property name="dataSource" ref="dataSource" />
        <!-- 配置實(shí)體類所在的包 -->
        <property name="packagesToScan" value="com.nnngu.entity" />
        <!-- 配置Hibernate的相關(guān)屬性 -->
        <property name="hibernateProperties">
            <!-- 在項(xiàng)目調(diào)試完成后要?jiǎng)h除show_sql和format_sql屬性否則對(duì)性能有顯著影響 -->
            <value>
                hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
            </value>
        </property>
    </bean>

    <!-- 配置Spring提供的Hibernate事務(wù)管理器 -->
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <!-- 通過setter注入Hibernate會(huì)話工廠 -->
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- 配置基于注解配置聲明式事務(wù) -->
    <tx:annotation-driven />

</beans>
package com.nnngu.dao;

import java.io.Serializable;
import java.util.List;

import com.nnngu.comm.QueryBean;
import com.nnngu.comm.QueryResult;

/**
 * 數(shù)據(jù)訪問對(duì)象接口(以對(duì)象為單位封裝CRUD操作)
 * @author nnngu
 *
 * @param <E> 實(shí)體類型
 * @param <K> 實(shí)體標(biāo)識(shí)字段的類型
 */
public interface BaseDao <E, K extends Serializable> {

    /**
     * 新增
     * @param entity 業(yè)務(wù)實(shí)體對(duì)象
     * @return 增加成功返回實(shí)體對(duì)象的標(biāo)識(shí)
     */
    public K save(E entity);

    /**
     * 刪除
     * @param entity 業(yè)務(wù)實(shí)體對(duì)象
     */
    public void delete(E entity);

    /**
     * 根據(jù)ID刪除
     * @param id 業(yè)務(wù)實(shí)體對(duì)象的標(biāo)識(shí)
     * @return 刪除成功返回true否則返回false
     */
    public boolean deleteById(K id);

    /**
     * 修改
     * @param entity 業(yè)務(wù)實(shí)體對(duì)象
     * @return 修改成功返回true否則返回false
     */
    public void update(E entity);

    /**
     * 根據(jù)ID查找業(yè)務(wù)實(shí)體對(duì)象
     * @param id 業(yè)務(wù)實(shí)體對(duì)象的標(biāo)識(shí)
     * @return 業(yè)務(wù)實(shí)體對(duì)象對(duì)象或null
     */
    public E findById(K id);

    /**
     * 根據(jù)ID查找業(yè)務(wù)實(shí)體對(duì)象
     * @param id 業(yè)務(wù)實(shí)體對(duì)象的標(biāo)識(shí)
     * @param lazy 是否使用延遲加載
     * @return 業(yè)務(wù)實(shí)體對(duì)象對(duì)象
     */
    public E findById(K id, boolean lazy);

    /**
     * 查找所有業(yè)務(wù)實(shí)體對(duì)象
     * @return 裝所有業(yè)務(wù)實(shí)體對(duì)象的列表容器
     */
    public List<E> findAll();

    /**
     * 分頁查找業(yè)務(wù)實(shí)體對(duì)象
     * @param page 頁碼
     * @param size 頁面大小
     * @return 查詢結(jié)果對(duì)象
     */
    public QueryResult<E> findByPage(int page, int size);

    /**
     * 分頁查找業(yè)務(wù)實(shí)體對(duì)象
     * @param queryBean 查詢條件對(duì)象
     * @param page 頁碼
     * @param size 頁面大小
     * @return 查詢結(jié)果對(duì)象
     */
    public QueryResult<E> findByPage(QueryBean queryBean, int page, int size);

}
package com.nnngu.dao;

import java.io.Serializable;
import java.util.List;

import com.nnngu.comm.QueryBean;
import com.nnngu.comm.QueryResult;

/**
 * BaseDao的缺省適配器
 * @author nnngu
 *
 * @param <E> 實(shí)體類型
 * @param <K> 實(shí)體標(biāo)識(shí)字段的類型
 */
public abstract class BaseDaoAdapter<E, K extends Serializable> implements
        BaseDao<E, K> {

    @Override
    public K save(E entity) {
        return null;
    }

    @Override
    public void delete(E entity) {
    }

    @Override
    public boolean deleteById(K id) {
        E entity = findById(id);
        if(entity != null) {
            delete(entity);
            return true;
        }
        return false;
    }

    @Override
    public void update(E entity) {
    }

    @Override
    public E findById(K id) {
        return null;
    }

    @Override
    public E findById(K id, boolean lazy) {
        return null;
    }

    @Override
    public List<E> findAll() {
        return null;
    }

    @Override
    public QueryResult<E> findByPage(int page, int size) {
        return null;
    }

    @Override
    public QueryResult<E> findByPage(QueryBean queryBean, int page, int size) {
        return null;
    }

}
package com.nnngu.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.nnngu.comm.HQLQueryBean;
import com.nnngu.comm.QueryBean;
import com.nnngu.comm.QueryResult;

/**
 * 基于Hibernate的BaseDao實(shí)現(xiàn)類
 * @author nnngu
 *
 * @param <E> 實(shí)體類型
 * @param <K> 主鍵類型
 */
@SuppressWarnings(value = {"unchecked"})
public abstract class BaseDaoHibernateImpl<E, K extends Serializable> extends BaseDaoAdapter<E, K> {
    @Autowired
    protected SessionFactory sessionFactory;

    private Class<?> entityClass;       // 業(yè)務(wù)實(shí)體的類對(duì)象
    private String entityName;          // 業(yè)務(wù)實(shí)體的名字

    public BaseDaoHibernateImpl() {
        ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
        entityClass = (Class<?>) pt.getActualTypeArguments()[0];
        entityName = entityClass.getSimpleName();
    }

    @Override
    public K save(E entity) {
        return (K) sessionFactory.getCurrentSession().save(entity);
    }

    @Override
    public void delete(E entity) {
        sessionFactory.getCurrentSession().delete(entity);
    }

    @Override
    public void update(E entity) {
        sessionFactory.getCurrentSession().update(entity);
    }

    @Override
    public E findById(K id) {
        return findById(id, false);
    }

    @Override
    public E findById(K id, boolean lazy) {
        Session session = sessionFactory.getCurrentSession();
        return (E) (lazy? session.load(entityClass, id) : session.get(entityClass, id));
    }

    @Override
    public List<E> findAll() {
        return sessionFactory.getCurrentSession().createCriteria(entityClass).list();
    }

    @Override
    public QueryResult<E> findByPage(int page, int size) {
        return new QueryResult<E>(
            findByHQLAndPage("from " + entityName , page, size),
            getCountByHQL("select count(*) from " + entityName)
        );
    }

    @Override
    public QueryResult<E> findByPage(QueryBean queryBean, int page, int size) {
        if(queryBean instanceof HQLQueryBean) {
            HQLQueryBean hqlQueryBean = (HQLQueryBean) queryBean;
            return new QueryResult<E>(
                findByHQLAndPage(hqlQueryBean.getQueryString(), page, size, hqlQueryBean.getParameters()),
                getCountByHQL(hqlQueryBean.getCountString(), hqlQueryBean.getParameters())
            );
        }
        return null;
    }

    /**
     * 根據(jù)HQL和可變參數(shù)列表進(jìn)行查詢
     * @param hql 基于HQL的查詢語句
     * @param params 可變參數(shù)列表
     * @return 持有查詢結(jié)果的列表容器或空列表容器
     */
    protected List<E> findByHQL(String hql, Object... params) {
        return this.findByHQL(hql, getParamList(params));
    }

    /**
     * 根據(jù)HQL和參數(shù)列表進(jìn)行查詢
     * @param hql 基于HQL的查詢語句
     * @param params 查詢參數(shù)列表
     * @return 持有查詢結(jié)果的列表容器或空列表容器
     */
    protected List<E> findByHQL(String hql, List<Object> params) {
        List<E> list = createQuery(hql, params).list();
        return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
    }

    /**
     * 根據(jù)HQL和參數(shù)列表進(jìn)行分頁查詢
     * @param hql 基于HQL的查詢語句
     * @page 頁碼
     * @size 頁面大小
     * @param params 可變參數(shù)列表
     * @return 持有查詢結(jié)果的列表容器或空列表容器
     */
    protected List<E> findByHQLAndPage(String hql, int page, int size, Object... params) {
        return this.findByHQLAndPage(hql, page, size, getParamList(params));
    }

    /**
     * 根據(jù)HQL和參數(shù)列表進(jìn)行分頁查詢
     * @param hql 基于HQL的查詢語句
     * @page 頁碼
     * @size 頁面大小
     * @param params 查詢參數(shù)列表
     * @return 持有查詢結(jié)果的列表容器或空列表容器
     */
    protected List<E> findByHQLAndPage(String hql, int page, int size, List<Object> params) {
        List<E> list = createQuery(hql, params)
                .setFirstResult((page - 1) * size)
                .setMaxResults(size)
                .list();
        return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
    }

    /**
     * 查詢滿足條件的記錄數(shù)
     * @param hql 基于HQL的查詢語句
     * @param params 可變參數(shù)列表
     * @return 滿足查詢條件的總記錄數(shù)
     */
    protected long getCountByHQL(String hql, Object... params) {
        return this.getCountByHQL(hql, getParamList(params));
    }

    /**
     * 查詢滿足條件的記錄數(shù)
     * @param hql 基于HQL的查詢語句
     * @param params 參數(shù)列表容器
     * @return 滿足查詢條件的總記錄數(shù)
     */
    protected long getCountByHQL(String hql, List<Object> params) {
        return (Long) createQuery(hql, params).uniqueResult();
    }

    // 創(chuàng)建Hibernate查詢對(duì)象(Query)
    private Query createQuery(String hql, List<Object> params) {
        Query query = sessionFactory.getCurrentSession().createQuery(hql);
        for(int i = 0; i < params.size(); i++) {
            query.setParameter(i, params.get(i));
        }
        return query;
    }

    // 將可變參數(shù)列表組裝成列表容器
    private List<Object> getParamList(Object... params) {
        List<Object> paramList = new ArrayList<>();
        if(params != null) {
            for(int i = 0; i < params.length; i++) {
                paramList.add(params[i]);
            }
        }
        return paramList.size() == 0? Collections.EMPTY_LIST : paramList;
    }

}
package com.nnngu.comm;

import java.util.List;

/**
 * 查詢條件的接口
 * @author nnngu
 *
 */
public interface QueryBean {

    /**
     * 添加排序字段
     * @param fieldName 用于排序的字段
     * @param asc 升序還是降序
     * @return 查詢條件對(duì)象自身(方便級(jí)聯(lián)編程)
     */
    public QueryBean addOrder(String fieldName, boolean asc);

    /**
     * 添加排序字段
     * @param available 是否添加此排序字段
     * @param fieldName 用于排序的字段
     * @param asc 升序還是降序
     * @return 查詢條件對(duì)象自身(方便級(jí)聯(lián)編程)
     */
    public QueryBean addOrder(boolean available, String fieldName, boolean asc);

    /**
     * 添加查詢條件
     * @param condition 條件
     * @param params 替換掉條件中參數(shù)占位符的參數(shù)
     * @return 查詢條件對(duì)象自身(方便級(jí)聯(lián)編程)
     */
    public QueryBean addCondition(String condition, Object... params);

    /**
     * 添加查詢條件
     * @param available 是否需要添加此條件
     * @param condition 條件
     * @param params 替換掉條件中參數(shù)占位符的參數(shù)
     * @return 查詢條件對(duì)象自身(方便級(jí)聯(lián)編程)
     */
    public QueryBean addCondition(boolean available, String condition, Object... params);

    /**
     * 獲得查詢語句
     * @return 查詢語句
     */
    public String getQueryString();

    /**
     * 獲取查詢記錄數(shù)的查詢語句
     * @return 查詢記錄數(shù)的查詢語句
     */
    public String getCountString();

    /**
     * 獲得查詢參數(shù)
     * @return 查詢參數(shù)的列表容器
     */
    public List<Object> getParameters();
}
package com.nnngu.comm;

import java.util.List;

/**
 * 查詢結(jié)果
 * @author nnngu
 *
 * @param <T> 泛型參數(shù)
 */
public class QueryResult<T> {
    private List<T> result;     // 持有查詢結(jié)果的列表容器
    private long totalRecords;  // 查詢到的總記錄數(shù)

    /**
     * 構(gòu)造器
     */
    public QueryResult() {
    }

    /**
     * 構(gòu)造器
     * @param result 持有查詢結(jié)果的列表容器
     * @param totalRecords 查詢到的總記錄數(shù)
     */
    public QueryResult(List<T> result, long totalRecords) {
        this.result = result;
        this.totalRecords = totalRecords;
    }

    public List<T> getResult() {
        return result;
    }

    public void setResult(List<T> result) {
        this.result = result;
    }

    public long getTotalRecords() {
        return totalRecords;
    }

    public void setTotalRecords(long totalRecords) {
        this.totalRecords = totalRecords;
    }
}
package com.nnngu.dao;

import com.nnngu.comm.QueryResult;
import com.nnngu.entity.Dept;

/**
 * 部門數(shù)據(jù)訪問對(duì)象接口
 * @author nnngu
 *
 */
public interface DeptDao extends BaseDao<Dept, Integer> {

    /**
     * 分頁查詢頂級(jí)部門
     * @param page 頁碼
     * @param size 頁碼大小
     * @return 查詢結(jié)果對(duì)象
     */
    public QueryResult<Dept> findTopDeptByPage(int page, int size);

}
package com.nnngu.dao.impl;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.nnngu.comm.QueryResult;
import com.nnngu.dao.BaseDaoHibernateImpl;
import com.nnngu.dao.DeptDao;
import com.nnngu.entity.Dept;

@Repository
public class DeptDaoImpl extends BaseDaoHibernateImpl<Dept, Integer> implements DeptDao {
    private static final String HQL_FIND_TOP_DEPT = " from Dept as d where d.superiorDept is null ";

    @Override
    public QueryResult<Dept> findTopDeptByPage(int page, int size) {
        List<Dept> list = findByHQLAndPage(HQL_FIND_TOP_DEPT, page, size);
        long totalRecords = getCountByHQL(" select count(*) " + HQL_FIND_TOP_DEPT);
        return new QueryResult<>(list, totalRecords);
    }

}
package com.nnngu.comm;

import java.util.List;

/**
 * 分頁器
 * @author nnngu
 *
 * @param <T> 分頁數(shù)據(jù)對(duì)象的類型
 */
public class PageBean<T> {
    private static final int DEFAUL_INIT_PAGE = 1;
    private static final int DEFAULT_PAGE_SIZE = 10;
    private static final int DEFAULT_PAGE_COUNT = 5;

    private List<T> data;           // 分頁數(shù)據(jù)
    private PageRange pageRange;    // 頁碼范圍
    private int totalPage;          // 總頁數(shù)
    private int size;               // 頁面大小
    private int currentPage;        // 當(dāng)前頁碼
    private int pageCount;          // 頁碼數(shù)量

    /**
     * 構(gòu)造器
     * @param currentPage 當(dāng)前頁碼
     * @param size 頁碼大小
     * @param pageCount 頁碼數(shù)量
     */
    public PageBean(int currentPage, int size, int pageCount) {
        this.currentPage = currentPage > 0 ? currentPage : 1;
        this.size = size > 0 ? size : DEFAULT_PAGE_SIZE;
        this.pageCount = pageCount > 0 ? size : DEFAULT_PAGE_COUNT;
    }

    /**
     * 構(gòu)造器
     * @param currentPage 當(dāng)前頁碼
     * @param size 頁碼大小
     */
    public PageBean(int currentPage, int size) {
        this(currentPage, size, DEFAULT_PAGE_COUNT);
    }

    /**
     * 構(gòu)造器
     * @param currentPage 當(dāng)前頁碼
     */
    public PageBean(int currentPage) {
        this(currentPage, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
    }

    /**
     * 構(gòu)造器
     */
    public PageBean() {
        this(DEFAUL_INIT_PAGE, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
    }

    public List<T> getData() {
        return data;
    }

    public int getStartPage() {
        return pageRange != null ? pageRange.getStartPage() : 1;
    }

    public int getEndPage() {
        return pageRange != null ? pageRange.getEndPage() : 1;
    }

    public long getTotalPage() {
        return totalPage;
    }

    public int getSize() {
        return size;
    }

    public int getCurrentPage() {
        return currentPage;
    }

    /**
     * 將查詢結(jié)果轉(zhuǎn)換為分頁數(shù)據(jù)
     * @param queryResult 查詢結(jié)果對(duì)象
     */
    public void transferQueryResult(QueryResult<T> queryResult) {
        long totalRecords = queryResult.getTotalRecords();

        data = queryResult.getResult();
        totalPage = (int) ((totalRecords + size - 1) / size); 
        totalPage = totalPage >= 0 ? totalPage : Integer.MAX_VALUE;
        this.pageRange = new PageRange(pageCount, currentPage, totalPage);
    }

}
package com.nnngu.comm;

/**
 * 頁碼范圍
 * @author nnngu
 *
 */
public class PageRange {
    private int startPage;  // 起始頁碼
    private int endPage;    // 終止頁碼

    /**
     * 構(gòu)造器
     * @param pageCount 總共顯示幾個(gè)頁碼
     * @param currentPage 當(dāng)前頁碼
     * @param totalPage 總頁數(shù)
     */
    public PageRange(int pageCount, int currentPage, int totalPage) {
        startPage = currentPage - (pageCount - 1) / 2;
        endPage = currentPage + pageCount / 2;
        if(startPage < 1) {
            startPage = 1;
            endPage = totalPage > pageCount ? pageCount : totalPage;
        }
        if (endPage > totalPage) {
            endPage = totalPage;
            startPage = (endPage - pageCount > 0) ? endPage - pageCount + 1 : 1;
        }
    }

    /**
     * 獲得起始頁頁碼
     * @return 起始頁頁碼
     */
    public int getStartPage() {
        return startPage;
    }

    /**
     * 獲得終止頁頁碼
     * @return 終止頁頁碼
     */
    public int getEndPage() {
        return endPage;
    }

}
package com.nnngu.biz;

import com.nnngu.comm.PageBean;
import com.nnngu.entity.Dept;

/**
 * 部門業(yè)務(wù)邏輯接口
 * @author nnngu
 *
 */
public interface DeptService {

    /**
     * 創(chuàng)建新的部門
     * @param department 部門對(duì)象
     * @return 創(chuàng)建成功返回true否則返回false
     */
    public boolean createNewDepartment(Dept department);

    /**
     * 刪除指定部門
     * @param id 要?jiǎng)h除的部門的編號(hào)
     * @return 刪除成功返回true否則返回false
     */
    public boolean deleteDepartment(Integer id);

    /**
     * 分頁獲取頂級(jí)部門
     * @param page 頁碼
     * @param size 頁碼大小
     * @return 部門對(duì)象的分頁器對(duì)象
     */
    public PageBean<Dept> getTopDeptByPage(int page, int size);

}
package com.nnngu.biz.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.nnngu.biz.DeptService;
import com.nnngu.comm.PageBean;
import com.nnngu.comm.QueryResult;
import com.nnngu.dao.DeptDao;
import com.nnngu.entity.Dept;

@Service
@Transactional  // 聲明式事務(wù)的注解
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptDao deptDao;

    @Override
    public boolean createNewDepartment(Dept department) {
        return deptDao.save(department) != null;
    }

    @Override
    public boolean deleteDepartment(Integer id) {
        return deptDao.deleteById(id);
    }

    @Override
    public PageBean<Dept> getTopDeptByPage(int page, int size) {
        QueryResult<Dept> queryResult = deptDao.findTopDeptByPage(page, size);
        PageBean<Dept> pageBean = new PageBean<>(page, size);
        pageBean.transferQueryResult(queryResult);
        return pageBean;
    }

}

154淀歇、如何在Web項(xiàng)目中配置Spring的IoC容器易核?

答:如果需要在Web項(xiàng)目中使用Spring的IoC容器,可以在Web項(xiàng)目配置文件web.xml中做出如下配置:

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>

155浪默、如何在Web項(xiàng)目中配置Spring MVC牡直?

答:要使用Spring MVC需要在Web項(xiàng)目配置文件中配置其前端控制器DispatcherServlet,如下所示:

<web-app>

    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

</web-app>

說明:上面的配置中使用了*.html的后綴映射纳决,這樣做一方面不能夠通過URL推斷采用了何種服務(wù)器端的技術(shù)碰逸,另一方面可以欺騙搜索引擎,因?yàn)樗阉饕娌粫?huì)搜索動(dòng)態(tài)頁面阔加,這種做法稱為偽靜態(tài)化饵史。

156、Spring MVC的工作原理是怎樣的胜榔?

答:Spring MVC的工作原理如下圖所示:

① 客戶端的所有請(qǐng)求都交給前端控制器DispatcherServlet來處理胳喷,它會(huì)負(fù)責(zé)調(diào)用系統(tǒng)的其他模塊來真正處理用戶的請(qǐng)求。
② DispatcherServlet收到請(qǐng)求后夭织,將根據(jù)請(qǐng)求的信息(包括URL吭露、HTTP協(xié)議方法、請(qǐng)求頭尊惰、請(qǐng)求參數(shù)讲竿、Cookie等)以及HandlerMapping的配置找到處理該請(qǐng)求的Handler(任何一個(gè)對(duì)象都可以作為請(qǐng)求的Handler)。
③在這個(gè)地方Spring會(huì)通過HandlerAdapter對(duì)該處理器進(jìn)行封裝择浊。
④ HandlerAdapter是一個(gè)適配器戴卜,它用統(tǒng)一的接口對(duì)各種Handler中的方法進(jìn)行調(diào)用。
⑤ Handler完成對(duì)用戶請(qǐng)求的處理后琢岩,會(huì)返回一個(gè)ModelAndView對(duì)象給DispatcherServlet投剥,ModelAndView顧名思義,包含了數(shù)據(jù)模型以及相應(yīng)的視圖的信息担孔。
⑥ ModelAndView的視圖是邏輯視圖江锨,DispatcherServlet還要借助ViewResolver完成從邏輯視圖到真實(shí)視圖對(duì)象的解析工作。
⑦ 當(dāng)?shù)玫秸嬲囊晥D對(duì)象后糕篇,DispatcherServlet會(huì)利用視圖對(duì)象對(duì)模型數(shù)據(jù)進(jìn)行渲染啄育。
⑧ 客戶端得到響應(yīng),可能是一個(gè)普通的HTML頁面拌消,也可以是XML或JSON字符串挑豌,還可以是一張圖片或者一個(gè)PDF文件。

157、如何在Spring IoC容器中配置數(shù)據(jù)源氓英?

答:
DBCP配置:

<bean id="dataSource"
        class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

C3P0配置:

<bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${jdbc.driverClassName}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<context:property-placeholder location="jdbc.properties"/>

提示: DBCP的詳細(xì)配置在第153題中已經(jīng)完整的展示過了侯勉。

158、如何配置事務(wù)增強(qiáng)铝阐?

答:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xmlns:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd">

  <!-- this is the service object that we want to make transactional -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- the transactional advice -->
  <tx:advice id="txAdvice" transaction-manager="txManager">
  <!-- the transactional semantics... -->
  <tx:attributes>
    <!-- all methods starting with 'get' are read-only -->
    <tx:method name="get*" read-only="true"/>
    <!-- other methods use the default transaction settings (see below) -->
    <tx:method name="*"/>
  </tx:attributes>
  </tx:advice>

  <!-- ensure that the above transactional advice runs for any execution
    of an operation defined by the FooService interface -->
  <aop:config>
  <aop:pointcut id="fooServiceOperation" 
    expression="execution(* x.y.service.FooService.*(..))"/>
  <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
  </aop:config>

  <!-- don't forget the DataSource -->
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" 
    destroy-method="close">
  <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
  <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl"/>
  <property name="username" value="scott"/>
  <property name="password" value="tiger"/>
  </bean>

  <!-- similarly, don't forget the PlatformTransactionManager -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
  </bean>

  <!-- other <bean/> definitions here -->

</beans>

159址貌、選擇使用Spring框架的原因(Spring框架為企業(yè)級(jí)開發(fā)帶來的好處有哪些)?

答:可以從以下幾個(gè)方面作答:

  • 非侵入式:支持基于POJO的編程模式徘键,不強(qiáng)制性的要求實(shí)現(xiàn)Spring框架中的接口或繼承Spring框架中的類练对。
  • IoC容器:IoC容器幫助應(yīng)用程序管理對(duì)象以及對(duì)象之間的依賴關(guān)系,對(duì)象之間的依賴關(guān)系如果發(fā)生了改變只需要修改配置文件而不是修改代碼吹害,因?yàn)榇a的修改可能意味著項(xiàng)目的重新構(gòu)建和完整的回歸測(cè)試螟凭。有了IoC容器,程序員再也不需要自己編寫工廠它呀、單例赂摆,這一點(diǎn)特別符合Spring的精神"不要重復(fù)的發(fā)明輪子"。
  • AOP(面向切面編程):將所有的橫切關(guān)注功能封裝到切面(aspect)中钟些,通過配置的方式將橫切關(guān)注功能動(dòng)態(tài)添加到目標(biāo)代碼上,進(jìn)一步實(shí)現(xiàn)了業(yè)務(wù)邏輯和系統(tǒng)服務(wù)之間的分離绊谭。另一方面政恍,有了AOP程序員可以省去很多自己寫代理類的工作。
  • MVC:Spring的MVC框架是非常優(yōu)秀的达传,從各個(gè)方面都可以甩Struts 2幾條街篙耗,為Web表示層提供了更好的解決方案。
  • 事務(wù)管理:Spring以寬廣的胸懷接納多種持久層技術(shù)宪赶,并且為其提供了聲明式的事務(wù)管理宗弯,在不需要任何一行代碼的情況下就能夠完成事務(wù)管理。
  • 其他:選擇Spring框架的原因還遠(yuǎn)不止于此搂妻,Spring為Java企業(yè)級(jí)開發(fā)提供了一站式選擇蒙保,你可以在需要的時(shí)候使用它的部分和全部,更重要的是欲主,你甚至可以在感覺不到Spring存在的情況下邓厕,在你的項(xiàng)目中使用Spring提供的各種優(yōu)秀的功能。

160扁瓢、Spring IoC容器配置Bean的方式详恼?

答:

  • 基于XML文件進(jìn)行配置。
  • 基于注解進(jìn)行配置引几。
  • 基于Java程序進(jìn)行配置(Spring 3+)
package com.nnngu.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
    private String name;
    private int age;
    @Autowired
    private Car car;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
    }

}
package com.nnngu.bean;

import org.springframework.stereotype.Component;

@Component
public class Car {
    private String brand;
    private int maxSpeed;

    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }

    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }

}
package com.nnngu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.nnngu.bean.Car;
import com.nnngu.bean.Person;

@Configuration
public class AppConfig {

    @Bean
    public Car car() {
        return new Car("Benz", 320);
    }

    @Bean
    public Person person() {
        return new Person("郭靖", 34);
    }
}
package com.nnngu.test;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.nnngu.bean.Person;
import com.nnngu.config.AppConfig;

class Test {

    public static void main(String[] args) {
        // TWR (Java 7+)
        try(ConfigurableApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class)) {
            Person person = factory.getBean(Person.class);
            System.out.println(person);
        }
    }
}

161昧互、闡述Spring框架中Bean的生命周期?

答:
① Spring IoC容器找到關(guān)于Bean的定義并實(shí)例化該Bean。
② Spring IoC容器對(duì)Bean進(jìn)行依賴注入敞掘。
③ 如果Bean實(shí)現(xiàn)了BeanNameAware接口叽掘,則將該Bean的id傳給setBeanName方法。
④ 如果Bean實(shí)現(xiàn)了BeanFactoryAware接口渐逃,則將BeanFactory對(duì)象傳給setBeanFactory方法够掠。
⑤ 如果Bean實(shí)現(xiàn)了BeanPostProcessor接口,則調(diào)用其postProcessBeforeInitialization方法茄菊。
⑥ 如果Bean實(shí)現(xiàn)了InitializingBean接口疯潭,則調(diào)用其afterPropertySet方法。
⑦ 如果有和Bean關(guān)聯(lián)的BeanPostProcessors對(duì)象面殖,則這些對(duì)象的postProcessAfterInitialization方法被調(diào)用竖哩。
⑧ 當(dāng)銷毀Bean實(shí)例時(shí),如果Bean實(shí)現(xiàn)了DisposableBean接口脊僚,則調(diào)用其destroy方法相叁。

162、依賴注入時(shí)如何注入集合屬性辽幌?

答:可以在定義Bean屬性時(shí)增淹,通過<list> / <set> / <map> / <props>分別為其注入列表、集合乌企、映射和鍵值都是字符串的映射屬性虑润。

163、Spring中的自動(dòng)裝配有哪些限制加酵?

答:

  • 如果使用了構(gòu)造器注入或者setter注入拳喻,那么將覆蓋自動(dòng)裝配的依賴關(guān)系。
  • 基本數(shù)據(jù)類型的值猪腕、字符串字面量冗澈、類字面量無法使用自動(dòng)裝配來注入。
  • 優(yōu)先考慮使用顯式的裝配來進(jìn)行更精確的依賴注入而不是使用自動(dòng)裝配陋葡。

164亚亲、在Web項(xiàng)目中如何獲得Spring的IoC容器?

答:

WebApplicationContext ctx = 
WebApplicationContextUtils.getWebApplicationContext(servletContext);

165. 大型網(wǎng)站在架構(gòu)上應(yīng)當(dāng)考慮哪些問題腐缤?

答:

  • 分層:分層是處理任何復(fù)雜系統(tǒng)最常見的手段之一朵栖,將系統(tǒng)橫向切分成若干個(gè)層面,每個(gè)層面只承擔(dān)單一的職責(zé)柴梆,然后通過下層為上層提供的基礎(chǔ)設(shè)施和服務(wù)以及上層對(duì)下層的調(diào)用來形成一個(gè)完整的復(fù)雜的系統(tǒng)陨溅。計(jì)算機(jī)網(wǎng)絡(luò)的開放系統(tǒng)互聯(lián)參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結(jié)構(gòu),大型網(wǎng)站的軟件系統(tǒng)也可以使用分層的理念將其分為持久層(提供數(shù)據(jù)存儲(chǔ)和訪問服務(wù))绍在、業(yè)務(wù)層(處理業(yè)務(wù)邏輯门扇,系統(tǒng)中最核心的部分)和表示層(系統(tǒng)交互雹有、視圖展示)。需要指出的是:(1)分層是邏輯上的劃分臼寄,在物理上可以位于同一設(shè)備上也可以在不同的設(shè)備上部署不同的功能模塊霸奕,這樣可以使用更多的計(jì)算資源來應(yīng)對(duì)用戶的并發(fā)訪問;(2)層與層之間應(yīng)當(dāng)有清晰的邊界吉拳,這樣分層才有意義质帅,才更利于軟件的開發(fā)和維護(hù)。
  • 分割:分割是對(duì)軟件的縱向切分留攒。我們可以將大型網(wǎng)站的不同功能和服務(wù)分割開煤惩,形成高內(nèi)聚低耦合的功能模塊(單元)。在設(shè)計(jì)初期可以做一個(gè)粗粒度的分割炼邀,將網(wǎng)站分割為若干個(gè)功能模塊魄揉,后期還可以進(jìn)一步對(duì)每個(gè)模塊進(jìn)行細(xì)粒度的分割,這樣一方面有助于軟件的開發(fā)和維護(hù)拭宁,另一方面有助于分布式的部署洛退,提供網(wǎng)站的并發(fā)處理能力和功能的擴(kuò)展。
  • 分布式:除了上面提到的內(nèi)容杰标,網(wǎng)站的靜態(tài)資源(JavaScript兵怯、CSS、圖片等)也可以采用獨(dú)立分布式部署并采用獨(dú)立的域名腔剂,這樣可以減輕應(yīng)用服務(wù)器的負(fù)載壓力摇零,也使得瀏覽器對(duì)資源的加載更快。數(shù)據(jù)的存取也應(yīng)該是分布式的桶蝎,傳統(tǒng)的商業(yè)級(jí)關(guān)系型數(shù)據(jù)庫(kù)產(chǎn)品基本上都支持分布式部署,而新生的NoSQL產(chǎn)品幾乎都是分布式的谅畅。當(dāng)然登渣,網(wǎng)站后臺(tái)的業(yè)務(wù)處理也要使用分布式技術(shù),例如查詢索引的構(gòu)建毡泻、數(shù)據(jù)分析等胜茧,這些業(yè)務(wù)計(jì)算規(guī)模龐大,可以使用Hadoop以及MapReduce分布式計(jì)算框架來處理仇味。
  • 集群:集群使得有更多的服務(wù)器提供相同的服務(wù)呻顽,可以更好的提供對(duì)并發(fā)的支持。
  • 緩存:所謂緩存就是用空間換取時(shí)間的技術(shù)丹墨,將數(shù)據(jù)盡可能放在距離計(jì)算最近的位置廊遍。使用緩存是網(wǎng)站優(yōu)化的第一定律。我們通常說的CDN贩挣、反向代理喉前、熱點(diǎn)數(shù)據(jù)都是對(duì)緩存技術(shù)的使用没酣。
  • 異步:異步是實(shí)現(xiàn)軟件實(shí)體之間解耦合的又一重要手段。異步架構(gòu)是典型的生產(chǎn)者消費(fèi)者模式卵迂,二者之間沒有直接的調(diào)用關(guān)系裕便,只要保持?jǐn)?shù)據(jù)結(jié)構(gòu)不變,彼此功能實(shí)現(xiàn)可以隨意變化而不互相影響见咒,這對(duì)網(wǎng)站的擴(kuò)展非常有利偿衰。使用異步處理還可以提高系統(tǒng)可用性,加快網(wǎng)站的響應(yīng)速度(用Ajax加載數(shù)據(jù)就是一種異步技術(shù))改览,同時(shí)還可以起到削峰作用(應(yīng)對(duì)瞬時(shí)高并發(fā))下翎。"能推遲處理的都要推遲處理"是網(wǎng)站優(yōu)化的第二定律,而異步是踐行網(wǎng)站優(yōu)化第二定律的重要手段恃疯。
  • 冗余:各種服務(wù)器都要提供相應(yīng)的冗余服務(wù)器以便在某臺(tái)或某些服務(wù)器宕機(jī)時(shí)還能保證網(wǎng)站可以正常工作漏设,同時(shí)也提供了災(zāi)難恢復(fù)的可能性。冗余是網(wǎng)站高可用性的重要保證今妄。

166郑口、你用過的網(wǎng)站前端優(yōu)化的技術(shù)有哪些?

答:
① 瀏覽器訪問優(yōu)化:

  • 減少HTTP請(qǐng)求數(shù)量:合并CSS盾鳞、合并JavaScript犬性、合并圖片(CSS Sprite)
  • 使用瀏覽器緩存:通過設(shè)置HTTP響應(yīng)頭中的Cache-Control和Expires屬性,將CSS腾仅、JavaScript乒裆、圖片等在瀏覽器中緩存,當(dāng)這些靜態(tài)資源需要更新時(shí)推励,可以更新HTML文件中的引用來讓瀏覽器重新請(qǐng)求新的資源
  • 啟用壓縮
  • CSS前置鹤耍,JavaScript后置
  • 減少Cookie傳輸
    ② CDN加速:CDN(Content Distribute Network)的本質(zhì)仍然是緩存,將數(shù)據(jù)緩存在離用戶最近的地方验辞,CDN通常部署在網(wǎng)絡(luò)運(yùn)營(yíng)商的機(jī)房稿黄,不僅可以提升響應(yīng)速度,還可以減少應(yīng)用服務(wù)器的壓力跌造。當(dāng)然杆怕,CDN緩存的通常都是靜態(tài)資源。
    ③ 反向代理:反向代理相當(dāng)于應(yīng)用服務(wù)器的一個(gè)門面壳贪,可以保護(hù)網(wǎng)站的安全性陵珍,也可以實(shí)現(xiàn)負(fù)載均衡的功能,當(dāng)然最重要的是它緩存了用戶訪問的熱點(diǎn)資源违施,可以直接從反向代理將某些內(nèi)容返回給用戶瀏覽器互纯。

167、你使用過的應(yīng)用服務(wù)器優(yōu)化技術(shù)有哪些磕蒲?

答:
① 分布式緩存:緩存的本質(zhì)就是內(nèi)存中的哈希表伟姐,如果設(shè)計(jì)一個(gè)優(yōu)質(zhì)的哈希函數(shù)收苏,那么理論上哈希表讀寫的漸近時(shí)間復(fù)雜度為O(1)。緩存主要用來存放那些讀寫比很高愤兵、變化很少的數(shù)據(jù)鹿霸,這樣應(yīng)用程序讀取數(shù)據(jù)時(shí)先到緩存中讀取,如果沒有或者數(shù)據(jù)已經(jīng)失效再去訪問數(shù)據(jù)庫(kù)或文件系統(tǒng)秆乳,并根據(jù)擬定的規(guī)則將數(shù)據(jù)寫入緩存懦鼠。對(duì)網(wǎng)站數(shù)據(jù)的訪問也符合二八定律(Pareto分布,冪律分布)屹堰,即80%的訪問都集中在20%的數(shù)據(jù)上肛冶,如果能夠?qū)⑦@20%的數(shù)據(jù)緩存起來,那么系統(tǒng)的性能將得到顯著的改善扯键。當(dāng)然睦袖,使用緩存需要解決以下幾個(gè)問題:

  • 頻繁修改的數(shù)據(jù)谚殊;
  • 數(shù)據(jù)不一致與臟讀谤专;
  • 緩存雪崩(可以采用分布式緩存服務(wù)器集群加以解決静秆,memcached是廣泛采用的解決方案)华畏;
  • 緩存預(yù)熱;
  • 緩存穿透(惡意持續(xù)請(qǐng)求不存在的數(shù)據(jù))伪嫁。
    ② 異步操作:可以使用消息隊(duì)列將調(diào)用異步化枫慷,通過異步處理將短時(shí)間高并發(fā)產(chǎn)生的事件消息存儲(chǔ)在消息隊(duì)列中周拐,從而起到削峰作用爱只。電商網(wǎng)站在進(jìn)行促銷活動(dòng)時(shí)皿淋,可以將用戶的訂單請(qǐng)求存入消息隊(duì)列,這樣可以抵御大量的并發(fā)訂單請(qǐng)求對(duì)系統(tǒng)和數(shù)據(jù)庫(kù)的沖擊恬试。目前窝趣,絕大多數(shù)的電商網(wǎng)站即便不進(jìn)行促銷活動(dòng),訂單系統(tǒng)都采用了消息隊(duì)列來處理训柴。
    ③ 使用集群哑舒。
    ④ 代碼優(yōu)化:
  • 多線程:基于Java的Web開發(fā)基本上都通過多線程的方式響應(yīng)用戶的并發(fā)請(qǐng)求,使用多線程技術(shù)在編程上要解決線程安全問題畦粮,主要可以考慮以下幾個(gè)方面:A. 將對(duì)象設(shè)計(jì)為無狀態(tài)對(duì)象(這和面向?qū)ο蟮木幊逃^點(diǎn)是矛盾的,在面向?qū)ο蟮氖澜缰斜灰暈椴涣荚O(shè)計(jì))乖阵,這樣就不會(huì)存在并發(fā)訪問時(shí)對(duì)象狀態(tài)不一致的問題宣赔。B. 在方法內(nèi)部創(chuàng)建對(duì)象,這樣對(duì)象由進(jìn)入方法的線程創(chuàng)建瞪浸,不會(huì)出現(xiàn)多個(gè)線程訪問同一對(duì)象的問題儒将。使用ThreadLocal將對(duì)象與線程綁定也是很好的做法,這一點(diǎn)在前面已經(jīng)探討過了对蒲。C. 對(duì)資源進(jìn)行并發(fā)訪問時(shí)應(yīng)當(dāng)使用合理的鎖機(jī)制钩蚊。
  • 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認(rèn)的比多線程的方式更能充分發(fā)揮服務(wù)器性能的應(yīng)用模式贡翘,基于Node.js構(gòu)建的服務(wù)器就采用了這樣的方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規(guī)范中又引入了異步Servlet的概念砰逻,這些都為在服務(wù)器端采用非阻塞I/O提供了必要的基礎(chǔ)鸣驱。
  • 資源復(fù)用:資源復(fù)用主要有兩種方式,一是單例蝠咆,二是對(duì)象池踊东,我們使用的數(shù)據(jù)庫(kù)連接池、線程池都是對(duì)象池化技術(shù)刚操,這是典型的用空間換取時(shí)間的策略闸翅,另一方面也實(shí)現(xiàn)對(duì)資源的復(fù)用,從而避免了不必要的創(chuàng)建和釋放資源所帶來的開銷菊霜。

168坚冀、什么是XSS攻擊?什么是SQL注入攻擊鉴逞?什么是CSRF攻擊记某?

答:

  • XSS(Cross Site Script,跨站腳本攻擊)是向網(wǎng)頁中注入惡意腳本在用戶瀏覽網(wǎng)頁時(shí)在用戶瀏覽器中執(zhí)行惡意腳本的攻擊方式华蜒≌尬常跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點(diǎn)擊一個(gè)嵌入惡意腳本的鏈接以達(dá)到攻擊的目標(biāo),目前有很多攻擊者利用論壇叭喜、微博發(fā)布含有惡意腳本的URL就屬于這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網(wǎng)站的數(shù)據(jù)庫(kù)中贺拣,用戶瀏覽網(wǎng)頁時(shí),惡意腳本從數(shù)據(jù)庫(kù)中被加載到頁面執(zhí)行捂蕴,QQ郵箱的早期版本就曾經(jīng)被利用作為持久型跨站腳本攻擊的平臺(tái))譬涡。XSS雖然不是什么新鮮玩意,但是攻擊的手法卻不斷翻新啥辨,防范XSS主要有兩方面:消毒(對(duì)危險(xiǎn)字符進(jìn)行轉(zhuǎn)義)和HttpOnly(防范XSS攻擊者竊取Cookie數(shù)據(jù))涡匀。
  • SQL注入攻擊是注入攻擊最常見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是通過OGNL實(shí)施OS注入攻擊導(dǎo)致的)),當(dāng)服務(wù)器使用請(qǐng)求參數(shù)構(gòu)造SQL語句時(shí)溉知,惡意的SQL被嵌入到SQL中交給數(shù)據(jù)庫(kù)執(zhí)行陨瘩。SQL注入攻擊需要攻擊者對(duì)數(shù)據(jù)庫(kù)結(jié)構(gòu)有所了解才能進(jìn)行,攻擊者想要獲得表結(jié)構(gòu)有多種方式:(1)如果使用開源系統(tǒng)搭建網(wǎng)站级乍,數(shù)據(jù)庫(kù)結(jié)構(gòu)也是公開的(目前有很多現(xiàn)成的系統(tǒng)可以直接搭建論壇舌劳,電商網(wǎng)站,雖然方便快捷但是風(fēng)險(xiǎn)是必須要認(rèn)真評(píng)估的)玫荣;(2)錯(cuò)誤回顯(如果將服務(wù)器的錯(cuò)誤信息直接顯示在頁面上甚淡,攻擊者可以通過非法參數(shù)引發(fā)頁面錯(cuò)誤從而通過錯(cuò)誤信息了解數(shù)據(jù)庫(kù)結(jié)構(gòu),Web應(yīng)用應(yīng)當(dāng)設(shè)置友好的錯(cuò)誤頁捅厂,一方面符合最小驚訝原則贯卦,一方面屏蔽掉可能給系統(tǒng)帶來危險(xiǎn)的錯(cuò)誤回顯信息)资柔;(3)盲注。防范SQL注入攻擊也可以采用消毒的方式撵割,通過正則表達(dá)式對(duì)請(qǐng)求參數(shù)進(jìn)行驗(yàn)證贿堰,此外,參數(shù)綁定也是很好的手段睁枕,這樣惡意的SQL會(huì)被當(dāng)做SQL的參數(shù)而不是命令被執(zhí)行官边,JDBC中的PreparedStatement就是支持參數(shù)綁定的語句對(duì)象,從性能和安全性上都明顯優(yōu)于Statement外遇。
  • CSRF攻擊(Cross Site Request Forgery注簿,跨站請(qǐng)求偽造)是攻擊者通過跨站請(qǐng)求,以合法的用戶身份進(jìn)行非法操作(如轉(zhuǎn)賬或發(fā)帖等)跳仿。CSRF的原理是利用瀏覽器的Cookie或服務(wù)器的Session诡渴,盜取用戶身份,其原理如下圖所示菲语。防范CSRF的主要手段是識(shí)別請(qǐng)求者的身份妄辩,主要有以下幾種方式:(1)在表單中添加令牌(token);(2)驗(yàn)證碼山上;(3)檢查請(qǐng)求頭中的Referer(前面提到防圖片盜鏈接也是用的這種方式)眼耀。令牌和驗(yàn)證都具有一次消費(fèi)性的特征,因此在原理上一致的佩憾,但是驗(yàn)證碼是一種糟糕的用戶體驗(yàn)哮伟,不是必要的情況下不要輕易使用驗(yàn)證碼,目前很多網(wǎng)站的做法是如果在短時(shí)間內(nèi)多次提交一個(gè)表單未獲得成功后才要求提供驗(yàn)證碼妄帘,這樣會(huì)獲得較好的用戶體驗(yàn)楞黄。

補(bǔ)充:防火墻的架設(shè)是Web安全的重要保障,ModSecurity是開源的Web防火墻中的佼佼者抡驼。企業(yè)級(jí)防火墻的架設(shè)應(yīng)當(dāng)有兩級(jí)防火墻鬼廓,Web服務(wù)器和部分應(yīng)用服務(wù)器可以架設(shè)在兩級(jí)防火墻之間的DMZ,而數(shù)據(jù)和資源服務(wù)器應(yīng)當(dāng)架設(shè)在第二級(jí)防火墻之后致盟。

169. 什么是領(lǐng)域模型(domain model)碎税?貧血模型(anaemic domain model)和充血模型(rich domain model)有什么區(qū)別?

答:領(lǐng)域模型是領(lǐng)域內(nèi)的概念類或現(xiàn)實(shí)世界中對(duì)象的可視化表示馏锡,又稱為概念模型或分析對(duì)象模型雷蹂,它專注于分析問題領(lǐng)域本身,發(fā)掘重要的業(yè)務(wù)領(lǐng)域概念眷篇,并建立業(yè)務(wù)領(lǐng)域概念之間的關(guān)系萎河。貧血模型是指使用的領(lǐng)域?qū)ο笾兄挥衧etter和getter方法(POJO)荔泳,所有的業(yè)務(wù)邏輯都不包含在領(lǐng)域?qū)ο笾卸欠旁跇I(yè)務(wù)邏輯層蕉饼。有人將我們這里說的貧血模型進(jìn)一步劃分成失血模型(領(lǐng)域?qū)ο笸耆珱]有業(yè)務(wù)邏輯)和貧血模型(領(lǐng)域?qū)ο笥猩倭康臉I(yè)務(wù)邏輯)虐杯,我們這里就不對(duì)此加以區(qū)分了。充血模型將大多數(shù)業(yè)務(wù)邏輯和持久化放在領(lǐng)域?qū)ο笾忻粮郏瑯I(yè)務(wù)邏輯(業(yè)務(wù)門面)只是完成對(duì)業(yè)務(wù)邏輯的封裝擎椰、事務(wù)和權(quán)限等的處理。下面兩張圖分別展示了貧血模型和充血模型的分層架構(gòu)创肥。

貧血模型

充血模型

貧血模型下組織領(lǐng)域邏輯通常使用事務(wù)腳本模式达舒,讓每個(gè)過程對(duì)應(yīng)用戶可能要做的一個(gè)動(dòng)作,每個(gè)動(dòng)作由一個(gè)過程來驅(qū)動(dòng)叹侄。也就是說在設(shè)計(jì)業(yè)務(wù)邏輯接口的時(shí)候巩搏,每個(gè)方法對(duì)應(yīng)著用戶的一個(gè)操作,這種模式有以下幾個(gè)有點(diǎn):

  • 它是一個(gè)大多數(shù)開發(fā)者都能夠理解的簡(jiǎn)單過程模型(適合國(guó)內(nèi)的絕大多數(shù)開發(fā)者)趾代。
  • 它能夠與一個(gè)使用行數(shù)據(jù)入口或表數(shù)據(jù)入口的簡(jiǎn)單數(shù)據(jù)訪問層很好的協(xié)作贯底。
  • 事務(wù)邊界的顯而易見,一個(gè)事務(wù)開始于腳本的開始撒强,終止于腳本的結(jié)束禽捆,很容易通過代理(或切面)實(shí)現(xiàn)聲明式事務(wù)。
    然而飘哨,事務(wù)腳本模式的缺點(diǎn)也是很多的胚想,隨著領(lǐng)域邏輯復(fù)雜性的增加,系統(tǒng)的復(fù)雜性將迅速增加芽隆,程序結(jié)構(gòu)將變得極度混亂浊服。開源中國(guó)社區(qū)上有一篇很好的譯文《貧血領(lǐng)域模型是如何導(dǎo)致糟糕的軟件產(chǎn)生》對(duì)這個(gè)問題做了比較細(xì)致的闡述。

170. 談一談測(cè)試驅(qū)動(dòng)開發(fā)(TDD)的好處以及你的理解摆马。

答:TDD是指在編寫真正的功能實(shí)現(xiàn)代碼之前先寫測(cè)試代碼臼闻,然后根據(jù)需要重構(gòu)實(shí)現(xiàn)代碼。在JUnit的作者Kent Beck的大作《測(cè)試驅(qū)動(dòng)開發(fā):實(shí)戰(zhàn)與模式解析》(Test-Driven Development: by Example)一書中有這么一段內(nèi)容:“消除恐懼和不確定性是編寫測(cè)試驅(qū)動(dòng)代碼的重要原因”囤采。因?yàn)榫帉懘a時(shí)的恐懼會(huì)讓你小心試探述呐,讓你回避溝通,讓你羞于得到反饋蕉毯,讓你變得焦躁不安乓搬,而TDD是消除恐懼、讓Java開發(fā)者更加自信更加樂于溝通的重要手段代虾。TDD會(huì)帶來的好處可能不會(huì)馬上呈現(xiàn)进肯,但是你在某個(gè)時(shí)候一定會(huì)發(fā)現(xiàn),這些好處包括:

  • 更清晰的代碼 — 只寫需要的代碼
  • 更好的設(shè)計(jì)
  • 更出色的靈活性 — 鼓勵(lì)程序員面向接口編程
  • 更快速的反饋 — 不會(huì)到系統(tǒng)上線時(shí)才知道bug的存在

補(bǔ)充:敏捷軟件開發(fā)的概念已經(jīng)有很多年了棉磨,而且也部分的改變了軟件開發(fā)這個(gè)行業(yè)江掩,TDD也是敏捷開發(fā)所倡導(dǎo)的。

TDD可以在多個(gè)層級(jí)上應(yīng)用,包括單元測(cè)試(測(cè)試一個(gè)類中的代碼)环形、集成測(cè)試(測(cè)試類之間的交互)策泣、系統(tǒng)測(cè)試(測(cè)試運(yùn)行的系統(tǒng))和系統(tǒng)集成測(cè)試(測(cè)試運(yùn)行的系統(tǒng)包括使用的第三方組件)。TDD的實(shí)施步驟是:紅(失敗測(cè)試)- 綠(通過測(cè)試) - 重構(gòu)抬吟。

在使用TDD開發(fā)時(shí)萨咕,經(jīng)常會(huì)遇到需要被測(cè)對(duì)象需要依賴其他子系統(tǒng)的情況,但是你希望將測(cè)試代碼跟依賴項(xiàng)隔離火本,以保證測(cè)試代碼僅僅針對(duì)當(dāng)前被測(cè)對(duì)象或方法展開危队,這時(shí)候你需要的是測(cè)試替身。測(cè)試替身可以分為四類:

  • 虛設(shè)替身:只傳遞但是不會(huì)使用到的對(duì)象钙畔,一般用于填充方法的參數(shù)列表
  • 存根替身:總是返回相同的預(yù)設(shè)響應(yīng)茫陆,其中可能包括一些虛設(shè)狀態(tài)
  • 偽裝替身:可以取代真實(shí)版本的可用版本(比真實(shí)版本還是會(huì)差很多)
  • 模擬替身:可以表示一系列期望值的對(duì)象,并且可以提供預(yù)設(shè)響應(yīng)
    Java世界中實(shí)現(xiàn)模擬替身的第三方工具非常多擎析,包括EasyMock盅弛、Mockito、jMock等叔锐。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挪鹏,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子愉烙,更是在濱河造成了極大的恐慌讨盒,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件步责,死亡現(xiàn)場(chǎng)離奇詭異返顺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蔓肯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門遂鹊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蔗包,你說我怎么就攤上這事秉扑。” “怎么了调限?”我有些...
    開封第一講書人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵舟陆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我耻矮,道長(zhǎng)秦躯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任裆装,我火速辦了婚禮踱承,結(jié)果婚禮上倡缠,老公的妹妹穿的比我還像新娘。我一直安慰自己茎活,他們只是感情好毡琉,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著妙色,像睡著了一般。 火紅的嫁衣襯著肌膚如雪慧耍。 梳的紋絲不亂的頭發(fā)上身辨,一...
    開封第一講書人閱讀 49,729評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音芍碧,去河邊找鬼煌珊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泌豆,可吹牛的內(nèi)容都是我干的定庵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼踪危,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蔬浙!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贞远,我...
    開封第一講書人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤畴博,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蓝仲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俱病,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年袱结,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亮隙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垢夹,死狀恐怖溢吻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情果元,我是刑警寧澤煤裙,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站噪漾,受9級(jí)特大地震影響硼砰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜欣硼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一题翰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦豹障、人聲如沸冯事。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昵仅。三九已至,卻和暖如春累魔,著一層夾襖步出監(jiān)牢的瞬間摔笤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工垦写, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吕世,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓梯投,卻偏偏與公主長(zhǎng)得像命辖,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子分蓖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348

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