176142998

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  116 Posts :: 0 Stories :: 45 Comments :: 0 Trackbacks

#

 

posted @ 2008-08-18 17:51 飞飞 阅读(11316) | 评论 (3)编辑 收藏

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中。
posted @ 2008-08-15 14:56 飞飞 阅读(379) | 评论 (0)编辑 收藏

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 -->
posted @ 2008-08-15 14:35 飞飞 阅读(266) | 评论 (0)编辑 收藏

一:
myeclipse 傻瓜式的完成spring和hibernate的载入;
注意需要把lib里面的asm-XXcommons-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;

/**
 * AbstractBook generated by MyEclipse Persistence Tools
 */

public abstract class AbstractBook implements java.io.Serializable {

 // Fields

 private Integer id;
 private String name;
 private String isbm;
 private String author;

 // Constructors

 /** default constructor */
 public AbstractBook() {
 }

 /** full constructor */
 public AbstractBook(String name, String isbm, String author) {
  this.name = name;
  this.isbm = isbm;
  this.author = author;
 }

 // Property accessors

 public Integer getId() {
  return this.id;
 }

 public void setId(Integer id) {
  this.id = id;
 }

 public String getName() {
  return this.name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getIsbm() {
  return this.isbm;
 }

 public void setIsbm(String isbm) {
  this.isbm = isbm;
 }

 public String getAuthor() {
  return this.author;
 }

 public void setAuthor(String author) {
  this.author = author;
 }

}


Book.java

package com;

// Generated by MyEclipse Persistence Tools

/**
 * Book generated by MyEclipse Persistence Tools
 */
public class Book extends AbstractBook implements java.io.Serializable {

 // Constructors

 /** default constructor */
 public Book() {
 }

 /** full constructor */
 public Book(String name, String isbm, String author) {
  super(name, isbm, author);
 }

}


BookDAO.java

package com;

import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.LockMode;
import org.springframework.context.ApplicationContext;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * Data access object (DAO) for domain model class Book.
 *
 * @see com.Book
 * @author MyEclipse Persistence Tools
 */

public class BookDAO extends HibernateDaoSupport {
 private static final Log log = LogFactory.getLog(BookDAO.class);

 protected void initDao() {
  // do nothing
 }

 public void save(Book transientInstance) {
  log.debug("saving Book instance");
  try {
   getHibernateTemplate().save(transientInstance);
   log.debug("save successful");
  } catch (RuntimeException re) {
   log.error("save failed", re);
   throw re;
  }
 }

 public void delete(Book persistentInstance) {
  log.debug("deleting Book instance");
  try {
   getHibernateTemplate().delete(persistentInstance);
   log.debug("delete successful");
  } catch (RuntimeException re) {
   log.error("delete failed", re);
   throw re;
  }
 }

 public Book findById(java.lang.Integer id) {
  log.debug("getting Book instance with id: " + id);
  try {
   Book instance = (Book) getHibernateTemplate().get("com.Book", id);
   return instance;
  } catch (RuntimeException re) {
   log.error("get failed", re);
   throw re;
  }
 }

 public List findByExample(Book instance) {
  log.debug("finding Book instance by example");
  try {
   List results = getHibernateTemplate().findByExample(instance);
   log.debug("find by example successful, result size: "
     + results.size());
   return results;
  } catch (RuntimeException re) {
   log.error("find by example failed", re);
   throw re;
  }
 }

 public List findByProperty(String propertyName, Object value) {
  log.debug("finding Book instance with property: " + propertyName
    + ", value: " + value);
  try {
   String queryString = "from Book as model where model."
     + propertyName + "= ?";
   return getHibernateTemplate().find(queryString, value);
  } catch (RuntimeException re) {
   log.error("find by property name failed", re);
   throw re;
  }
 }

 public List findAll() {
  log.debug("finding all Book instances");
  try {
   String queryString = "from Book";
   return getHibernateTemplate().find(queryString);
  } catch (RuntimeException re) {
   log.error("find all failed", re);
   throw re;
  }
 }

 public Book merge(Book detachedInstance) {
  log.debug("merging Book instance");
  try {
   Book result = (Book) getHibernateTemplate().merge(detachedInstance);
   log.debug("merge successful");
   return result;
  } catch (RuntimeException re) {
   log.error("merge failed", re);
   throw re;
  }
 }

 public void attachDirty(Book instance) {
  log.debug("attaching dirty Book instance");
  try {
   getHibernateTemplate().saveOrUpdate(instance);
   log.debug("attach successful");
  } catch (RuntimeException re) {
   log.error("attach failed", re);
   throw re;
  }
 }

 public void attachClean(Book instance) {
  log.debug("attaching clean Book instance");
  try {
   getHibernateTemplate().lock(instance, LockMode.NONE);
   log.debug("attach successful");
  } catch (RuntimeException re) {
   log.error("attach failed", re);
   throw re;
  }
 }

 public static BookDAO getFromApplicationContext(ApplicationContext ctx) {
  return (BookDAO) ctx.getBean("BookDAO");
 }
}


web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext.xml</param-value>
 </context-param>
 <listener>
  <listener-class>
   org.springframework.web.context.ContextLoaderListener
  </listener-class>
 </listener>
 <servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
  <init-param>
   <param-name>debug</param-name>
   <param-value>true</param-value>
  </init-param>
 </servlet>

 <servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
 </servlet-mapping>
 <welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
</web-app>

 



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() {
  return bookDAO;
 }

 public void setBookDAO(BookDAO bookDAO) {
  this.bookDAO = bookDAO;
 }
 public void addBook(Book book) {
  System.out.println("impl:"+book);
  bookDAO.save(book);
 }

 public void deleteBook(Integer id) {
  Book book = bookDAO.findById(id);
  bookDAO.delete(book);
 }


 @SuppressWarnings("unchecked")
 public List<Book> getAllBooks() {

  return bookDAO.findAll();
 }


 @SuppressWarnings("unchecked")
 public List<Book> getBookByName(String name) {

  return bookDAO.findByProperty(name, name);
 }

 public void updateBook(Book book) {
  bookDAO.attachDirty(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


<html>
 <head>
  <title>DWR test</title>
  <script type='text/javascript' src='/shdwr/dwr/interface/BookManageService.js'></script>
  <script type='text/javascript' src='/shdwr/dwr/engine.js'></script>
  <script type='text/javascript' src='/shdwr/dwr/util.js'></script>

  <script type="text/javascript">
        var bookCache ={};
        var currentBook = null;
       
        function loadAllBooks(){
            BookManageService.getAllBooks(handleGetAllBooks,handleGetAllBooksError);
        }
        function handleGetAllBooks(books){
            dwr.util.removeAllRows("booksBody",{ filter:function(tr){
       return (tr.id != "pattern");
       }});
 
            var book,id;
            for(var i = 0; i < books.length; i++){
                book = books[i];
              
                id = book.id;
          
                dwr.util.cloneNode("pattern", { idSuffix:id });
                dwr.util.setValue("t_name" + id, book.name);
                dwr.util.setValue("t_isbm" + id, book.isbm);
                dwr.util.setValue("t_author" + id,book.author);
               $("pattern" + id).style.display = "block";
               bookCache[id] = book;
            }
        }
        function handleGetAllBooksError(msg){
            alert("Error: " + msg);
        }
       
        function addBook(){
            var book ={name:null,isbm:null,author:null};
            dwr.util.getValues(book);
            dwr.engine.beginBatch();
                 BookManageService.addBook(book);
                 loadAllBooks();
              dwr.engine.endBatch();
        }
       
        function editBook(btId){
            currentBook = bookCache[btId.substring(4)];
            dwr.util.setValues(currentBook);
        }
       
        function updateBook(){
            var book = {id:null,name:null,isbm:null,author:null};
            dwr.util.getValues(book);
            book.id = currentBook.id;
            BookManageService.updateBook(book,handleUpdateBook,handleUpdateBookError);
        }
       
        function handleUpdateBook(){
            alert("Update book successfully!");
            loadAllBooks();
        }
       
        function handleUpdateBookError(msg){
            alert("Error: " + msg);
        }
       
        function deleteBook(btId){
            var i = confirm("Are you sure to delete the book?");
            if(i == true)
                BookManageService.deleteBook(btId.substring(6),handleDeleteBook,handleDeleteBookError);
        }
       
        function handleDeleteBook(){
          alert("The book has been delete successfully!");
          loadAllBooks();
        }
       
        function handleDeleteBookError(msg){
          alert("Error: " + msg);
        }
        function reset(){
           dwr.util.setValue("name", "");
           dwr.util.setValue("isbm", "");
           dwr.util.setValue("author", "");
        }
    </script>
 </head>


 <body onload="loadAllBooks()">
  <div>
   <h2>
    Add book
   </h2>
   <table>
    <tr>
     <td>
      Name:
     </td>
     <td>
      <input type="text" id="name">
     </td>
    </tr>
    <tr>
     <td>
      ISBN:
     </td>
     <td>
      <input type="text" id="isbm">
     </td>
    </tr>

    <tr>
     <td>
      Author:
     </td>
     <td>
      <input type="text" id="author">
     </td>
    </tr>
    <tr>
     <td colspan="2">
      <input type="button" id="add" value="Add" onclick="addBook()">
      <input type="reset"  id="reset" value="reset" onclick="reset()">
      <input type="button" id="update" value="Update"
       onclick="updateBook()">
     </td>
    </tr>
   </table>
  </div>
  <hr>
  <div id="list">
   <table border="1">
    <thead>
     <tr>
      <th>
       Name
      </th>
      <th>
       ISBN
      </th>
      <th>
       Author
      </th>
      <th>
       Action
      </th>
     </tr>
    </thead>
    <tbody id="booksBody">
     <tr id="pattern" style="display: none;">
      <td>
       <span id="t_name"></span>
      </td>
      <td>
       <span id="t_isbm"></span>
      </td>
      <td>
       <span id="t_author"></span>
      </td>
      <td>
       <span id="action"> <input id="edit" type="button"
         value="Edit" onclick="editBook(this.id)" /> <input id="delete"
         type="button" value="Delete" onclick="deleteBook(this.id)" />
       </span>
      </td>
     </tr>
    </tbody>
   </table>
  </div>
 </body>


posted @ 2008-08-13 19:55 飞飞 阅读(645) | 评论 (0)编辑 收藏

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以后调用
posted @ 2008-08-13 15:23 飞飞 阅读(833) | 评论 (0)编辑 收藏

一:
用myeclipse中的傻瓜式添加spring组件。
修改web.xml文件内容,如下:(红色标记处)

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
 http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/applicationContext-dwr.xml</param-value>
 </context-param>
 <listener>
  <listener-class>
   org.springframework.web.context.ContextLoaderListener
  </listener-class>
 </listener>
 <servlet>
  <servlet-name>dwr-invoker</servlet-name>
  <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
  <init-param>
   <param-name>debug</param-name>
   <param-value>true</param-value>
  </init-param>
 </servlet>

 <servlet-mapping>
  <servlet-name>dwr-invoker</servlet-name>
  <url-pattern>/dwr/*</url-pattern>
 </servlet-mapping>
 <welcome-file-list>
  <welcome-file>index.jsp</welcome-file>
 </welcome-file-list>
</web-app>
二:
将文件交给spring来管理

<!DOCTYPE dwr PUBLIC "-//GetAhead Limited//DTD Direct Web Remoting 2.0//EN" "http://getahead.org/dwr//dwr20.dtd">

<dwr>
  <allow>
    <create creator="spring" javascript="DWRUserAccess">
      <param name="beanName" value="dwrUserAccess"/>
    </create>
    <convert match="com.dwr.User" converter="bean"></convert>
  </allow>
</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。
posted @ 2008-08-13 11:22 飞飞 阅读(1569) | 评论 (0)编辑 收藏

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组件,并进行相关测试。
posted @ 2008-08-13 10:21 飞飞 阅读(447) | 评论 (0)编辑 收藏

  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可以提供一个回调函数句柄传入存根,待存根函数返回调用该回调,这样即达到异步通信。
posted @ 2008-08-12 16:41 飞飞 阅读(616) | 评论 (0)编辑 收藏

DWR(Direct Web Remoting)是一个开放源clip_image002码的使用 Apache 许可协议的解决方案,它包含服务器端 Java 库、一个 DWR Servlet 以及 JavaScript 库。虽然 DWR 不是 Java 平台上唯一可用的 Ajax-RPC 工具包,但是它是最成熟的,而且提供了许多有用的功能。为什么要使用DWR,我们首先介绍基本AJAX流程,从中可以看到引入DWR会带来什么好处。 DWR的标准流程如右图所示:

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

也可以使用精简的一份配置:

<!– add the Servlet for DWR –>
    <servlet>
    <servlet-name>dwr-invoker</servlet-name>
    <display-name>DWR Servlet</display-name>
    <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>config</param-name>
        <param-value>/WEB-INF/dwr/dwr.xml</param-value>
    </init-param>
</servlet>

    <!– Action Servlet Mapping –>
    <servlet-mapping>
        <servlet-name>dwr-invoker</servlet-name>
        <url-pattern>/dwr/*</url-pattern>
    </servlet-mapping>

在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:

<%@ page language=”java” contentType=”text/html; charset=UTF-8″
    pageEncoding=”UTF-8″%>
<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”>
<html>
<head>
<meta http-equiv=”Content-Type” content=”text/html; charset=UTF-8″>
<script type=’text/javascript’ src=’/Tms/dwr/interface/login.js’></script>
<script type=’text/javascript’ src=’/Tms/dwr/engine.js’></script>
<script type=’text/javascript’ src=’/Tms/dwr/util.js’></script>

<script>
function getDWRMessage () {
var value=document.getElementById(’user’).value;
//var bean= DWRUtil.getValues(value);
login.sayHello(value,showMessage);
}

function showMessage(user) {
if(user.name==’Allen’){
alert(user.message);
}else{
  document.getElementById(’MessageSpan’).innerHTML = user.name+” is illegal!!!”;
}
}
</script>

<title>Logon</title>
</head>

<body>
<table>
    <tr>
        <td id=MessageSpan></td>
    </tr>
    <tr>
        <td><input id=’user’ type=”text” value=”"></td>
    </tr>
    <tr>
        <td><input id=’submitBtn’ type=”button” onClick=”getDWRMessage()”
            value=”Submit”></td>
    </tr>
</table>
</body>
</html>

效果如下:

做完配置后,可以加载http://127.0.0.1:8080/Tms/dwr看看哪些服务可以用。

Classes known to DWR:

  • login (com.webex.tmis.test.Login)

如果你需要使用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>
<servlet-name>dwr-user-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>config-user</param-name>
<param-value>WEB-INF/dwr-user.xml</param-value>
</init-param>
</servlet>
<servlet>
<servlet-name>dwr-admin-invoker</servlet-name>
<servlet-class>org.directwebremoting.servlet.DwrServlet</servlet-class>
<init-param>
<param-name>config-admin</param-name>
<param-value>WEB-INF/dwr-admin.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-admin-invoker</servlet-name>
<url-pattern>/dwradmin/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>dwr-user-invoker</servlet-name>
<url-pattern>/dwruser/*</url-pattern>
</servlet-mapping>

 

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);

posted @ 2008-08-12 15:38 飞飞 阅读(514) | 评论 (0)编辑 收藏

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">
        <property name="dataSource" ref="dataSource"/>
    </bean>

实际使用: 

SqlRowSet rs = jdbcTemplate.queryForRowSet(sql, params);

Tips1: jdbcTemplate有很多的ORM化回调操作将返回结果转为对象列表,但很多时候还是需要返回ResultSet,Spring有提供一个类似ResultSet的 Spring SqlRowSet对象。

         

Tips2:.注意jdbcTemplate尽量只执行查询操作,莫要进行更新,否则很容易破坏Hibernate的二级缓存体系。


Chapter 11. 使用JDBC进行数据访问

11.1. 简介

Spring JDBC抽象框架所带来的价值将在以下几个方面得以体现:(注:使用了Spring JDBC抽象框架之后,应用开发人员只需要完成斜体字部分的编码工作。)

  1. 指定数据库连接参数

  2. 打开数据库连接

  3. 声明SQL语句

  4. 预编译并执行SQL语句

  5. 遍历查询结果(如果需要的话)

  6. 处理每一次遍历操作

  7. 处理抛出的任何异常

  8. 处理事务

  9. 关闭数据库连接

Spring将替我们完成所有单调乏味的JDBC底层细节处理工作。

11.1.1. Spring JDBC包结构

Spring JDBC抽象框架由四个包构成:coredataSourceobject以及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,这样我们就可以对传递到调用者的异常进行有选择的捕获。

11.2. 利用JDBC核心类实现JDBC的基本操作和错误处理

11.2.1. JdbcTemplate

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进行定制的话,可能是它的子类名)。

11.2.2. NamedParameterJdbcTemplate

NamedParameterJdbcTemplate类增加了在SQL语句中使用命名参数的支持。在此之前,在传统的SQL语句中,参数都是用'?'占位符来表示的。 NamedParameterJdbcTemplate类内部封装了一个普通的JdbcTemplate,并作为其代理来完成大部分工作。下面的内容主要针对NamedParameterJdbcTemplateJdbcTemplate的不同之处来加以说明,即如何在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风格的名值对将命名参数传递给NamedParameterJdbcTemplateNamedParameterJdbcTemplate实现了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中共享该实例。

11.2.3. SimpleJdbcTemplate

[Note] Note

请注意该类所提供的功能仅适用于Java 5 (Tiger)。

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);
}

11.2.4. DataSource接口

为了从数据库中取得数据,我们首先需要获取一个数据库连接。 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("");

11.2.5. SQLExceptionTranslator接口

SQLExceptionTranslator是一个接口,如果你需要在 SQLExceptionorg.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 这个类可以作为一个具体类使用,不需要进行定制,那么这个规则将不适用。

  • 接着将SQLException的error code与错误代码集中的error code进行匹配。 默认情况下错误代码集将从SQLErrorCodesFactory取得。 错误代码集来自classpath下的sql-error-codes.xml文件, 它们将与数据库metadata信息中的database name进行映射。

  • 如果仍然无法匹配,最后将调用fallbackTranslator属性的translate方法,SQLStateSQLExceptionTranslator类实例是默认的fallbackTranslator。

 

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中获取错误代码集。

11.2.6. 执行SQL语句

我们仅需要非常少的代码就可以达到执行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;
}
}

11.2.7. 执行查询

除了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}]

11.2.8. 更新数据库

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;
}
}

11.3. 控制数据库连接

11.3.1. DataSourceUtils

DataSourceUtils作为一个帮助类提供易用且强大的数据库访问能力, 我们可以使用该类提供的静态方法从JNDI获取数据库连接以及在必要的时候关闭之。 它提供支持线程绑定的数据库连接(比如使用DataSourceTransactionManager 的时候,将把数据库连接绑定到当前的线程上)。

注:getDataSourceFromJndi(..)方法主要用于那些没有使用bean factory 或者application context的场合。如果使用application context,那么最好是在 JndiObjectFactoryBean中配置bean或者直接使用 JdbcTemplate实例。JndiObjectFactoryBean 能够通过JNDI获取DataSource并将 DataSource作为引用参数传递给其他bean。 这样,在不同的DataSource之间切换只需要修改配置文件即可, 甚至我们可以用一个非JNDI的DataSource来替换 FactoryBean定义!

11.3.2. SmartDataSource接口

SmartDataSourceDataSource 接口的一个扩展,用来提供数据库连接。使用该接口的类在指定的操作之后可以检查是否需要关闭连接。 该接口在某些情况下非常有用,比如有些情况需要重用数据库连接。

11.3.3. AbstractDataSource

AbstractDataSource是一个实现了DataSource 接口的abstract基类。它实现了DataSource接口的 一些无关痛痒的方法,如果你需要实现自己的DataSource,那么继承 该类是个好主意。

11.3.4. SingleConnectionDataSource

SingleConnectionDataSourceSmartDataSource接口 的一个实现,其内部包装了一个单连接。该连接在使用之后将不会关闭,很显然它不能在多线程 的环境下使用。

当客户端代码调用close方法的时候,如果它总是假设数据库连接来自连接池(就像使用持久化工具时一样), 你应该将suppressClose设置为true。 这样,通过该类获取的将是代理连接(禁止关闭)而不是原有的物理连接。 需要注意的是,我们不能把使用该类获取的数据库连接造型(cast)为Oracle Connection之类的本地数据库连接。

SingleConnectionDataSource主要在测试的时候使用。 它使得测试代码很容易脱离应用服务器而在一个简单的JNDI环境下运行。 与DriverManagerDataSource不同的是,它始终只会使用同一个数据库连接, 从而避免每次建立物理连接的开销。

11.3.5. DriverManagerDataSource

DriverManagerDataSource类实现了 SmartDataSource接口。在applicationContext.xml中可以使用 bean properties来设置JDBC Driver属性,该类每次返回的都是一个新的连接。

该类主要在测试以及脱离J2EE容器的独立环境中使用。它既可以用来在application context中作为一个 DataSource bean,也可以在简单的JNDI环境下使用。 由于Connection.close()仅仅只是简单的关闭数据库连接,因此任何能够获取 DataSource的持久化代码都能很好的工作。不过使用JavaBean风格的连接池 (比如commons-dbcp)也并非难事。即使是在测试环境下,使用连接池也是一种比使用 DriverManagerDataSource更好的做法。

11.3.6. TransactionAwareDataSourceProxy

TransactionAwareDataSourceProxy作为目标DataSource的一个代理, 在对目标DataSource包装的同时,还增加了Spring的事务管理能力, 在这一点上,这个类的功能非常像J2EE服务器所提供的事务化的JNDI DataSource

[Note] Note

该类几乎很少被用到,除非现有代码在被调用的时候需要一个标准的 JDBC DataSource接口实现作为参数。 这种情况下,这个类可以使现有代码参与Spring的事务管理。通常最好的做法是使用更高层的抽象 来对数据源进行管理,比如JdbcTemplateDataSourceUtils等等。

如果需要更详细的资料,请参考TransactionAwareDataSourceProxy JavaDoc 。

11.3.7. DataSourceTransactionManager

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不支持隔离级别的定制!

11.4. 用Java对象来表达JDBC操作

org.springframework.jdbc.object包下的类允许用户以更加 面向对象的方式去访问数据库。比如说,用户可以执行查询并返回一个list, 该list作为一个结果集将把从数据库中取出的列数据映射到业务对象的属性上。 用户也可以执行存储过程,以及运行更新、删除以及插入SQL语句。

[Note] Note

在许多Spring开发人员中间存在有一种观点,那就是下面将要提到的各种RDBMS操作类 (StoredProcedure类除外) 通常也可以直接使用JdbcTemplate相关的方法来替换。 相对于把一个查询操作封装成一个类而言,直接调用JdbcTemplate方法将更简单 而且更容易理解。

必须说明的一点就是,这仅仅只是一种观点而已, 如果你认为你可以从直接使用RDBMS操作类中获取一些额外的好处, 你不妨根据自己的需要和喜好进行不同的选择。

11.4.1. SqlQuery

SqlQuery是一个可重用、线程安全的类,它封装了一个SQL查询。 其子类必须实现newResultReader()方法,该方法用来在遍历 ResultSet的时候能使用一个类来保存结果。 我们很少需要直接使用SqlQuery,因为其子类 MappingSqlQuery作为一个更加易用的实现能够将结果集中的行映射为Java对象。 SqlQuery还有另外两个扩展分别是 MappingSqlQueryWithParametersUpdatableSqlQuery

11.4.2. MappingSqlQuery

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对象,如果有对象满足查询条件的话。

11.4.3. SqlUpdate

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);
}
}

11.4.4. StoredProcedure

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);
}
}

11.4.5. SqlFunction

SqlFunction RDBMS操作类封装了一个SQL“函数”包装器(wrapper), 该包装器适用于查询并返回一个单行结果集。默认返回的是一个int值, 不过我们可以采用类似JdbcTemplate中的queryForXxx 做法自己实现来返回其它类型。SqlFunction优势在于我们不必创建 JdbcTemplate,这些它都在内部替我们做了。

该类的主要用途是调用SQL函数来返回一个单值的结果集,比如类似“select user()”、 “select sysdate from dual”的查询。如果需要调用更复杂的存储函数, 可以使用StoredProcedureSqlCall

SqlFunction是一个具体类,通常我们不需要它的子类。 其用法是创建该类的实例,然后声明SQL语句以及参数就可以调用相关的run方法去多次执行函数。 下面的例子用来返回指定表的记录行数:

public int countRows() {
SqlFunction sf = new SqlFunction(dataSource, "select count(*) from mytable");
sf.compile();
return sf.run();
}
posted @ 2008-08-12 15:31 飞飞 阅读(3927) | 评论 (2)编辑 收藏

仅列出标题
共12页: 上一页 1 2 3 4 5 6 7 8 9 下一页 Last