Struts2部分
1說明整個Struts2的流程,越詳細越好(10分)
初始化:
struts2是由過濾器驅動的:核心過濾器StrutsPrepareAndExecuteFilter在web應用啓動的時候被服務器創建,並駐留服務器內存;
並調用init方法進行初始化;
過程包括:
核心過濾器的init方法主要做的事:
1 初始化配置文件
配置文件默認解析順序爲
u default.properties(struts的常量信息)--->
u struts-default.xml--->
u struts-plugin.xml--->
u struts.xml;
如果其中有相同信息則後者覆蓋前者,因此我們要覆蓋struts默認的常量最好是放在struts.xml中的constant元素中;最後將這些配置信息封裝到ConfigurationManager中;
當然一些插件爲了覆蓋struts的默認常量也會在對應的struts-plugin.xml中定義常量;並最終彙總到struts.xml中;
2 初始化核心分發器:Dispatcher
Dispatcher dispatcher = init.initDispatcher(config);進行初始化,核心分發器Dispatcher在被創建的過程中需要接收兩個參數;
new Dispatcher(filterConfig.getServletContext(), params);和ServletContext;
dispatcher在初始化的時候做的事情:
a 靜態注入:將struts框架工作所需的對象創建並放入到容器中,這個容器的實現類是ContainerImpl
通過如下方法進行注值;
org.apache.struts2.config.BeanSelectionProvider.register(ContainerBuilder, LocatableProperties);
需要注入的是,這些值的注入都有一個特點:比如;
<bean type="com.opensymphony.xwork2.ActionProxyFactory" name="xwork"
class="com.opensymphony.xwork2.DefaultActionProxyFactory"/>
<bean type="com.opensymphony.xwork2.ActionProxyFactory" name="struts"
class="org.apache.struts2.impl.StrutsActionProxyFactory"/>
都有一個公共的接口:com.opensymphony.xwork2.ActionProxyFactory,並針對該接口有多個實現;struts內部肯定是通過接口
引用調用實現類的方法,這又是面向接口編程,大大提高了程序的擴展性;如果你要改寫這些配置,
只要在你的struts.xml配置文件中進行配置,但是前提有一個你必須實現它的接口,並將type定義爲該接口,將你的實現類
定義爲class,並取和上面相同的名稱,達到覆蓋的效果;
3 初始化PrepareOperations
n 準備struts的執行環境,具體來說就是創建ActionContext;
n 對request進行包裝;這是通過el表達式能訪問到值棧中的數據的原理
n action路徑的尋址和過濾;
4 初始化ExecuteOperations
n 執行靜態資源請求;
n 執行action;
struts的執行流程;
1 加載並解析配置文件,解析順序
struts-default.xml,struts-plugin.xml,struts.xml;
如果這三個文件中有相同內容,則後者覆蓋前者;配置文件加載後創建struts基本元素:即<bean type=*,class=*>
結果集和攔截器(只有被引用到的攔截器纔會被創建,即interceptor-ref的)
需要注意的是<bean>節點對應的元素,type和class的關係,type:
是父類或者接口,而class是對應的實現類,我們說過配置文件是有加載順序的,我們要覆蓋struts的默認配置
可以在struts.xml,但是覆蓋有一個前提,你的實現類,type必須和默認中的type相同;
實現類自己定義,因爲在struts調用你的實現類是通過type對應的引用來調用的,實際上的實現類是由你提供的'
這是完全的面向接口(父類)編程,符合開閉原則;比如說
<bean type="com.opensymphony.xwork2.ObjectFactory" name="struts"
class="org.apache.struts2.impl.StrutsObjectFactory" />這是struts中的默認配置,你要修改他的創建模式;
就必須和他繼承相同的父類;比如spring集成struts的插件中的struts-plugin.xml配置文件的配置;
<bean type="com.opensymphony.xwork2.ObjectFactory" name="spring"
class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
在struts中一定有這麼一行代碼:
ObjectFactory objectFactory;並提供相應的set,get方法
然後在使用到的地方直接就是
objectFactory.buildBean();//至於這個buildBean怎麼執行不管,但是你的實現類一定要有這個方法;
否則就會導致整個框架的混亂
開閉原則:
實現類你愛怎麼實現怎麼實現,這是開的一方面;
但是你必須得保證,我父類有的方法一定要有實現;能被執行;
或者說我的buildBean一定要能執行;這是閉的一方面;
同時配置文件中的內容允許覆蓋也正是體現了這點,如果你都不允許他人覆蓋你的配置,把什麼都寫死了,三大框架整合就是個笑話.
一句話:開閉原則就是對於擴展性我完全開放,但是對於我struts整個的執行流程,誰都不能動,這是我的根基;
攔截器的創建時機:
struts2攔截器的創建機制:只有使用到的攔截器纔會被創建,這個很好理解,是爲了內存消耗的考慮,你沒事叫人過來幹嘛,
叫過來,又叫人回去,啥事也不幹,你不是有病嗎?
細節:
將解析到的一個個interceptor-ref節點封裝成InterceptorMapping,多個InterceptorMapping組成一個集合
List<InterceptorMapping> interceptors;多個集合構成一個
InterceptorStackConfig攔截器棧映射對象;
然後交給ObjectFactory統一創建,具體的是由StrutsObjectFactory進行創建;
protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException {
//這句代碼的意思是,看看配置文件中哪些攔截器棧被引用了,引用了,就進一步的追蹤攔截器棧中的攔截器
NodeList interceptorStackList = element.getElementsByTagName("interceptor-stack");
for (int i = 0; i < interceptorStackList.getLength(); i++) {
Element interceptorStackElement = (Element) interceptorStackList.item(i);
InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context);
context.addInterceptorStackConfig(config);
}
}
init的方法執行完成後,所有的對象都會跑到ContainerImpl這個容器中,這是struts2的核心容器;即IOC容器;
2 當請求到來的時候,首先經過核心過濾器的doFilter方法;這個方法實現了以下功能;
a: request,reponse 本地編碼 當前線程 初始化action執行環境,ActionContext;
對請求url進行解析,並判斷你的url是否是需要經過struts的核心;
如果需要做進一步的處理,如果不需要執行chain.doFilter放行;
當然這一步是粗粒度的進行判斷,接下來對請求進行包裝,解析出對應的action;
並在ActionMapping中找看是否存在對應的action映射;
如果不存在,繼續判斷是否是要使用到struts的靜態資源,即路徑中有
"/struts/":對應於org.apache.struts2.template中的靜態資源;
"/static/"對應於org.apache.struts2下的static包中的靜態資源;
如果有調用靜態資源處理器進行處理,如果沒有直接放行;
如果 actionMapping中有對應的action;
執行execute.executeAction(request, response, mapping);方法,進入struts的核心
調用核心分發器的serviceAction方法創建值棧對象ValueStack和contextMap;
並創建出ActionProxy,由ActionProxy創建出ActionInvocation,即核心控制單元,他採用的是command設計模式進行設計;
由ActionInvocation控制攔截器和action,和result的執行;
在執行proxy.invoke方法就回到ActionInvocation,由ActionInvocation
負責攔截器的執行,攔截器執行完成後,執行目標action的目標方法,
並返回一個路由串,即結果視圖,然後又回到ActionInvocation,繼續執行Result中的代碼,
result根據配置文件中配置的結果視圖類型和頁面跳轉地址,
這時候通常還得調用jsp等模板對結果進行處理,---是否使用到標籤用OGNL解析器進行處理,
形成最終的數據;
然後重新回到ActionInvocation,繼續調用
攔截器的清理工作,攔截器都執行完成後, 響應消息寫入到response緩衝區,由服務器取出,
以http響應格式輸出到瀏覽器供瀏覽器解析,
核心過濾器的doFilter方法執行結束,
本次請求結束ActionContext中的數據被清空;
我們發現核心過濾器和其他過濾器共同構成一個職責鏈設計模式,整個職責鏈由tomcat服務器進行控制;
每個過濾器完成自己的功能:1要麼直接返回數據到頁面 2要麼執行完自己的功能後通知下一個過濾器執行;
而在覈心過濾器中又有一個action核心控制單元,ActionInvocation,他控制着攔截器棧,action,及結果視圖Result;
的執行.
對框架的理解;
爲什麼要有框架,框架幫我們解決了什麼問題,好比如說我們爲什麼要學struts2,不用struts2,我們用servlet,
也可以開發,struts2號稱是嚴格遵循MVC設計模式的web層框架,他幫我們解決了什麼問題?這個我們得回過頭來看
MVC設計模式;一個Controller要完成如下工作:
1 客戶端提交的數據的封裝及校驗;
2 調用service層處理業務邏輯;
3 根據業務邏輯的處理情況,跳轉到相應的頁面;
4 異常的處理;
在servlet中我們隊數據的封裝是通過對request中獲取請求參數,用beanutils將數據注入到formbean中,並對formbean進行校驗;
然後將formbean中的數據copy到我們的數據bean中,即這裏說的Model;
而關於頁面的跳轉我們用的是request,或者response直接跳轉;
異常處理我們是手動的try catch;
這樣做下來我們發現,我們在做很多很多重複性的工作,每次請求都得封裝數據並校驗,處理業務邏輯
然後跳轉頁面周而復始,真正不同的是我們的業務邏輯處理代碼,而這正是我們真正關心的;
那麼我們可以回過頭來看
struts幫我們解決的問題:
1 數據的封裝以及檢驗,錯誤消息提示及數據回顯:DefaultActionValidatorManager
2 異常處理:ExceptionMappingInterceptor
3 頁面跳轉:Result
4 文件上傳:
5 國際化:
6 類型轉換;
..........
而且實現形式還多樣化,可以手動,可以用配置文件,可以用註解;
這些功能的實現都是通過攔截器進行配置的,可見struts2的核心是攔截器;
我們只要寫配置文件就可以完成和之前手動寫N行
代碼的效果,而且高手寫的代碼從效率和安全性來將都超過我們,可以稱得上是最佳實踐;
將重複性的代碼抽取出來,程序員只需配置一下文件就可以了
大大的提高了我們的開發效率;
使得程序員有更多的時間去處理業務邏輯;
說道這裏框架的本質也就說完了
總結:框架是一組程序的集合,包含了一系列的最佳實踐,作用是解決某個領域的問題;本質是對jdk的擴展;(總結來源於網絡)
我的理解:框架就是來幫我們幹活的,簡化了我們的開發;
2,說說你對Struts-plugin.xml 文件的理解(5分)
1 是struts插件的配置文件,充分體現了struts的擴展性,可插拔的框架,是開閉原則(OCP,Open Close Principle,是指一個程序應該是可擴展而不可修改的,即對擴展開發,對修改封閉的原則)的體現;
開的一方面體現在:你可以通過插件及插件配置文件覆蓋struts默認的配置,閉的原則是你不能改變struts的執行流程,這是他的核心
誰都不能動,其實我覺得還體現在面向接口編程,也就是你覆蓋可以,但是你的實現類必須實現我給你提供的指定接口;
因爲框架內部是通過接口的引用調用實現類的方法的;必須實現指定的接口---閉,
實現類開放:你愛怎麼實現怎麼實現---開;
2 分離關注;separate aware;好比如說集成spring,你只要把對spring支持的插件包和具體的實現類加到classpath;
在struts.xml文件中根本就沒有spring的影子;因爲struts-plugin.xml中已經配置好了,當然這依賴於struts配置文件的加載順序;和相同內容覆蓋原則
也就是將插件和struts核心分開處理;使得邏輯更加的清晰
如果沒有這個插件你還得在struts.xml中配置或者導入;東西一多,嘿嘿,你懂的!!
ps:其實攔截器也是體現了分離關注separate aware;因爲每個攔截器都實現自己的功能,並且互相不干擾,使得代碼邏輯更加清晰,先執行哪個,後執行哪個,你配就是了,怎麼配置我怎麼執行;
3,說明struts2的屬性驅動和模型驅動的實現原理,並把關鍵代碼寫出來(5分)
模型驅動是將你的action實現ModelDriven接口,並覆蓋對應的getModel方法,該方法返回的是你定義的模型;
如果你的模型類已經創建則經過ModelDrivenInterceptor的時候
if (action instanceof ModelDriven) {
ModelDriven modelDriven = (ModelDriven) action;
ValueStack stack = invocation.getStack();
Object model = modelDriven.getModel();//獲得模型類即User;
if (model != null) {
stack.push(model);//將model壓入棧頂;
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
將你的模型類壓入棧頂,然後經過params攔截器,他有一個valuestack.setValue();//將參數注入棧頂對象中;從而實現對模型的注值;
如果模型類一開始沒有值將會調用ObjectFactory.buildBean方法創建,然後繼續這樣的操作;這樣效率更低;
因此如果使用模型驅動,最好對模型類進行顯示初始化;這應該做爲一條最佳實踐;
屬性驅動用的是利用ParameterInterceptor攔截器
這個方法會用反射把Action中有set方法的屬性創建出對象;
ReflectionContextState.setCreatingNullObjects(contextMap, true);
接着會調用setParameters(action, stack, parameters)方法將參數中的值注入到action中的屬性中,
最終調用的是ValueStack.setValue();方法---真正的爲屬性注值
他是利用反射機制,調用對象屬性的set方法,即method.invoke;
總結:
其實兩種方式都得使用到parameterinterceptor攔截器,爲action的屬性注值;
由於action會默認的壓到棧頂屬性隨之也到了棧頂,模型驅動也就是多了這麼個壓棧的方法,因此作用不是很大;
--依據:將params攔截器註釋掉,action中的exec方法引用user的屬性報空指針異常
s:debug標籤中的內容;
4,配置一個自己的攔截器,實現的功能:如果session中的user爲Null,則不會跳轉到相應的action
如果不爲null,則跳轉到相應的action(3分)
Object user = ServletActionContext.getRequest().getSession().getAttribute("user");
if(user!=null&&user.getClass()==User.class){
//加上類型判斷更加安全;因爲只有user類對象才能做爲標記;
return invocation.invoke();
}
return null;
攔截器配置;省略interceptors中的配置;
<action name="login" class="com.itheima.web.struts.actions.LoginAction"
method="login">
<interceptor-ref name="loginInterceptor"/>
<interceptor-ref name="defaultStack"/>
<result>/index.jsp</result>
</action>
注意:如果你的攔截器依賴於默認的攔截器請將你的攔截器定義在默認攔截器棧後,反之亦然,
在這裏由於我的是判斷是否登錄,不依賴於默認,因此配置在之前,提高效率,即你都沒有登錄,其他的操作沒必要執行;
這也應該成爲一條最佳實踐:看自己的攔截器是否依賴默認攔截器棧,合理規劃配置;
ps:我們可以通過struts.xml配置文件,改變攔截器的執行順序,當然得根據你的具體業務邏輯來修改,這也是開閉原則的體現,非常的靈活;
5,說明s:iterator迭代器的用法(10分)
1 迭代List集合;每次迭代的都是list中的一個元素:並用一個變量存儲,壓入棧頂;
提問:爲什麼不能是set集合呢?這是因爲iterator是需要角標滴;
用法:
<s:iterator value="{'a','b','c'}" var="xx">
<s:property value="xx"/>
</s:iterator>
這裏會報錯;說是ClassCastExc; Character cant't cast to String
而 <s:iterator value="{'a','b','c'}">
<s:property />--默認取棧頂元素,用是是vs,findString();方法
</s:iterator> 確沒有絲毫問題;我認爲這是他的一個bug;至於原因,找了好久沒有找到根源;我覺得要麼兩個都沒問題,要沒都有問題,
解決方案:用單引號套雙引號:
即:
<s:iterator value='{"a","b","c"}' var="xx">
<s:property value="xx"/>
</s:iterator>
2 迭代Map集合;
<s:iterator value="#{'name':'xxx','age':'111'}">
<s:property value="key" />
<s:property value="value" />
</s:iterator>
注意#號不能丟,他是構建一個map集合放入OGNL上下文中,迭代時取出每一個map.entry,放到棧頂;
取的時候用key,value取對應的值,否則默認取得的一個map.entry;
因爲他底層會去調用entry.getKey,getValue方法
還可以取得ActionContext中的六個map中的數據;取的時候注意用#號;
應用場景:隔行間色效果實現,用statu,類似於jstl的foreach標籤;
6,寫一個自定義的結果集,實現轉發(3分)
思路:寫一個DispatcherResult,繼承自ServletDispatcherResult
只要我的結果集直接或者間接繼承了Result接口即可,struts的ActionInvocation
會自動調用我的結果集並根據具體的路由串,跳轉到指定頁面;
public void execute(ActionInvocation invocation) throws Exception {
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
//location即我們的跳轉頁面,就是下面配置的param.jsp;
request.getRequestDispatcher(super.getLocation()).forward(request,
response);
}
<result-types>
<result-type name="mydispatcher" class="com.itheima.results.DispatcherResult">
</result-type>
</result-types>
<global-results>
<result type="mydispatcher">
/param.jsp
</result>
</global-results>
<action name="login" class="com.itheima.web.struts.actions.LoginAction">
</action>
PS:大部分的view層插件都是以結果集的形式和struts2進行整合的;比如,json,ITEXTS,JFreeChart等;
其實我們完全可以拋開struts不管,只要結果視圖返回一個NONE;
然後用傳統的request,response,照樣可以做開發;尤其是文件下載的時候,由於要配置多個什麼項的;
因爲之前用習慣了response輸出,然後就是手動用
struts輸出驗證碼也可以用這種方式實現----
7,說明Struts2怎樣做到定製action(15分):
所謂的定製action其實就是你自己定義一個類繼承自ObjectFactory,因爲這個類是struts中創建對象的核心工廠類;
並覆蓋對應是buildAction方法即可;
因此所謂的SSH整合,其實就是將struts中action的創建策略由默認的org.apache.struts2.impl.StrutsObjectFactory改爲Spring提供的
org.apache.struts2.spring.StrutsSpringObjectFactory類,但是不管怎麼樣
他們都得實現或者繼承自com.opensymphony.xwork2.ObjectFactory,因爲在struts內部,肯定是通過父類或者接口的引用調用子類的方法;
不管你怎麼實現,怎麼覆蓋,你必須得有對應的方法,即可以通過父類的引用.buildBean();進行調用;
如果我們真的要這樣做的話,可以做成一個插件的形式,寫一個類繼承org.apache.struts2.impl.StrutsObjectFactory,覆蓋其中的buildAction方法即可;
然後在struts-plugin.xml中配置
<bean type="com.opensymphony.xwork2.ObjectFactory" name="myActionFactory" class="com.itheima.factory.MyObjectFactory" />
<constant name="struts.objectFactory" value="spring" />
然後打成jar包,放到classpath構建路徑下即可,就將struts創建action方式變成了自己的,這是其開閉原則的體現;
Hibernate 部分
1,簡述hibernate的執行流程(5分)
1、創建Configuration對象
2、利用Configuration對象的config方法加載配置文件
3、config.buildSessionFactory方法產生SessionFactory
sessionFactory
1、包含了數據庫的鏈接信息
2、配置文件的信息
3、映射文件的信息
4、持久化類的信息
5、一個sessionFactory表明鏈接了一個數據庫
6、sessionFactory是一個單例的對象
4、sessionFactory產生session
session
1、創建一次session,就打開數據庫的鏈路一次
2、session的打開和關閉的次數越多,效率越低
3、session中的一級緩存緩存了數據
5、由session創建事務
6、進行crud的操作
7、提交事務
8、關閉session
2,簡述hibernate list()iterot()(5分)
共同點:兩者都是處理查詢返回的結果,但是處理的方式不一樣,導致效率不一樣。
1、加載的是對象的所有的內容
2、該hql語句查詢出來的是List<Classes>,正好符合對象緩存,所以
該結果會在一級緩存,二級緩存中存儲
3、如果hql語句是"select name from Classes",查詢出來的結果
不符合對象緩存,這個時候,結果不會儲存在一級緩存和二級緩存中
4、當第二次執行query.list的時候,必須設置query.setCacheable(true);
才能從查詢緩存中提取數據
1、先加載所有的id
2、再根據id加載所有的值
3、該方法把對象放入到了二級緩存中,查詢的時候利用的是二級緩存,不利用查詢緩存
(2)從執行結果可以看出list輸出一條語句,而iterator輸出的是兩條sql語句,我們可想一下,爲什麼會輸出這樣的效果?
因爲他們獲取數據的方式不一樣,list()加載的是對象的所有的內容,iterator()會先到數據庫中把id都取出來,然後真正要遍歷某個對象的時候先到緩存中找,如果找不到,以id爲條件再發一條sql到數據庫,這樣如果緩存中沒有數據,則查詢數據庫的次數爲n+1次
(3)list只查詢一級緩存,而iterator會從二級緩存中查
(4)list方法返回的對象都是實體對象,而iterator返回的是代理對象
(5) session中list第二次發出,仍會到數據庫査詢
(6) iterate 第二次,首先找session 級緩存
共同點:兩者都是處理查詢返回的結果,
list()加載的是對象的所有的內容,)list方法返回的對象都是實體對象
iterator()會先到數據庫中把id都取出來,然後真正要遍歷某個對象的時候先到緩存中找,如果找不到,以id爲條件再發一條sql到數據庫,這樣如果緩存中沒有數據,則查詢數據庫的次數爲n+1次,而iterator返回的是代理對象
list(): 一個查詢通常在調用list()時執行,執行結果會完全裝載進內存中的一個集合(collection).查詢返回的對象持久狀態。
Iterator():某些情況下,可以使用iterate()方法得到更好的性能。這通常是你預期返回的結果在session,或二級緩存中已經存在的情況。如若不然,iterate()會比list()慢,而且可能簡單查詢也需要進行多次數據庫訪問:iterate()會首先使用1條語句得到所有對象 的持久化標識(identifiers),再根據持久化標識執行n條附加的select語句實例化實際的對象。
3,Hibernate中load()/get()區別.(3分)
Hibernate對於load方法認爲該數據在數據庫中一定存在,可以放心的使用代理來延遲加載,load默認支持延遲加載,在用到對象中的其他屬性據時才查詢數據庫,但是萬一數據庫中不存在該記錄,只能拋異常ObjectNotFoundException;所說的load方法拋異常是指在使用該對象的數據時,數據庫中不存在該數據時拋異常,而不是在他創建這個對象時。由於session中的緩存對於hibernate來說是個相當廉價的資源,所在在load時會先查一下session緩存看看該id對應的對象是否存在,不存在則創建代理 (load時候查詢一級緩存,不存在則創建代理)。Get()先在一級緩存找,沒有就去二緩緩存找,再沒有去數據庫找,實在沒有就返回null;
其實hibernate還存在着副本的概念,只針對修改操作,如果你的持久化數據
4,針對hibernate的一對一,一對多,多對多,編寫持久化類和映射文件,完成這些關係映射(10分)
類與類之間的關係的主要體現當我們要在表與表之間的關係進行操作,它們都是對對象進行操作,我們程序中把所有的表與類都映射在一起,它們通過配置文件中的many-to-one 、 one-to-many 、many-to-many以及在相應的類上申明類型爲Set 的JavaBean屬性或者類型爲關聯類的JavaBean屬性。
One-to-many:
在Customer.hbm.xml配置,一對多映射
在Order.hbm.xml配置,多對一映射
Many-to-Many:
}
Course.hbm.xml
}
Student.hbm.xml
One-to-One: 第一種形式:與主鍵關聯
}
Company.hbm.xml
}
Address.hbm.xml
或者另一種形式與外鍵關聯的即 採用one-to-many關係 ,並在多的一方的映射文件上的many-to-one的標籤上添加 unique=”true”屬性。
5,hibernate中,什麼情況下,對象的狀態可以由持久的變成託管的?(3分)
Hibernate中將PO 對象狀態分爲三種:
1. 瞬時態(臨時) transient : 沒有持久化標識OID,未與Session關聯;
2. 持久態 persistent: 具有持久化標識OID,與Session關聯 (處於事務中)
3. 脫管態(離線) detached: 具有持久化標識OID,未與Session關聯
所以只要是能將原有的持久化標識OID去除的方法或者與Session變得不關聯的方法都可以將對象的持久態轉變脫管狀態,這樣的方法有
Session.clear(), session.evict(Object obj), session.close();
另:事務直接提交也會使持久化變成脫管狀態。
6ibernate中的ID(主鍵)生成器由哪些?(3分)
1、uuid
a.必須是varchar類型; b.是由hibernate內部產生的
底層用的是UUID:算法產生全球唯一的id;
2、assigned
a.主鍵可以是任何類型; b.必須在程序中手動賦值;c.一般不推薦使用
3、increment
a.主鍵必須是數字類型; b.由hibernate操作數據庫:先獲取主鍵的最大值,然後最大值加1; c.效率不是很高,會發出max語句
4、identity
a.主鍵 必須是數字類型; b.表支持自動增長; c.主鍵是由數據庫生成的; d.效率比較高,但是數字不是很連貫。
7,說明延遲加載和抓取策略(5分)
延遲加載
是當在真正需要數據的時候,才真正執行數據加載操作,從而節省了服務器的內存開銷,提高了服務器的性能。可以簡單理解爲,只有在使用的時候,纔會發出sql語句進行查詢.它的關鍵在於控制什麼時候發出SQL語句加載數據。
延遲加載的有效期是在session打開的情況下,當session關閉後,會報異常。當調用load方法加載對象時,返回代理對象,等到真正用到對象的內容時才發出sql語句。
Hibernate2實現延遲加載有2種方式:1.實體對象 2.集合
Hibernate3中又引入了一種新的加載方式:3.屬性的延遲加載
一般使用load()的方法來實現延遲加載,在實現無限級聯動時使用延遲加載效率比較好。
抓取策略
是指Hibernate如何獲取關聯對象的策略,它主要控制如何發出SQL語句獲取Set集合的數據,從而減少與數據庫的鏈接次數來提高性能的。
抓取策略可以在映射文件中聲明,在set標籤上添加fetch屬性值,它有三個,分別爲join, select(默認), subselect 。具體作用如下:
Join:
1、左外連接,一次性把一端的數據和多端的數據全部提取出來
2、當需求分析含有子查詢的時候,join不再適用
3、集合的延遲加載不再適用
Select:
1、默認的查詢方式
2、當集合在迭代的時候纔要發出sql語句
3、會發出n+1條SQL語句(有時候)
Subselect
1、如果需求分析中含有子查詢,用這樣的查詢效率比較高
2、集合什麼時候發出SQL語句,要看集合的延遲加載
true + select =當迭代集合的時候發出sql語句,通過每一個cid查詢student
true/false +join =如果需要分析中沒有子查詢,一條語句加載classes和student,這個時候lazy可以忽
8,說明session的一級緩存的用法和意義?(10分)
當使用session從數據庫中查詢出一個對象時,Session也是先從自己內部查看是否存在這個對象,存在則直接返回,不存在纔去訪問數據庫,並將查詢的結果保存在自己內部。由於Session代表一次會話過程,一個Session與個數據庫連接相關連,所以Session最好不要長時間保持打開,通常僅用於一個事務當中,在事務結束時就應關閉。
一級緩存的存取 :
當程序調用Session的save()、update()、savaeOrUpdate()、get()或load(),以及調用查詢接口的list()、iterate()或filter()方法時,如果在Session緩存中還不存在相應的對象,Hibernate就會把該對象加入到第一級緩存中。當清理緩存時,Hibernate會根據緩存中對象的狀態變化來同步更新數據庫。
Session爲應用程序提供了兩個管理緩存的方法:
evict(Object obj):從緩存中清除參數指定的持久化對象。
clear():清空緩存中所有持久化對象。
緩存的意義:它提高了對查詢結果的複用,使程序在運行時從緩存讀寫數據,降低了程序對數據庫訪問的頻次,從而提高程序的運行性能。
9,hibernate中Cascade和Inverse有什麼區別?(5分)
Cascade所描述的是級聯的策略,它有save-update,delete,all,none(default)三個屬性值. 級聯,就是根據級聯的策略對關聯類進行連鎖操作,比如當我們在客戶端save classses時它內部會自動對關聯的對象進行保存或者更新操作. 這樣可以省略一大批代碼,使代碼變得簡潔.
Inverse用來描述是否維護關係,也稱爲維護反轉. 它有三個值false , true ,default 默認下它表示false. 表示維護. 在實際情況下 , 一對多,一的一方通常要設置爲true ,禁止它維護關係 . 也就是說一旦設置了inverse爲true, Cascade的作用將無效化.
10,說說hibernate的二級緩存(5分)
v 二級緩存是SessionFactory的外置緩存,是一個可配置的緩存插件,在默認的情況下,SessionFactory不會啓用,外置緩存是數據庫數據的複製,二級緩存的物理介質可以是內存或者硬盤
v 適合放入二級緩存中的數據:很少被修改;不是很重要的數據, 允許出現偶爾的併發問題。不適合放入二級緩存中的數據:經常被修改;財務數據, 絕對不允許出現併發問題與其他應用數據共享的數據
v Hibernate 的二級緩存是進程或集羣範圍內的緩存, 緩存中存放的是對象的散裝數據。二級緩存是可配置的的插件, Hibernate 允許選用以下類型的緩存插件:EHCache,OpenSymphony OSCache,SwarmCache,JBossCache
v 配置進程範圍內的二級緩存(配置ehcache緩存)
1. 拷貝ehcache-1.5.0.jar到當前工程的lib目錄下
依賴 backport-util-concurrent 和 commons-logging
2. 開啓二級緩存
<property name="hibernate.cache.use_second_level_cache">true</property>
3. 要指定緩存的供應商
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider</property>
4. 指定使用二級緩存的類
* 方法一 在使用類的*.hbm.xml配置
選擇需要使用二級緩存的持久化類, 設置它的二級緩存的併發訪問策略, <class> 元素的 cache 子元素表明 Hibernate 會緩存對象的簡單屬性, 但不會緩存集合屬性, 若希望緩存集合屬性中的元素, 必須在 <set> 元素中加入 <cache> 子元素
* 方法二 在hibernate.cfg.xml文件中配置(建議)
<!-- 指定使用二級緩存的類 放在maping下面 -->
<!-- 配置類級別的二級緩存 -->
<class-cache class="cn.itcast.c3p0.Customer" usage="read-write"/>
<class-cache class="cn.itcast.c3p0.Order" usage="read-write"/>
<!-- 配置集合級別的二級緩存 -->
<collection-cache collection="cn.itcast.c3p0.Customer.orders"
usage="read-write"/>
5. 配置ehcache默認的配置文件ehcache.xml(名字固定)(放在類路徑下)
<diskStore path="D:\cache" />
<cache name=" “
maxElementsInMemory="10“
eternal=“true"
overflowToDisk="true“
maxElementsOnDisk=“10000000”
diskPersistent="false“
diskExpriyThreadIntervalSeconds=“120” />
</ehcache>
v 當把一個數據放入到二級緩存中以後,只要sessionFactory沒有關閉,二級緩存中的數據就能夠存在, 也就是說它的生命週期伴隨着SessionFactory的生命週期存在和消亡。
11,在hibernate中,把緩存中的數據同步到數據庫中,用到的方法是什麼?(2分)
Flush( ) + transaction.commit( )
Flush()方法
1.它會先檢查session一級緩存中的持久化對象/持久化對象的關聯對象/副本.
2.會去檢查相應的映射文件中的映射文件中的inverse 和cascade
3.如果沒有ID值,則發出insert語句;如果有ID值, 則對照副本;如果一致,則什麼都不做;如果不一致,則發出update語句;
4.把語句刷向數據庫.
transaction.commit( ):把事務提交,對數據庫進行更新。
12,類Course,類Student,是多對多的關係.解除Sid爲1的學生爲cid爲1的課程之間的關係有幾種寫法?(10分)
一:從course一方出發
二.從student一方出發
13,寫一個hql語句,查詢班級中的所有的學生,並且把該學生所有的課程查詢出來,並且沒有學生的班級也查詢出來.(5分)
from Studnet s left outer join fetch s.courses left outer join fetch s.classes
Spring部分
1 談談你對Spring的理解?(5分)
Aspring是一個IOC和AOP容器框架;可以大大的簡化企業開發;
所謂的IOC和DI:說白了就是一個一個對象工廠,將對象的創建和銷燬交給外部容器,即Spring進行管理;
Spring容器用反射機制調用javabean的默認構造函數進行創建,然後放入到spring的容器中;
根據你的配置不同,創建時機也不一樣;
Spring中的AOP基於IOC,爲什麼這麼說呢?這是因爲AOP的原理是爲目標類創建代理,創建代理的方式是:
1 當你的目標類實現了接口就用jdk的這個proxy
2 當你的目標類沒有實現接口就要用CGLIB方式動態產生,他產生的類是目標類子類;
注意:如果你產生代理方式是動態選擇的,即不知道是用jdk,還是cglib,先判斷該類是否實現了接口,如果實現了
就用jdk,如果沒有,就用cglib,因此請你仔細檢查你目標類:是否實現了接口;
動態判斷的情況下,如果你實現了一些標記接口,但是你實際是希望用CGLIB產生的,就會產生問題;
因此如果你的目標類都沒有產生對象,即沒有IOC容器,對象都沒有,代理個啥呀;況且切面也是有Spring IOC容器進行管理的
至於AOP和IOC下面已經有解釋了就不贅述了
B: 對JDBC,及Hibernate進行簡單封裝,比如JdbcTemplate是對JDBC的輕量級封裝,類似於DBUtils中的QueryRunner,
對HibernateTemplate工具類對hibernate進行封裝;用他我們可以實現DAO設計模型(老王講過的);--用session也可以的;
就是在dao層進行抽象,將所有dao共同的操作放到父類中取,子類繼承就可以直接使用,
但是爲了增強程序的可讀性,請遵循命名規範;
代碼如下:
這是所有crud操作的父接口;
public interface DAO<T> {
public void persistent(T t);//雖然和save方法的功能一樣,但是hibernate官方推薦使用persistent,
public void update(T t);
public void delete(Serializable id);
public T find(Serializable id);
public List<T> listAll();
}
public abstract class DaoBase<T> implements DAO<T> {
//公有,並且要提供set方法因爲要交給Spring進行管理,包括事務;
public HibernateTemplate hibernateTemplate;
private Class clazz;
public DaoBase() {
// 子類在初始化時,要調用父類的構造函數;可以通過這種方式獲取到子類中的泛型實際參數
ParameterizedType type = (ParameterizedType) this.getClass()
.getGenericSuperclass();
clazz = (Class) type.getActualTypeArguments()[0];
}
public void persistent(T t) {
hibernateTemplate.persist(t);
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
}
UserDao接口繼承自DAO<T>並提供泛型參數;他只要提供自己特有的方法,要實現類進行實現,實現類也就只要實現UserDao中
的方法,其他的繼承自DaoBase;
實現類UserDaoImpl代碼如下;
public class UserDaoImpl extends DaoBase<User> implements UserDao {
public User findUserByName(String username) {
List<User> users = hibernateTemplate.find(
"from User user where user.username=?", username);
return users != null && users.size() > 0 ? users.get(0) : null;
}
public User findUserByNameAndPassword(String username, String password) {
List<User> users = hibernateTemplate.find(
"from User user where user.username=? and user.password=?",
username, password);
return users != null && users.size() > 0 ? users.get(0) : null;
}
}
整個繼承關係
DAO<T>
---DaoBase<T>
---UserDao
---...和具體操作對象有關的其他DAO;
UserDaoImpl實現UserDao,繼承DaoBase,就擁有DaoBase的基本crud方法,並獲得HibernateTemplate對象;
Spring配置:
<bean id="daoBase" abstract="true" class="com.itheima.dao.impl.DaoBase">
<property name="hibernateTemplate" ref="hibernateTemplate" />
</bean>
<bean id="userDao" class="com.itheima.dao.impl.UserDaoImpl"
parent="daoBase">
</bean>
注意:daoBase是抽象的,userDao用parent;
核心思想:
1:子類在用默認構造函數初始化的時候要調用父類的構造函數;
2:抽象類不能被實例化,我們用的只是他的引用,其實真正幹活的還是實現類;
C: 聲明式的事務管理,JavaMailSenderImpl郵件開發工具類;
SchedulerFactoryBean定時器工具類
2 說說你對面向切面編程(AOP)的理解(10分)
所謂的面向切面編程其實就是對公共代碼的抽取,實現分離關注separate aware.
比如說我們在實際開發中經常要解決的問題:權限,日誌,事務,異常等等經常性的代碼,
我們將他們進行抽取,分開管理,在需要的地方將切面織入;
這樣有什麼好處呢:
1 提高了代碼的可維護性,比如說我們都權限的管理的需求改變了,如果不用aop,在每個地方都進行相同的權限判斷
需求變了,所有的地方都得變,使得代碼的維護成本大大提升;時不時的出個bug,你就瘋了
而做成公共的,改的時候只要動一處就行了,同時如果由專人進行管理,每個人都做自己最擅長的事,可以大大提高開發效率
和安全性,因爲做的久了更加的專業;
實現了分離關注;
struts2中的攔截器就是這麼個東東;將所有的功能寫成對應的攔截器;
在需要的時候注入就可以了:配置文件中通過interceptor-ref:進行引用即可;他就會給你在action執行前後進行攔截;
3 簡單介紹一下IOC的實現原理.(寫出代碼最好)(3分)
1解析配置文件,解析出class ,id 用對象工廠的方式,反射調用器構造函數創建,然後用id爲key,保存在一個map中;
如果有依賴注入:即解析字段,解析出對應的類型;通過反射方式創建出來;然後反射該字段的set方法;
給屬性注值;具體代碼如下:
Map<String,Object> beans=new HashMap<String,Object>();
//解析配置文件就算了
Class service = Class.forName("com.itheima.service.impl.UserServiceImpl");
if(service.isAnnotationPresent(Component.class)){
Field[] fields = service.getDeclaredFields();
if(fields!=null){
for(Field field:fields){
if(field.isAnnotationPresent(Autowired.class)){
Object userDao = Class.forName("com.itheima.dao.impl.UserDaoImpl").newInstance();
field.setAccessible(true);
Object serviceInstance = service.newInstance();
field.set(serviceInstance, userDao);
beans.put(service.getSimpleName(), serviceInstance);
}
}
}
}
UserService userService = (UserService) beans.get(service.getSimpleName());
userService.save();
4 Spring中Bean的作用域有哪幾種?
singleton:
prototype:
下面三個:將針對web開發而言,不知道具體有什麼用;
request:HttpServletRequest範圍的作用域;
session:HttpSession的作用域;
global_session:生命週期是portlet容器組件下的ApplicationContext;範圍;
5 spring中定義爲Prototype作用域的bean,在什麼時候會被spring銷燬?(2分)
bean的scope有這麼幾種:
singleton,單例;默認的創建機制;在容器初始化的時候就創建,如果lazy-init:true;即只有使用到就行;
prototype:原型,默認是使用的時候創建,使用該屬性,lazy-init就沒用了,都是使用時才創建;
如果是prototype,spring不負責銷燬,而是由GC給幹掉;
這幾個並不常見:request,session, global_session;
6 spring的控制反轉解釋一下(3分)
所謂的控制反轉就是指對象的創建和維護交給第三方,由第三方進行控制,自己在使用的時候不用管這個對象是否有值,直接使用
好比如:現在招聘公司招聘,也可以用這種思想取招,把任務交給獵頭公司負責招聘,今天說要什麼樣的人才,
招聘公司就招什麼樣的人才,什麼面試,筆試,人品測試,通通都不用管,第二天上班時程序員就來了,你可以指揮他幹嘛幹嘛;
這個招聘公司就是個spring容器,負責程序員的創建和維護,你只要在配置文件中進行聲明(跟招聘公司說你的要求);
然後使用就可以了;
對於用人公司來說了:直接用就是了--框架的使用者;
對於招聘公司來說:他可以爲所有有招聘需求的公司進行服務;這樣就是一個框架的思想;爲別人服務;
用別人寫的代碼和寫代碼給別人用境界就不一樣;
7 談一下你對spring聲明式事務處理的理解(10分)
聲明式事務處理是將所有整個應用中的事務管理做成一個切面aspect,統一事務的管理,
在需要的地方只要配置對應的事務切入點和事務通知即可,大大的提高了開發效率和程序的可維護性;
你只要配置事務管理器的實現類,和實現類依賴的數據庫環境,DataSource或者SessionFactory;
並將事務管理器加入到事務切面中即可;使用非常的方便;
PlatformTransactionManager:是spring中對應所有的事務管理器的總接口;
他有幾個實現類比較重要,看源代碼後分析如下:
1 DataSourceTransactionManager,針對於只要你是用的JDBC數據源獲取數據庫連接,我都能處理;
我們知道通過JDBC數據源對象的getConnection()獲取的是線程本地的連接,或者說和當前線程綁定的數據庫連接;
當前線程結束,數據庫連接放回數據庫連接池中繼續使用;
但是數據源即datasource不能和當前線程進行綁定,否則會造成混亂
2 JtaTransactionManager:JTA:Java Transaction Architecture;Java 事務體系架構
JAVAEE13項技術之一;
他是將事務管理交給JavaEE服務器的協調者;然而我們往往是通過配置內嵌在web應用的本地JTA提供者來實現的;
適合處理分佈式的事務;
3 HibernateTransactionManager
針對hibernate的事務管理的實現,他適用於通過SessionFactory獲取到和本地線程進行綁定的Session進行操作的事務管理;
4 針對JPA(JAVA Persistent API持久化規範,JAVAEE十三項技術),JDO,Ibatis等持久層框架也分別有實現;這裏就不多做分析;
總結:我們看到了針對市面上的幾乎所有的DAO或者說持久層,Spring都有實現,同時也提供了接口,你想用哪個自己在配置文件中
配置即可,但是對於每個實現的需要依賴的資源需要程序員手動去配,因爲怎麼實現程序員最清楚,是用SessionFactory還是
DataSource自己配,如果你對Spring提供的實現類不是很滿意,你可以自己寫,實現總接口PlatformTransactionManager,或者繼承
抽象類都可以,只要你將你的實現類進行配置即可;這是完全的面向接口編程,同時還提供了默認實現;
大大提高了擴展性和靈活性;
8 說明三大框架的整合原理(10分)
整合三大框架得先整合Spring和Hibernate,主要是將事務的管理交給Spring進行管理;
經過測試後,即dao,service層都測試通過後,再集成Struts2;
集成struts2的步驟:
1 Spring容器的啓動交給監聽器;ContextLoaderListener
2 Struts2的啓動仍然是核心過濾器StrutsPrepareAndExecuteFilter;
3 如果要使用OpenSessionInView解決Hibernate 的懶加載出錯(session已經關閉,因爲事務提交的時候session已經沒了),配置下過濾器;
OpenSessionInViewFilter;
4 在struts中加入對spring集成插件;將struts中對象工廠的創建交個spring進行管理;
在struts.xml中加入配置<constant name="struts.objectFactory" value="spring" />
其實這個不加入也可以,因爲struts-plugin.xml中也有這個配置;但是爲什麼還得在struts.xml中進行配置呢?
僅僅是增強可讀性,這是我的理解;
原理分析:
1 監聽器的啓動順序在過濾器器啓動之前;這是Struts2和Spring整合的前提:爲什麼呢?
因爲如果過濾器先啓動,即struts先啓動,啓動後他的基本配置的bean都加載了即創建了,spring才啓動;
同時struts中配置的SpringObjectFactory被指定爲實現類,可是spring容器都沒有啓動,肯定報錯;
2 struts的插件機制,即struts的配置文件的加載順序,struts-default.xml,struts-plugin.xml,struts.xml
先後被加載,並且相同內容後者覆蓋前者,因此我們才能夠覆蓋struts默認的bean創建機制,即Result,Action,Interceptor
的創建才能交給Spring進行創建;維護;
注意:OpenSessionInViewFilter攔截器要配置在StrutsPrepareAndExecuteFilter之前,即要在struts啓動之前開啓session;
這是由於StrutsPrepareAndExecuteFilter在處理完請求後沒有放行機制----況且struts在調用service層處理的時候就需要事務了;一級緩存和二級緩存稱爲對象緩存
對象緩存(對象在數據庫中有對應的表)
1、對象緩存是根據主鍵進行標示的
2、對象緩存會把數據庫表中相應的行的所有的字段全部加載到對象中
如果一個表有50個字段,而需要的是5個字段,這樣利用對象緩存
會把很多沒有用的數據加載到緩存中
查詢緩存是數據緩存
數據緩存
1、數據緩存是根據hql語句進行標示的
2、能按照需求緩存數據
3、實現
1、基於二級緩存
2、在配置文件中開啓查詢緩存
<property name="cache.use_query_cache">true</property>
3、利用session.createQuery方法,把數據放入到查詢緩存中
SQL 優化
(1)選擇最有效率的表名順序(只在基於規則的優化器中有效):
Oracle的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作爲基礎表。如果有3個以上的表連接查詢, 那就需要選擇交叉表(intersection table)作爲基礎表, 交叉表是指那個被其他表所引用的表。
(2)WHERE子句中的連接順序:
Oracle採用自下而上的順序解析WHERE子句,根據這個原理,表之間的連接必須寫在其他WHERE條件之前, 那些可以過濾掉最大數量記錄的條件必須寫在WHERE子句的末尾。
(3)SELECT子句中避免使用‘*’:
Oracle在解析的過程中, 會將‘*’依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 這意味着將耗費更多的時間。
(4)使用DECODE函數來減少處理時間:
使用DECODE函數可以避免重複掃描相同記錄或重複連接相同的表。
(5)整合簡單,無關聯的數據庫訪問:
如果你有幾個簡單的數據庫查詢語句,你可以把它們整合到一個查詢中(即使它們之間沒有關係)。
(6)用TRUNCATE替代DELETE:
當刪除表中的記錄時,在通常情況下, 回滾段(rollback segments ) 用來存放可以被恢復的信息. 如果你沒有COMMIT事務,ORACLE會將數據恢復到刪除之前的狀態(準確地說是恢復到執行刪除命令之前的狀況) 而當運用TRUNCATE時, 回滾段不再存放任何可被恢復的信息。當命令運行後,數據不能被恢復.因此很少的資源被調用,執行時間也會很短。(TRUNCATE只在刪除全表適用,TRUNCATE是DDL不是DML)。
(7)使用表的別名(Alias):
當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上。這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。
(8)用>=替代>:
高效:SELECT * FROM EMP WHERE DEPTNO >=4 低效: SELECT * FROM EMP WHERE DEPTNO >3
兩者的區別在於,前者DBMS將直接跳到第一個DEPT等於4的記錄而後者將首先定位到DEPTNO=3的記錄並且向前掃描到第一個DEPT大於3的記錄。
(9)SQL語句用大寫的;因爲Oracle總是先解析SQL語句,把小寫的字母轉換成大寫的再執行。
(10)用Where子句替換HAVING子句:
避免使用HAVING子句,HAVING 只會在檢索出所有記錄之後纔對結果集進行過濾。這個處理需要排序,總計等操作。如果能通過WHERE子句限制記錄的數目,那就能減少這方面的開銷。