web service 相關
什么是Web Service?
答:從表面上看,Web Service就是一個應用程序,它向外界暴露出一個能夠通過Web進行調用的API。這就是說,你能夠用編程的方法透明的調用這個應用程序,不需要了解它的任何細節,跟你使用的編程語言也沒有關系。例如可以創建一個提供天氣預報的Web Service,那么無論你用哪種編程語言開發的應用都可以通過調用它的API并傳入城市信息來獲得該城市的天氣預報。之所以稱之為Web Service,是因為它基于HTTP協議傳輸數據,這使得運行在不同機器上的不同應用無須借助附加的、專門的第三方軟件或硬件,就可相互交換數據或集成。
REST和SOAP比較
REST(Representational State Transfer),REST采用簡單的URL的方式來代表一個對象,例如一個URL就對應一個對象。
REST的優點:
- 輕量級的解決方案,不必向SOAP那樣要構建一個標準的SOAP XML。
- 可讀性比較好:可以把URL的名字取得有實際意義。
- 不需要SDK支持:直接一個Http請求就可以,但是SOAP則可能需要使用到一些Webservice的類庫(例如Apache的Axis)。
REST的缺點: - 復雜的應用中,URL可能非常長,而且不容易解析。
Amazon、Yahoo和國內的阿里軟件都提供了REST方式的Webservice調用。
SOAP的優點:
- 定義嚴格。必須符合SOAP的格式
- 某些時候使用比較方便
- 開發工具支持比較多一點。
Google基本上采用SOAP方式的Webservice。
概念解釋:SOAP、WSDL、UDDI。
- SOAP:簡單對象訪問協議(Simple Object Access Protocol),是Web Service中交換數據的一種協議規范。
- WSDL:Web服務描述語言(Web Service Description Language),它描述了Web服務的公共接口。這是一個基于XML的關于如何與Web服務通訊和使用的服務描述;也就是描述與目錄中列出的Web服務進行交互時需要綁定的協議和信息格式。通常采用抽象語言描述該服務支持的操作和信息,使用的時候再將實際的網絡協議和信息格式綁定給該服務。
- UDDI:統一描述、發現和集成(Universal Description, Discovery and Integration),它是一個基于XML的跨平臺的描述規范,可以使世界范圍內的企業在互聯網上發布自己所提供的服務。簡單的說,UDDI是訪問各種WSDL的一個門面(可以參考設計模式中的門面模式)
Java規范中和Web Service相關的規范有哪些?
Java規范中和Web Service相關的有三個:
- JAX-WS(JSR 224):這個規范是早期的基于SOAP的Web Service規范JAX-RPC的替代版本,它并不提供向下兼容性,因為RPC樣式的WSDL以及相關的API已經在Java EE5中被移除了。WS-MetaData是JAX-WS的依賴規范,提供了基于注解配置Web Service和SOAP消息的相關API。
- JAXM(JSR 67):定義了發送和接收消息所需的API,相當于Web Service的服務器端。
- JAX-RS(JSR 311 & JSR 339 & JSR 370):是Java針對REST(Representation State Transfer)架構風格制定的一套Web Service規范。REST是一種軟件架構模式,是一種風格,它不像SOAP那樣本身承載著一種消息協議, (兩種風格的Web Service均采用了HTTP做傳輸協議,因為HTTP協議能穿越防火墻,Java的遠程方法調用(RMI)等是重量級協議,通常不能穿越防火墻),因此可以將REST視為基于HTTP協議的軟件架構。REST中最重要的兩個概念是資源定位和資源操作,而HTTP協議恰好完整的提供了這兩個點。HTTP協議中的URI可以完成資源定位,而GET、POST、OPTION、DELETE方法可以完成資源操作。因此REST完全依賴HTTP協議就可以完成Web Service,而不像SOAP協議那樣只利用了HTTP的傳輸特性,定位和操作都是由SOAP協議自身完成的,也正是由于SOAP消息的存在使得基于SOAP的Web Service顯得笨重而逐漸被淘汰。
介紹一下你了解的Java領域的Web Service框架
- Axis2(Axis的升級版本)
- Jersey(RESTful的Web Service框架)
- CXF(XFire的延續版本)
- Hessian、Turmeric、JBoss SOA等。
Spring 相關
什么是IoC和DI?DI是如何實現的?
IoC(Inversion of Control)控制反轉,DI(Dependency Injection)依賴注入,是對IoC更簡單的詮釋。控制反轉是把傳統上由程序代碼直接操控的對象的調用權交給容器,通過容器來實現對象組件的裝配和管理。所謂的"控制反轉"就是對組件對象控制權的轉移,從程序代碼本身轉移到了外部容器,由容器來創建對象并管理對象之間的依賴關系。
IoC體現了好萊塢原則 - "Don’t call me, we will call you"。依賴注入的基本原則是應用組件不應該負責查找資源或者其他依賴的協作對象。配置對象的工作應該由容器負責,查找資源的邏輯應該從應用組件的代碼中抽取出來,交給容器來完成。DI是對IoC更準確的描述,即組件之間的依賴關系由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關系注入到組件之中。
舉個例子:一個類A需要用到接口B中的方法,那么就需要為類A和接口B建立關聯或依賴關系,最原始的方法是在類A中創建一個接口B的實現類C的實例,但這種方法需要開發人員自行維護二者的依賴關系,也就是說當依賴關系發生變動的時候需要修改代碼并重新構建整個系統。如果通過一個容器來管理這些對象以及對象的依賴關系,則只需要在類A中定義好用于關聯接口B的方法(構造器或setter方法),將類A和接口B的實現類C放入容器中,通過對容器的配置來實現二者的關聯。
依賴注入可以通過setter方法注入(設值注入)、構造器注入和接口注入三種方式來實現,Spring支持setter注入和構造器注入,通常使用構造器注入來注入必須的依賴關系,對于可選的依賴關系,則setter注入是更好的選擇,setter注入需要類提供無參構造器或者無參的靜態工廠方法來創建對象。
Spring中Bean的作用域有哪些?
答:
- singleton:單例模式;在 Spring IoC 容器中,僅存在一個 Bean 實例。
- prototype:原型模式;每次從容器調用 Bean ,都返回一個新實例。
- request:每次 HTTP 請求,都創建一個新實例,該作用域適用于 WebApplicationContext 環境。
- session:同一個 HTTP Session 共享一個 Bean,不同 Session 使用不同 Bean,僅適用于 WebApplicationContext 環境。
- globalSession:
在Spring的早期版本中,僅有兩個作用域:singleton和prototype,前者表示Bean以單例的方式存在;后者表示每次從容器中調用Bean時,都會返回一個新的實例,prototype通常翻譯為原型。
補充:設計模式中的創建型模式中也有一個原型模式,原型模式也是一個常用的模式,例如做一個室內設計軟件,所有的素材都在工具箱中,而每次從工具箱中取出的都是素材對象的一個原型,可以通過對象克隆來實現原型模式。
Spring 2.x中針對WebApplicationContext新增了3個作用域,分別是:request(每次HTTP請求都會創建一個新的Bean)、session(同一個HttpSession共享同一個Bean,不同的HttpSession使用不同的Bean)和globalSession(同一個全局Session共享一個Bean)。
說明:單例模式和原型模式都是重要的設計模式。一般情況下,無狀態或狀態不可變的類適合使用單例模式。在傳統開發中,由于DAO持有Connection這個非線程安全對象因而沒有使用單例模式;但在Spring環境下,所有DAO類對可以采用單例模式,因為Spring利用AOP和Java API中的ThreadLocal對非線程安全的對象進行了特殊處理。
ThreadLocal為解決多線程程序的并發問題提供了一種新的思路。ThreadLocal,顧名思義是線程的一個本地化對象,當工作于多線程中的對象使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程分配一個獨立的變量副本,所以每一個線程都可以獨立的改變自己的副本,而不影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量。
ThreadLocal類非常簡單好用,只有四個方法,能用上的也就是下面三個方法:
- void set(T value):設置當前線程的線程局部變量的值。
- T get():獲得當前線程所對應的線程局部變量的值。
- void remove():刪除當前線程中線程局部變量的值。
ThreadLocal是如何做到為每一個線程維護一份獨立的變量副本的呢?在ThreadLocal類中有一個Map,鍵為線程對象,值是其線程對應的變量的副本,
解釋一下什么叫AOP(面向切面編程)?
AOP(Aspect-Oriented Programming)指一種程序設計范型,該范型以一種稱為切面(aspect)的語言構造為基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting concern)。
Spring中自動裝配的方式有哪些?
- no:不進行自動裝配,手動設置Bean的依賴關系。
- byName:根據Bean的名字進行自動裝配。
- byType:根據Bean的類型進行自動裝配。
- constructor:類似于byType,不過是應用于構造器的參數,如果正好有一個Bean與構造器的參數類型相同則可以自動裝配,否則會導致錯誤。
- autodetect:如果有默認的構造器,則通過constructor的方式進行自動裝配,否則使用byType的方式進行自動裝配。
說明:自動裝配沒有自定義裝配方式那么精確,而且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。
Spring中的自動裝配有哪些限制?
- 如果使用了構造器注入或者setter注入,那么將覆蓋自動裝配的依賴關系。
- 基本數據類型的值、字符串字面量、類字面量無法使用自動裝配來注入。
- 優先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。
Spring中如何使用注解來配置Bean?有哪些相關的注解?
首先需要在Spring配置文件中增加如下配置:
<context:component-scan base-package="org.example"/>
然后可以用@Component、@Controller、@Service、@Repository注解來標注需要由Spring IoC容器進行對象托管的類。這幾個注解沒有本質區別,只不過@Controller通常用于控制器,@Service通常用于業務邏輯類,@Repository通常用于倉儲類(例如我們的DAO實現類),普通的類用@Component來標注。
闡述Spring框架中Bean的生命周期?
- Spring IoC容器找到關于Bean的定義并實例化該Bean。
- Spring IoC容器對Bean進行依賴注入。
- 如果Bean實現了BeanNameAware接口,則將該Bean的id傳給setBeanName方法。
- 如果Bean實現了BeanFactoryAware接口,則將BeanFactory對象傳給setBeanFactory方法。
- 如果Bean實現了BeanPostProcessor接口,則調用其postProcessBeforeInitialization方法。
- 如果Bean實現了InitializingBean接口,則調用其afterPropertySet方法。
- 如果有和Bean關聯的BeanPostProcessors對象,則這些對象的postProcessAfterInitialization方法被調用。
- 當銷毀Bean實例時,如果Bean實現了DisposableBean接口,則調用其destroy方法。
依賴注入時如何注入集合屬性?
可以在定義Bean屬性時,通過<list> / <set> / <map> / <props>分別為其注入列表、集合、映射和鍵值都是字符串的映射屬性。
Spring IoC容器配置Bean的方式?
- 基于XML文件進行配置。
- 基于注解進行配置。
- 基于Java程序進行配置(Spring 3+)
在Web項目中如何獲得Spring的IoC容器?
WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
如何在Web項目中配置Spring MVC?
要使用Spring MVC需要在Web項目配置文件中配置其前端控制器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>
Spring MVC的工作原理是怎樣的?
- 客戶端的所有請求都交給前端控制器DispatcherServlet來處理,它會負責調用系統的其他模塊來真正處理用戶的請求。
- ispatcherServlet收到請求后,將根據請求的信息(包括URL、HTTP協議方法、請求頭、請求參數、Cookie等)以及HandlerMapping的配置找到處理該請求的Handler(任何一個對象都可以作為請求的Handler)。
- 在這個地方Spring會通過HandlerAdapter對該處理器進行封裝。
- andlerAdapter是一個適配器,它用統一的接口對各種Handler中的方法進行調用。
- andler完成對用戶請求的處理后,會返回一個ModelAndView對象給DispatcherServlet,ModelAndView顧名思義,包含了數據模型以及相應的視圖的信息。
- ModelAndView的視圖是邏輯視圖,DispatcherServlet還要借助ViewResolver完成從邏輯視圖到真實視圖對象的解析工作。
- 當得到真正的視圖對象后,DispatcherServlet會利用視圖對象對模型數據進行渲染。
- 客戶端得到響應,可能是一個普通的HTML頁面,也可以是XML或JSON字符串,還可以是一張圖片或者一個PDF文件。
Hibernate 相關
什么是ORM?
答:對象關系映射(Object-Relational Mapping,簡稱ORM)是一種為了解決程序的面向對象模型與數據庫的關系模型互不匹配問題的技術;簡單的說,ORM是通過使用描述對象和數據庫之間映射的元數據(在Java中可以用XML或者是注解),將程序中的對象自動持久化到關系數據庫中或者將關系數據庫表中的行轉換成Java對象,其本質上就是將數據從一種形式轉換到另外一種形式。
JPA、Hibernate、Mybatis都含有ORM模型。
持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?
答:所謂"持久"就是將數據保存到可掉電式存儲設備中以便今后使用,簡單的說,就是將內存中的數據保存到關系型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專注于實現數據持久化的相對獨立的層面。
持久層設計的目標包括:
- 數據存儲邏輯的分離,提供抽象化的數據訪問接口。
- 數據訪問底層實現的分離,可以在不修改代碼的情況下切換底層實現。
- 資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。
- 數據抽象,提供更面向對象的數據操作。
持久層框架有:
- Hibernate
- MyBatis
- TopLink
- Guzz
- jOOQ
- Spring Data
- ActiveJDBC
Hibernate中SessionFactory是線程安全的嗎?Session是線程安全的嗎(兩個線程能夠共享同一個Session嗎)?
答:SessionFactory對應Hibernate的一個數據存儲的概念,它是線程安全的,可以被多個線程并發訪問。SessionFactory一般只會在啟動的時候構建。對于應用程序,最好將SessionFactory通過單例模式進行封裝以便于訪問。Session是一個輕量級非線程安全的對象(線程間不能共享session),它表示與數據庫進行交互的一個工作單元。Session是由SessionFactory創建的,在任務完成之后它會被關閉。Session是持久層服務對外提供的主要接口。Session會延遲獲取數據庫連接(也就是在需要的時候才會獲取)。為了避免創建太多的session,可以使用ThreadLocal將session和當前線程綁定在一起,這樣可以讓同一個線程獲得的總是同一個session。Hibernate 3中SessionFactory的getCurrentSession()方法就可以做到。
Hibernate中Session的load和get方法的區別是什么?
答:主要有以下三項區別:
- 如果沒有找到符合條件的記錄,get方法返回null,load方法拋出異常。
- get方法直接返回實體類對象,load方法返回實體類對象的代理。
- 在Hibernate3之前,get方法只在一級緩存中進行數據查找,如果沒有找到對應的數據則越過二級緩存,直接發出SQL語句完成數據讀取;load方法則可以從二級緩存中獲取數據;從Hibernate 3開始,get方法不再是對二級緩存只寫不讀,它也是可以訪問二級緩存的。
說明:對于load()方法Hibernate認為該數據在數據庫中一定存在可以放心的使用代理來實現延遲加載,如果沒有數據就拋出異常,而通過get()方法獲取的數據可以不存在。
Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分別是做什么的?有什么區別?
答:Hibernate的對象有三種狀態:瞬時態(transient)、持久態(persistent)和游離態(detached)。
瞬時態的實例可以通過調用save()、persist() 或 saveOrUpdate() 方法變成持久態;
游離態的實例可以通過調用update()、lock()、replicate() 或 saveOrUpdate() 變成持久態。
save() 和 persist() 會引發SQL的INSERT語句;
update() 或 merge() 會引發UPDATE語句。
save() 和 update() 的區別在于一個是將瞬時態對象變成持久態,一個是將游離態對象變為持久態。
merge()方法可以完成 save() 和 update() 方法的功能,它的意圖是將新的狀態合并到已有的持久化對象上或創建新的持久化對象。
對于persist()方法,按照官方文檔的說明:
- persist()方法把一個瞬時態的實例持久化,但是并不保證標識符被立刻填入到持久化實例中,標識符的填入可能被推遲到flush的時間;
- persist()方法保證當它在一個事務外部被調用的時候并不觸發一個INSERT語句,當需要封裝一個長會話流程的時候,persist()方法是很有必要的;
- save()方法不保證第②條,它要返回標識符,所以它會立即執行INSERT語句,不管是在事務內部還是外部。
- update()方法是把一個已經更改過的脫管狀態的對象變成持久狀態;lock()方法是把一個沒有更改過的脫管狀態的對象變成持久狀態。
闡述Session加載實體對象的過程。
答:Session加載實體對象的步驟是:
- Session在調用數據庫查詢功能之前,首先會在一級緩存中通過實體類型和主鍵進行查找,如果一級緩存查找命中且數據狀態合法,則直接返回;
- 如果一級緩存沒有命中,接下來Session會在當前NonExists記錄(相當于一個查詢黑名單,如果出現重復的無效查詢可以迅速做出判斷,從而提升性能)中進行查找,如果NonExists中存在同樣的查詢條件,則返回null;
- 如果一級緩存查詢失敗則查詢二級緩存,如果二級緩存命中則直接返回;
- 如果之前的查詢都未命中,則發出SQL語句,如果查詢未發現對應記錄則將此次查詢添加到Session的NonExists中加以記錄,并返回null;
- 根據映射配置和SQL語句得到ResultSet,并創建對應的實體對象;
- 將對象納入Session(一級緩存)的管理;
- 如果有對應的攔截器,則執行攔截器的onLoad方法;
- 如果開啟并設置了要使用二級緩存,則將數據對象納入二級緩存;
- 返回數據對象。
Query 接口的 list 方法和 iterate 方法有什么區別?
答:
- list()方法無法利用一級緩存和二級緩存(對緩存只寫不讀),它只能在開啟查詢緩存的前提下使用查詢緩存;iterate()方法可以充分利用緩存,如果目標數據只讀或者讀取頻繁,使用iterate()方法可以減少性能開銷。
- list()方法不會引起N+1查詢問題,而iterate()方法可能引起N+1查詢問題
說明:關于N+1查詢問題,可以參考CSDN上的一篇文章《什么是N+1查詢》
Hibernate如何實現分頁查詢?
答:通過Hibernate實現分頁查詢,開發人員只需要提供HQL語句(調用Session的createQuery()方法)或查詢條件(調用Session的createCriteria()方法)、設置查詢起始行數(調用Query或Criteria接口的setFirstResult()方法)和最大查詢行數(調用Query或Criteria接口的setMaxResults()方法),并調用Query或Criteria接口的list()方法,Hibernate會自動生成分頁查詢的SQL語句。
鎖機制有什么用?簡述Hibernate的悲觀鎖和樂觀鎖機制。
答:有些業務邏輯在執行過程中要求對數據進行排他性的訪問,于是需要通過一些機制保證在此過程中數據被鎖住不會被外界修改,這就是所謂的鎖機制。
Hibernate支持悲觀鎖和樂觀鎖兩種鎖機制。
- 悲觀鎖,顧名思義悲觀的認為在數據處理過程中極有可能存在修改數據的并發事務(包括本系統的其他事務或來自外部系統的事務),于是將處理的數據設置為鎖定狀態。悲觀鎖必須依賴數據庫本身的鎖機制才能真正保證數據訪問的排他性,關于數據庫的鎖機制和事務隔離級別在《Java面試題大全(上)》中已經討論過了。
- 樂觀鎖,顧名思義,對并發事務持樂觀態度(認為對數據的并發操作不會經常性的發生),通過更加寬松的鎖機制來解決由于悲觀鎖排他性的數據訪問對系統性能造成的嚴重影響。最常見的樂觀鎖是通過數據版本標識來實現的,讀取數據時獲得數據的版本號,更新數據時將此版本號加1,然后和數據庫表對應記錄的當前版本號進行比較,如果提交的數據版本號大于數據庫中此記錄的當前版本號則更新數據,否則認為是過期數據無法更新。
Hibernate中通過Session的get()和load()方法從數據庫中加載對象時可以通過參數指定使用悲觀鎖;而樂觀鎖可以通過給實體類加整型的版本字段再通過XML或@Version注解進行配置。
提示:使用樂觀鎖會增加了一個版本字段,很明顯這需要額外的空間來存儲這個版本字段,浪費了空間,但是樂觀鎖會讓系統具有更好的并發性,這是對時間的節省。因此樂觀鎖也是典型的空間換時間的策略。
闡述實體對象的三種狀態以及轉換關系。
答:最新的Hibernate文檔中為Hibernate對象定義了四種狀態(原來是三種狀態,面試的時候基本上問的也是三種狀態),分別是:瞬時態(new, or transient)、持久態(managed, or persistent)、游狀態(detached)和移除態(removed,以前Hibernate文檔中定義的三種狀態中沒有移除態),如下圖所示,就以前的Hibernate文檔中移除態被視為是瞬時態。
- 瞬時態:當new一個實體對象后,這個對象處于瞬時態,即這個對象只是一個保存臨時數據的內存區域,如果沒有變量引用這個對象,則會被JVM的垃圾回收機制回收。這個對象所保存的數據與數據庫沒有任何關系,除非通過Session的save()、saveOrUpdate()、persist()、merge()方法把瞬時態對象與數據庫關聯,并把數據插入或者更新到數據庫,這個對象才轉換為持久態對象。
- 持久態:持久態對象的實例在數據庫中有對應的記錄,并擁有一個持久化標識(ID)。對持久態對象進行delete操作后,數據庫中對應的記錄將被刪除,那么持久態對象與數據庫記錄不再存在對應關系,持久態對象變成移除態(可以視為瞬時態)。持久態對象被修改變更后,不會馬上同步到數據庫,直到數據庫事務提交。
- 游離態:當Session進行了close()、clear()、evict()或flush()后,實體對象從持久態變成游離態,對象雖然擁有持久和與數據庫對應記錄一致的標識值,但是因為對象已經從會話中清除掉,對象不在持久化管理之內,所以處于游離態(也叫脫管態)。游離態的對象與臨時狀態對象是十分相似的,只是它還含有持久化標識。
提示:關于這個問題,在Hibernate的官方文檔中有更為詳細的解讀。
如何理解Hibernate的延遲加載機制?在實際應用中,延遲加載與Session關閉的矛盾是如何處理的?
答:延遲加載就是并不是在讀取的時候就把數據加載進來,而是等到使用時再加載。Hibernate使用了虛擬代理機制實現延遲加載,我們使用Session的load()方法加載數據或者一對多關聯映射在使用延遲加載的情況下從一的一方加載多的一方,得到的都是虛擬代理,簡單的說返回給用戶的并不是實體本身,而是實體對象的代理。代理對象在用戶調用getter方法時才會去數據庫加載數據。但加載數據就需要數據庫連接。而當我們把會話關閉時,數據庫連接就同時關閉了。
延遲加載與session關閉的矛盾一般可以這樣處理:
- 關閉延遲加載特性。這種方式操作起來比較簡單,因為Hibernate的延遲加載特性是可以通過映射文件或者注解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現"no session or session was closed"通常說明系統中已經存在主外鍵關聯,如果去掉延遲加載的話,每次查詢的開銷都會變得很大。
- 在session關閉之前先獲取需要查詢的數據,可以使用工具方法Hibernate.isInitialized()判斷對象是否被加載,如果沒有被加載則可以使用Hibernate.initialize()方法加載對象。
- 使用攔截器或過濾器延長Session的生命周期直到視圖獲得數據。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種做法。
舉一個多對多關聯的例子,并說明如何實現多對多關聯映射。
答:例如:商品和訂單、學生和課程都是典型的多對多關系。可以在實體類上通過@ManyToMany注解配置多對多關聯或者通過映射文件中的和標簽配置多對多關聯,但是實際項目開發中,很多時候都是將多對多關聯映射轉換成兩個多對一關聯映射來實現的。
談一下你對繼承映射的理解。
答:繼承關系的映射策略有三種:
- 每個繼承結構一張表(table per class hierarchy),不管多少個子類都用一張表。
- 每個子類一張表(table per subclass),公共信息放一張表,特有信息放單獨的表。
- 每個具體類一張表(table per concrete class),有多少個子類就有多少張表。
第一種方式屬于單表策略,其優點在于查詢子類對象的時候無需表連接,查詢速度快,適合多態查詢;缺點是可能導致表很大。后兩種方式屬于多表策略,其優點在于數據存儲緊湊,其缺點是需要進行連接查詢,不適合多態查詢。
簡述Hibernate常見優化策略。
答:這個問題應當挑自己使用過的優化策略回答,常用的有:
- 制定合理的緩存策略(二級緩存、查詢緩存)。
- 采用合理的Session管理機制。
- 盡量使用延遲加載特性。
- 設定合理的批處理參數。
- 如果可以,選用UUID作為主鍵生成器。
- 如果可以,選用基于版本號的樂觀鎖替代悲觀鎖。
- 在開發過程中, 開啟hibernate.show_sql選項查看生成的SQL,從而了解底層的狀況;開發完成后關閉此選項。
- 考慮數據庫本身的優化,合理的索引、恰當的數據分區策略等都會對持久層的性能帶來可觀的提升,但這些需要專業的DBA(數據庫管理員)提供支持。
談一談Hibernate的一級緩存、二級緩存和查詢緩存。
答:Hibernate的Session提供了一級緩存的功能,默認總是有效的,當應用程序保存持久化實體、修改持久化實體時,Session并不會立即把這種改變提交到數據庫,而是緩存在當前的Session中,除非顯示調用了Session的flush()方法或通過close()方法關閉Session。通過一級緩存,可以減少程序與數據庫的交互,從而提高數據庫訪問性能。
SessionFactory級別的二級緩存是全局性的,所有的Session可以共享這個二級緩存。不過二級緩存默認是關閉的,需要顯示開啟并指定需要使用哪種二級緩存實現類(可以使用第三方提供的實現)。一旦開啟了二級緩存并設置了需要使用二級緩存的實體類,SessionFactory就會緩存訪問過的該實體類的每個對象,除非緩存的數據超出了指定的緩存空間。
一級緩存和二級緩存都是對整個實體進行緩存,不會緩存普通屬性,如果希望對普通屬性進行緩存,可以使用查詢緩存。查詢緩存是將HQL或SQL語句以及它們的查詢結果作為鍵值對進行緩存,對于同樣的查詢可以直接從緩存中獲取數據。查詢緩存默認也是關閉的,需要顯示開啟。
Hibernate中DetachedCriteria類是做什么的?
答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法創建的,也就意味著離開創建它的Session,Criteria就無法使用了。DetachedCriteria不需要Session就可以創建(使用DetachedCriteria.forClass()方法創建),所以通常也稱其為離線的Criteria,在需要進行查詢操作的時候再和Session綁定(調用其getExecutableCriteria(Session)方法),這也就意味著一個DetachedCriteria可以在需要的時候和不同的Session進行綁定。
@OneToMany注解的mappedBy屬性有什么作用?
答:@OneToMany用來配置一對多關聯映射,但通常情況下,一對多關聯映射都由多的一方來維護關聯關系,例如學生和班級,應該在學生類中添加班級屬性來維持學生和班級的關聯關系(在數據庫中是由學生表中的外鍵班級編號來維護學生表和班級表的多對一關系),如果要使用雙向關聯,在班級類中添加一個容器屬性來存放學生,并使用@OneToMany注解進行映射,此時mappedBy屬性就非常重要。如果使用XML進行配置,可以用<set>標簽的inverse="true"設置來達到同樣的效果。
MyBatis中使用#和$書寫占位符有什么區別?
答:#將傳入的數據都當成一個字符串,會對傳入的數據自動加上引號;$將傳入的數據直接顯示生成在SQL中。注意:使用$占位符可能會導致SQL注射攻擊,能用#的地方就不要使用$,寫order by子句的時候應該用$而不是#。
解釋一下MyBatis中命名空間(namespace)的作用。
答:在大型項目中,可能存在大量的SQL語句,這時候為每個SQL語句起一個唯一的標識(ID)就變得并不容易了。為了解決這個問題,在MyBatis中,可以為每個映射文件起一個唯一的命名空間,這樣定義在這個映射文件中的每個SQL語句就成了定義在這個命名空間中的一個ID。只要我們能夠保證每個命名空間中這個ID是唯一的,即使在不同映射文件中的語句ID相同,也不會再產生沖突了。
MyBatis中的動態SQL是什么意思?
答:對于一些復雜的查詢,我們可能會指定多個查詢條件,但是這些條件可能存在也可能不存在,例如在58同城上面找房子,我們可能會指定面積、樓層和所在位置來查找房源,也可能會指定面積、價格、戶型和所在位置來查找房源,此時就需要根據用戶指定的條件動態生成SQL語句。如果不使用持久層框架我們可能需要自己拼裝SQL語句,還好MyBatis提供了動態SQL的功能來解決這個問題。MyBatis中用于實現動態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>
當然也可以像下面這些書寫。
<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>
再看看下面這個例子。
<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>
常用設計模式
- 裝飾器模式、如 IO 流,BufferedInputStream
- 單例模式
- 原型模式,Spring bean 每次從容器中調用Bean時,都會返回一個新的實例
- 工廠模式,