這部分主要是開源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等叔锐。