2.調用有簡單返回值的java方法
2.1、dwr.xml的配置 配置同1.1 <dwr> <allow> <create creator="new" javascript="testClass" > <param name="class" value="com.dwr.TestClass" /> <include method="testMethod2"/> </create> </allow> </dwr> 2.2、javascript中調用 首先,引入javascript腳本 其次,編寫調用java方法的javascript函數和接收返回值的回調函數 Function callTestMethod2(){ testClass.testMethod2(callBackFortestMethod2); } Function callBackFortestMethod2(data){ //其中date接收方法的返回值 //可以在這里對返回值進行處理和顯示等等 alert("the return value is " + data); } 其中callBackFortestMethod2是接收返回值的回調函數 3、調用有簡單參數的java方法 3.1、dwr.xml的配置 配置同1.1 <dwr> <allow> <create creator="new" javascript="testClass" > <param name="class" value="com.dwr.TestClass" /> <include method="testMethod3"/> </create> </allow> </dwr> 3.2、javascript中調用 首先,引入javascript腳本 其次,編寫調用java方法的javascript函數 Function callTestMethod3(){ //定義要傳到java方法中的參數 var data; //構造參數 data = “test String”; testClass.testMethod3(data); } 4、調用返回JavaBean的java方法 4.1、dwr.xml的配置 <dwr> <allow> <create creator="new" javascript="testClass" > <param name="class" value="com.dwr.TestClass" /> <include method="testMethod4"/> </create> <convert converter="bean" match=""com.dwr.TestBean"> <param name="include" value="username,password" /> </convert> </allow> </dwr> <creator>標簽負責公開用于Web遠程的類和類的方法,<convertor>標簽則負責這些方法的參數和返回類型。convert元素的作用是告訴DWR在服務器端Java 對象表示和序列化的JavaScript之間如何轉換數據類型。DWR自動地在Java和JavaScript表示之間調整簡單數據類型。這些類型包括Java原生類型和它們各自的封裝類表示,還有String、Date、數組和集合類型。DWR也能把JavaBean轉換成JavaScript 表示,但是出于安全性的原因,要求顯式的配置,<convertor>標簽就是完成此功能的。converter="bean"屬性指定轉換的方式采用JavaBean命名規范,match=""com.dwr.TestBean"屬性指定要轉換的javabean名稱,<param>標簽指定要轉換的JavaBean屬性。 4.2、javascript中調用 首先,引入javascript腳本 其次,編寫調用java方法的javascript函數和接收返回值的回調函數 Function callTestMethod4(){ testClass.testMethod4(callBackFortestMethod4); } Function callBackFortestMethod4(data){ //其中date接收方法的返回值 //對于JavaBean返回值,有兩種方式處理 //不知道屬性名稱時,使用如下方法 for(var property in data){ alert("property:"+property); alert(property+":"+data[property]); } //知道屬性名稱時,使用如下方法 alert(data.username); alert(data.password); } 其中callBackFortestMethod4是接收返回值的回調函數 5、調用有JavaBean參數的java方法 5.1、dwr.xml的配置 配置同4.1 <dwr> <allow> <create creator="new" javascript="testClass" > <param name="class" value="com.dwr.TestClass" /> <include method="testMethod5"/> </create> <convert converter="bean" match="com.dwr.TestBean"> <param name="include" value="username,password" /> </convert> </allow> </dwr> 5.2、javascript中調用 首先,引入javascript腳本 其次,編寫調用java方法的javascript函數 Function callTestMethod5(){ //定義要傳到java方法中的參數 var data; //構造參數,date實際上是一個object data = { username:"user", password:"password" } testClass.testMethod5(data); } 6、調用返回List、Set或者Map的java方法 6.1、dwr.xml的配置 配置同4.1 <dwr> <allow> <create creator="new" javascript="testClass" > <param name="class" value="com.dwr.TestClass" /> <include method="testMethod6"/> </create> <convert converter="bean" match="com.dwr.TestBean"> <param name="include" value="username,password" /> </convert> </allow> </dwr> 注意:如果List、Set或者Map中的元素均為簡單類型(包括其封裝類)或String、Date、數組和集合類型,則不需要<convert>標簽。 6.2、javascript中調用(以返回List為例,List的元素為TestBean) 首先,引入javascript腳本 其次,編寫調用java方法的javascript函數和接收返回值的回調函數 Function callTestMethod6(){ testClass.testMethod6(callBackFortestMethod6); } Function callBackFortestMethod6(data){ //其中date接收方法的返回值 //對于JavaBean返回值,有兩種方式處理 //不知道屬性名稱時,使用如下方法 for(var i=0;i<data.length;i++){ for(var property in data){ alert("property:"+property); alert(property+":"+data[property]); } } //知道屬性名稱時,使用如下方法 for(var i=0;i<data.length;i++){ alert(data[i].username); alert(data[i].password); } } 7、調用有List、Set或者Map參數的java方法 7.1、dwr.xml的配置 <dwr> <allow> <create creator="new" javascript="testClass" > <param name="class" value="com.dwr.TestClass" /> <include method="testMethod7"/> </create> <convert converter="bean" match="com.dwr.TestBean"> <param name="include" value="username,password" /> </convert> </allow> <signatures> <![CDATA[ import java.util.List; import com.dwr.TestClass; import com.dwr.TestBean; TestClass.testMethod7(List<TestBean>); ]]> </signatures> </dwr> <signatures>標簽是用來聲明java方法中List、Set或者Map參數所包含的確切類,以便java代碼作出判斷。 7.2、javascript中調用(以返回List為例,List的元素為TestBean) 首先,引入javascript腳本 其次,編寫調用java方法的javascript函數 Function callTestMethod7(){ //定義要傳到java方法中的參數 var data; //構造參數,date實際上是一個object數組,即數組的每個元素均為object data = [ { username:"user1", password:"password2" }, { username:"user2", password:" password2" } ]; testClass.testMethod7(data); } 注意: 1、對于第6種情況,如果java方法的返回值為Map,則在接收該返回值的javascript回調函數中如下處理: function callBackFortestMethod(data){ //其中date接收方法的返回值 for(var property in data){ var bean = data[property]; alert(bean.username); alert(bean.password); } } 2、對于第7種情況,如果java的方法的參數為Map(假設其key為String,value為TestBean),則在調用該方法的javascript函數中用如下方法構造要傳遞的參數: function callTestMethod (){ //定義要傳到java方法中的參數 var data; //構造參數,date實際上是一個object,其屬性名為Map的key,屬性值為Map的value data = { "key1":{ username:"user1", password:"password2" }, "key2":{ username:"user2", password:" password2" } }; testClass.testMethod(data); } 并且在dwr.xml中增加如下的配置段 <signatures> <![CDATA[ import java.util.List; import com.dwr.TestClass; import com.dwr.TestBean; TestClass.testMethod7(Map<String,TestBean>); ]]> </signatures> 3、由以上可以發現,對于java方法的返回值為List(Set)的情況,DWR將其轉化為Object數組,傳遞個javascript;對于java方法的返回值為Map的情況,DWR將其轉化為一個Object,其中Object的屬性為原Map的key值,屬性值為原Map相應的value值。 4、如果java方法的參數為List(Set)和Map的情況,javascript中也要根據3種所說,構造相應的javascript數據來傳遞到java中。
FreeMarker的指令的文件就稱為模板(Template)。
模板設計者不關心數據從那兒來,只知道使用已經建立的數據模型。 數據模型由程序員編程來創建,向模板提供變化的信息,這些信息來自于數據庫、文件,甚至于在程序中直接生成。 數據類型: 一、基本: 1、scalars:存儲單值 字符串:簡單文本由單或雙引號括起來。 數字:直接使用數值。 日期:通常從數據模型獲得 布爾值:true或false,通常在<#if …>標記中使用 2、hashes:充當其它對象的容器,每個都關聯一個唯一的查詢名字 具有一個唯一的查詢名字和他包含的每個變量相關聯。 3、sequences:充當其它對象的容器,按次序訪問 使用數字和他包含的每個變量相關聯。索引值從0開始。 4、集合變量: 除了無法訪問它的大小和不能使用索引來獲得它的子變量:集合可以看作只能由<#list...>指令使用的受限sequences。 5、方法:通過傳遞的參數進行計算,以新對象返回結果 方法變量通常是基于給出的參數計算值在數據模型中定義。 6、用戶自定義FTL指令:宏和變換器 7、節點 節點變量表示為樹型結構中的一個節點,通常在XML處理中使用。 模板: 使用FTL(freeMarker模板語言)編寫 組成部分 一、整體結構 1、注釋:<#--注釋內容-->,不會輸出。 2、文本:直接輸出。 3、interpolation:由 ${var} 或 #{var} 限定,由計算值代替輸出。 4、FTL標記 二、指令: freemarker指令有兩種: 1、預定義指令:引用方式為<#指令名稱> 2、用戶定義指令:引用方式為<@指令名稱>,引用用戶定義指令時須將#換為@。 注意:如果使用不存在的指令,FreeMarker不會使用模板輸出,而是產生一個錯誤消息。 freemarker指令由FTL標記來引用,FTL標記和HTML標記類似,名字前加#來加以區分。如HTML標記的形式為<h1></h1>則FTL標記的形式是<#list></#list>(此處h1標記和list指令沒有任何功能上的對應關系,只是做為說明使用一下)。 有三種FTL標記: 1)、開始標記:<#指令名稱> 2)、結束標記:</#指令名稱> 3)、空標記:<#指令名稱/> 注意: 1) FTL會忽略標記之中的空格,但是,<#和指令 與 </#和指令 之間不能有空格。 2) FTL標記不能夠交叉,必須合理嵌套。每個開始標記對應一個結束標記,層層嵌套。 如: <#list> <li> ${數據} <#if 變量> <p>game over!</p> </#if> </li> </#list> 注意事項: 1)、FTL對大小寫敏感。所以使用的標記及interpolation要注意大小寫。name與NAME就是不同的對象。<#list>是正確的標記,而<#List>則不是。 2)、interpolation只能在文本部分使用,不能位于FTL標記內。如<#if ${var}>是錯誤的,正確的方法是:<#if var>,而且此處var必須為布爾值。 3)、FTL標記不能位于另一個FTL標記內部,注釋例外。注釋可以位于標記及interpolation內部。 三、表達式 1、直接指定值: 1-1、字符串: 由雙引號或單引號括起來的字符串,其中的特殊字符(如' " \等)需要轉義。 1-2、raw字符串: 有一種特殊的字符串稱為raw字符串,被認為是純文本,其中的\和{等不具有特殊含義,該類字符串在引號前面加r,下面是一個例子: ${r"/${data}"year""}屏幕輸出結果為:/${data}"year" 轉義 含義 序列 \" 雙引號(u0022) \' 單引號(u0027) \\ 反斜杠(u005C) \n 換行(u000A) \r Return (u000D) \t Tab (u0009) \b Backspace (u0008) \f Form feed (u000C) \l < \g > \a & \{ { \xCode 4位16進制Unicode代碼 1-3、數字:直接輸入,不需要引號 1)、精度數字使用“.”分隔,不能使用分組符號 2)、目前版本不支持科學計數法,所以“1E3”是錯誤的 3)、不能省略小數點前面的0,所以“.5”是錯誤的 4)、數字8、+8、08和8.00都是相同的 1-4、布爾值:true和false,不使用引號 1-5、序列:由逗號分隔的子變量列表,由[]方括號限定。 1)、子變量列表可以是表達式 2)、可以使用數字范圍定義數字序列,<b>不需要方括號限定</b>,例如2..5等同于[2, 3, 4, 5],但是更有效率,可以定義反遞增范圍如:5..2。 1-6、散列(hash) 1)、由逗號分隔的鍵/值列表,由{}大括號限定,鍵和值之間用冒號分隔,如:{"key1":valu1,"key2":"character string"....} 2)、鍵和值都是表達式,但是鍵必須是字符串。 2、獲取變量: 2-1、頂層變量:${變量名} 變量名只能是字母、數字、下劃線、$、#、@ 的組合,且不能以數字開頭。 2-2、散列:有兩種方法 1)、點語法:變量名字和頂層變量的名字受同樣的限制 2)、方括號語法:變量名字無限制,可以是任意的表達式的結果 book.author.name book.author.["name"] book["author"].name book["author"]["name"] 以上是等價的。 2-3、序列:使用散列的方括號語法獲取變量,方括號中的表達式結果必須為數字。注意:第一個項目的索引為0。可以使用 [startindex..endindex]語法獲取序列片段。 2-4、特殊變量:FreeMarker內定義變量,使用.variablename語法訪問。 3、字符串操作 3-1、interpolation:使用${}或#{}在文本部分插入表達式的值,例如: ${"hello${username}!"} ${"${username}${username}${username}"} 也可以使用+來獲得同樣的結果: ${"hello"+username+"!"} ${username+username+username} 注意:${}只能用于文本部分而不能出現于標記內。 <#if ${user.login}>或<#if "${user.login}">都是錯誤的; <#if user.login>是正確的。 本例中user.login的值必須是布爾類型。 3-2、子串: 舉例說明:假如user的值為"Big Joe" ${user[0]}${user[4]}結果是:BJ ${user[1..4]}結果是:ig J 4、序列操作 4-1、連接操作:可以使用+來操作,例如: ["title","author"]+["month","day"] 5、散列操作 5-1、連接操作:可以使用+來操作,如果有相同的KEY,則右邊的值會替代左邊的值,例如: {"title":散列,"author":"emma"}+{"month":5,"day":5}+{"month":6}結果month的值就是6。 6、算術運算 6-1、操作符:+、-、*、/、% 除+號以外的其他操作符兩邊的數據,必須都是數字類型。 如果+號操作符一邊有一個字符型數據,會自動將另一邊的數據轉換為字符型數據,運算結果為字符型數據。 6-2、比較操作符: 1)、= 2)、== 3)、!= 4)、< 5)、<= 6)、> 7)、>= 1-3的操作符,兩邊的數據類型必須相同,否則會產生錯誤 4-7的操作符,對于日期和數字可以使用,字符串不可以使用。 注意: 1)、FreeMarker是精確比較,所以"x" "x " "X"是不等的。 2)、因為<和>對FTL來說是開始和結束標記,所以,可以用兩種方法來避免這種情況: 一種是使用括號<#if (a<b)> 另一是使用替代輸出,對應如下: < lt <= lte > gt >= gte 6-3、邏輯操作符:只能用于布爾值,否則會出現錯誤。 &&(and)與運算 ||(or)或運算 !(not)非運算 6-4、內建函數:使用方法類似于訪問散列的子變量,只是使用?代替.例如:${test?upper_case?html} 常用的內建函數列舉如下: 1)、字符串使用: html:對字符串進行HTML編碼 cap_first:字符串第一個字母大寫 lower_first:字符串第一個字母小寫 upper_case:將字符串轉換成大寫 trim:去年字符前后的空白字符 2)、序列使用: size:獲得序列中元素的數目 3)、數字使用: int:取得數字的整數部分 7、操作符的優先順序: 后綴:[subbarName][subStringRange].(mathodParams) 一元:+expr、-expr、! (not) 內建:? 乘法:*、/、% 加法:+、- 關系:<、<=、>、>= (lt、lte、gt、gte) 相等:=、==、!= 邏輯與:&& (and) 邏輯或:|| (or) 數字范圍:.. 四、interpolation inperpolation只能用于文本,有兩種類型:通用interpolation及數字interpolation 1、通用interpolation 如${expr} 1-1、插入字符串值:直接輸出表達式結果。 1-2、插入數字值:根據缺省格式(由setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string來格式化單個interpolation 如: <#setting number_format="currency" /> <#assign answer=42 /> ${answer} <#-- ¥42.00 --> ${answer?string} <#-- ¥42.00 --> ${answer?string.number} <#-- 42 --> ${answer?string.currency} <#-- ¥42.00 --> ${answer?string.percent} <#-- 42,00% --> 1-3、插入日期值:根據缺省格式(由setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string來格式化單個interpolation 如: ${lastupdata?string("yyyy-MM-dd HH:mm:ss zzzz")} <#-- 2003-04-08 21:24:44 Pacific Daylight Time --> ${lastupdata?string("EEE,MMM d, ''yy")} <#-- tue,Apr 8, '03 --> ${lastupdata?string("EEEE,MMMM dd, yyyy,hh:mm:ss a '('zzz')'")} <#-- Tuesday,April 08, 2003, 09:24:44 PM (PDT)--> 1-4、插入布爾值:根據缺省格式(由setting指令設置)將表達式結果轉換成文本輸出;可以使用內建函數string來格式化單個interpolation 如: <#assign foo=ture /> ${foo?string("yes","no")} <#-- yes --> 2、數字interpolation: 有兩種形式: 1)、#{expr} 2)、#{expr;format}:format可以用來格式化數字,format可以是如下: mX:小數部分最小X位 MX:小數部分最大X位 例如: <#assign x=2.582 /> <#assign y=4 /> #{x;M2} <#-- 2.58 --> #{y;M2} <#-- 4 --> #{x;m1} <#-- 2.582 --> #{y;m1} <#-- 4.0 --> #{x;m1M2} <#-- 2.58 --> #{y;m1M2} <#-- 4.0 --> 雜項 一、用戶定義指令 宏和變換器變量是兩種不同類型的用戶自定義指令,他們的區別是: 宏可以在模板中用macro指令來定義 變換器是在模板外由程序定義 1、宏:和某個變量關聯的模板片段,以便在模板中通過用戶自定義指令使用該變量 1-1、基本用法: 例如: <#macro greet> <font size="+2"> Hello JOE!</font> </#macro> 使用時: <@greet></@greet> 如果沒有體內容也可以用 <@greet /> 1-2、變量: 1)、可以在宏定義之后定義參數,宏參數是局部變量,只在宏定義中有效。如: <#macro greet person> <font size="+2"> Hello ${person}!</font> </#macro> 使用時: <@greet person="emma"> and <@greet person="LEO"> 輸出為: <font size="+2"> Hello emma!</font> <font size="+2"> Hello LEO!</font> 注意:宏的參數是FTL表達式,所以,person=emma和上面的例子中具有不同的意義,這意味著將變量emma的值傳給person,這個值可能是任意一種數據類型,甚至是一個復雜的表達式。 宏可以有多個參數,使用時參數的次序是無關的,但是只能使用宏中定義的參數,并且對所有參數賦值。如: <#macro greet person color> <font size="+2" color="${color}"> Hello ${person}!</font> </#macro> 使用時: <@greet color="black" person="emma" />正確 <@greet person="emma" />錯誤,color沒有賦值,此時,如果在定義宏時為color定義缺省值<#macro greet person color="black">這樣的話,這個使用方法就是正確的。 <@greet color="black" person="emma" bgcolor="yellow" />錯誤,宏greet定義中未指定bgcolor這個參數 2、嵌套內容: 2-1、自定義指令可以有嵌套內容,使用<#nested>指令,執行自定義指令開始和結束標記之間的模板片段。例如: <#macro greet> <p> <#nested> </p> </#macro> <@greet>hello Emma!</@greet> 輸出為 <p>hello Emma!</p> 2-2、<#nested>指令可以被多次調用,例如 <#macro greet> <p> <#nested> <#nested> <#nested> <#nested> </p> </#macro> <@greet>hello Emma!</@greet> 輸出為 <p> hello Emma! hello Emma! hello Emma! hello Emma! </p> 2-3、嵌套的內容可以是有效的FTL,例如: <#macro welcome> <p> <#nested> </p> </#macro> <#macro greet person color="black"> <font size="+2" color="${color}"> Hello ${person}!</font> </#macro> <@welcome> <@greet person="Emma" color="red" /> <@greet person="Andrew" /> <@greet person="Peter" /> </@welcome> 輸出為: <p> <font size="+2" color="red"> Hello Emma!</font> <font size="+2" color="black"> Hello Andrew!</font> <font size="+2" color="black"> Hello Peter!</font> </p> 2-4、宏定義中的局部變量對嵌套內容是不可見的,例如: <#macro repeat count> <#local y="test" /> <#list 1..count as x> ${y}${count}/${x}:<#nested /> </#list> </#macro> <@repeat count=3> ${y?default("?")} ${x?default("?")} ${count?default("?")} </@repeat> 輸出結果為 test 3/1:??? test 3/2:??? test 3/3:??? 2-5、在宏定義中使用循環變量,通常用來重復嵌套內容,基本用法為:作為nested指令的參數,傳遞循環變量的實際值,而在調用自定義指令時,在標記的參數后面指定循環變量的名字。 例如: <#macro repeat count> <#list 1..count as x> <#nested x,x/2,x==count /> </#list> </#macro> <@repeat count=4;c,halfc,last> ${c}. ${halfc} <#if last> last! </#if> </@repeat> 輸出結果是 1. 0.5 2. 1 3. 1.5 4. 2last! 注意:指定循環變量的數目和用戶定義指令開始標記指定的不同不會有問題 調用時,少指定循環變量,多指定的值會不見 調用時,多指定循環變量,多余的循環變量不會被創建 二、在模板中定義變量 1、在模板中定義的變量有三種類型 1-1、plain變量:可以在模板的任何地方訪問,包括使用include指令插入的模板,使用assign指令創建和替換。 1-2、局部變量:在宏定義體中有效,使用local指令創建和替換。 1-3、循環變量:只能存在于指令的嵌套內容,由指令(如list)自動創建。 注意: 1)、宏的參數是局部變量,不是循環變量。 2)、局部變量隱藏同名的plain變量 3)、循環變量隱藏同名的plain變量和局部變量。 例如: <#assign x="plain"> 1. ${x} <#-- plain --> <@test /> 6. ${x} <#list ["loop"] as x> 7. ${x} <#-- loop --> <#assign x="plain2"> 8. ${x} <#-- loop --> </#list> 9. ${x} <#-- plain2 --> <#macro test> 2. ${x} <#-- plain --> <#local x="local"> 3. ${x} <#-- local --> <#list ["loop"] as x> 4. ${x} <#-- loop --> </#list> 5. ${x} <#-- local --> </#macro> 4)、內部循環變量隱藏同名的外部循環變量 <#list ["loop1"] as x> ${x} <#-- loop1 --> <#list ["loop2"] as x> ${x} <#-- loop2 --> <#list ["loop3"] as x> ${x} <#-- loop3 --> </#list> ${x} <#-- loop2 --> </#list> ${x} <#-- loop1 --> </#list> 5)、模板中的變量會隱藏數據模型中的同名變量,如果需訪問數據模型中的變量,使用特殊變量global。 例如: 假設數據模型中的user值為Emma <#assign user="Man"> ${user} <#-- Man --> ${.global.user} <#-- Emma -->
一:
myeclipse 傻瓜式的完成spring和hibernate的載入; 注意需要把lib里面的asm-XX和commons-collections-XX 低版本的刪除掉,因為載入spring和hibernate的時候會裝在2個不一樣的版本,包沖突. 記得把dwr的包放進去 數據庫用mysql. CREATE TABLE `book` ( `id` int(11) NOT NULL auto_increment, `name` varchar(11) default NULL, `isbm` varchar(11) default NULL, `author` varchar(11) default NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=gbk; id為自動增長 二: 完成上步后用hibernate的反向機制,完成數據庫表的映射 如下: AbstractBook.java package com; /** public abstract class AbstractBook implements java.io.Serializable { // Fields private Integer id; // Constructors /** default constructor */ /** full constructor */ // Property accessors public Integer getId() { public void setId(Integer id) { public String getName() { public void setName(String name) { public String getIsbm() { public void setIsbm(String isbm) { public String getAuthor() { public void setAuthor(String author) { } Book.java package com; // Generated by MyEclipse Persistence Tools /** // Constructors /** default constructor */ /** full constructor */ } BookDAO.java package com; import java.util.List; /** public class BookDAO extends HibernateDaoSupport { protected void initDao() { public void save(Book transientInstance) { public void delete(Book persistentInstance) { public Book findById(java.lang.Integer id) { public List findByExample(Book instance) { public List findByProperty(String propertyName, Object value) { public List findAll() { public Book merge(Book detachedInstance) { public void attachDirty(Book instance) { public void attachClean(Book instance) { public static BookDAO getFromApplicationContext(ApplicationContext ctx) { web.xml <?xml version="1.0" encoding="UTF-8"?> <servlet-mapping>
Book.hbm.xml <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Mapping file autogenerated by MyEclipse Persistence Tools --> <hibernate-mapping> <class name="com.Book" table="book" catalog="test"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native" /> </id> <property name="name" type="java.lang.String"> <column name="name" length="11" /> </property> <property name="isbm" type="java.lang.String"> <column name="isbm" length="11" /> </property> <property name="author" type="java.lang.String"> <column name="author" length="11" /> </property> </class> </hibernate-mapping> BookManageService.java 暴露給dwr使用的接口 package com; import java.util.List; public interface BookManageService { public List<Book> getAllBooks(); public List<Book> getBookByName(String name); public void updateBook(Book book); public void addBook(Book book); public void deleteBook(Integer id); BookManageServiceImpl.java 接口的實現類 package com; import java.util.List; public class BookManageServiceImpl implements BookManageService { private BookDAO bookDAO; public BookDAO getBookDAO() { public void setBookDAO(BookDAO bookDAO) { public void deleteBook(Integer id) {
return bookDAO.findAll();
return bookDAO.findByProperty(name, name); public void updateBook(Book book) {
applicationContext.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"> </property> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource"> <ref bean="dataSource" /> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect"> org.hibernate.dialect.MySQLDialect </prop> <prop key="hibernate.show_sql">true</prop> </props> </property> <property name="mappingResources"> <list> <value>com/Book.hbm.xml</value> </list> </property> </bean> <bean id="BookDAO" class="com.BookDAO"> <property name="sessionFactory"> <ref bean="sessionFactory" /> </property> </bean> <bean id="bookManageServiceTarget" class="com.BookManageServiceImpl"> <property name="bookDAO"> <ref bean="BookDAO" /> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory"> <ref bean="sessionFactory" /> </property> </bean> <bean id="bookManageService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="bookManageServiceTarget" /> <property name="transactionAttributes"> <props> <prop key="add*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop> </props> </property> </bean> </beans> dwr.xml <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr//dwr20.dtd"> <dwr> <allow> <convert converter="bean" match="com.Book" /> <create creator="spring" javascript="BookManageService"> <param name="beanName" value="bookManageService" /> <include method="getAllBooks" /> <include method="getBookByName" /> <include method="updateBook" /> <include method="addBook" /> <include method="deleteBook" /> </create> </allow> </dwr> index.jsp
<script type="text/javascript">
<tr>
1、addRows 增添數據行到指定的table
方法基本語法:dwr.util.addRows(id, array, cellfuncs, [options]); * id:table 的 id (最好是一個tbody的id) * array: 需要被填充到table里的數據對象,可以是數組,集合等對象,每一個數組元素對應table的一行 * cellfuncs: function數組 每個元素對應table某一列數據的取得方式 * options: 包含幾個屬性的對象(可選) options:(來源:www.iocblog.net) # 屬性rowCreator: 一個function,默認返回document.createElement("tr"),可以編輯返回自定義的tr(比如不同的樣式) # 屬性cellCreator: 一個function,默認返回document.createElement("td"),可以編輯返回自定義的tr(比如不同的樣式) # 屬性escapeHtml: 是否轉義<,>,&,",' 當所有call back function 都需要轉義,則可以使用dwr.util.setEscapeHtml(false) 2、removeAllRows 把指定table的所有行都移除 方法基本語法:dwr.util.removeAllRows(id); * id:table 的 id (最好是一個tbody的id) 3、byId 你可以把它看成是document.getElementById()的替代版,如果指定的id有多個匹配項,就會返回一個element數組 方法基本語法:dwr.util.byId(id) 另:在引入util.js的環境下,你還可以使用$(id)代替document.getElementById(),但是為了不和Prototype相沖突,還是建議各位使用 byId。 4、getValue 取得html 頁面元素的value 方法基本語法:dwr.util.getValue(id); 5、getText 用法和getValue方法相同,唯一的不同在于getText是用來取得下拉框列表的Text值,而非Value 6、getValues 方法基本語法:dwr.util.getValues(object) ; 參數是一個擁有多個屬性的javascript object,屬性名稱是html頁面元素的id,屬性value為html頁面元素的value,該方法不返回任何東西,而是改變了object的屬性值。 7、setValue 設置html 頁面元素的value 方法基本語法:dwr.util.setValue(id, value [, options]) ; 如果id參數指定的頁面元素是select列表,該列表與value參數值相匹配的option選項會處于選中狀態。 8、setValues 方法基本語法:dwr.util.setValues(object) ; 參數是一個擁有多個屬性的javascript object,屬性名稱是html頁面元素的id,屬性value為html頁面元素的value 9、addOptions 方法基本語法:dwr.util.addOptions(...); 有多種調用方式: # dwr.util.addOptions(id,["first","second","third"]) id參數指定的頁面元素可以是ol、ul或select,String數組將被set到id指定的頁面元素 # dwr.util.addOptions(id,[{name:"first",value:"1"},{name:"second",value:"2"},{name:"third",value:"3"}],"value","name") 這種方式只對應select的情形,如上所述,Object數組里每個元素的value屬性值將被set到option的value里,name屬性將被set到option的text里。 如果沒有第四個參數,將會把value屬性值同時set到option的value和text里。 # dwr.util.addOptions(id,{first:"1",second:"1",third:"3"}) 這種方式也只對應select的情形,第二個參數是一個Object,屬性名set到option的value里,屬性值set到option的text里 10、removeAllOptions 方法基本語法:dwr.util.removeAllOptions(id); 除去所有動態加載的Options或列表項,與addOptions配合使用 11、onReturn 方法基本語法:dwr.util.onReturn(event, func) 當輸入回車時,調用func名指定的方法 12、useLoadingMessage 方法基本語法:dwr.util.useLoadingMessage(); 顯示一個正在加載的圖片。必須在頁面loaded以后調用 一: <?xml version="1.0" encoding="UTF-8"?> <servlet-mapping> <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr//dwr20.dtd"> <dwr> 在web-inf下新建applicationContext-dwr.xml文件,內容如下 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans default-autowire="byName"> <bean name="dwrUserAccess" class="com.dwr.DWRUserAccess"></bean> </beans> OK。
DWR(Direct Web Remoting)是一個WEB遠程調用框架.利用這個框架可以讓AJAX開發變得很簡單.利用DWR可以在客戶端利用JavaScript直接調用服務端的Java方法并返回值給JavaScript就好像直接本地客戶端調用一樣(DWR根據Java類來動態生成JavaScrip代碼).它的最新版本DWR0.6添加許多特性如:支持Dom Trees的自動配置,支持Spring(JavaScript遠程調用spring bean),更好瀏覽器支持,還支持一個可選的commons-logging日記操作.
以上摘自open-open,它通過反射,將java翻譯成javascript,然后利用回調機制,輕松實現了javascript調用Java代碼。 其大概開發過程如下: 1.編寫業務代碼,該代碼是和dwr無關的。 2.確認業務代碼中哪些類、哪些方法是要由javascript直接訪問的。 3.編寫dwr組件,對步驟2的方法進行封裝。 4.配置dwr組件到dwr.xml文件中,如果有必要,配置convert,進行java和javascript類型互轉。 5.通過反射機制,dwr將步驟4的類轉換成javascript代碼,提供給前臺頁面調用。 5.編寫網頁,調用步驟5的javascript中的相關方法(間接調用服務器端的相關類的方法),執行業務邏輯,將執行結果利用回調函數返回。 6.在回調函數中,得到執行結果后,可以繼續編寫業務邏輯的相關javascript代碼。 下面以用戶注冊的例子,來說明其使用。(注意,本次例子只是用于演示,說明DWR的使用,類設計并不是最優的)。 1.先介紹下相關的Java類 User: 用戶類, public class User { //登陸ID,主鍵唯一 private String id; //姓名 private String name; //口令 private String password; //電子郵件 private String email; //以下包含getXXX和setXXX方法 ....... } UserDAO:實現User的數據庫訪問,這里作為一個演示,編寫測試代碼 public class UserDAO { //存放保存的數據 private static Map dataMap = new HashMap(); //持久用戶 public boolean save(User user) { if (dataMap.containsKey(user.getId())) return false; System.out.println("下面開始保存用戶"); System.out.println("id:"+user.getId()); System.out.println("password:"+user.getPassword()); System.out.println("name:"+user.getName()); System.out.println("email:"+user.getEmail()); dataMap.put(user.getId(), user); System.out.println("用戶保存結束"); return true; } //查找用戶 public User find(String id) { return (User)dataMap.get(id); } } DWRUserAccess:DWR組件,提供給javascript訪問的。 public class DWRUserAccess { UserDAO userDAO = new UserDAO(); public boolean save(User user) { return userDAO.save(user); } public User find(String id) { return userDAO.find(id); } } 下面說明下程序執行的流程 1.用戶在頁面上輸入相關注冊信息,id、name、password、email,點擊“提交”按鈕 2.javascript代碼開始執行,根據用戶填寫相關信息,通過dwr提供的DWRUserAccess.js里save的方法,調用服務器端的DWRUserAccess類save方法,將注冊信息保存。 3.通過DWRUserAccess.jsp里的find方法,調用服務器端DWRUserAccess類里的find方法,執行用戶信息查找。 注意,在以上的執行過程中,DWRUserAccess是供DWR調用的,是DWR組件,因此需要將DWRUserAccess類配置到dwr中。 接下來講解本次dwr測試環境的配置。 1.新建一個webapp,命名為testApp 2.將dwr.jar拷貝到testApp的WEB-INF的lib目錄下 3.編譯上面的User,UserDAO,DWRUserAccess類,放到classes目錄下 4.在web.xml中配置servlet,適配路徑到dwr目錄下,如下所示 <servlet> <servlet-name>dwr-invoker</servlet-name> <display-name>DWR Servlet</display-name> <description>Direct Web Remoter Servlet</description> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>scriptCompressed</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> 以上的配置可以攔截testApp下所有指向dwr的請求,關于這個攔截器,我們會在后面介紹。 5.WEB-INF下新建一個dwr.xml文件,內容如下: < xml version="1.0" encoding="UTF-8" > <!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" "http://www.getahead.ltd.uk/dwr/dwr10.dtd"> <dwr> <allow> <create creator="new" javascript="DWRUserAccess"> <param name="class" value="test.DWRUserAccess"/> </create> <convert converter="bean" match="test.User"/> </allow> </dwr> 這里我們把DWRUserAccess配置到了dwr中,create元素中,creater="new"表示每調用一次DWRUserAccess時,需要new一個這樣的類;javascript="DWRUserAccess",表示提供給前臺頁面調用的javascirpt文件是DWRUserAccess.js。 convert元素用于數據類型轉換,即java類和javascript之間相互轉換,因為和前臺交換的是User對象,因此需要對此使用bean轉換,我們將在后面介紹這個類。 4.編寫測試的HTML頁面 test.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE>DWR測試</TITLE> <meta http-equiv=Content-Type content="text/html; charset=gb2312"> <script src="/oblog312/dwr/engine.js"></script> <script src="/oblog312/dwr/util.js"></script> <script src="/oblog312/dwr/interface/DWRUserAccess.js"></script> </HEAD> <BODY> <B>用戶注冊</B><br> ------------------------------------------------ <Br> <form name="regForm"> 登陸ID:<input type="text" name="id"><br> 口 令:<input type="password" name="password"><br> 姓 名:<input type="text" name="name"><br> 電子郵件:<input type="text" name="email"><br> <input type="button" name="submitBtn" value="提交" onclick="OnSave()"><br> </form> <br> <br><B>用戶查詢</B><br> ------------------------------------------------ <Br> <form name="queryForm"> 登陸ID:<input type="text" name="id"><br> <input type="button" name="submitBtn" value="提交" onclick="OnFind()"><br> </form> <br> </BODY> </HTML> <SCRIPT LANGUAGE="JavaScript"> <!-- function saveFun(data) { if (data) { alert("注冊成功!"); } else { alert("登陸ID已經存在!"); } } function OnSave() { var userMap = {}; userMap.id = regForm.id.value; userMap.password = regForm.password.value; userMap.name = regForm.name.value; userMap.email = regForm.email.value; DWRUserAccess.save(userMap, saveFun); } function findFun(data) { if (data == null) { alert("無法找到用戶:"+queryForm.id.value); return; } alert("找到用戶,nid:"+data.id+",npassword:"+data.password+",nname:"+data.name+",nemail:"+data.email); } function OnFind() { DWRUserAccess.find(queryForm.id.value, findFun); } //--> </SCRIPT> 以下對頁面的javascript進行解釋 <script src="/oblog312/dwr/engine.js"></script> <script src="/oblog312/dwr/util.js"></script> 這兩個是dwr提供的,用戶可以不必關心,只需要導入即可 <script src="/oblog312/dwr/interface/DWRUserAccess.js"></script> 是我們編寫的DWRUserAccess類,經dwr反射后,生成的javascript代碼,它和DWRUserAccess.java是對應的,供用戶調用,實際上我們就是通過這個js文件去調用服務器端的DWRUserAccess類的。 <SCRIPT LANGUAGE="JavaScript"> <!-- function saveFun(data) { if (data) { alert("注冊成功!"); } else { alert("用戶名已經存在!"); } } function OnSave() { var userMap = {}; userMap.id = regForm.id.value; userMap.password = regForm.password.value; userMap.name = regForm.name.value; userMap.email = regForm.email.value; DWRUserAccess.save(userMap, saveFun); } function findFun(data) { if (data == null) { alert("無法找到用戶:"+queryForm.id.value); return; } alert("找到用戶,nid:"+data.id+",npassword:"+data.password+",nname:"+data.name+",nemail:"+data.email); } function OnFind() { DWRUserAccess.find(queryForm.id.value, findFun); } //--> </SCRIPT> 這段javascirpt代碼,我們來看下OnSave函數,首先它構造一個map,將表單數據都設置到map中,然后調用DWRUserAccess.save(userMap, saveFun),執行save操作。大家可以注意到,服務器端的DWRUserAccess中的save方法是這樣的:boolean save(User user),其參數是一個User對象,返回一個boolean值;而客戶端的方法是這樣的:save(userMap,saveFun),第一個參數userMap是javascirpt中的map對象,在這里相當于服務器端的User對象(在服務器端執行時,會通過convert轉換成User對象),前面我們提到dwr是利用回調函數來返回執行結果的,第二個參數saveFun即是一個回調函數。在函數function saveFun(data)中,data是執行結果,這里是一個bool值,非常簡單的,我們通過判斷data是否為真,可以知道用戶名是否重復,用戶是否注冊成功。 看一下OnFind查找函數,執行結果在回調函數findFun(data)中,因為服務器端返回的是一個User對象,通過convert,將會轉換成javascript的一個map對象, 于是在findFun中,通過data.id、data.name、data.password、data.email我們可以輕松的訪問到這個User對象。 好了配置完畢,啟動服務器,在目錄中打入localhost/testApp/test.html。 1.在“用戶注冊”表單中,id框中輸入admin,password中輸入123456,name中輸入chenbug,email中輸入chenbug@zj.com,點擊提交按鈕,彈出對話框:“注冊成功”,在服務器后臺可以看到信息如下: 下面開始保存用戶 id:admin password:123456 name:chenbug email:chenbug@zj.com 用戶保存結束 再次點擊提交按鈕,彈出對話框“登陸ID已經存在”。 2.在“用戶查詢”對話框中,輸入登陸ID為admin,點擊提交按鈕,提示找到用戶,并顯示相關信息,輸入admin123,點擊提交按鈕,提示無法找到用戶。 至此,測試結束。 后續: 1。攔截器 uk.ltd.getahead.dwr.DWRServlet 該類攔截所有指向dwr目錄下的請求,并調用Processor的handler方法進行處理,在uk.ltd.getahead.dwr.impl.DefaultProcessor下,我們可以看到詳細的處理過程。 if (pathInfo.length() == 0 || pathInfo.equals(HtmlConstants.PATH_ROOT) || pathInfo.equals(req.getContextPath())) { resp.sendRedirect(req.getContextPath() + servletPath + HtmlConstants.FILE_INDEX); } else if (pathInfo.startsWith(HtmlConstants.FILE_INDEX)) { index.handle(req, resp); } else if (pathInfo.startsWith(HtmlConstants.PATH_TEST)) { test.handle(req, resp); } else if (pathInfo.startsWith(HtmlConstants.PATH_INTERFACE)) { iface.handle(req, resp); } else if (pathInfo.startsWith(HtmlConstants.PATH_EXEC)) { exec.handle(req, resp); } else if (pathInfo.equalsIgnoreCase(HtmlConstants.FILE_ENGINE)) { file.doFile(req, resp, HtmlConstants.FILE_ENGINE, HtmlConstants.MIME_JS); } else if (pathInfo.equalsIgnoreCase(HtmlConstants.FILE_UTIL)) { file.doFile(req, resp, HtmlConstants.FILE_UTIL, HtmlConstants.MIME_JS); } else if (pathInfo.equalsIgnoreCase(HtmlConstants.FILE_DEPRECATED)) { file.doFile(req, resp, HtmlConstants.FILE_DEPRECATED, HtmlConstants.MIME_JS); } else { log.warn("Page not found (" + pathInfo + "). In debug/test mode try viewing /[WEB-APP]/dwr/"); //$NON-NLS-1$ //$NON-NLS-2$ resp.sendError(HttpServletResponse.SC_NOT_FOUND); } 通過判斷request請求的servlet路徑,進行處理,大家可以自己去參看,這里不詳細討論。 2.bean轉換器,<convert converter="bean" match="test.User"/> 將dwr.jar解壓縮,在路徑ukltdgetaheaddwr下可以看到dwr.xml,這里配置了系統默認的一些轉換器, <converter id="bean" class="uk.ltd.getahead.dwr.convert.BeanConverter"/>即是剛才用到User類的轉換器,進入代碼我們來看看它是如何在javascript和java間進行轉換的。 打開BeanConverter代碼,定位到函數 public Object convertInbound(Class paramType, InboundVariable iv, InboundContext inctx) throws ConversionException 即是將javascript對象轉換成java對象的,其中 paramType即Class類型,在上面的例子中是test.User, InboundVariable iv,是傳入的值,通過iv.getValue可以得到傳入的javascript值串 InboundContext inctx,是入口參數上下文,用于保存轉換的后java對象。 因為前臺傳入的是一個javascript的map類型,而map肯定是以{開始和以}結束的,于是在這個函數一開始進行了判斷 if (!value.startsWith(ConversionConstants.INBOUND_MAP_START)) { throw new IllegalArgumentException(Messages.getString("BeanConverter.MissingOpener", ConversionConstants.INBOUND_MAP_START)); //$NON-NLS-1$ } if (!value.endsWith(ConversionConstants.INBOUND_MAP_END)) { throw new IllegalArgumentException(Messages.getString("BeanConverter.MissingCloser", ConversionConstants.INBOUND_MAP_START)); //$NON-NLS-1$ } javascript中,map里各個項是用逗號連接的,如var userMap = {id:'admin',password:'123456',name:'chenbug',email:'chenbug@zj.com'};而每個項的鍵值對是用冒號連接的, 在convertInbound函數的接下來的處理中,即是通過分析map字串,通過paramType構造java實例(即User類),然后通過反射,將這些鍵值對設置到java實例中,并返回。 這樣就完成了javascript到java的轉換。 另一個函數 public String convertOutbound(Object data, String varname, OutboundContext outctx) throws ConversionException 即是將java對象轉換為javascript對象(其實是聲明和賦值語句)。 Object data ,是待轉換的java對象 String varname,是javascript中的該對象的變量名 OutboundContext outctx,傳出參數上下文,用于保存轉換后的javascript值 StringBuffer buffer = new StringBuffer(); buffer.append("var "); //$NON-NLS-1$ buffer.append(varname); buffer.append("={};"); //$NON-NLS-1$ 這里聲明了map類型的變量。 即下來來的代碼即是通過反射進行變量賦值,如下 buffer.append(varname); buffer.append('.'); buffer.append(name); buffer.append('='); buffer.append(nested.getAssignCode()); buffer.append(';'); 大家可以自己去參看更多的代碼。 3.dwr本身提供了一個測試環境,大家在配置完后,可以在IE中輸入地址http://localhost/testApp/dwr/index.html,看到配置的各DWR組件,并進行相關測試。
DWR(直接Web遠程控制)項目是在Apache許可下的一個開源的解決方案,它提供了一種簡單的方式使得HTML頁面上的javascript可以訪問應用服務器的Java對象方法,是Ajax開發者可以方便使用的一個優雅框架。DWR具有一套Javascript功能集,它們把從HTML頁面調用應用服務器上的Java對象的方法簡化,操控不同類型的參數,同時保持了HTML代碼的可讀性。DWR實現的AJAX在某些方面很先進,包括動態生成javascript代碼;隱藏http協議等。
web.xml配置: <servlet> <servlet-name>dwr-invoker</servlet-name> <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> DWRServlet是DWR主Servlet,所有的/dwr/*所有請求都由這個servlet來處理。 DWRServlet init()主要做了以下工作: 1實例化DWR用到的Singleton類:AccessControl,Configuration,ConverterManager,CreatorManager,Processor 2 讀去配置文件:包括dwr.jar包中的dwr.xml,WEB-INF/dwr.xml,web.xml。
WEB-INF/dwr.xml文件告訴DWR哪些服務是要直接向JavaScript代碼公開的,以DWR的test為例:<create creator="new" javascript="JDate"> <param name="class" value="java.util.Date"/> <exclude method="getHours"/> <auth method="getMinutes" role="admin"/> <auth method="getMinutes" role="devel"/> </create> DWR會根據此自動生成對應的JDate.js文件。new意味著DWR調用類的構造函數獲得實例,還可以通過跟Spring集成達到該目的。 DWRServlet的doGet和doPost均直接調用Processor的handle方法處理,handle處理的/dwr/*請求包含以下幾類: 1 dwr/index.html,dwr/test/只能在debug模式下供調試用 2 對dwr/engine.js,dwr/util.js,dwr/deprecated.js的請求調用,直接從包中讀取相應的js文件流響應回去,
并對相應做緩存處理,緩存以一個hashmap實現。3 對dwr/interface/的請求調用,DWR生成跟Java對應的javascript存根,DWR通過ConverterManager自動調整Java和javacript的參數類型匹配。
4 客戶端javascript的實際調用,將通過handler方法的doExec執行。由此,DWR的運作流程是: 客戶端包含3類javascript腳本 1 java代碼的存根,即通過dwr/interface/調用,如<script type='text/javascript' src='dwr/interface/JDate.js'></script>
如上所述,該存根由DWR具體是CreatorManager和ConvertorManager根據dwr.xml的配置進行自動生成2 DWR核心javascript庫,至少要包含<script type='text/javascript' src='dwr/engine.js'></script>,有時也需要包含util.js,util.js封裝了prototype的若干函數,便于第三方javascript調用
3 第三方javascript,該腳本需要調用第一類存根腳本所封裝的Java代碼,第二類的engine.js等腳本為該調用提供通信支撐。這樣就達到了一個基本的RPC的目的,由于RPC本質上是同步進行,而AJAX的XMLHTTP為異步調用,為了實現異步機制,第三方javascript可以提供一個回調函數句柄傳入存根,待存根函數返回調用該回調,這樣即達到異步通信。
DWR(Direct Web Remoting)是一個開放源
![]() DWR框架基本介紹 從最簡單的角度來說,DWR 是一個引擎,可以把服務器端 Java 對象的方法公開給 JavaScript 代碼。使用 DWR 可以有效地從應用程序代碼中把 Ajax 的全部請求-響應循環消除掉。這意味著客戶端代碼再也不需要直接處理 XMLHttpRequest 對象或者服務器的響應。不再需要編寫對象的序列化代碼或者使用第三方工具才能把對象變成 XML。甚至不再需要編寫 servlet 代碼把 Ajax 請求調整成對 Java 域對象的調用。 DWR 是作為 Web 應用程序中的 servlet 部署的。把它看作一個黑盒子,這個 servlet 有兩個主要作用:首先,對于公開的每個類,DWR 動態地生成包含在 Web 頁面中的 JavaScript。生成的 JavaScript 包含存根函數,代表 Java 類上的對應方法并在幕后執行 XMLHttpRequest。這些請求被發送給 DWR,這時它的第二個作用就是把請求翻譯成服務器端 Java 對象上的方法調用并把方法的返回值放在 servlet 響應中發送回客戶端,編碼成 JavaScript。 頁面觸發eventHandler()事件,事件內部調用了AjaxService.getOptions方法,當調用完成后,利用服務端返回的數據用客戶端的populateList()方法進行數據展現。 我們通過一個簡單的DWR示例來說明如何使用DWR。 為了使用DWR,需要將DWR的jar文件拷入Web應用的WEB-INF/lib目錄中(可在http://getahead.org/dwr下載),在web.xml中增加一個servlet聲明,并創建DWR的配置文件。 服務端web.xml的配置: <!– add the Servlet for DWR –> <servlet> <servlet-name>dwr-invoker</servlet-name> <display-name>DWR Servlet</display-name> <description>Direct Web Remoter Servlet</description> <servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class> <init-param> <param-name>debug</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>activeReverseAjaxEnabled</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>initApplicationScopeCreatorsAtStartup</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>maxWaitAfterWrite</param-name> <param-value>500</param-value> </init-param> <init-param> <param-name>logLevel</param-name> <param-value>debug</param-value> </init-param> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/dwr/dwr.xml</param-value> </init-param> <load-on-startup>6</load-on-startup> </servlet> <!– Action Servlet Mapping –> <servlet-mapping> <servlet-name>dwr-invoker</servlet-name> <url-pattern>/dwr/*</url-pattern> </servlet-mapping> servlet-class值為uk.ltd.getahead.dwr.DWRServlet (如果dwr版本是1.0版本的,則必須用這個class)也可以是org.directwebremoting.servlet.DwrServlet 也可以使用精簡的一份配置:
在WEB-INF中的dwr文件夾中創建文件dwr.xml: <?xml version=”1.0″ encoding=”UTF-8″?> <!DOCTYPE dwr PUBLIC “-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN” “http://getahead.org/dwr/dwr20.dtd”> <dwr> <allow> <create creator=”new” javascript=”login”> <param name=”class” value=”com.webex.tmis.test.Login” /> <include method=”sayHello” /> </create> <convert converter=”bean” match=”com.webex.tmis.test.User”> </convert> </allow> </dwr> 服務端創建Login.java和User.java public class Login { public User sayHello(String name) { User user=new User(); user.setName(name); user.setMessage(“Hello,”+name); return user; } } public class User { private String name; private String message; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } 客戶端的配置logon.jsp:
效果如下: 做完配置后,可以加載http://127.0.0.1:8080/Tms/dwr看看哪些服務可以用。
如果你需要使用ajax完成表單提交的操作,那么你應該使用DWRUtil.getValues,參數或者是個form對象,或者是個與領域對象對應的js對象 DWR框加的高級話題 A.配置服務端方法的可調用范圍 在以上的2個DWR示例中,我們配置了兩個JAVA類,將它們的所有屬性和方法都暴露給了客戶端,為了提高安全性,我們通常在dwr.xml中應該只配置那些客戶端需要使用的方法和屬性。DWR支持對dwr.xml更細粒度的配置來提高安全性,我們先看一下與配置有關的兩個元素: create 元素
create 元素告訴 DWR 應當公開給 Ajax 請求的服務器端類,并定義 DWR 應當如何獲得要進行遠程的類的實例。這里的 creator 屬性被設置為值 new,這意味著 DWR 應當調用類的默認構造函數來獲得實例。其他的可能有:通過代碼段用 Bean 腳本框架(Bean Scripting Framework,BSF)創建實例,或者通過與 IOC 容器 Spring 進行集成來獲得實例。默認情況下,到 DWR 的 Ajax 請求會調用 creator,實例化的對象處于頁面范圍內,因此請求完成之后就不再可用。 create 的 javascript 屬性指定從 JavaScript 代碼訪問對象時使用的名稱。嵌套在 create 元素內的 param 元素指定 creator 要創建的 Java 類。最后,include 元素指定應當公開的方法的名稱。顯式地說明要公開的方法是避免偶然間允許訪問有害功能的良好實踐 —— 如果漏了這個元素,類的所有方法都會公開給遠程調用。反過來,可以用 exclude 元素指定那些想防止被訪問的方法。 convert 元素 convert 元素的作用是告訴 DWR 在服務器端 Java 對象表示和序列化的 JavaScript 之間如何轉換數據類型。DWR 自動地在 Java 和 JavaScript 表示之間調整簡單數據類型。這些類型包括 Java 原生類型和它們各自的類表示,還有 String、Date、數組和集合類型。DWR 也能把 JavaBean 轉換成 JavaScript 表示,但是出于安全性的原因,做這件事要求顯式的配置。
DWR分模塊配置 一般來說,你只需要一個dwr.xml文件,并且放置在默認的位置:WEB-INF/dwr.xml。如果有大量的遠程調用類,則可以將dwr.xml分成多個文件。 則在每web .xml中可以這樣配置: <servlet>
DWR批量調用 在 DWR 中,可以在一個 HTTP 請求中向服務器發送多個遠程調用。調用 DWREngine.beginBatch() 告訴 DWR 不要直接分派后續的遠程調用,而是把它們組合到一個批請求中。DWREngine.endBatch() 調用則把批請求發送到服務器。遠程調用在服務器端順序執行,然后調用每個 JavaScript 回調。 批處理在兩方面有助于降低延遲:第一,避免了為每個調用創建 XMLHttpRequest 對象并建立相關的 HTTP 連接的開銷。第二,在生產環境中,Web 服務器不必處理過多的并發 HTTP 請求,改進了響應時間。 F.DWR同步與異步 在頁面的執行中,如果在js中使用了dwr去遠程調用數據,這時,下面的JS將會是繼續執行, 如果你是用作校驗的話,那就導致不同步問題,返回結果無法生效, 這時你可以通過設置DWR為同步來達到效果 DWREngine.setAsync(false); => 默認為異步,即 true; 調用完后,設置還原 DWREngine.setAsync(true); Spring的JDBCTemplate當hql等查詢方式不能滿足性能或靈活性的要求,必須使用SQL時,大家有三種選擇: 第一、使用Hibernate 的sql 查詢函數,將查詢結果對象轉為Entity對象。 第二、使用Hibernate Session的getConnection 獲得JDBC Connection,然后進行純JDBC API操作; 第三、選擇把Spring的JDBCTemplate作為一種很不錯的JDBC Utils來使用。 JDBCTemplate的使用很簡單,只要在ApplicationContext文件里定義一個jdbcTemplate節點,POJO獲得注入后可以直接執行操作,不需要繼承什么基類,詳見JDBCTemplate參考文檔 AplicationContext定義: <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 實際使用: SqlRowSet rs = jdbcTemplate.queryForRowSet(sql, params); Tips1: jdbcTemplate有很多的ORM化回調操作將返回結果轉為對象列表,但很多時候還是需要返回ResultSet,Spring有提供一個類似ResultSet的 Spring SqlRowSet對象。
Tips2:.注意jdbcTemplate盡量只執行查詢操作,莫要進行更新,否則很容易破壞Hibernate的二級緩存體系。 Spring JDBC抽象框架所帶來的價值將在以下幾個方面得以體現:(注:使用了Spring JDBC抽象框架之后,應用開發人員只需要完成斜體字部分的編碼工作。)
Spring將替我們完成所有單調乏味的JDBC底層細節處理工作。 Spring JDBC抽象框架由四個包構成:core、 dataSource、object以及support。 org.springframework.jdbc.core包由JdbcTemplate類以及相關的回調接口(callback interface)和類組成。 org.springframework.jdbc.datasource包由一些用來簡化DataSource訪問的工具類,以及各種DataSource接口的簡單實現(主要用于單元測試以及在J2EE容器之外使用JDBC)組成。工具類提供了一些靜態方法,諸如通過JNDI獲取數據連接以及在必要的情況下關閉這些連接。它支持綁定線程的連接,比如被用于DataSourceTransactionManager的連接。 接下來,org.springframework.jdbc.object包由封裝了查詢、更新以及存儲過程的類組成,這些類的對象都是線程安全并且可重復使用的。它們類似于JDO,與JDO的不同之處在于查詢結果與數據庫是“斷開連接”的。它們是在org.springframework.jdbc.core包的基礎上對JDBC更高層次的抽象。 最后,org.springframework.jdbc.support包提供了一些SQLException的轉換類以及相關的工具類。 在JDBC處理過程中拋出的異常將被轉換成org.springframework.dao包中定義的異常。因此使用Spring JDBC進行開發將不需要處理JDBC或者特定的RDBMS才會拋出的異常。所有的異常都是unchecked exception,這樣我們就可以對傳遞到調用者的異常進行有選擇的捕獲。 JdbcTemplate是core包的核心類。它替我們完成了資源的創建以及釋放工作,從而簡化了我們對JDBC的使用。它還可以幫助我們避免一些常見的錯誤,比如忘記關閉數據庫連接。JdbcTemplate將完成JDBC核心處理流程,比如SQL語句的創建、執行,而把SQL語句的生成以及查詢結果的提取工作留給我們的應用代碼。它可以完成SQL查詢、更新以及調用存儲過程,可以對ResultSet進行遍歷并加以提取。它還可以捕獲JDBC異常并將其轉換成org.springframework.dao包中定義的,通用的,信息更豐富的異常。 使用JdbcTemplate進行編碼只需要根據明確定義的一組契約來實現回調接口。PreparedStatementCreator回調接口通過給定的Connection創建一個PreparedStatement,包含SQL和任何相關的參數。CallableStatementCreateor實現同樣的處理,只不過它創建的是CallableStatement。RowCallbackHandler接口則從數據集的每一行中提取值。 我們可以在一個service實現類中通過傳遞一個DataSource引用來完成JdbcTemplate的實例化,也可以在application context中配置一個JdbcTemplate bean,來供service使用。需要注意的是DataSource在application context總是配制成一個bean,第一種情況下,DataSource bean將傳遞給service,第二種情況下DataSource bean傳遞給JdbcTemplate bean。因為JdbcTemplate使用回調接口和SQLExceptionTranslator接口作為參數,所以一般情況下沒有必要通過繼承JdbcTemplate來定義其子類。 JdbcTemplate中使用的所有SQL將會以“DEBUG”級別記入日志(一般情況下日志的category是JdbcTemplate相應的全限定類名,不過如果需要對JdbcTemplate進行定制的話,可能是它的子類名)。 NamedParameterJdbcTemplate類增加了在SQL語句中使用命名參數的支持。在此之前,在傳統的SQL語句中,參數都是用'?'占位符來表示的。 NamedParameterJdbcTemplate類內部封裝了一個普通的JdbcTemplate,并作為其代理來完成大部分工作。下面的內容主要針對NamedParameterJdbcTemplate與JdbcTemplate的不同之處來加以說明,即如何在SQL語句中使用命名參數。 通過下面的例子我們可以更好地了解NamedParameterJdbcTemplate的使用模式(在后面我們還有更好的使用方式)。 // some JDBC-backed DAO class...
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return template.queryForInt(sql, namedParameters);
}
在上面例子中,sql變量使用了命名參數占位符“first_name”,與其對應的值存在namedParameters變量中(類型為MapSqlParameterSource)。 如果你喜歡的話,也可以使用基于Map風格的名值對將命名參數傳遞給NamedParameterJdbcTemplate(NamedParameterJdbcTemplate實現了NamedParameterJdbcOperations接口,剩下的工作將由調用該接口的相應方法來完成,這里我們就不再贅述): // some JDBC-backed DAO class...
public int countOfActorsByFirstName(String firstName) {
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource());
Map namedParameters = new HashMap();
namedParameters.put("first_name", firstName);
return template.queryForInt(sql, namedParameters);
}
另外一個值得一提的特性是與NamedParameterJdbcTemplate位于同一個包中的SqlParameterSource接口。在前面的代碼片斷中我們已經看到了該接口的實現(即MapSqlParameterSource類),SqlParameterSource可以用來作為NamedParameterJdbcTemplate命名參數的來源。MapSqlParameterSource類是一個非常簡單的實現,它僅僅是一個java.util.Map適配器,當然其用法也就不言自明了(如果還有不明了的,可以在Spring的JIRA系統中要求提供更多的相關資料)。 SqlParameterSource接口的另一個實現--BeanPropertySqlParameterSource為我們提供了更有趣的功能。該類包裝一個類似JavaBean的對象,所需要的命名參數值將由包裝對象提供,下面我們使用一個例子來更清楚地說明它的用法。 // some JavaBean-like class... public class Actor { private Long id; private String firstName; private String lastName; public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } public Long getId() { return this.id; } // setters omitted... } // some JDBC-backed DAO class... public int countOfActors(Actor exampleActor) { // notice how the named parameters match the properties of the above 'Actor' class String sql = "select count(0) from T_ACTOR where first_name = :firstName and last_name = :lastName"; NamedParameterJdbcTemplate template = new NamedParameterJdbcTemplate(this.getDataSource()); SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor); return template.queryForInt(sql, namedParameters); } 大家必須牢記一點:NamedParameterJdbcTemplate類內部包裝了一個標準的JdbcTemplate類。如果你需要訪問其內部的JdbcTemplate實例(比如訪問JdbcTemplate的一些方法)那么你需要使用getJdbcOperations()方法返回的JdbcOperations接口。(JdbcTemplate實現了JdbcOperations接口)。 NamedParameterJdbcTemplate類是線程安全的,該類的最佳使用方式不是每次操作的時候實例化一個新的NamedParameterJdbcTemplate,而是針對每個DataSource只配置一個NamedParameterJdbcTemplate實例(比如在Spring IoC容器中使用Spring IoC來進行配置),然后在那些使用該類的DAO中共享該實例。
SimpleJdbcTemplate類是JdbcTemplate類的一個包裝器(wrapper),它利用了Java 5的一些語言特性,比如Varargs和Autoboxing。對那些用慣了Java 5的程序員,這些新的語言特性還是很好用的。 SimpleJdbcTemplate 類利用Java 5的語法特性帶來的好處可以通過一個例子來說明。在下面的代碼片斷中我們首先使用標準的JdbcTemplate進行數據訪問,接下來使用SimpleJdbcTemplate做同樣的事情。 // classic JdbcTemplate-style... public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?"; RowMapper mapper = new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); actor.setId(rs.getLong(Long.valueOf(rs.getLong("id")))); actor.setFirstName(rs.getString("first_name")); actor.setLastName(rs.getString("last_name")); return actor; } }; // normally this would be dependency injected of course... JdbcTemplate jdbcTemplate = new JdbcTemplate(this.getDataSource()); // notice the cast, and the wrapping up of the 'id' argument // in an array, and the boxing of the 'id' argument as a reference type return (Actor) jdbcTemplate.queryForObject(sql, mapper, new Object[] {Long.valueOf(id)}); } 下面是同一方法的另一種實現,惟一不同之處是我們使用了SimpleJdbcTemplate,這樣代碼顯得更加清晰。 // SimpleJdbcTemplate-style... public Actor findActor(long id) { String sql = "select id, first_name, last_name from T_ACTOR where id = ?"; ParameterizedRowMapper<Actor> mapper = new ParameterizedRowMapper<Actor>() { // notice the return type with respect to Java 5 covariant return types public Actor mapRow(ResultSet rs, int rowNum) throws SQLException { Actor actor = new Actor(); actor.setId(rs.getLong("id")); actor.setFirstName(rs.getString("first_name")); actor.setLastName(rs.getString("last_name")); return actor; } }; // again, normally this would be dependency injected of course... SimpleJdbcTemplate simpleJdbcTemplate = new SimpleJdbcTemplate(this.getDataSource()); return simpleJdbcTemplate.queryForObject(sql, mapper, id); } 為了從數據庫中取得數據,我們首先需要獲取一個數據庫連接。 Spring通過DataSource對象來完成這個工作。 DataSource是JDBC規范的一部分, 它被視為一個通用的數據庫連接工廠。通過使用DataSource, Container或Framework可以將連接池以及事務管理的細節從應用代碼中分離出來。 作為一個開發人員,在開發和測試產品的過程中,你可能需要知道連接數據庫的細節。 但在產品實施時,你不需要知道這些細節。通常數據庫管理員會幫你設置好數據源。 在使用Spring JDBC時,你既可以通過JNDI獲得數據源,也可以自行配置數據源( 使用Spring提供的DataSource實現類)。使用后者可以更方便的脫離Web容器來進行單元測試。 這里我們將使用DriverManagerDataSource,不過DataSource有多種實現, 后面我們會講到。使用DriverManagerDataSource和你以前獲取一個JDBC連接 的做法沒什么兩樣。你首先必須指定JDBC驅動程序的全限定名,這樣DriverManager 才能加載JDBC驅動類,接著你必須提供一個url(因JDBC驅動而異,為了保證設置正確請參考相關JDBC驅動的文檔), 最后你必須提供一個用戶連接數據庫的用戶名和密碼。下面我們將通過一個例子來說明如何配置一個 DriverManagerDataSource: DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); dataSource.setUrl("jdbc:hsqldb:hsql://localhost:"); dataSource.setUsername("sa"); dataSource.setPassword(""); SQLExceptionTranslator是一個接口,如果你需要在 SQLException和org.springframework.dao.DataAccessException之間作轉換,那么必須實現該接口。 轉換器類的實現可以采用一般通用的做法(比如使用JDBC的SQLState code),如果為了使轉換更準確,也可以進行定制(比如使用Oracle的error code)。 SQLErrorCodeSQLExceptionTranslator是SQLExceptionTranslator的默認實現。 該實現使用指定數據庫廠商的error code,比采用SQLState更精確。 轉換過程基于一個JavaBean(類型為SQLErrorCodes)中的error code。 這個JavaBean由SQLErrorCodesFactory工廠類創建,其中的內容來自于 "sql-error-codes.xml"配置文件。該文件中的數據庫廠商代碼基于Database MetaData信息中的 DatabaseProductName,從而配合當前數據庫的使用。
SQLErrorCodeSQLExceptionTranslator使用以下的匹配規則:
SQLErrorCodeSQLExceptionTranslator可以采用下面的方式進行擴展: public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) { if (sqlex.getErrorCode() == -12345) { return new DeadlockLoserDataAccessException(task, sqlex); } return null; } } 在上面的這個例子中,error code為'-12345'的SQLException 將采用該轉換器進行轉換,而其他的error code將由默認的轉換器進行轉換。 為了使用該轉換器,必須將其作為參數傳遞給JdbcTemplate類 的setExceptionTranslator方法,并在需要使用這個轉換器器的數據 存取操作中使用該JdbcTemplate。 下面的例子演示了如何使用該定制轉換器: // create a JdbcTemplate and set data source JdbcTemplate jt = new JdbcTemplate(); jt.setDataSource(dataSource); // create a custom translator and set the DataSource for the default translation lookup MySQLErrorCodesTransalator tr = new MySQLErrorCodesTransalator(); tr.setDataSource(dataSource); jt.setExceptionTranslator(tr); // use the JdbcTemplate for this SqlUpdate SqlUpdate su = new SqlUpdate(); su.setJdbcTemplate(jt); su.setSql("update orders set shipping_charge = shipping_charge * 1.05"); su.compile(); su.update(); 在上面的定制轉換器中,我們給它注入了一個數據源,因為我們仍然需要 使用默認的轉換器從sql-error-codes.xml中獲取錯誤代碼集。 我們僅需要非常少的代碼就可以達到執行SQL語句的目的,一旦獲得一個 DataSource和一個JdbcTemplate, 我們就可以使用JdbcTemplate提供的豐富功能實現我們的操作。 下面的例子使用了極少的代碼完成創建一張表的工作。 import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class ExecuteAStatement { private JdbcTemplate jt; private DataSource dataSource; public void doExecute() { jt = new JdbcTemplate(dataSource); jt.execute("create table mytable (id integer, name varchar(100))"); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } } 除了execute方法之外,JdbcTemplate還提供了大量的查詢方法。 在這些查詢方法中,有很大一部分是用來查詢單值的。比如返回一個匯總(count)結果 或者從返回行結果中取得指定列的值。這時我們可以使用queryForInt(..)、 queryForLong(..)或者queryForObject(..)方法。 queryForObject方法用來將返回的JDBC類型對象轉換成指定的Java對象,如果類型轉換失敗將拋出 InvalidDataAccessApiUsageException異常。 下面的例子演示了兩個查詢的用法,一個返回int值,另一個返回 String。 import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class RunAQuery { private JdbcTemplate jt; private DataSource dataSource; public int getCount() { jt = new JdbcTemplate(dataSource); int count = jt.queryForInt("select count(*) from mytable"); return count; } public String getName() { jt = new JdbcTemplate(dataSource); String name = (String) jt.queryForObject("select name from mytable", String.class); return name; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } } 除了返回單值的查詢方法,JdbcTemplate還提供了一組返回List結果 的方法。List中的每一項對應查詢返回結果中的一行。其中最簡單的是queryForList方法, 該方法將返回一個List,該List中的每一條 記錄是一個Map對象,對應應數據庫中某一行;而該Map 中的每一項對應該數據庫行中的某一列值。下面的代碼片斷接著上面的例子演示了如何用該方法返回表中 所有記錄: public List getList() { jt = new JdbcTemplate(dataSource); List rows = jt.queryForList("select * from mytable"); return rows; } 返回的結果集類似下面這種形式: [{name=Bob, id=1}, {name=Mary, id=2}] JdbcTemplate還提供了一些更新數據庫的方法。 在下面的例子中,我們根據給定的主鍵值對指定的列進行更新。 例子中的SQL語句中使用了“?”占位符來接受參數(這種做法在更新和查詢SQL語句中很常見)。 傳遞的參數值位于一個對象數組中(基本類型需要被包裝成其對應的對象類型)。 import javax.sql.DataSource; import org.springframework.jdbc.core.JdbcTemplate; public class ExecuteAnUpdate { private JdbcTemplate jt; private DataSource dataSource; public void setName(int id, String name) { jt = new JdbcTemplate(dataSource); jt.update("update mytable set name = ? where id = ?", new Object[] {name, new Integer(id)}); } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } } DataSourceUtils作為一個幫助類提供易用且強大的數據庫訪問能力, 我們可以使用該類提供的靜態方法從JNDI獲取數據庫連接以及在必要的時候關閉之。 它提供支持線程綁定的數據庫連接(比如使用DataSourceTransactionManager 的時候,將把數據庫連接綁定到當前的線程上)。 注:getDataSourceFromJndi(..)方法主要用于那些沒有使用bean factory 或者application context的場合。如果使用application context,那么最好是在 JndiObjectFactoryBean中配置bean或者直接使用 JdbcTemplate實例。JndiObjectFactoryBean 能夠通過JNDI獲取DataSource并將 DataSource作為引用參數傳遞給其他bean。 這樣,在不同的DataSource之間切換只需要修改配置文件即可, 甚至我們可以用一個非JNDI的DataSource來替換 FactoryBean定義! SmartDataSource是DataSource 接口的一個擴展,用來提供數據庫連接。使用該接口的類在指定的操作之后可以檢查是否需要關閉連接。 該接口在某些情況下非常有用,比如有些情況需要重用數據庫連接。 AbstractDataSource是一個實現了DataSource 接口的abstract基類。它實現了DataSource接口的 一些無關痛癢的方法,如果你需要實現自己的DataSource,那么繼承 該類是個好主意。 SingleConnectionDataSource是SmartDataSource接口 的一個實現,其內部包裝了一個單連接。該連接在使用之后將不會關閉,很顯然它不能在多線程 的環境下使用。 當客戶端代碼調用close方法的時候,如果它總是假設數據庫連接來自連接池(就像使用持久化工具時一樣), 你應該將suppressClose設置為true。 這樣,通過該類獲取的將是代理連接(禁止關閉)而不是原有的物理連接。 需要注意的是,我們不能把使用該類獲取的數據庫連接造型(cast)為Oracle Connection之類的本地數據庫連接。 SingleConnectionDataSource主要在測試的時候使用。 它使得測試代碼很容易脫離應用服務器而在一個簡單的JNDI環境下運行。 與DriverManagerDataSource不同的是,它始終只會使用同一個數據庫連接, 從而避免每次建立物理連接的開銷。 DriverManagerDataSource類實現了 SmartDataSource接口。在applicationContext.xml中可以使用 bean properties來設置JDBC Driver屬性,該類每次返回的都是一個新的連接。 該類主要在測試以及脫離J2EE容器的獨立環境中使用。它既可以用來在application context中作為一個 DataSource bean,也可以在簡單的JNDI環境下使用。 由于Connection.close()僅僅只是簡單的關閉數據庫連接,因此任何能夠獲取 DataSource的持久化代碼都能很好的工作。不過使用JavaBean風格的連接池 (比如commons-dbcp)也并非難事。即使是在測試環境下,使用連接池也是一種比使用 DriverManagerDataSource更好的做法。 TransactionAwareDataSourceProxy作為目標DataSource的一個代理, 在對目標DataSource包裝的同時,還增加了Spring的事務管理能力, 在這一點上,這個類的功能非常像J2EE服務器所提供的事務化的JNDI DataSource。
如果需要更詳細的資料,請參考TransactionAwareDataSourceProxy JavaDoc 。 DataSourceTransactionManager類是 PlatformTransactionManager接口的一個實現,用于處理單JDBC數據源。 它將從指定DataSource取得的JDBC連接綁定到當前線程,因此它也支持了每個數據源對應到一個線程。 我們推薦在應用代碼中使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,而不是使用J2EE標準的DataSource.getConnection。因為前者將拋出 unchecked的org.springframework.dao異常,而不是checked的 SQLException異常。Spring Framework中所有的類(比如 JdbcTemplate)都采用這種做法。如果不需要和這個 DataSourceTransactionManager類一起使用,DataSourceUtils 提供的功能跟一般的數據庫連接策略沒有什么兩樣,因此它可以在任何場景下使用。 DataSourceTransactionManager類支持定制隔離級別,以及對SQL語句查詢超時的設定。 為了支持后者,應用代碼必須使用JdbcTemplate或者在每次創建SQL語句時調用 DataSourceUtils.applyTransactionTimeout方法。 在使用單個數據源的情形下,你可以用DataSourceTransactionManager來替代JtaTransactionManager, 因為DataSourceTransactionManager不需要容器支持JTA。如果你使用DataSourceUtils.getConnection(DataSource)來獲取 JDBC連接,二者之間的切換只需要更改一些配置。最后需要注意的一點就是JtaTransactionManager不支持隔離級別的定制! org.springframework.jdbc.object包下的類允許用戶以更加 面向對象的方式去訪問數據庫。比如說,用戶可以執行查詢并返回一個list, 該list作為一個結果集將把從數據庫中取出的列數據映射到業務對象的屬性上。 用戶也可以執行存儲過程,以及運行更新、刪除以及插入SQL語句。
SqlQuery是一個可重用、線程安全的類,它封裝了一個SQL查詢。 其子類必須實現newResultReader()方法,該方法用來在遍歷 ResultSet的時候能使用一個類來保存結果。 我們很少需要直接使用SqlQuery,因為其子類 MappingSqlQuery作為一個更加易用的實現能夠將結果集中的行映射為Java對象。 SqlQuery還有另外兩個擴展分別是 MappingSqlQueryWithParameters和UpdatableSqlQuery。 MappingSqlQuery是一個可重用的查詢抽象類,其具體類必須實現 mapRow(ResultSet, int)抽象方法來將結果集中的每一行轉換成Java對象。 在SqlQuery的各種實現中, MappingSqlQuery是最常用也是最容易使用的一個。 下面這個例子演示了一個定制查詢,它將從客戶表中取得的數據映射到一個 Customer類實例。 private class CustomerMappingQuery extends MappingSqlQuery { public CustomerMappingQuery(DataSource ds) { super(ds, "SELECT id, name FROM customer WHERE id = ?"); super.declareParameter(new SqlParameter("id", Types.INTEGER)); compile(); } public Object mapRow(ResultSet rs, int rowNumber) throws SQLException { Customer cust = new Customer(); cust.setId((Integer) rs.getObject("id")); cust.setName(rs.getString("name")); return cust; } } 在上面的例子中,我們為用戶查詢提供了一個構造函數并為構造函數傳遞了一個 DataSource參數。在構造函數里面我們把 DataSource和一個用來返回查詢結果的SQL語句作為參數 調用父類的構造函數。SQL語句將被用于生成一個PreparedStatement對象, 因此它可以包含占位符來傳遞參數。而每一個SQL語句的參數必須通過調用 declareParameter方法來進行聲明,該方法需要一個 SqlParameter(封裝了一個字段名字和一個 java.sql.Types中定義的JDBC類型)對象作為參數。 所有參數定義完之后,我們調用compile()方法來對SQL語句進行預編譯。 下面讓我們看看該定制查詢初始化并執行的代碼: public Customer getCustomer(Integer id) { CustomerMappingQuery custQry = new CustomerMappingQuery(dataSource); Object[] parms = new Object[1]; parms[0] = id; List customers = custQry.execute(parms); if (customers.size() > 0) { return (Customer) customers.get(0); } else { return null; } } 在上面的例子中,getCustomer方法通過傳遞惟一參數id來返回一個客戶對象。 該方法內部在創建CustomerMappingQuery實例之后, 我們創建了一個對象數組用來包含要傳遞的查詢參數。這里我們只有唯一的一個 Integer參數。執行CustomerMappingQuery的 execute方法之后,我們得到了一個List,該List中包含一個 Customer對象,如果有對象滿足查詢條件的話。 SqlUpdate類封裝了一個可重復使用的SQL更新操作。 跟所有RdbmsOperation類一樣,SqlUpdate可以在SQL中定義參數。 該類提供了一系列update()方法,就像SqlQuery提供的一系列execute()方法一樣。 SqlUpdate是一個具體的類。通過在SQL語句中定義參數,這個類可以支持 不同的更新方法,我們一般不需要通過繼承來實現定制。 import java.sql.Types; import javax.sql.DataSource; import org.springframework.jdbc.core.SqlParameter; import org.springframework.jdbc.object.SqlUpdate; public class UpdateCreditRating extends SqlUpdate { public UpdateCreditRating(DataSource ds) { setDataSource(ds); setSql("update customer set credit_rating = ? where id = ?"); declareParameter(new SqlParameter(Types.NUMERIC)); declareParameter(new SqlParameter(Types.NUMERIC)); compile(); } /** * @param id for the Customer to be updated * @param rating the new value for credit rating * @return number of rows updated */ public int run(int id, int rating) { Object[] params = new Object[] { new Integer(rating), new Integer(id)}; return update(params); } } StoredProcedure類是一個抽象基類,它是對RDBMS存儲過程的一種抽象。 該類提供了多種execute(..)方法,不過這些方法的訪問類型都是protected的。 從父類繼承的sql屬性用來指定RDBMS存儲過程的名字。 盡管該類提供了許多必須在JDBC3.0下使用的功能,但是我們更關注的是JDBC 3.0中引入的命名參數特性。 下面的程序演示了如何調用Oracle中的sysdate()函數。 這里我們創建了一個繼承StoredProcedure的子類,雖然它沒有輸入參數, 但是我必須通過使用SqlOutParameter來聲明一個日期類型的輸出參數。 execute()方法將返回一個map,map中的每個entry是一個用參數名作key, 以輸出參數為value的名值對。 import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.datasource.*;
import org.springframework.jdbc.object.StoredProcedure;
public class TestStoredProcedure {
public static void main(String[] args) {
TestStoredProcedure t = new TestStoredProcedure();
t.test();
System.out.println("Done!");
}
void test() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("oracle.jdbc.OracleDriver");
ds.setUrl("jdbc:oracle:thin:@localhost:1521:mydb");
ds.setUsername("scott");
ds.setPassword("tiger");
MyStoredProcedure sproc = new MyStoredProcedure(ds);
Map results = sproc.execute();
printMap(results);
}
private class MyStoredProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public MyStoredProcedure(DataSource ds) {
setDataSource(ds);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Map execute() {
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
return execute(new HashMap());
}
}
private static void printMap(Map results) {
for (Iterator it = results.entrySet().iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
}
}
下面是StoredProcedure的另一個例子,它使用了兩個Oracle游標類型的輸出參數。 import oracle.jdbc.driver.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "AllTitlesAndGenres";
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}
public Map execute() {
// again, this sproc has no input parameters, so an empty Map is supplied...
return super.execute(new HashMap());
}
}
值得注意的是TitlesAndGenresStoredProcedure構造函數中 declareParameter(..)的SqlOutParameter參數, 該參數使用了RowMapper接口的實現。 這是一種非常方便而強大的重用方式。 下面我們來看一下RowMapper的兩個具體實現。 首先是TitleMapper類,它簡單的把ResultSet中的每一行映射為一個Title Domain Object。 import com.foo.sprocs.domain.Title; import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; public final class TitleMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Title title = new Title(); title.setId(rs.getLong("id")); title.setName(rs.getString("name")); return title; } } 另一個是GenreMapper類,也是非常簡單的將ResultSet中的每一行映射為一個Genre Domain Object。 import org.springframework.jdbc.core.RowMapper; import java.sql.ResultSet; import java.sql.SQLException; import com.foo.domain.Genre; public final class GenreMapper implements RowMapper { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { return new Genre(rs.getString("name")); } } 如果你需要給存儲過程傳輸入參數(這些輸入參數是在RDBMS存儲過程中定義好了的), 則需要提供一個指定類型的execute(..)方法, 該方法將調用基類的protected execute(Map parameters)方法。 例如: import oracle.jdbc.driver.OracleTypes; import org.springframework.jdbc.core.SqlOutParameter; import org.springframework.jdbc.object.StoredProcedure; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; public class TitlesAfterDateStoredProcedure extends StoredProcedure { private static final String SPROC_NAME = "TitlesAfterDate"; private static final String CUTOFF_DATE_PARAM = "cutoffDate"; public TitlesAfterDateStoredProcedure(DataSource dataSource) { super(dataSource, SPROC_NAME); declaraParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE); declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper())); compile(); } public Map execute(Date cutoffDate) { Map inputs = new HashMap(); inputs.put(CUTOFF_DATE_PARAM, cutoffDate); return super.execute(inputs); } } SqlFunction RDBMS操作類封裝了一個SQL“函數”包裝器(wrapper), 該包裝器適用于查詢并返回一個單行結果集。默認返回的是一個int值, 不過我們可以采用類似JdbcTemplate中的queryForXxx 做法自己實現來返回其它類型。SqlFunction優勢在于我們不必創建 JdbcTemplate,這些它都在內部替我們做了。 該類的主要用途是調用SQL函數來返回一個單值的結果集,比如類似“select user()”、 “select sysdate from dual”的查詢。如果需要調用更復雜的存儲函數, 可以使用StoredProcedure或SqlCall。 SqlFunction是一個具體類,通常我們不需要它的子類。 其用法是創建該類的實例,然后聲明SQL語句以及參數就可以調用相關的run方法去多次執行函數。 下面的例子用來返回指定表的記錄行數: public int countRows() { SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable"); sf.compile(); return sf.run(); } |