Java面試題集(第四部分)(86-115)

摘要:下面的內容包括Struts 2和hibernate的常見面試題,雖然Struts 2在2013年6月曝出高危漏洞後已經顯得江河日下,而spring MVC的異軍突起更加加速了Struts 2的隕落,但面試中仍然有可能被問及和此框架相關的內容,畢竟Struts 2曾經被阿里巴巴、京東以及政府企業門戶網站廣泛採用。另一方面,Hibernate目前仍然是ORM框架中的中堅力量,MyBatis在此領域也有不容忽視的一席之地,因此瞭解這兩個ORM框架對Java程序員是很有必要的。第一期發佈的Java面試題集中的150題並未包含Spring MVC和MyBatis的內容,後面會陸續爲大家奉上。


86、Struts 2中,Action通過什麼方式獲得用戶從頁面輸入的數據,又是通過什麼方式把其自身的數據傳給視圖的?

答:Action從頁面獲取數據有三種方式:

①通過Action屬性接受參數

②通過域模型獲取參數

③通過模型驅動獲取參數 (ModelDriven<T>)

Action將數據存入值棧(Value Stack)中,視圖可以通過表達式語言(EL)從值棧中獲取數據。

 

87、簡述Struts 2是如何實現MVC架構模式的。

答:MVC架構模式要求應用程序的輸入、處理和輸出三者分離,將系統分成模型(Model)、視圖(View)、控制器(Controller)三個部分,通過控制器實現模型和視圖的解耦合,使得應用程序的開發和維護變得容易,如下圖所示。其中,模型代表了應用程序的數據和處理這些數據的規則,同時還可以爲視圖提供的查詢保存相關的狀態,通常由JavaBean來實現,模型的代碼寫一次就可以被多個視圖重用;視圖用來組織模型的內容,它從模型中獲得數據,並將數據展現給用戶,在Struts 2中通常由JSP、Freemarker模板等來實現;控制器負責從客戶端接受請求並將其轉換爲某種行爲,行爲完成後再選擇一個視圖來呈現給用戶,控制器本身不需要輸出任何內容,它只是接收請求並決定調用哪個模型組件去處理請求,StrutsPrepareAndExecuteFilter過濾器是Struts 2中的核心,它和一系列的Action構成了Struts 2中的控制器。


圖-1 MVC架構模式圖

88、闡述Struts 2如何實現用戶輸入驗證。在你做過的項目中使用的是那種驗證方式,爲什麼選擇這種方式?

答:Struts 2可以使用手動驗證和自動驗證框架實現用戶輸入驗證。自動驗證框架是將對輸入的驗證規則放在XML文件中,這種方式比較靈活,可以在不修改代碼的情況下修改驗證的規則。

 

89、闡述Struts 2中的Action如何編寫?Action是否採用了單例?

答:Struts2的Action有三種寫法:

①POJO

②實現Action接口重寫execute()方法

③繼承ActionSupport類

Action沒有像Servlet一樣使用單實例多線程的工作方式,很明顯,每個Action要接收不同用戶的請求參數,這就意味着Action是有狀態的,因此在設計上使用了每個請求對應一個Action的處理方式。

 

90、Struts 2中的Action並沒有直接收到用戶的請求,那它爲什麼可以處理用戶的請求,又憑什麼知道一個請求到底交給哪個Action來處理?

答:Struts2的核心過濾器接收到用戶請求後,會對用戶的請求進行簡單的預處理(例如解析、封裝參數),然後通過反射來創建Action實例,並調用Action中指定的方法來處理用戶請求。

要決定請求交給哪一個Action來處理有兩種方式:1利用配置文件:可以在配置文件中通過<action>標籤配置和請求對應的Action類以及要調用的方法;2利用約定:Struts2中可以使用約定(convention)插件,例如約定xxx總是對應XxxAction,這是對約定優於配置理念的踐行。

 

91、你經常用到的Struts 2常量配置有哪些?

答:

①struts.i18n.encoding– 指定默認編碼

②struts.objectFactory/ struts.objectFactory.spring.autoWire – 對象工廠 / Spring的自動裝配方式(名字、類型)

③struts.devMode– 是否使用開發模式

④struts.locale– 指定默認區域,默認值是en_US

⑤struts.i18n.resources– 國際化使用的資源文件

⑥struts.enable.DynamicMethodInvocation– 是否允許動態方法調用

 

92、簡述Struts2的異常處理機制。

答:Struts 2提供了聲明式的異常處理機制,可以在配置文件中加入如下代碼:

<global-exception-mappings>

<exception-mapping exception=”…” result=”…”/>

</global-exception-mappings>

 

93、說一下你對約定優於配置(CoC)的理解。

答:約定優於配置(convention over configuration),也稱作按約定編程,是一種軟件設計範式,旨在減少軟件開發人員需做決定的數量,獲得簡單的好處而又不失靈活性。CoC本質是說,開發人員僅需規定應用中不符約定的部分。例如,如果模型中有個名爲Sale的類,那麼數據庫中對應的表就會默認命名爲sales。只有在偏離這一約定時,例如將該表命名爲products_sold,才需寫有關這個名字的配置。如果您所用工具的約定與你的期待相符,便可省去配置;反之,你可以配置來達到你所期待的方式。遵循約定雖然損失了一定的靈活性,不能隨意安排目錄結構,不能隨意進行函數命名,但是卻能減少配置。更重要的是,遵循約定可以幫助開發人員遵守構建標準,包括各種命名的規範,這對團隊開發是非常有利的。

 

94、Struts2中如何實現I18N?

答:首先,爲不同語言地區編寫不同的資源文件;然後在Struts 2配置文件中配置struts.i18n.custom.resources常量;在Action中可以通過調用getText()方法讀取資源文件獲取國際化資源。

 

95、簡述攔截器的工作原理以及你在項目中使用過哪些自定義攔截器。

答:Struts 2中定義了攔截器的接口以及默認實現,實現了Interceptor接口或繼承了AbstractInterceptor的類可以作爲攔截器。接口中的init()方法在攔截器被創建後立即被調用,它在攔截器的生命週期內只被調用一次,可以在該方法中對相關資源進行必要的初始化。每攔截一個請求,intercept()方法就會被調用一次。destory()方法將在攔截器被銷燬之前被調用, 它在攔截器的生命週期內也只被調用一次。

項目中使用過的有權限攔截器、執行時間攔截器、令牌攔截器等。

 

96、如何在Struts2中使用Ajax功能?

答:以下是Struts 2中實現Ajax的可選方式:

①JSON plugin+ jQuery

②DOJO plugin

③DWR (DirectWeb Remoting)

 

97、談一下攔截器和過濾器的區別。

答:攔截器和過濾器都可以用來實現橫切關注功能,其區別主要在於:

①攔截器是基於Java反射機制的,而過濾器是基於接口回調的。

②過濾器依賴於Servlet容器,而攔截器不依賴於Servlet容器。

③攔截器只能對Action請求起作用,而過濾器可以對所有請求起作用。

④攔截器可以訪問Action上下文、值棧裏的對象,而過濾器不能。

 

98、談一下Struts 1和Struts 2的區別。

答:



99、談一下你的項目選擇Struts 2的理由

答:①Action是POJO,沒有依賴Servlet API,具有良好的可測試性;②強大的攔截器簡化了開發的複雜度;③支持多種表現層技術:JSP、Freemarker等等;④靈活的驗證方式;⑤國際化(I18N)支持;⑥聲明式異常管理;⑦通過JSON插件簡化Ajax;⑧通過Spring插件跟Spring整合。

【補充】有人爲選擇和評判Web框架提出了20條標準,包括:開發人員的工作效率(能用1-5天搭建一個CRUD頁面嗎)、開發人員的看法(用起來有意思嗎)、學習曲線(學了一個星期或一個月後能幹活嗎)、項目健康狀況(項目陷入絕境了嗎)、開發人員的充足性(能找到經驗豐富的開發人員嗎)、就業趨勢(將來能招到人嗎)、模板化(遵循DRY原則嗎)、組件(自帶日期選擇器之類的控件嗎)、Ajax(是否支持異步調用和局部刷新)、插件或附加項(能加入Facebook集成之類的功能嗎)、擴展性(默認的控制處理的併發用戶數能到500+嗎)、測試支持(能夠做測試驅動的開發嗎)、I18N和L10N(有多國語言、地域支持嗎)、校驗(能輕鬆校驗用戶輸入並迅速反饋嗎)、多編程語言支持(能夠同時使用多種語言開發嗎)、文檔的質量(常見的用例和問題都在文檔中有體現嗎)、出版的圖書(有沒有行業專家使用了它並分享了自己的使用經驗)、REST支持(能按HTTP協議的設計宗旨使用該協議嗎)、移動支持(是否很容易就能支持AndroidiOS和其他移動智能終端)、風險程度(能不能做大型項目)。很明顯,Java其實算不上最優秀的Web開發語言,但是它卻滿足了這20條中的很多,尤其是充足的開發人員、成熟的解決方案這兩點,而且Java的生態系統是非常良好的,這也是在Java已經顯得江河日下的今天大家仍然一如既往的將其作爲開發語言首選的原因。

 

100、Struts 2中如何訪問HttpServletRequest、HttpSession和ServletContext三個域對象

答:有兩種方式:

①通過ServletActionContext的方法獲得;

②通過ServletRequestAware、SessionAware和ServletContextAware接口注入。

 

101、Struts 2中的默認包struts-default有什麼作用?

答:它定義了Struts 2內部的衆多攔截器和Result類型,而Struts 2很多核心的功能都是通過這些內置的攔截器實現,如:從請求中把請求參數封裝到action、文件上傳和數據驗證等等都是通過攔截器實現的。在Struts 2的配置文件中,自定義的包繼承了struts-default包就可以使用Struts 2爲我們提供的這些功能。

 

102、簡述值棧(Value-Stack)的原理和生命週期

答:Value-Stack貫穿整個 Action 的生命週期,保存在request作用域中,所以它和request的生命週期一樣。當Struts 2接受一個請求時,會創建ActionContext、Value-Stack和Action對象,然後把Action存放進Value-Stack,所以Action的實例變量可以通過OGNL訪問。由於Action是多實例的,和使用單例的Servlet不同,  每個Action都有一個對應的Value-Stack,Value-Stack存放的數據類型是該Action的實例,以及該Action中的實例變量,Action對象默認保存在棧頂。

 

103、SessionFactory是線程安全的嗎?Session是線程安全的嗎,兩個線程能夠共享同一個Session嗎?

答:SessionFactory對應Hibernate的一個數據存儲的概念,它是線程安全的,可以被多個線程併發訪問。SessionFactory一般只會在啓動的時候構建。對於應用程序,最好將SessionFactory通過單例的模式進行封裝以便於訪問。Session是一個輕量級非線程安全的對象(線程間不能共享session),它表示與數據庫進行交互的一個工作單元。Session是由SessionFactory創建的,在任務完成之後它會被關閉。Session是持久層服務對外提供的主要接口。Session會延遲獲取數據庫連接(也就是在需要的時候纔會獲取)。爲了避免創建太多的session,可以使用ThreadLocal來取得當前的session,無論你調用多少次getCurrentSession()方法,返回的都是同一個session。

 

104、Session的load和get方法的區別是什麼?

答:主要有以下三項區別:

① 如果沒有找到符合條件的記錄, get方法返回null,load方法拋出異常

②get方法直接返回實體類對象, load方法返回實體類對象的代理

③ 在Hibernate 3之前,get方法只在一級緩存(內部緩存)中進行數據查找, 如果沒有找到對應的數據則越過二級緩存, 直接發出SQL語句完成數據讀取; load方法則可以充分利用二級緩存中的現有數據;當然從Hibernate 3開始,get方法不再是對二級緩存只寫不讀,它也是可以訪問二級緩存的

簡單的說,對於load()方法Hibernate認爲該數據在數據庫中一定存在可以放心的使用代理來實現延遲加載,如果沒有數據就拋出異常,而通過get()方法去取的數據可以不存在。

 

105、Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法有什麼區別?

答:Hibernate的對象有三種狀態:瞬態、持久態和遊離態。遊離狀態的實例可以通過調用save()、persist()或者saveOrUpdate()方法進行持久化;脫管狀態的實例可以通過調用 update()、0saveOrUpdate()、lock()或者replicate()進行持久化。save()和persist()將會引發SQL的INSERT語句,而update()或merge()會引發UPDATE語句。save()和update()的區別在於一個是將瞬態對象變成持久態,一個是將遊離態對象變爲持久態。merge方法可以完成save()和update()方法的功能,它的意圖是將新的狀態合併到已有的持久化對象上或創建新的持久化對象。按照官方文檔的說明:(1)persist()方法把一個瞬態的實例持久化,但是並”不保證”標識符被立刻填入到持久化實例中,標識符的填入可能被推遲到flush的時間;(2) persist”保證”,當它在一個事務外部被調用的時候並不觸發一個Insert語句,當需要封裝一個長會話流程的時候,一個persist這樣的函數是需要的。(3)save”不保證”第2條,它要返回標識符,所以它會立即執行Insert語句,不管是不是在事務內部還是外部。update()方法是把一個已經更改過的脫管狀態的對象變成持久狀態;lock()方法是把一個沒有更改過的脫管狀態的對象變成持久狀態。

 

106、闡述Session加載實體對象的過程。

答:Session加載實體對象的步驟是:

① Session在調用數據庫查詢功能之前, 首先會在緩存中進行查詢, 在一級緩存中, 通過實體類型和主鍵進行查找, 如果一級緩存查找命中且數據狀態合法, 則直接返回

③ 如果一級緩存沒有命中, 接下來Session會在當前NonExists記錄(相當於一個查詢黑名單, 如果出現重複的無效查詢可以迅速判斷, 從而提升性能)中進行查找, 如果NonExists中存在同樣的查詢條件,則返回null

③ 對於load方法, 如果一級緩存查詢失敗則查詢二級緩存, 如果二級緩存命中則直接返回

④ 如果之前的查詢都未命中, 則發出SQL語句, 如果查詢未發現對應記錄則將此次查詢添加到Session的NonExists中加以記錄, 並返回null

⑤ 根據映射配置和SQL語句得到ResultSet,並創建對應的實體對象

⑥ 將對象納入Session(一級緩存)管理

⑦ 執行攔截器的onLoad方法(如果有對應的攔截器)

⑧將數據對象納入二級緩存

⑨返回數據對象

 

107、Query接口的list方法和iterate方法有什麼區別?

答:

1) list方法無法利用緩存,它對緩存只寫不讀; iterate方法可以充分利用緩存, 如果目標數據只讀或者讀取頻繁, iterate可以減少性能開銷

2) list方法不會引起N+1查詢問題, 而iterate方法會引起N+1查詢問題

 

108、Hibernate如何實現分頁查詢?

答:通過Hibernate實現分頁查詢,開發人員只需要提供HQL語句、查詢起始行數(setFirstresult()方法)和最大查詢行數(setMaxResult()方法),並調用Query接口的list()方法,Hibernate會自動生成分頁查詢的SQL語句。


109、鎖機制有什麼用?簡述Hibernate的悲觀鎖和樂觀鎖機制。

答:有些業務邏輯在執行過程中往往需要保證數據訪問的排他性,於是需要通過一些機制保證在此過程中數據被鎖住不會被外界修改,這就是所謂的鎖機制。

Hibernate支持悲觀鎖和樂觀鎖兩種鎖機制。悲觀鎖,顧名思義,它悲觀的認爲在數據處理過程中一定存在修改數據的併發事務(包括本系統的其他事務或來自外部系統的事務),於是將處理的數據設置爲鎖定狀態。悲觀鎖必須依賴數據庫本身的鎖機制才能真正保證數據訪問的排他性。樂觀鎖,顧名思義,對併發事務持樂觀態度(認爲對數據的併發操作很少發生),通過更加寬鬆的鎖機制解決悲觀鎖排他的數據訪問對系統性能造成的嚴重影響。最常見的樂觀鎖是通過數據版本標識來實現的,讀取數據時獲得數據的版本號,更新數據時將此版本號加1,然後和數據庫表對應記錄的當前版本號進行比較,如果提交的數據版本號大於數據庫中此記錄的當前版本號則更新數據,否則認爲是過期數據。

 

110、闡述實體對象的三種狀態以及轉換關係。

答:Hibernate中對象有三種狀態:臨時態(transient)、持久態(persistent)和遊狀態(detached),如下圖所示。


圖 Hibernate實體狀態轉換圖

  • 臨時狀態:當new一個實體對象後,這個對象處於臨時狀態,即這個對象只是一個保存臨時數據的內存區域,如果沒有變量引用這個對象,則會被JVM的垃圾回收機制回收。這個對象所保存的數據與數據庫沒有任何關係,除非通過Session的save或者saveOrUpdate把臨時對象與數據庫關聯,並把數據插入或者更新到數據庫,這個對象才轉換爲持久對象。
  • 持久狀態:持久化對象的實例在數據庫中有對應的記錄,並擁有一個持久化標識。對持久化對象進行delete操作後,數據庫中對應的記錄將被刪除,那麼持久化對象與數據庫記錄不再存在對應關係,持久化對象變成臨時狀態。持久化對象被修改變更後,不會馬上同步到數據庫,直到數據庫事務提交。
  • 遊離狀態:當Session進行了close、clear或者evict後,持久化對象雖然擁有持久化標識符和與數據庫對應記錄一致的值,但是因爲會話已經消失,對象不在持久化管理之內,所以處於遊離狀態(也叫脫管狀態)。遊離狀態的對象與臨時狀態對象是十分相似的,只是它還含有持久化標識。

 

111、如何理解Hibernate的延遲加載機制。在實際應用中,延遲加載與session關閉的矛盾是如何處理的?

答:延遲加載就是並不是在讀取的時候就把數據加載進來,而是等到使用時再加載。Hibernate使用了虛擬代理機制實現延遲加載。返回給用戶的並不是實體本身,而是實體對象的代理。代理對象在用戶調用getter方法時就會去數據庫加載數據。但加載數據就需要數據庫連接。而當我們把會話關閉時,數據庫連接就同時關閉了。

延遲加載與session關閉的矛盾一般可以這樣處理:

① 關閉延遲加載特性。這種方式操作起來比較簡單,因爲hibernate的延遲加載特性是可以通過映射文件或者註解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現no session or session was closed就證明了系統中已經存在主外鍵關聯,如果去掉延遲加載的話,則每次查詢的開銷都會變得很大。

②在session關閉之前先獲取需要查詢的數據(Hibernate.initialize()方法)。

③ 使用攔截器(Interceptor)或過濾器(Filter)控制Session。

 

112、舉一個多對多關聯的例子,並說明如何實現多對多關聯映射。

答:例如:商品和訂單、學生和課程都是典型的多對多關係。可以在實體類上通過@ManyToMany註解配置多對多關聯或者通過映射文件中的<set>和<many-to-many>標籤配置多對多關聯,但是通常情況下,可以將多對多關聯轉換成兩個多對一關聯來實現多對多關聯映射。

 

113、談一下你對繼承映射的理解。

答:繼承關係的映射策略有三種:

①每個繼承結構一張表(table per class hierarchy)

②每個子類一張表(table per subclass)

③ 每個具體類一張表(table per concrete class)

第一種方式屬於單表策略,其優點在於查詢子類對象的時候無需表連接,查詢速度快,適合多態查詢;缺點是可能導致表很大。後兩種方式屬於多表策略,其優點在於數據存儲緊湊,其缺點是需要進行連接查詢,不適合多態查詢。

 

114、簡述Hibernate常見優化策略。

答:

①制定合理的緩存策略

② 採用合理的Session管理機制

③ 儘量使用延遲加載特性

④設定合理的批處理參數

⑤ 如果可以, 選用UUID作爲主鍵生成器

⑥如果可以, 選用基於version的樂觀鎖替代悲觀鎖

⑦ 在開發過程中, 開啓hibernate.show_sql選項查看生成的SQL, 從而瞭解底層的狀況;開發完成後關閉此選項

⑧ 數據庫本身的優化(合理的索引, 緩存, 數據分區策略等)也會對持久層的性能帶來可觀的提升, 這些需要專業的DBA提供支持

 

115、談一談Hibernate的一級緩存、二級緩存和查詢緩存。

答:Hibernate的Session提供了一級緩存的功能,默認總是有效的,當應用程序保存持久化實體、修改持久化實體時,Session並不會立即把這種改變提交到數據庫,而是緩存在當前的Session中,除非顯示調用了Session的flush()方法或通過close()方法關閉Session。通過一級緩存,可以減少程序與數據庫的交互,從而提高數據庫訪問性能。

SessionFactory級別的二級緩存是全局性的,所有的Session可以共享這個二級緩存。不過二級緩存默認是關閉的,需要顯示開啓並指定需要使用哪種二級緩存實現類(可以使用第三方提供的實現)。一旦開啓了二級緩存並設置了需要使用二級緩存的實體類,SessionFactory就會緩存訪問過的該實體類的每個對象,除非緩存的數據超出了指定的緩存空間。

一級緩存和二級緩存都是對整個實體進行緩存,不會緩存普通屬性,如果希望對普通屬性進行緩存,可以使用查詢緩存。查詢緩存是將HQL或SQL語句以及它們的查詢結果作爲鍵值對進行緩存,對於同樣的查詢可以直接從緩存中獲取數據。查詢緩存默認也是關閉的,需要顯示開啓。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章