你正在学习CSS布局吗?是不是还不能完全掌握纯CSS布局?通常有两种情况阻碍你的学习:
第一种可能是你还没有理解CSS处理页面的原理。在你考虑你的页面整体表现效果前,你应当先考虑内容的语义和结构,然后再针对语义、结构添加CSS。这篇文章将告诉你应该怎样把HTML结构化。
另一种原因是你对那些非常熟悉的表现层属性(例如:cellpadding,、hspace、align="left"等等)束手无策,不知道该转换成对应的什么CSS语句。当你解决了第一种问题,知道了如何结构化你的HTML,我再给出一个列表,详细列出原来的表现属性用什么CSS来代替。
结构化HTML
我们在刚学习网页制作时,总是先考虑怎么设计,考虑那些图片、字体、颜色、以及布局方案。然后我们用Photoshop或者Fireworks画出来、切割成小图。最后再通过编辑HTML将所有设计还原表现在页面上。
如果你希望你的HTML页面用CSS布局(是CSS-friendly的),你需要回头重来,先不考虑“外观”,要先思考你的页面内容的语义和结构。
外观并不是最重要的。一个结构良好的HTML页面可以以任何外观表现出来,CSS Zen Garden是一个典型的例子。CSS Zen Garden帮助我们最终认识到CSS的强大力量。
HTML不仅仅只在电脑屏幕上阅读。你用photoshop精心设计的画面可能不能显示在PDA、移动电话和屏幕阅读机上。但是一个结构良好的HTML页面可以通过CSS的不同定义,显示在任何地方,任何网络设备上。
开始思考
首先要学习什么是"结构",一些作家也称之为"语义"。这个术语的意思是你需要分析你的内容块,以及每块内容服务的目的,然后再根据这些内容目的建立起相应的HTML结构。
如果你坐下来仔细分析和规划你的页面结构,你可能得到类似这样的几块:
标志和站点名称
主页面内容
站点导航(主菜单)
子菜单
搜索框
功能区(例如购物车、收银台)
页脚(版权和有关法律声明)
我们通常采用DIV元素来将这些结构定义出来,类似这样:
<div id="header"></div>
<div id="content"></div>
<div id="globalnav"></div>
<div id="subnav"></div>
<div id="search"></div>
<div id="shop"></div>
<div id="footer"></div>
这不是布局,是结构。这是一个对内容块的语义说明。当你理解了你的结构,就可以加对应的ID在DIV上。DIV容器中可以包含任何内容块,也可以嵌套另一个DIV。内容块可以包含任意的HTML元素---标题、段落、图片、表格、列表等等。
根据上面讲述的,你已经知道如何结构化HTML,现在你可以进行布局和样式定义了。每一个内容块都可以放在页面上任何地方,再指定这个块的颜色、字体、边框、背景以及对齐属性等等。
使用选择器是件美妙的事
id的名称是控制某一内容块的手段,通过给这个内容块套上DIV并加上唯一的id,你就可以用CSS选择器来精确定义每一个页面元素的外观表现,包括标题、列表、图片、链接或者段落等等。例如你为#header写一个CSS规则,就可以完全不同于#content里的图片规则。
另外一个例子是:你可以通过不同规则来定义不同内容块里的链接样式。类似这样:#globalnav a:link或者 #subnav a:link或者#content a:link。你也可以定义不同内容块中相同元素的样式不一样。例如,通过#content p和#footer p分别定义#content和#footer中p的样式。从结构上讲,你的页面是由图片、链接、列表、段落等组成的,这些元素本身并不会对显示在什么网络设备中(PDA还是手机或者网络电视)有影响,它们可以被定义为任何的表现外观。
一个仔细结构化的HTML页面非常简单,每一个元素都被用于结构目的。当你想缩进一个段落,不需要使用blockquote标签,只要使用p标签,并对p加一个CSS的margin规则就可以实现缩进目的。p是结构化标签,margin是表现属性,前者属于HTML,后者属于CSS。(这就是结构于表现的相分离.)
良好结构的HTML页面内几乎没有表现属性的标签。代码非常干净简洁。例如,原先的代码<table width="80%" cellpadding="3" border="2" align="left">,现在可以只在HTML中写<table>,所有控制表现的东西都写到CSS中去,在结构化的HTML中,table就是表格,而不是其他什么(比如被用来布局和定位)。
亲自实践一下结构化
上面说的只是最基本的结构,实际应用中,你可以根据需要来调整内容块。常常会出现DIV嵌套的情况,你会看到"container"层中又有其它层,结构类似这样:
<div id="navcontainer">
<div id="globalnav">
<ul>a list</ul>
</div>
<div id="subnav">
<ul>another list</ul>
</div>
</div>
嵌套的div元素允许你定义更多的CSS规则来控制表现,例如:你可以给#navcontainer一个规则让列表居右,再给#globalnav一个规则让列表居左,而给#subnav的list另一个完全不同的表现。
用CSS替换传统方法
下面的列表将帮助你用CSS替换传统方法:
HTML属性以及相对应的CSS方法
HTML属性 CSS方法 说明
align="left"
align="right" float: left;
float: right; 使用CSS可以浮动 任何元素:图片、段落、div、标题、表格、列表等等
当你使用float属性,必须给这个浮动元素定义一个宽度。
marginwidth="0" leftmargin="0" marginheight="0" topmargin="0" margin: 0; 使用CSS, margin可以设置在任何元素上, 不仅仅是body元素.更重要的,你可以分别指定元素的top, right, bottom和left的margin值。
vlink="#333399" alink="#000000" link="#3333FF" a:link #3ff;
a:visited: #339;
a:hover: #999;
a:active: #00f;
在HTML中,链接的颜色作为body的一个属性值定义。整个页面的链接风格都一样。使用CSS的选择器,页面不同部分的链接样式可以不一样。
bgcolor="#FFFFFF" background-color: #fff; 在CSS中,任何元素都可以定义背景颜色,不仅仅局限于body和table元素。
bordercolor="#FFFFFF" border-color: #fff; 任何元素都可以设置边框(boeder),你可以分别定义top, right, bottom和left
border="3"
cellspacing="3" border-width: 3px; 用CSS,你可以定义table的边框为统一样式,也可以分别定义top, right, bottom and left边框的颜色、尺寸和样式。
你可以使用 table, td or th 这些选择器.
如果你需要设置无边框效果,可以使用CSS定义: border-collapse: collapse;
<br clear="left">
<br clear="right">
<br clear="all">
clear: left;
clear: right;
clear: both;
许多2列或者3列布局都使用 float属性来定位。如果你在浮动层中定义了背景颜色或者背景图片,你可以使用clear属性.
cellpadding="3"
vspace="3"
hspace="3" padding: 3px; 用CSS,任何元素都可以设定padding属性,同样,padding可以分别设置top, right, bottom and left。padding是透明的。
align="center" text-align: center;
margin-right: auto; margin-left: auto;
Text-align 只适用于文本.
象div,p这样的块级怨毒可以通过margin-right: auto; 和margin-left: auto;来水平居中
一些令人遗憾的技巧和工作环境
由于浏览器对CSS支持的不完善,我们有时候不得不采取一些技巧(hacks)或建立一种环境(Workarounds)来让CSS实现传统方法同样的效果。例如块级元素有时侯需要使用水平居中的技巧,盒模型bug的技巧等等。所有这些技巧都在Molly Holzschlag的文章《Integrated Web Design: Strategies for Long-Term CSS Hack Management》中有详细说明。
另外一个关于CSS技巧的资源站点是Big John和Holly Bergevin的“Position is Everything”。
数据库索引
在oracle中,有很多理由需要使用索引,在一个OLTP系统中,有可能索引占用的空间会大于所在表占用的空间,对数据访问速度的加快也能提高后续的反应速度。我们知道,oracle提供了很多类型的索引 :
1.B树索引 这是最普通的索引,从oracle的早期版本就引入了。
2.位图索引(Bitmap indexes) 位图索引适用于一列只含有很少数量的不同的值,即low cardinality,这种索引对于只读数据库来说非常快,但是对于经常要进行更新操作来说就不适用了。
3.位图连接索引(Bitmap join indexes) 这种索引是多列索引(multi-column index),建立在和其他表进行连接的基础上。 这是唯一的建立这种索引的方法,它使用了和sql类似的from和where子句。
对SQL SERVER 索引可以加快数据检索的速度,但它会使数据的select,insert,delete变慢,对于聚集索引,数据是按照逻辑顺序存放在一定的物理位置,当变更数据时,根据新的数据顺序,需要将许多数据进行物理位置的移动。对非聚集索引,数据更新时也需要更新索引页,这也需要占用系统时间。因此在一个表中使用太多的索引,会影响数据库的性能。
在《数据库原理》里面,对聚簇索引的解释是:聚簇索引的顺序就是数据的物理存储顺序,而对非聚簇索引的解释是:索引顺序与数据物理排列顺序无关。正式因为如此,所以一个表最多只能有一个聚簇索引。
索引的管理成本
1、 存储索引的磁盘空间
2、 执行数据修改操作(INSERT、UPDATE、DELETE)产生的索引维护
3、 在数据处理时回需额外的回退空间。
对于索引的优化,从索引实现本身:
1.对于等职查询使用哈希结构的索引,对含非等值查询的使用B树。
2.压缩索引键值,减少索引层数
对于用户的选择来说:
1.对于查询一个范围的值,或者多个记录的值时使用聚集索引,而对于经常查询少数记录的使用非聚集索引
2.对于插入,删除,更新操作多的尽量不使用聚集索引,对于比较小的表结构最好不使用索引,因为索引带来的负荷可能成为瓶颈。
3.一般聚集索引只有一个,要将聚集索引建立在,用以缩小查询范围或需要排序的字段上
如何快速新建大数据量表的索引
如果一个表的记录达到100万以上的话,要对其中一个字段建索引可能要花很长的时间,甚至导致服务器数据库死机,因为在建索引的时候ORACLE要将索引字段所有的内容取出并进行全面排序,数据量大的话可能导致服务器排序内存不足而引用磁盘交换空间进行,这将严重影响服务器数据库的工作。解决方法是增大数据库启动初始化中的排序内存参数,如果要进行大量的索引修改可以设置10M以上的排序内存(ORACLE缺省大小为64K),在索引建立完成后应将参数修改回来,因为在实际OLTP数据库应用中一般不会用到这么大的排序内存。
几个C++的问题
刚刚整理了一下网络和文档中对几个C++问题的说明。
1.extern "C"的作用
因为 C 语言和 C++ 语言的编译规则不一样,所以要告诉系统哪些函数是用 C 方式编译,哪些函数需要用 C++ 方式编译。
如果你不加 extern "C" ,在编译时,系统会提示找不到此函数。
extern "C"表示编译生成的内部符号名使用C约定
例如:
int Fun(int i,int j)
C:_Fun
C++:_Fun_int_int
具体生成什么可能与编译器有关
由于C++支持重载,而重载是在编译期确定的,所以C++必须在内部符号名上区分各重载函数,所以就将参数类型加在函数名后。
2. C++引用与指针的比较
下面程序中,n是m的一个引用(reference),m是被引用物(referent)。
int m;
int &n = m;
n相当于m的别名(绰号),对n的任何操作就是对m的操作。
所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。
引用的规则:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
以下示例程序中,k被初始化为i的引用。
语句k = j并不能将k修改成为j的引用,只是把k的值改变成为6。
由于k是i的引用,所以i的值也变成了6。
int i = 5;
int j = 6;
int &k = i;
k = j; // k和i的值都变成了6;
3. struct和class的区别
1)C++中的struct只是为了和C兼容而做的一个默认为public的class。
2)struct默认访问权限为pulbic; class默认访问权限为private
3)在继承下 struct默认pulbic继承; class默认访问private继承;
C++中 ,结构和类只是其默认权限不同,除此之外没有差别。
4.堆和栈的区别
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456/0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
5.Debug 和 Release 编译方式的本质区别
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
6.sizeof用法总结
A. 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。
B. 参数为数组或指针。下面举例说明.
int a[50]; //sizeof(a)=4*50=200; 求数组所占的空间大小
int *a=new int[50];// sizeof(a)=4; a为一个指针,sizeof(a)是求指针
//的大小,在32位系统中,当然是占4个字节。
C. 参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。 第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一 个实例在内存中都有唯一的地址。
下面举例说明,
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,s为一个指针。
Class test1{ };//sizeof(test1)=1;
D. 参数为其他。下面举例说明。
int func(char s[5]);
{
//数的参数在传递的时候系统处理为一个指针,所
//以sizeof(s)实际上为求指针的大小。
}
sizeof(func(“1234”))=4//因为func的返回类型为int,所以相当于
//求sizeof(int).
多线程程序设计的相关问题
一、 什么是进程?什么是线程?
进程是一大堆系统对象拥有权的集合。如进程拥有内存上下文,文件句柄,可以派生出很多线程,也可以拥有很多DLL模块。在windows系统中,进程并不完成实质的工作,只是提供一个相对独立的运行环境,线程才是完成实际工作的载体。线程从属于进程,共享进程所拥有的系统对象。线程是操作系统调度的单位。实质上,线程就是一段可执行代码。
采用多进程的优点和缺点:
优点:运行环境相对独立,某一进程的崩溃一般不会影响到其它进程的执行。
缺点:
耗时耗资源:启动一个进程需要申请大量的系统资源,其中包括虚拟内存、文件句柄以及加载各种必要的动态链接库;线程则不需要以上动作,因为它共享进程中的所有资源。
“系统准备一个进程环境可能需要好几M的空间”
通信复杂:进程的地址空间独立,进程A的地址X,在进程B中可能是无意义的,这样,当进程间需要共享数据时,就需要特殊的机制来完成这些工作。线程则在同一地址空间,数据共享方便快捷。“线程是一个物美价廉的选择,在一个Windows上拥有500个线程是一件很轻易的事情,但是500个进程将是难以想象的”。
二、 为什么需要多线程(解释何时考虑使用线程)
从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
1、 程序包含复杂的计算任务时
主要是利用多线程获取更多的CPU时间(资源)。
2、 处理速度较慢的外围设备
比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
3、 程序设计自身的需要
WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。
三、 使用多线程可能出现的问题(列举问题)
事实上,单纯的使用线程不会产生任何问题,其启动、运行和结束都是非常简单的事情。在Win32环境下,启动:CreateThread,运行就是函数执行的过程,中止就是函数返回的过程或者调用ExitThread。但是由于下列原因可能会使在使用线程的过程中带来一系列问题:
1、 版本问题
多任务的概念是随着实际需求的提出而产生,最初的程序设计者并没有考虑到代码需要在多线程环境下运行,在多线程环境下使用这些代码无疑将产生访问冲突。最典型的例子就是C runtime library。最早的C runtime library产生于20世纪70年代,当时连多任务都是一个新奇的概念,更别说什么多线程了,该版本的库中使用了大量全局变量和静态变量(产生竞争条件的根源,对局部变量无此要求,因为局部变量都使用栈,每个线程都有自己的栈空间,另外在启动线程时,给线程函数的参数应该是尽量使用值,而非指针或引用,这样可以避免因此带来的冲突问题),如在该库中统一使用一个errno变量来表明程序的错误码,如果在多线程中使用该库,并且都需要设置错误码时,此时即产生了一个冲突。
VC为防止以上问题,提供了另外一个线程安全的C runtime library,因此在写多线程程序时,需要注意所连接库的版本是否正确(该过程一般由应用程序向导完成,因此平时编程并无此问题)。与此有关的还有一些其它版本:单线程版、多线程版调试版和多线程发行版。
2、 线程间共享资源时形成竞争条件(race condition)
一般而言,线程并不是单独行动,通常是多个线程分工协作,完成一个大任务中的不同小任务,此时,这些线程之间就需要共同操作一些资源,比较典型的例子是多个线程进行文件操作或屏幕打印的情况:线程A在写文件进行了一半时,发生了context switch,另外一个线程B继续进行写文件操作,此时文件的内容将会凌乱不堪。甚至造成异常错误。典型的例子是,三个线程,线程A在堆中申请了一块内存并填入了一个值,线程B读取了该值后将该内存释放,如果线程C还要对该内存操作时,将导致异常。
3、 线程间的通信问题
线程协作完成某一任务时,有时还需要通信以控制任务的执行步骤,典型的例子就是读写者线程:写线程在对某内存区域写完数据后,需要通知读线程来取,读完之后又需要通知写线程可以继续往里写入数据。更为广泛的例子是:某线程需要等待某一事件发生,以决定是否继续工作。此时,如果没有正确控制线程的执行过程,将导致不可预料的错误发生。
4、 由于不规范的使用线程导致系统效率下降
进程中包含了一个以上的线程,这些线程可能会动态的申请某些资源,如某些数据库线程可能会动态加载数据库方面的动态链接库,但是在该线程结束时,并没有及时释放该动态链接库即被其他线程强行终止,于是该进程中的该动态链接库引用计数不为0,从而导致该动态链接库在该进程中存有一个副本。当这种情况频繁时,将对系统效率产生很大的影响。
四、 线程的类型(解释UI线程和WORKER线程的区别和联系)
严格说来,线程并没有什么本质区别,但是Win32编程文档中却反复强调UI线程和Worker线程的区别。并给出了它们的定义:
UI线程就是:拥有消息队列和窗口的线程,并且它的主要职责是处理窗口消息。Worker线程则没有消息队列,但是当Worker线程产生一个用户界面(消息框和模式对话框除外)时,则该线程则摇身一变,成为UI线程。
问题:
1、 线程的消息队列和窗口的消息队列
在Win32中,每个线程都有它自己专属的消息队列,而窗口并不总是有消息队列,因为一个UI线程可以创建很多个窗口。
2、 UI线程到底跟Worker线程存在什么差别?
职责不一样:UI线程负责处理与用户界面有关的消息,一般而言,用户界面消息来自用户输入(如鼠标键盘消息)、系统消息(如WM_PAINT)以及程序产生的用户自定义消息。因此,在该线程下一般不能存在等待(wait…)函数,这样该线程就会挂起,从而影响消息队列的处理。Worker线程不用处理用户界面消息,而是完成一般性的计算任务,该线程等待计算过程中必要的资源时,不会影响到界面的刷新动作。
操作系统的管理不一样:对UI线程来说,产生一个UI线程实际上产生了两个线程,一个是其自身,另一个是操作系统为响应其GDI调用而产生的影子线程。
3、 Worker线程变成UI线程有什么不好?
Worker线程一般用于计算,此时如果它转换为UI线程的话,将无暇顾及用户界面的消息响应。
4、 Worker线程可否拥有自己的消息队列?
Worker线程同样可以拥有自己的消息队列,该队列一般通过PeekMessage()调用建立,通过GetMessage调用来解析。(具体实现看源码)
5、 用以下规则来管理win32中线程、消息和窗口的互动
所有传送给某一窗口的消息,将由产生该窗口的线程负责处理。
五、 线程的启动和中止(解释启动线程的不同方式及其它们的区别和实用场合)
随C Runtime Library库的更新和编程环境的不同,线程的启动方式也有所不同,以下介绍几种典型的线程启动方式。
1、_beginthread和_endthread
该函数是C Runtime Library中的函数,它负责初始化函数库;其原型如下unsigned long _beginthread( void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist );“该函数被认为是头脑简单的函数”,使用该函数导致无法有效的控制被创建线程,如不能在启动时将该线程挂起,无法为该线程设置优先权等。另外,该函数为隐藏Win32的实现细节,启动线程的第一件事情即将自己的Handle关闭,因此也就无法利用这个Handle来等待该线程结束等操作。该函数是早期的C Runtime Library的产物,不提倡使用,后期的改良版本为_beginthreadex。
通过_beginthread启动的线程在应当通过调用_endthread结束,以保证清除与线程相关的资源。
2、_beginthreadex和_endthreadex
该函数是C Runtime Library中的一个函数,用标准C实现,相比_beginthread,_beginthreadex对线程控制更为有力(比前者多三个参数),是_beginthread的加强版。其原型为unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr );该函数返回新线程的句柄,通过该句柄可实现对线程的控制。虽然,该函数是用标准C写的(即可不加修改就可以移植到其他系统执行),但是由于它与Windows系统有着紧密的联系(需要手动关闭该线程产生的Handle),因此实现时,往往需要包含windows.h。
通过_beginthreadex启动的线程通过调用_endthreadex做相关清理。
3、CreateThread和ExitThread
CreateThread是Win32 API函数集中的一个函数,其原型为HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD wCreationFlags,LPDWORD lpThreadId);该函数使用Win32编程环境中的类型约定,只适用于Windows系统。参数形式与_beginthreadex一致,对线程控制能力也与之一致,只是该函数与C Runtime Library没有任何关系,它不负责初始化该库,因此在多线程环境中,如果使用该函数启动线程,则不应使用C Runtime Library中的多线程版本的函数。取而代之的应该是功能相对应的 Win32 API函数;另外,应当自己手工提供线程同步的代码。
通过CreateThread创建的线程则通过ExitThread做清理工作。
4、AfxBeginThread和AfxEndThread
AfxBeginThread是MFC提供的线程启动方式,它是个重载函数,有两种调用形式:Worker线程版和UI线程版。MFC对Win32线程做了小心的很好的封装(CWinThread),虽然其总是调用了_beginthreadex来启动一个线程,但是其额外做的工作使得在MFC环境下,操作线程变得简单明了,并且不需要太多的关注细节问题。MFC在线程的封装方面主要做了下列事情:
1、 自动清除CWinThread对象
2、 关闭线程handle,线程对象自动释放
3、 存储了线程相关的重要参数,即线程handle和线程ID
4、 辅之以其它MFC同步对象,方便的实现线程同步
5、 使用了严格的断言调试语句,使线程调试变得相对简单
“(C Runtime Library是用标准C开发的实用函数集)如果多线程程序中使用了标准C库函数,并用CreateThread()和ExitThread(),则会导致内存泄漏。解决这个问题的方法是用C运行库(run-time library)函数来启动和终止线程,而不用WIN32 API定义的CreateThread()和ExitThread()。在C运行库函数中,它们的替代函数分别是_beginthreadex()和_exitthreadex(),需要的头文件是_process.h。在VC6.0下,还需在Project->Settings->C/C++->Code Generation中选择Multithreaded Runtime Library。当然,也可以通过避免使用C标准库函数的方法来解决上述问题,WIN32提供了一些C标准库函数的替代函数,例如,可用wsprintf()和lstrlen()来代替sprintf()和strlen()。这样,使用CreateThread()和ExitThread()不会出现问题。”
六、 线程的同步问题(介绍Windows的同步机制)
1、 怎样等待一个线程结束(忙等(busy loop)和高效的等(WaitForSingleObject))
1) 忙等(busy loop)
hThrd = CreateThread(NULL,0,ThreadFunc,(LPVOID)1,0,&threadId );
for (;;)
{
GetExitCodeThread(hThrd, &exitCode);
if ( exitCode != STILL_ACTIVE )
break;
}
CloseHandle(hThrd);
缺点:耗费CPU资源,且如果在UI线程中这样等待将导致窗口无法刷新。不推荐使用。
2) 高效的等待
(1)WaitForSingleObject;
关于WaitForSingleObject的参数,前者为等待的对象,后者为等待的时间,对某些执行时间较长的线程,可以设置一个合适的值,等待完这个时间后,更新界面,然后继续等待,或者强行终止线程。
将以上的等待部分的代码改为:
WaitForSingleObject(hThrd,INFINITE);
该函数相当于Sleep函数,当需要等待的对象(句柄)没有被触发时,等待的线程将被自动挂起。该方法解决了耗费CPU时间的问题,但是在UI线程中,仍不能使用该方法来等待某一线程结束。
解决方法之一:创建一个Worker管理者线程,在该线程中等待,工作者线程完成,然后由管理者线程发消息通知UI线程更新窗口。
(2)WaitForMultipleObject
该函数允许在同一时间等待多个对象,函数的原型如下:
DWORD WaitForMultipleObject(DWORD nCount,CONST HANDE *lpHandles,BOOL bWaitAll,dwMilliseconds);
第一个参数表示句柄数组的大小;等待的对象不能超过64
第二个参数为句柄数组;
第三个参数表明是否等待所有对象激发。True表示是。
第四个参数为等待时间。
关于WaitForMultipleObject的返回值:
当bWaitAll为True时,返回值为WAIT_OBJECT_0;
当bWaitAll为false时,返回值减去WAIT_OBJECT_0,就是激发对象所在的下标。
应用:
A) 解决多个工人n完成多个任务m(n<m)的问题(bWaitAll设置为false)
解决的思路如下:先从m个任务中取出n个任务,对应地用n个工人去完成,然后利用该函数等待其中任意一个工人结束任务,一旦结束则让其做另外一个任务
B) 解决等待多个资源的问题(bWaitAll设置为true)
哲学家就餐问题:5个哲学家在圆桌旁,每个哲学家左手边放着1只筷子,哲学家做两件事情,吃饭和思考,吃饭时同时需要其左右的两只筷子。
解决思路:将哲学家模拟为线程,筷子为资源,只有哲学家线程同时获得两个资源时,方可进一步动作(吃饭)。即:
WaitForMultipleObjects(2, myChopsticks, TRUE, INFINITE);
MyChopsticks是一个大小为5的核心对象数组。
(3)MsgWaitForMultipleObjects
原型:
DWORD MsgWaitForMultipleObjects( DWORD nCount,CONST HANDLE pHandles,BOOL fWaitAll,DWORD dwMilliseconds,DWORD dwWakeMask);
前几个参数含义同WaitForMultipleObject,最后一个是消息屏蔽标识,指示接收消息的类型。此外返回值也有额外的意义:当消息到达时,该函数返回WAIT_OBJECT_0+nCount。以下是常见的使用MsgWaitForMultipleObjects的架构:
while (!quit)
{ // Wait for next message or object being signaled
DWORD dwWake;
dwWake = MsgWaitForMultipleObjects(
gNumPrinting,
gPrintJobs,
FALSE,
INFINITE,
QS_ALLEVENTS);
if (dwWake >= WAIT_OBJECT_0 && dwWake < WAIT_OBJECT_0 + gNumPrinting)
{
//对象被触发
} // end if
else if (dwWake == WAIT_OBJECT_0 + gNumPrinting)
{
//有消息到达
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{ // Get Next message in queue
if (msg.message == WM_QUIT)
{
quit = TRUE;
exitCode = msg.wParam;
break;
} // end if
TranslateMessage(&msg);
DispatchMessage(&msg);
} // end while
}
} // end while
2、 怎样有效的控制一个线程
在任何情况下,切记线程的核心属性为:线程的句柄,线程的ID号。因此控制一个线程也需从这两方面着手。
1) 使用能返回线程Handle的启动函数来启动线程(除_beginthread外)
2) 尽量不要使一个工作量较大的线程成为“闷葫芦”,从而使该线程能够接收外界通知消息;如下列代码:
MSG msg;
while(1)
{
PeekMessage(&msg,NULL,0,0,PM_REMOVE);
if(msg.message==WM_MY)
break;
Sleep(100);
}
注:GetMessage也是用来得到消息队列中一条消息的函数,它们的区别在于GetMessage是同步的,即如果消息队列中没有消息的话,该线程将自动挂起。使用GetMessage可以使Worker线程成为一个一步一动的线程!
MSG msg;
while(GetMessage(&msg,NULL,0,0))
{
if(msg.message==WM_MY)
{
//Do something here
}
}
以上的过程也可以通过事件对象予以实现。
悬而未决的问题:怎么控制一个正在等待其他事件的线程。如:一个TCP监听线程,在某一Socket上listen,此时该线程处于挂起状态!但是现在主线程又需要关闭该线程,应该怎么操作!
3、 怎样互斥访问一个资源(CMutex和Critical Section)
何时需要一个互斥对象?
常见的情形:多个线程需要不定时的操作同一链表(锁链表的头指针);多个线程需要不定时的进行写文件或是进行屏幕输出(锁文件句柄或屏幕句柄);多个线程需要不定时对某个计数器进行操作(锁这个变量);在多线程环境吓,凡是涉及到对全局变量、静态变量、堆中的内存进行访问时,都应该考虑,是否可能出现一个race condition(竞争条件)。
1) 互斥器
Win32提供了对互斥资源访问的一整套机制,其中之一就是互斥器,MFC将这些API函数加以封装,形成了CMutex互斥类,使用这两种方法都能够实现对资源的互斥访问。
Win32中的API:
CreateMutex:
原型:
HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName );
第一个参数为安全属性;
第二个参数用来指示互斥器的拥有者是否为当前线程;
第三个参数为互斥器的名称;
当不再需要互斥器时,应当调用CloseHandle关闭。
约定:互斥器产生之后,由某一线程完成锁定工作(即调用Wait…函数),此时系统将该mutex的拥有权交于该线程,然后短暂地将该对象设置为激发态,于是Wait…函数返回,做完相应的工作之后(如:修改链表指针、修改计数器、写文件等),调用ReleaseMutex释放拥有权。周而复始。
MFC中的互斥器CMutex对象:
A、 利用其构造函数产生一个互斥器对象
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner, LPCTSTR lpName);
B、 配合CSingleLock或者CmutipleLock产生一个临时对象,对产生的互斥器进行加锁和释放的动作;
2) 临界区
另一个提供互斥访问的机制是Critical Section,该机制较前一种方法廉价,因为它不属于不是系统的核心对象;临界区可以反复进入,这一点与Mutex有所区别,这需要我们在使用临界区时,保证进入的次数要等于离开的次数。
相关函数为InitializeCriticalSection、DeleteCriticalSection、EnterCriticalSection、LeaveCriticalSection。
4、 怎样等待多个不同(或者相同)资源(WaitForMultiObject)
等待多个不同资源在多线程程序设计中时常遇到,如:等待某一线程结束和某一个资源被释放,等待缓冲区和设备准备好两个资源;这种现实情况,可以分别为不同的资源设置系统对象,然后利用WaitForMultiObject进行等待。
5、 怎样等待多个资源中的一个(使用CSemaphore)
现实中还可能出现如下情形:客人租相机的问题:有若干客人需要,租相机,总相机数为n,相机租完后,客人必须等待,只要有一个相机,则某客人就可以等到租借。还有许多问题可以用这种Producer/consumer模型加以概括。
这种情形即是等待多个资源中的一个的情况,在Win32程序设计中则经常使用信号量(Semaphore)来解决此问题。
Win32系统中,信号量具有以下特性:
一个信号量可以被锁定N次,N一般代表可用资源的个数,上例中即可代表相机的个数,信号量初始化后,在Win32环境下调用一次Wait…操作即表示对其的一次锁定,信号量的值相应加1,操作完后,调用ReleaseSemaphore操作,即代表资源释放(上述例子中就是归还相机)。MFC对Win32信号量的相关API函数进行了封装(CSemaphore),配合CMultiLock 或者 CSingleLock即可实现锁定和资源释放的动作。
七、 线程间的通信
线程间的通信有许多方法可以实现,视场合不同也有不同的应用,大致可以分为两类:进程内的线程通信和进程间的通信。关于进程内线程的通信,前面所述的各种同步互斥等待机制也可归属线程间通信的范畴,
1、 使用线程消息实现线程通信
2、 使用事件对象实现线程通信
Win32还提供了一种比较灵活的核心对象,该对象完全受控于程序(只是清除的时候由系统回收),这就是Event(事件)对象。事件对象一般用于线程间的通知。下面先看事件对象的一些属性:
创建一个事件对象可以调用Win32 API函数完成,也可以使用MFC封装的事件对象。其API原型为:
HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState, LPCTSTR lpName );
第二个参数指示事件对象是否为手动修改状态(手动修改需要显式调用ResEvent函数);第三个参数设置事件对象的初态,true为激发态,false为非激发态。第四个参数为事件的名字。
事件对象自从创建后即在程序的控制下处于激发态和非激发态之间翻转。
八、 线程代码的调试
九、 什么是线程安全的代码
十、 多线程程序设计的几个原则
关键词:Windows Socket编程,聊天程序,Win32 API编程
摘要:本程序使用C语言和Win32 API编程,使用Windows Socket函数库完成聊天程序的基本功能。可以互发消息,并且有聊天记录。
目录
1、程序运行
2、聊天程序实现原理简述
3、总结
――――――――――――――――――――――――――――――――――――――
1 程序运行
程序集服务器端和客户端于一体,运行时运行两个实例,一个做服务器端,一个做客户端。
启动之后,最初界面如下图1:
图1,起始界面
然后启动服务器端,监听;启动客户端,连接服务器。二者建立连接之后,就可以互相发消息了。启动操作如下图2:
图2,启动C/S两端
互发消息,如图3,4:
图3,4,互发消息
2 聊天程序实现原理简述
由于程序是服务器端和客户端是一体的,所以我加了一个BOOL server变量,来判断是服务器端,还是客户端。点击菜单“文件”->“启动服务器”,则server=TRUE,表示程序实例是服务器端;反之,server=FALSE,表示程序实例是客户端。
程序对话框在WM_INITDIALOG消息响应中使用WSAStartup初始化Socket
if(WSAStartup(WINSOCK_VERION,&ws)) { MessageBox(hwnd,"Winsock初始化失败", szDlgTitle,MB_OK|MB_ICONSTOP); WSACleanup(); return FALSE; }//初始化 |
在WM_CLOSE消息响应中释放Socket:
if(connected_skt != INVALID_SOCKET) { closesocket(connected_skt); } if( skt != INVALID_SOCKET ) { closesocket(skt); } if( WSACleanup() != 0 ) { MessageBox(hwnd, "不能释放Winsocket",szDlgTitle,MB_OK ); } |
2.1 服务器端
监听,按下监听之后调用CreateServer(hwnd)函数。
因为Socket已被初始化,所以这里就创建一个socket:
skt=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
创建成功,接着就绑定创建的socket:
bind(skt,(SOCKADDR*)&addr,sizeof(addr);
绑定成功之后,监听这个socket:
listen(skt,MAX_CONNECTED_NUM );
监听成功之后,就开始选择监听客户端的连接事件:
if( WSAAsyncSelect(skt,hwnd,SOCKETMSG,FD_ACCEPT) == SOCKET_ERROR ) { MessageBox(hwnd,"WSAAsyncSelect() 失败", szDlgTitle,MB_OK); return FALSE; } |
当有客户端连接的时候,WSAAsyncSelect(skt,hwnd,SOCKETMSG,FD_ACCEPT)函数拦截到这一事件,函数就向程序发送消息SOCKETMSG(这是一个自定义消息),然后程序处理这一消息:
有客户端连接就接受该连接,并创建一个新的用来与客户端通信的socket:connected_skt,原来最初创建的socket――skt就继续监听有没有客户端连接事件。
if((connected_skt=accept(skt,(struct sockaddr *)&clientaddr,&Len))== INVALID_SOCKET ) { MessageBox(hwnd,"接受客户端的Socket连接失败", szDlgTitle,MB_OK); return FALSE; } SetDlgItemText(hwnd,IDC_REVTXT,"已经接受客户端连接"); //连接上了,然后监听客户端的FD_READ和关闭 WSAAsyncSelect(connected_skt, hwnd,SOCKETMSG,FD_READ|FD_CLOSE); |
接受了连接之后,就可以与客户端互发消息了。
2.2 客户端连接
按下连接服务器之后,使用CreateClient(hwnd)函数创建客户端,并连接到服务器端。
首先创建socket,然后connect到服务器,然后使socket对读,关闭,连接事件进行过滤选择:WSAAsyncSelect(skt,hwnd,SOCKETMSG,FD_READ|FD_CLOSE|FD_CONNECT )。
接着就可以和服务器互发消息了。
3 总结
通过这次聊天程序的编写,基本上了解了socket的CS构架原理,通信流程也清楚了,体会了TCP/IP连接的稳定性,学会了WinSock编程。
今后的工作就是增加多线程处理,添加多个用户,实现真正的多人聊天。同时可以增加的功能就是文件互传,语音和视频聊天。
还有一个问题就是优化代码,提高执行效率,使用更强的容错处理。
#include <Winsock2.h>
#include <windows.h>
#include "resource.h"
#pragma comment(lib, "ws2_32.lib")
#define MAX_CONNECTED_NUM 1 //最大连接数,设置为1个
#define MAX_IPADDRESS 260
#define SOCKETMSG WM_USER+501
#define WINSOCK_VERION 0x0101 //MAKEWORD(2, 0)
char fname[MAX_PATH];
char bufIP[MAX_IPADDRESS];
char bufPORT[80];
char szDlgTitle[] = "聊天程序1.0版";
WSADATA ws;
SOCKET skt=INVALID_SOCKET;//服务器(或者是客户端)
//SOCKET clientSkt[MAX_CONNECTED_NUM];//客户端数组,暂定为5个
SOCKET connected_skt=INVALID_SOCKET;//服务器和客户端所连接上之后,所建立的SOCKET
//连接建立之后,所有的数据通信,都是通过这个 Connected_skt进行的
//这个新建的SOCKET不可以再接受客户端连接,只有原来的那一个可以接受
struct sockaddr_in addr;//服务器的socket地址
struct sockaddr_in clientaddr;//客户端的
void LogFile(char *p)
{
FILE *fp=fopen(fname,"a+");
fprintf(fp,"%s/n",p);
fclose(fp);
}
BOOL CreateServer(HWND hwnd)
{
if(WSAStartup(WINSOCK_VERION,&ws))
{
MessageBox(hwnd,"Winsock初始化失败", szDlgTitle,MB_OK|MB_ICONSTOP);
WSACleanup();
return FALSE;
}//初始化
if((skt=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
MessageBox(hwnd,"创建Socket失败!", szDlgTitle,MB_OK);
closesocket(skt);
return FALSE;
}
GetDlgItemText(hwnd,IDC_IP,bufIP,MAX_IPADDRESS);
GetDlgItemText(hwnd,IDC_PORT,bufPORT,79);
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //inet_addr("192.168.10.4");//
addr.sin_port=htons(atoi(bufPORT));
if( bind(skt,(SOCKADDR*)&addr,sizeof(addr)) == SOCKET_ERROR )
{
MessageBox(hwnd,"Socket绑定失败!", szDlgTitle,MB_OK);
return FALSE;
}
if( listen(skt,MAX_CONNECTED_NUM ) == SOCKET_ERROR )
{
MessageBox(hwnd,"监听失败!", szDlgTitle,MB_OK);
return FALSE;
}
char szIP[127],szPORT[127],buf[256];
GetDlgItemText(hwnd,IDC_IP,szIP,sizeof(szIP));
GetDlgItemText(hwnd,IDC_PORT,szPORT,sizeof(szPORT));
wsprintf(buf,">>>>>>服务器在地址:%s端口:%d监听",szIP,htons(atoi(bufPORT)));
SetDlgItemText(hwnd,IDC_REVTXT,buf);
//listen之后开始监听客户端的连接事件
if( WSAAsyncSelect(skt,hwnd,SOCKETMSG,FD_ACCEPT ) == SOCKET_ERROR )
{
MessageBox(hwnd,"WSAAsyncSelect() 失败", szDlgTitle,MB_OK);
return FALSE;
}
return TRUE;
}
BOOL CreateClient(HWND hwnd)
{
if(WSAStartup(WINSOCK_VERION,&ws)!=0)
{
MessageBox(hwnd,"Winsock初始化失败", szDlgTitle,MB_OK|MB_ICONSTOP);
WSACleanup();
return FALSE;
}//初始化
if((skt=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET)
{
MessageBox(hwnd,"创建Socket失败!", szDlgTitle,MB_OK);
closesocket(skt);
return FALSE;
}
GetDlgItemText(hwnd,IDC_IP,bufIP,MAX_IPADDRESS);
GetDlgItemText(hwnd,IDC_PORT,bufPORT,79);
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=inet_addr(bufIP);
addr.sin_port=htons(atoi(bufPORT));
/*
if( bind(skt,(SOCKADDR*)&addr,sizeof(addr)) == SOCKET_ERROR )
{
MessageBox(hwnd,"Socket Bind 错误!", szDlgTitle,MB_OK);
return FALSE;
}
*/
int nConnect=connect(skt,(SOCKADDR*)&addr,sizeof(addr)); //请求连接
if(nConnect)
MessageBox(hwnd,"连接失败!",NULL,MB_OK);
else
MessageBox(hwnd,"连接成功!",NULL,MB_OK);
if( WSAAsyncSelect(skt,hwnd,SOCKETMSG,FD_READ|FD_CLOSE|FD_CONNECT ) == SOCKET_ERROR )
{
MessageBox(hwnd,"WSAAsyncSelect() 失败", szDlgTitle,MB_OK);
return FALSE;
}
return TRUE;
}
BOOL HttpClient(void)
{
WSADATA ws;
SOCKET s;
struct sockaddr_in addr;
int iResult;
long lResult;
char strSubAddr[100], strBuffer[100];
lResult = WSAStartup(0x0101,&ws);
s = socket(AF_INET,SOCK_STREAM,0); //建立的是一种TCP/IP 连接
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
addr.sin_addr.s_addr = inet_addr("168.160.224.185"); // 计算机世界日报
iResult=connect(s,(struct sockaddr *)&addr, sizeof(addr));
if(SOCKET_ERROR == iResult)
{
// 连接失败
WSACleanup();
return FALSE;
}
else
{
// 连接成功
strcpy(strSubAddr, "GET /99/tips/ /r/n");
strcpy(fname, "C://index.htm");
iResult = send(s, strSubAddr,strlen(strSubAddr),0);
// 下载文件
do
{
strset(strBuffer,' ');
iResult = recv(s,strBuffer,sizeof(strBuffer),0);
LogFile(strBuffer);
} while( iResult !=0 );
}
WSACleanup();
return TRUE;
}
#include "main.h"
/* Declare Dialog procedure */
BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
/* Make the class name into a global variable */
HINSTANCE hinst;
HMENU menu;
BOOL server;
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
hinst=hThisInstance;
hwnd = CreateDialog(hThisInstance,
MAKEINTRESOURCE(IDD_DEMO),//identifies dialog box template name
0,//HWND_DESKTOP,// handle to owner window
(DLGPROC)DlgProc// pointer to dialog box procedure
);
if (!hwnd)
{
char buf[100];
//formats and stores a series of characters and values in a buffer
wsprintf (buf,"Error x%x", GetLastError());
MessageBox(0,buf,szDlgTitle,MB_ICONEXCLAMATION | MB_OK);
return 1;
}
/* Run the message loop. It will run until GetMessage() returns 0 */
int status;
while ((status=GetMessage (&messages, NULL, 0, 0))!=0)
{
if(!IsDialogMessage(hwnd,&messages))
{
if (status==-1)
return -1;
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
BOOL CALLBACK DlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HWND hSend;
switch (message) /* handle the messages */
{
case WM_INITDIALOG:
menu = LoadMenu(hinst, MAKEINTRESOURCE(ID_MENU));
SetMenu(hwnd, menu);
hSend=GetDlgItem(hwnd,IDB_SEND);
//SetWindowText(hSend,"dsds" );
SetDlgItemText(hwnd,IDB_LC,"LC" );
EnableWindow(GetDlgItem(hwnd,IDB_LC),FALSE);
EnableWindow(GetDlgItem(hwnd,IDC_IP),FALSE);
EnableWindow(GetDlgItem(hwnd,IDC_PORT),FALSE);
SetDlgItemText(hwnd,IDC_IP,"192.168.10.4");
SetDlgItemText(hwnd,IDC_PORT,"7007" );
//创建一个计时器
SetTimer(hwnd,1,1000,NULL);
//SetFocus(GetDlgItem(hwnd,IDC_SENDTXT));
break;
case WM_COMMAND:
switch(wParam)
{
case IDM_FILENEW:
HttpClient();
break;
case IDM_FILEOPEN:
case IDM_FILELISTEN:
server=TRUE;
SetDlgItemText(hwnd,IDB_LC,"监听(&L)" );
SetWindowText(hwnd,"服务器端" );
EnableWindow(GetDlgItem(hwnd,IDB_LC),TRUE);
EnableWindow(GetDlgItem(hwnd,IDC_IP),FALSE);
EnableWindow(GetDlgItem(hwnd,IDC_PORT),FALSE);
EnableMenuItem(menu,IDM_FILECONNECT,MF_GRAYED);
break;
case IDM_FILECONNECT:
server=FALSE;
SetDlgItemText(hwnd,IDB_LC,"连接服务器(&C)" );
SetWindowText(hwnd,"客户端" );
EnableWindow(GetDlgItem(hwnd,IDB_LC),TRUE);
EnableWindow(GetDlgItem(hwnd,IDC_IP),TRUE);
EnableWindow(GetDlgItem(hwnd,IDC_PORT),TRUE);
EnableMenuItem(menu,IDM_FILELISTEN,MF_GRAYED);
break;
case IDM_EDITUNDO:
case IDM_EDITCUT:
case IDM_EDITCOPY:
case IDM_EDITPASTE:
case IDM_EDITDELETE:
case IDM_TRANSPORTFILE:
MessageBox( hwnd, (LPSTR) "功能没有实现",
(LPSTR)szDlgTitle,
MB_ICONINFORMATION | MB_OK );
break;
case IDM_HELPHELP:
WinHelp( hwnd, (LPSTR) "HELPFILE.HLP",
HELP_HELPONHELP, 0L );
break;
case IDM_FILEEXIT:
SendMessage( hwnd, WM_CLOSE, 0, 0L );
break;
case IDM_HELPABOUT:
MessageBox(hwnd,"欢迎使用聊天程序1.0版/n/n**科技大学胡子/n2004(C)版权所有 " ,
(LPSTR)szDlgTitle,
MB_ICONINFORMATION | MB_OK );
break;
case IDM_HELPMYBLOG:
ShellExecute(hwnd,"open","http://blog.csdn.net/huyoo",NULL,NULL,SW_NORMAL);
break;
case IDCANCEL:
//MessageBox(hwnd,"你单击了退出, 对话框将会关闭./n/n再见!",szDlgTitle,0);
EndDialog(hwnd,TRUE);
SendMessage( hwnd, WM_CLOSE, 0, 0L );
break;
case IDB_SEND:
char szText[128],buf[256];
GetDlgItemText(hwnd,IDC_SENDTXT,szText,sizeof(szText));
wsprintf(buf,"你单击了发送消息./n/n你输入的文本是:%s",szText);
if(server)
{
if( send(connected_skt,buf,strlen(buf),0) == SOCKET_ERROR ) //客户端是阻塞式的
{
MessageBox(hwnd, "发送消息出错!",szDlgTitle,MB_OK);
break;
}
}
else
{
if( send(skt,buf,strlen(buf),0) == SOCKET_ERROR ) //客户端是阻塞式的
{
MessageBox(hwnd, "发送消息出错!",szDlgTitle,MB_OK);
break;
}
//recv(skt,buf,strlen(buf),0); //它将会停在这里,直到接到数据为止
}
SetDlgItemText(hwnd,IDC_REVTXT,szText);
break;
case IDB_QQ:
ShellExecute(hwnd,"open","qq.exe",NULL,NULL,SW_NORMAL);
break;
case IDB_LC:
if(server)
{
//MessageBox(hwnd,"listen",szDlgTitle,MB_OK);
if(!CreateServer(hwnd))
{
MessageBox(hwnd,"服务器创建失败",(LPSTR)szDlgTitle,MB_ICONINFORMATION | MB_OK );
break;
}
}
else
{
//MessageBox(hwnd,"connect",szDlgTitle,MB_OK);
if(!CreateClient(hwnd))
{
MessageBox(hwnd,"客户端创建失败",(LPSTR)szDlgTitle,MB_ICONINFORMATION | MB_OK );
break;
}
char szIP[127],szPORT[127],buf[256];
GetDlgItemText(hwnd,IDC_IP,szIP,sizeof(szIP));
GetDlgItemText(hwnd,IDC_PORT,szPORT,sizeof(szPORT));
wsprintf(buf,">>>>>>连接到服务器:%s端口:%s",szIP,szPORT);
SetDlgItemText(hwnd,IDC_REVTXT,buf);
}
break;
}
break;
case WM_TIMER:
SYSTEMTIME ti;
char buf[256];
GetLocalTime(&ti);
wsprintf(buf,"现在时间是%04d年%02d月%02d日%02d:%02d:%02d,周%d",
ti.wYear,ti.wMonth,ti.wDay,ti.wHour,ti.wMinute,ti.wSecond,ti.wDayOfWeek);
SetWindowText(GetDlgItem(hwnd,IDC_TIME),buf);
break;
case SOCKETMSG:
//if(WSAGETSELECTERROR(lParam))
//break;
switch(WSAGETSELECTEVENT(lParam))
{
case FD_READ:
char buf[256];
recv(connected_skt,buf,strlen(buf),0);
SetDlgItemText(hwnd,IDC_REVTXT,buf);//显示接受到的消息
break;
case FD_WRITE:
break;
case FD_CLOSE:
closesocket(connected_skt);
break;
case FD_CONNECT:
SetDlgItemText(hwnd,IDC_REVTXT,"已经连接上");//显示接受到的消息
break;
case FD_ACCEPT:
int Len = sizeof(clientaddr);
//建立连接,得到的地址存入客户端clientaddr地址中去
if((connected_skt=accept(skt,(struct sockaddr *)&clientaddr,&Len))== INVALID_SOCKET )
{
MessageBox(hwnd,"接受客户端的Socket连接失败", szDlgTitle,MB_OK);
return FALSE;
}
//连接上了,然后监听客户端的FD_READ和关闭
WSAAsyncSelect(connected_skt, hwnd,SOCKETMSG,FD_READ|FD_CLOSE);
char *welcome="欢迎来到聊天室!!";
send(wParam, welcome, strlen(welcome), 0);
break;
}
SetDlgItemText(hwnd,IDC_REVTXT,"客户端发送消息过来了");//显示接受到的消息
break;
case WM_CLOSE:
if(connected_skt != INVALID_SOCKET)
{
closesocket(connected_skt);
}
if( skt != INVALID_SOCKET )
{
closesocket(skt);
}
if( WSACleanup() != 0 )
{
MessageBox(hwnd, "不能释放Winsocket",szDlgTitle,MB_OK );
}
KillTimer(hwnd,1);
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default:
return FALSE;
}
return TRUE;
}
//Microsoft Developer Studio generated resource script.
//
#define APSTUDIO_READONLY_SYMBOLS
/
//
// Generated from the TEXTINCLUDE 2 resource.
//
#define APSTUDIO_HIDDEN_SYMBOLS
#include "windows.h"
#undef APSTUDIO_HIDDEN_SYMBOLS
#include "resource.h"
/
#undef APSTUDIO_READONLY_SYMBOLS
/
// Chinese (P.R.C.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
#ifdef _WIN32
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
#pragma code_page(936)
#endif //_WIN32
/
//
// Menu
//
501 MENU DISCARDABLE
BEGIN
POPUP "文件(&F)"
BEGIN
MENUITEM "启动服务器(&S)", IDM_FILELISTEN
MENUITEM "启动客户端(&C)", IDM_FILECONNECT
MENUITEM SEPARATOR
MENUITEM "退出(&E)", IDM_FILEEXIT
END
POPUP "编辑(&E)"
BEGIN
MENUITEM "撤销(&U)", IDM_EDITUNDO
MENUITEM SEPARATOR
MENUITEM "剪切(&X)/tCtrl+X", IDM_EDITCUT
MENUITEM "复制(&C)/tCtrl+C", IDM_EDITCOPY
MENUITEM "粘贴(&P)/tCtrl+V", IDM_EDITPASTE
MENUITEM SEPARATOR
MENUITEM "传送文件(&S)", IDM_TRANSPORTFILE
END
POPUP "帮助(&H)"
BEGIN
MENUITEM "使用帮助(&U)", IDM_HELPHELP
MENUITEM SEPARATOR
MENUITEM "访问我的博客(&B)", IDM_HELPMYBLOG
MENUITEM "关于(&A)...", IDM_HELPABOUT
END
END
/
//
// Dialog
//
IDD_DEMO DIALOG DISCARDABLE 0, 0, 247, 227
STYLE DS_3DLOOK | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE |
WS_CAPTION | WS_SYSMENU
CAPTION "聊天程序"
FONT 9, "System"
BEGIN
LTEXT "IP:",IDC_STATIC,4,4,8,12,NOT WS_GROUP
LTEXT "PORT:",IDC_STATIC,78,4,20,12,NOT WS_GROUP
EDITTEXT IDC_IP,16,4,60,12,ES_AUTOHSCROLL
EDITTEXT IDC_PORT,100,4,28,12,ES_AUTOHSCROLL | ES_NUMBER
EDITTEXT IDC_REVTXT,4,29,234,111,ES_MULTILINE | ES_AUTOVSCROLL |
ES_READONLY | ES_WANTRETURN | WS_VSCROLL
PUSHBUTTON "LC",IDB_LC,130,4,60,12,BS_CENTER
PUSHBUTTON "QQ",IDB_QQ,194,4,20,12,BS_CENTER
PUSHBUTTON "SEND(&S)",IDB_SEND,136,200,46,12,BS_CENTER
PUSHBUTTON "EXIT(&X)",IDCANCEL,202,200,30,12,BS_CENTER
EDITTEXT IDC_SENDTXT,3,144,235,52,ES_MULTILINE | ES_AUTOVSCROLL |
ES_WANTRETURN | WS_VSCROLL
EDITTEXT IDC_NICKNAME,38,198, 40,12,ES_AUTOHSCROLL
LTEXT "NickName",IDC_STATIC,4,200,34,12
LTEXT "",IDC_TIME,6,19,214,8
END
#ifdef APSTUDIO_INVOKED
/
//
// TEXTINCLUDE
//
1 TEXTINCLUDE DISCARDABLE
BEGIN
"resource.h/0"
END
2 TEXTINCLUDE DISCARDABLE
BEGIN
"#define APSTUDIO_HIDDEN_SYMBOLS/r/n"
"#include ""windows.h""/r/n"
"#undef APSTUDIO_HIDDEN_SYMBOLS/r/n"
"#include ""res.h""/r/n"
"/0"
END
3 TEXTINCLUDE DISCARDABLE
BEGIN
"/r/n"
"/0"
END
#endif // APSTUDIO_INVOKED
/
//
// DESIGNINFO
//
#ifdef APSTUDIO_INVOKED
GUIDELINES DESIGNINFO DISCARDABLE
BEGIN
IDD_DEMO, DIALOG
BEGIN
RIGHTMARGIN, 246
BOTTOMMARGIN, 226
END
END
#endif // APSTUDIO_INVOKED
#endif // Chinese (P.R.C.) resources
/
#ifndef APSTUDIO_INVOKED
/
//
// Generated from the TEXTINCLUDE 3 resource.
//
/
#endif // not APSTUDIO_INVOKED
//====================================================================
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by res.rc
//
#define ID_MENU 501
#define IDD_DEMO 801
#define IDM_FILENEW 200
#define IDM_FILEOPEN 201
#define IDM_FILELISTEN 202
#define IDM_FILECONNECT 203
#define IDM_FILEEXIT 207
#define IDM_EDITUNDO 210
#define IDM_EDITCUT 211
#define IDM_EDITCOPY 212
#define IDM_EDITPASTE 213
#define IDM_EDITDELETE 214
#define IDM_TRANSPORTFILE 215
#define IDM_HELPHELP 217
#define IDM_HELPABOUT 218
#define IDM_HELPMYBLOG 219
#define IDC_STATIC -1
#define IDC_IP 1001
#define IDC_PORT 1002
#define IDC_SENDTXT 1003
#define IDC_REVTXT 1004
#define IDC_TIME 1005
#define IDC_NICKNAME 1006
#define IDB_SEND 2001
#define IDB_LC 2002
#define IDB_QQ 2003
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 101
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
网页播放的视频代码
第一种是通过调用window media player进行播放诸如:wmv,asf等格式文件:
<object align=middle class=OBJECT classid=CLSID:22d6f312-b0f6-11d0-94ab-0080c74c7e95 height=320 id=MediaPlayer width=356>
<param name="ShowStatusBar" value="-1">
<param name="Filename" value="电影地址">
<embed type=application/x-oleobject codebase=http://activex.microsoft.com/activex/controls/mplayer/en/nsmp2inf.cab#Version=5,1,52,701 flename=mp src=电影地址 width=356 height=320>
</embed>
</object>
第二种是通过调用replayer进行播放诸如:rm,ram等格式文件
播放框:
<object classid=clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA height=285 id=RAOCX name=rmplay width=356>
<param name="SRC" value="影片地址">
<param name="CONSOLE" value="Clip1">
<param name="CONTROLS" value="imagewindow">
<param name="AUTOSTART" value="true">
<embed src="影片地址" autostart="true" controls="ImageWindow" console="Clip1" pluginspage="http://www.real.com"/’ target="_blank" >http://www.real.com"; width="356" height="285">
</embed>
</object>
控制框:可紧跟播放框一起
<object classid=clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA height=27 id=video1 width=356>
<param name="_ExtentX" value="7276">
<param name="_ExtentY" value="1058">
<param name="AUTOSTART" value="0">
<param name="SHUFFLE" value="0">
<param name="PREFETCH" value="0">
<param name="NOLABELS" value="0">
<param name="CONTROLS" value="ControlPanel">
<param name="CONSOLE" value="Clip1">
<param name="LOOP" value="0">
<param name="NUMLOOP" value="0">
<param name="CENTER" value="0">
<param name="MAINTAINASPECT" value="0">
<param name="BACKGROUNDCOLOR" value="#ffffff">
<embed type="audio/x-pn-realaudio-plugin" console="Clip1" controls="ControlPanel" height="27" width="356" autostart="0" _extentx="7276" _extenty="1058" shuffle="0" prefetch="0" nolabels="0" loop="0" numloop="0" center="0" maintainaspect="0" backgroundcolor="#ffffff">
</embed>
</object>
第三种是通过调用flashplayer进行播放:swf类的flash文档,分别如下:
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,29,0" width="356" height=320>
<param name="movie" value="flash地址">
<param name="quality" value="high">
<embed src="flash地址" quality="high" pluginspage="http://www.macromedia.com/go/getflashplayer"’ target="_blank" >http://www.macromedia.com/go/getflashplayer"; type="application/x-shockwave-flash" width="356" height=320>
</embed>
</object>
| |
|
sql server的作业调度来建立自动备份的方法:
1、进入企业管理器中->管理->sql server代理->作业;
2、新建作业,作业名称随便取,例如:data备份,所有者选择sa,当然你也可以选择其他用户,前提是该用户有执行作业的权限;
3、点击步骤标签,进入步骤面板。新建步骤,步骤名可以随便填写,如步骤1,类型和数据库默认,不需要修改。命令中写入以下语句:
BACKUP DATABASE [数据库名] TO DISK = N'F:/data/数据库备份' WITH NOINIT , NOUNLOAD , NAME = N'数据库 备份', NOSKIP , STATS = 10, NOFORMAT
注意:需要修改的地方,数据库名,DISK=(这里需要填写路径和你的数据库备份的名称)后面的Name=可以随便填写。
4、点击调度标签,进入调度面板,新建调度,名称随便填写,选择反复出现,点更改可以选择你想要执行任务的随意调度。如每天,每2天,每星期,每月等。根据需要自己设置;
5、确定后,不要忘记一件事情,在你刚才建立的工作上点右键,启动工作,如果你的工作没有问题,将会提示执行成功,并有相对应的备份文件在你的磁盘上出现;
6、还有一个重要的问题就是你的sql server agent服务器已经启动。
如果我们需要根据每天的日期来生成一个新的备份,以便我们区别备份文件。这时,我们需要修改一下刚才的sql语句。参考实例:
declare @filename nvarchar(100)
set @filename='F:/AddIn/备份/data'+convert(char(10),getdate(),112)
print @filename
BACKUP DATABASE [addin] TO DISK = @filename WITH NOINIT , NOUNLOAD , NAME = N'addin 备份', NOSKIP , STATS = 10, NOFORMAT
或者按如下写法:
DECLARE @BACKFILENAME VARCHAR(200)
DECLARE @DATE CHAR(10)
DECLARE @FILENAME VARCHAR(200)
DECLARE @NAME VARCHAR(200)
SET @DATE=CONVERT(CHAR(10),GETDATE(),120)
SET @FILENAME='F:/SQLServerBackup/DefectStoreDKH_'
SET @BACKFILENAME=@FILENAME+@DATE
SET @NAME='DefectStoreDKH 备份'
BACKUP DATABASE [DefectStoreDKH]
TO DISK = @BACKFILENAME WITH INIT , NOUNLOAD , NAME = @NAME, NOSKIP , STATS = 10, NOFORMAT
Windows下Oracle9i数据库文件的自动备份 <script type="text/javascript"></script>
第一步:
在D盘根目录下新建文件夹Backup,这个目录可以任意,复制exp.exe文件到这个目录下,在该目录下新建文件expbkup.bat
exp test/test@test file=d:/backup/%date:~0,10%.dmp log=d:/backup/%date:~0,10%.log compress=n buffer=8092 consistent=y direct=n constraints=y feedback=10000 grants=y record=y indexes=y triggers=y rows=y
sid是test,用户名/密码是test/test,调用exp命令在d:/backup目录下生成相应的数据文件和日志文件。
第二步:
在控制面板的任务计划下新建一任务计划向导,选择执行任务的文件expbkup.bat,任务名随意expbkup,选择每天执行这个任务,起始时间18:00,起始日期默认为从当天开始,输入用户名和密码,点完成可添加每天下午6:00备份一次Oracle数据库的计划任务。
ORACLE_HOME=/opt/oracle/product/9.0.2
export ORACLE_HOME
. /opt/oracle/product/.bash_profile
export DATE=$(date +%Y%m%d) #调用linux日期函数,实现文件按日期命名每天一个备份
/opt/oracle/product/9.0.2/bin/exp goldring/123456@szdb file=/data/OracleDB_Backup/goldring.dmp log=/data/OracleDB_Back
up/loggoldring.dmp consistent=yes
/opt/oracle/product/9.0.2/bin/exp jltgame/123456@szdb file=/data/OracleDB_Backup/ jltgame$DATE.dmp log=/data/OracleDB_Ba
ckup/logjltgame.dmp consistent=yes
/opt/oracle/product/9.0.2/bin/exp userid=oraclebackup/"abcd456&*("@szdb owner=moonprincess file=/data/OracleDB_Backup/moon
princess.dmp log=/data/OracleDB_Backup/logmoonprincess.dmp consistent=yes
#ftp -n 10.0.0.3 < /opt/oracle/product/movedata/ftpcommand
把以上脚本放到crontab中即可
注:oracle实现自动备份关键是要在备份脚本中加入oracle的环境变量和在备份文件名中加入$DATE变量
Checked vs UnChecked 异常 ,使用场合?
异常的概念
任何的异常都是Throwable类(为何不是接口??),并且在它之下包含两个字类Error / Exception,而Error仅在当在Java虚拟机中发生动态连接失败或其它的定位失败的时候,Java虚拟机抛出一个Error对象。典型的简易程序不捕获或抛出Errors对象,你可能永远不会遇到需要实例化Error的应用,那就让我们关心一下Exception。
Exception中比较重要的就是RuntimeException(运行时异常)-可能在执行方法期间抛出但未被捕获的 RuntimeException 的任何子类都无需在 throws 子句中进行声明,也就是说你的应用应该不去“关心”(说不关心是不服责任的,但只是你不应该试图实例化它的字类)。 RuntimeException,就如同你不应该关心Error的产生与处理一样!RuntimeException描述的是程序的错误引起来的,因该由程序负担这个责任!(从责任这个角度看Error属于JVM需要负担的责任;RuntimeException是程序应该负担的责任;checked exception 是具体应用负担的责任)
除了Error与RuntimeException,其他剩下的异常都是你需要关心的,而这些异常类统称为Checked Exception,至于Error与RuntimeException则被统称为Unchecked Exception.
关于 Java 中引入的 Checked Exceptions,目前存在着很多反对意见。正方的观点是引入 Checked Exceptions,可以增加程度的鲁棒性。反方的观点是 Checked Exceptions 很少被开发人员正确使用过,并且降低了程序开发的生产率和代码的执行效率。
Java 中定义了两类异常:
1) Checked exception: 这类异常都是Exception的子类 。异常的向上抛出机制进行处理,如果子类可能产生A异常,那么在父类中也必须throws A异常。可能导致的问题:代码效率低,耦合度过高。C#中就没有使用这种异常机制。
2) Unchecked exception: 这类异常都是RuntimeException的子类,虽然RuntimeException同样也是Exception的子类,但是它们是特殊的,它们不能通过client code来试图解决,所以称为Unchecked exception 。
(JAVA视线论坛robbin's view,个人觉得用来做业务流程控制违背了Exception设计的初衷,但可以借鉴一下)
在使用UseCase来描述一个场景的时候,有一个主事件流和n个异常流。异常流可能发生在主事件流的过程,而try语句里面实现的是主事件流,而catch里面实现的是异常流,在这里Exception不代表程序出现了异常或者错误,Exception只是面向对象化的业务逻辑控制方法。如果没有明白这一点,那么我认为并没有真正明白应该怎么使用Java来正确的编程。
而我自己写的程序,会自定义大量的Exception类,所有这些Exception类都不意味着程序出现了异常或者错误,只是代表非主事件流的发生的,用来进行那些分支流程的流程控制的。例如你往权限系统中增加一个用户,应该定义1个异常类,UserExistedException,抛出这个异常不代表你插入动作失败,只说明你碰到一个分支流程,留待后面的catch中来处理这个分支流程。传统的程序员会写一个if else来处理,而一个合格的OOP程序员应该有意识的使用try catch 方式来区分主事件流和n个分支流程的处理,通过try catch,而不是if else来从代码上把不同的事件流隔离开来进行分别的代码撰写。
(另外一种观点,不同于robbin,个人赞同,并引用于此)
1。什么时候抛出异常--涉及到服务类
2。抛出checked还是unchecked的异常--涉及到客户类
对第一个问题来说,我想异常本身这个字解释了某些东西,异常就是我们认为在正常情况下不可能发生的问题,并且服务代码不知道如何去处理。譬如说我做一个监控程序,需要用压缩卡提供的API去初始化所有的板卡,API提供的是boolean型的返回值,但我把这个API变成抛出一个异常,因为除非特殊原因,我不认为会发生初始化失败的情况,当然更不知道怎样去处理这个问题。又譬如Hibernate里面的LoadObject使用没有发现这个对象存在,那Hibernate也是认为不可能的,除非其他代码直接删除了数据库里面的记录,那么也需要抛出异常。当然Hibernate本身也不知道如何处理这种情况。
但是如果发生的情况是可以预期的,那我不认为应该抛出例外。象上面这个userExist的情况,我认为应该在前面已经分流,应该首先判断这个用户是否存在,if(userExists()),然后进行处理,而不应当抛出例外。以及login应当返回true或者false。也就是说,这些属于程序的正常流程,而不是例外,不是异常。把例外作为正常程序流程的控制机制,只不过是把服务代码中的if转移到客户代码去,没有减少任何需要处理的代码,反而增加了系统的负担(生成例外栈)。
还有抛出异常的情况是违反方法的先决条件,每一个方法都有自己的先决条件和后置条件,方法只有在正确的前提下才能执行达到一个正确的后果,(所谓类的不变量)。譬如你去存取一个数组的某一个元素,这个存取方法有一个前提条件,就是你的索引应当落入它的最大下标和最小下标之间,不然就应当抛出一个例外。
对于第二个问题,端视于客户代码是否能够根据这个例外进行合理的处理。如果客户代码根本就不知道如何处理这个例外,应当把它作为一个unchecked例外,例如上面下标的问题,客户代码用一个不合法的下标来存取数组,那么抛出一个checked例外以后,客户代码是+1还是-1?显然根本就不可能做出“合理的”处理,客户既然不能处理,还要强制它去处理,那么就是捕获,打印了事,没有增加任何价值。但是如果是客户可以处理的,或者可以选择不同的方式处理的,那么就可能需要用checked,但我发现很少有这样的情况。对于类似于RemoteException或者SQLException这些Exception,我一般都转换为具体的业务Exception,而我所有的业务Exception都是RuntimeException.
所以我的观点是,是否抛出例外就是服务代码是否进行合理的处理,抛出什么类型的例外就是客户代码是否能够合理的处理。
保护封装性的代码实例:
不要让你要抛出的checked exception升级到较高的层次。例如,不要让SQLException延伸到业务层。业务层并不需要(不关心?)SQLException。你有两种方法来解决这种问题:
1)转变SQLException为另外一个checked exception,如果客户端并不需要恢复这种异常的话;
2)转变SQLException为一个unchecked exception,如果客户端对这种异常无能为力的话;
多数情况下,客户端代码都是对SQLException无能为力的,因此你要毫不犹豫的把它转变为一个unchecked exception,看看下边的代码:
public void dataAccessCode(){
try{
. .some code that throws SQLException
}catch(SQLException ex){
ex.printStacktrace();
}
}
上边的catch块紧紧打印异常信息而没有任何的直接操作,这是情有可原的,因为对于SQLException你还奢望客户端做些什么呢?(但是显然这种就象什么事情都没发生一样的做法是不可取的)那么有没有另外一种更加可行的方法呢? 看下面代码:
public void dataAccessCode(){
try{
..some code that throws SQLException
}catch(SQLException ex){
throw new RuntimeException(ex);
}
}
上边的做法是把SQLException转换为RuntimeException,一旦SQLException被抛出,那么程序将抛出RuntimeException,此时程序被挂起并返回客户端异常信息。 调用该方法的程序代码中也不用再捕获SQLException异常:
public void do(){
dataAccessCode();
}
记录集分页显示策略
可以有以下五种方法实现:
1. 使用forward-only的resultset,rs.next()移动记录集就可以了
选从50-100行
int CurrentRow = 1;
int MinRow = 50;
int MaxRow = 100;
while(rs.next())
{
if (CurrentRow<MinRow)
{
CurrentRow++;
continue;
}
}
2.使用可滚动记录集的游标进行分页,用abslout(int row)定位
Connection cn = 。。。;
stmt = cn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs = stmt.executeQuery(sql);
这样就可以使用rs.absolute(pos)进行定位了
3.使用SQL语句-通用
select top 50 *
from (select top 100 * from tablex order by id desc) as a
order by id
Oracle的语句:
select * from (select rownum r ,* from test) tt
where tt.r > 50 and tt.r <= 100;
DB2中:
select * from payment fetch first 5 row only --查前5条记录
4.使用ibatis,hibernate等,分页已经内部实现了,设置一下就可以了
1)hibernate
Query q = session.createQuery("from Cat as c");
q.setFirstResult(50);
q.setMaxResults(100);
List l = q.list();
那么Hibernate底层如何实现分页的呢?实际上Hibernate的查询定义在net.sf.hibernate.loader.Loader这个类里面,仔细阅读该类代码,就可以把问题彻底搞清楚。
Hibernate2.0.3的Loader源代码第480行以下:
if (useLimit) sql = dialect.getLimitString(sql);
PreparedStatement st = session.getBatcher().prepareQueryStatement(sql, scrollable);
如果相应的数据库定义了限定查询记录的sql语句,那么直接使用特定数据库的sql语句。
然后来看net.sf.hibernate.dialect.MySQLDialect:
public boolean supportsLimit() {
return true;
}
public String getLimitString(String sql) {
StringBuffer pagingSelect = new StringBuffer(100);
pagingSelect.append(sql);
pagingSelect.append(" limit ?, ?");
return pagingSelect.toString();
}
这是MySQL的专用分页语句,再来看net.sf.hibernate.dialect.Oracle9Dialect:
public boolean supportsLimit() {
return true;
}
public String getLimitString(String sql) {
StringBuffer pagingSelect = new StringBuffer(100);
pagingSelect.append("select * from ( select row_.*, rownum rownum_ from ( ");
pagingSelect.append(sql);
pagingSelect.append(" ) row_ where rownum <= ?) where rownum_ > ?");
return pagingSelect.toString();
}
Oracle采用嵌套3层的查询语句结合rownum来实现分页,这在Oracle上是最快的方式,如果只是一层或者两层的查询语句的rownum不能支持order by。
2)ibatis
PaginatedList list =sqlMap.queryForPaginatedList ("getProductList”, null, 10);
list.nextPage();
list.previousPage();
5.使用The Pager Tag Library
到http://jsptags.com/tags/navigation/pager/下载pager-taglib-2.0.war包,解压后可获得pager-taglib.jar,将其放到web-inf/lib/包下
0.0 2004.8.1 夏昕第一版
1.0 2004.9.1 夏昕补充ibatis in Spring 部分
OpenDoc 版权说明
本文档版权归原作者所有。
在免费、且无任何附加条件的前提下,可在网络媒体中自由传播。
如需部分或者全文引用,请事先征求作者意见。
如果本文对您有些许帮助,表达谢意的最好方式,是将您发现的问题和文档改进意见及时反馈给
作者。当然,倘若有时间和能力,能为技术群体无偿贡献自己的所学为最好的回馈。
另外,笔者近来试图就日本、印度的软件开发模式进行一些调研。如果诸位可以赠阅日本、印度
软件研发过程中的需求、设计文档以供研究,感激不尽!
ibatis 开发指南
ibatis Quick Start............................................................................................ 5
准备工作.......................................................................................................... 5
构建ibatis 基础代码.................................................................................... 5
ibatis 配置........................................................................................................... 11
ibatis 基础语义...................................................................................................... 16
XmlSqlMapClientBuilder................................................................... 16
SqlMapClient ........................................................................................... 16
SqlMapClient 基本操作示例.......................................................... 16
OR 映射........................................................................................................... 19
ibatis 高级特性...................................................................................................... 26
数据关联........................................................................................................ 26
一对多关联............................................................................................ 26
一对一关联............................................................................................ 28
延迟加载........................................................................................................ 30
动态映射........................................................................................................ 31
事务管理........................................................................................................ 35
基于JDBC 的事务管理机制................................................................ 35
基于JTA 的事务管理机制................................................................... 36
外部事务管理......................................................................................... 38
Cache .............................................................................................................. 39
MEMORY 类型Cache 与WeakReference ........................................ 40
LRU 型Cache ....................................................................................... 42
FIFO 型Cache ...................................................................................... 43
OSCache................................................................................................. 43
ibatis 开发指南
相对Hibernate 和Apache OJB 等“一站式”ORM 解决方案而言,ibatis 是一种“半
自动化”的ORM 实现。
所谓“半自动”,可能理解上有点生涩。纵观目前主流的ORM ,无论Hibernate 还是
Apache OJB,都对数据库结构提供了较为完整的封装,提供了从POJO 到数据库表的全
套映射机制。程序员往往只需定义好了POJO 到数据库表的映射关系,即可通过Hibernate
或者OJB 提供的方法完成持久层操作。程序员甚至不需要对SQL 的熟练掌握,
Hibernate/OJB 会根据制定的存储逻辑,自动生成对应的SQL 并调用JDBC 接口加以执
行。
大多数情况下( 特别是对新项目,新系统的开发而言)
,这样的机制无往不利,大有一
统天下的势头。但是,在一些特定的环境下,这种一站式的解决方案却未必灵光。
在笔者的系统咨询工作过程中,常常遇到以下情况:
1. 系统的部分或全部数据来自现有数据库,处于安全考虑,只对开发团队提供几
条Select SQL(或存储过程)以获取所需数据,具体的表结构不予公开。
2. 开发规范中要求, 所有牵涉到业务逻辑部分的数据库操作,必须在数据库层由
存储过程实现(就笔者工作所面向的金融行业而言,工商银行、中国银行、交
通银行,都在开发规范中严格指定)
3. 系统数据处理量巨大,性能要求极为苛刻,这往往意味着我们必须通过经过高
度优化的SQL 语句(或存储过程)才能达到系统性能设计指标。
面对这样的需求,再次举起Hibernate 大刀,却发现刀锋不再锐利,甚至无法使用,
奈何?恍惚之际,只好再摸出JDBC 准备拼死一搏……,说得未免有些凄凉,直接使用JDBC
进行数据库操作实际上也是不错的选择,只是拖沓的数据库访问代码,乏味的字段读取操作
令人厌烦。
“半自动化”的ibatis,却刚好解决了这个问题。
这里的“半自动化”,是相对Hibernate 等提供了全面的数据库封装机制的“全自动化”
ORM 实现而言,“全自动”ORM 实现了POJO 和数据库表之间的映射,以及SQL 的自动
生成和执行。而ibatis 的着力点,则在于POJO 与SQL 之间的映射关系。也就是说,ibatis
并不会为程序员在运行期自动生成SQL 执行。具体的SQL 需要程序员编写,然后通过映
射配置文件,将SQL 所需的参数,以及返回的结果字段映射到指定POJO 。
使用ibatis 提供的ORM 机制,对业务逻辑实现人员而言,面对的是纯粹的Java 对象,
这一层与通过Hibernate 实现ORM 而言基本一致,而对于具体的数据操作,Hibernate
会自动生成SQL 语句,而ibatis 则要求开发者编写具体的SQL 语句。相对Hibernate 等
“全自动”ORM 机制而言,ibatis 以SQL 开发的工作量和数据库移植性上的让步,为系统
设计提供了更大的自由空间。作为“全自动”ORM 实现的一种有益补充,ibatis 的出现显
得别具意义。
ibatis Quick Start
准备工作
1. 下载ibatis 软件包(http://www.ibatis.com)。
2. 创建测试数据库,并在数据库中创建一个t_user 表,其中包含三个字段:
. id(int)
. name(varchar)
. sex(int) 。
3. 为了在开发过程更加直观,我们需要将ibatis 日志打开以便观察ibatis 运作的细节。
ibatis 采用Apache common_logging,并结合Apache log4j 作为日志输出组件。在
CLASSPATH 中新建log4j.properties 配置文件,内容如下:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%c{1} -%m%n
log4j.logger.java.sql.PreparedStatement=DEBUG
构建ibatis 基础代码
ibatis 基础代码包括:
1. ibatis 实例配置
一个典型的配置文件如下(具体配置项目的含义见后):
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true"
errorTracingEnabled="true"
maxRequests="32"
maxSessions="10"
maxTransactions="5"
useStatementNamespaces="false"
/>
<transactionManager type="JDBC">
<dataSource type="SIMPLE">
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/>
</sqlMapConfig>
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
2. POJO(Plain Ordinary Java Object)
下面是我们用作示例的一个POJO:
public class User implements Serializable {
private Integer id;
private String name;
private Integer sex;
private Set addresses = new HashSet();
/** default constructor */
public User() {
}
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 Integer getSex() {
return this.sex;
}
public void setSex(Integer sex) {
this.sex = sex;
}
}
3. 映射文件
与Hibernate 不同。因为需要人工编写SQL 代码,ibatis 的映射文件一般采
用手动编写(通过Copy/Paste,手工编写映射文件也并没想象中的麻烦)。
针对上面POJO 的映射代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
<![CDATA[
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
]]>
</update>
<insert id="insertUser"
parameterClass="user"
>
INSERT INTO t_user (
name,
sex)
VALUES (
#name#,
#sex#
)
</insert>
<delete id="deleteUser"
parameterClass="java.lang.String">
delete from t_user
where id = #value#
</delete>
</sqlMap>
从上面的映射文件可以看出,通过<insert>、<delete>、<update>、
<select>四个节点,我们分别定义了针对TUser 对象的增删改查操作。在这
四个节点中,我们指定了对应的SQL 语句,以update 节点为例:
……
<update id="updateUser" ⑴
parameterClass="user"> ⑵
<![CDATA[ ⑶
UPDATE t_user ⑷
SET (
IBATIS Developer’s Guide Version 1.0
name=#name#, ⑸
sex=#sex# ⑹
)
WHERE id = #id# ⑺
]]>
</update>
……
⑴ ID
指定了操作ID,之后我们可以在代码中通过指定操作id 来执行此节点所定
义的操作,如:
sqlMap.update("updateUser",user);
ID 设定使得在一个配置文件中定义两个同名节点成为可能(两个update 节
点,以不同id 区分)
⑵ parameterClass
指定了操作所需的参数类型,此例中update 操作以
com.ibatis.sample.User 类型的对象作为参数,目标是将提供的User
实例更新到数据库。
parameterClass="user"中,user 为“com.ibatis.sample.User”
类的别名,别名可通过typeAlias 节点指定,如示例配置文件中的:
<typeAlias alias="user" type="com.ibatis.sample.User"/>
⑶ <![CDATA[……]]>
通过<![CDATA[……]]>节点,可以避免SQL 中与XML 规范相冲突的字符对
XML 映射文件的合法性造成影响。
⑷ 执行更新操作的SQL,这里的SQL 即实际数据库支持的SQL 语句, 将由
ibatis 填入参数后交给数据库执行。
⑸ SQL 中所需的用户名参数,
“#name#”在运行期会由传入的user 对象的name
属性填充。
⑹ SQL 中所需的用户性别参数“#sex#”, 将在运行期由传入的user 对象的
sex 属性填充。
⑺ SQL 中所需的条件参数“#id#”, 将在运行期由传入的user 对象的id 属性
填充。
对于这个示例,ibatis 在运行期会读取id 为“updateUser”的update 节点
的SQL 定义,并调用指定的user 对象的对应getter 方法获取属性值,并用此
属性值,对SQL 中的参数进行填充后提交数据库执行。
此例对应的应用级代码如下,其中演示了的基本使用方法:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
ibatis SQLMap
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系统初始化完毕,开始执行update操作
try{
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(1));
sqlMap.update("updateUser",user);
sqlMap.commitTransaction();
finally{
sqlMap.endTransaction();
}
其中,SqlMapClient 是ibatis 运作的核心,所有操作均通过SqlMapClient
实例完成。
可以看出,对于应用层而言,程序员面对的是传统意义上的数据对象,而非JDBC
中烦杂的ResultSet,这使得上层逻辑开发人员的工作量大大减轻,同时代码更
加清晰简洁。
数据库操作在映射文件中加以定义,从而将数据存储逻辑从上层逻辑代码中独立
出来。
而底层数据操作的SQL 可配置化,使得我们可以控制最终的数据操作方式,通过
SQL 的优化获得最佳的数据库执行效能,这在依赖SQL 自动生成的“全自动”ORM
机制中是所难以实现的。
ibatis 配置
结合上面示例中的ibatis 配置文件。下面是对配置文件中各节点的说明:
<?xml version="1.0" encoding="UTF-8" ?>
PUBLIC
">
<sqlMapConfig>
<settings ⑴
cacheModelsEnabled=
enhancementEnabled=
lazyLoadingEnabled=
errorTracingEnabled=
maxRequests=
maxSessions=
maxTransactions=
useStatementNamespaces=
/>
<transactionManager type="JDBC"> ⑵
<dataSource type="SIMPLE"> ⑶
<property name="JDBC.Driver"
value="com.p6spy.engine.spy.P6SpyDriver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/sample"/>
<property name="JDBC.Username" ="user"/>
<property name="JDBC.Password" ="mypass"/>
<property name=
value="10"/>
<property name=value="5"/>
<property name=
value="120000"/>
<property name="Pool.TimeToWait" ="500"/>
<property name="Pool.PingQuery" ="select 1 from
ACCOUNT"/>
<property name="Pool.PingEnabled" ="false"/>
<property name=
value="1"/>
<property name=
value="1"/>
</dataSource>
<!DOCTYPE sqlMapConfig
"-//iBATIS.com//DTD SQL Map Config 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-config-2.dtd"true"
"true"
"true"
"true"
"32"
"10"
"5"
"false"
valuevalue"Pool.MaximumActiveConnections"
"Pool.MaximumIdleConnections"
"Pool.MaximumCheckoutTime"
valuevaluevalue"Pool.PingConnectionsOlderThan"
"Pool.PingConnectionsNotUsedFor"
</transactionManager>
<sqlMap resource="com/ibatis/sample/User.xml"/> ⑷
<sqlMap resource="com/ibatis/sample/Address.xml"/>
</sqlMapConfig>
⑴ Settings 节点
参数描述
cacheModelsEnabled 是否启用SqlMapClient 上的缓存机制。
建议设为"true"
enhancementEnabled 是否针对POJO 启用字节码增强机制以提升
getter/setter 的调用效能,避免使用Java
Reflect 所带来的性能开销。
同时,这也为Lazy Loading 带来了极大的性能
提升。
建议设为"true"
errorTracingEnabled 是否启用错误日志,在开发期间建议设为"true"
以方便调试
lazyLoadingEnabled 是否启用延迟加载机制,建议设为"true"
maxRequests 最大并发请求数(Statement 并发数)
maxTransactions 最大并发事务数
maxSessions 最大Session 数。即当前最大允许的并发
SqlMapClient 数。
maxSessions 设定必须介于
maxTransactions 和maxRequests 之间,即
maxTransactions<maxSessions=<
maxRequests
useStatementNamespaces 是否使用Statement 命名空间。
这里的命名空间指的是映射文件中,sqlMap 节点
的namespace 属性,如在上例中针对t_user
表的映射文件sqlMap 节点:
<sqlMap namespace="User">
这里,指定了此sqlMap 节点下定义的操作均从
属于"User"命名空间。
在useStatementNamespaces="true"的情
况下,Statement 调用需追加命名空间,如:
⑵ transactionManager 节点
sqlMap.update("User.updateUser",use
r);
否则直接通过Statement 名称调用即
sqlMap.update("updateUser",user);
但请注意此时需要保证所有映射
定义无重名。
可,如:
文件中,
Statement
transactionManager 节点定义了ibatis 的事务管理器,目前提供了以下几
种选择:
. JDBC
通过传统JDBC Connection.commit/rollback 实现事务支持。
. JTA
使用容器提供的JTA 服务实现全局事务管理。
. EXTERNAL
外部事务管理, 如在EJB 中使用ibatis,通过EJB 的部署配置即可实现自
动的事务管理机制。此时ibatis 将把所有事务委托给外部容器进行管理。
此外,通过Spring 等轻量级容器实现事务的配置化管理也是一个不错的选
择。关于结合容器实现事务管理,参见“高级特性”中的描述。
⑶ dataSource 节点
dataSource 从属于transactionManager 节点,用于设定ibatis 运行期使
用的DataSource 属性。
type 属性: dataSource 节点的type属性指定了dataSource 的实现类型。
可选项目:
. SIMPLE:
SIMPLE 是ibatis 内置的dataSource 实现,其中实现了一个简单的
数据库连接池机制,对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.SimpleDataSourceFactory 。
. DBCP:
基于Apache DBCP 连接池组件实现的DataSource 封装,当无容器提
供DataSource 服务时,建议使用该选项,对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.DbcpDataSourceFactory 。
. JNDI:
使用J2EE 容器提供的DataSource 实现,DataSource 将通过指定
的JNDI Name 从容器中获取。对应ibatis 实现类为
com.ibatis.sqlmap.engine.datasource.JndiDataSourceFacto
ry。
dataSource 的子节点说明(SIMPLE&DBCP):
参数描述
JDBC.Driver JDBC 驱动。
如:org.gjt.mm.mysql.Driver
JDBC.ConnectionURL 数据库URL 。
如:jdbc:mysql://localhost/sample
如果用的是SQLServer JDBC Driver,需要
在url 后追加SelectMethod=Cursor 以获得
JDBC 事务的多Statement 支持。
JDBC.Username 数据库用户名
JDBC.Password 数据库用户密码
Pool.MaximumActiveConn 数据库连接池可维持的最大容量。
ections
Pool.MaximumIdleConnec
tions
数据库连接池中允许的挂起(idle)连接数。
以上子节点适用于SIMPLE 和DBCP 模式,分别针对SIMPLE 和DBCP 模式的
DataSource 私有配置节点如下:
SIMPLE:
参数描述
Pool.MaximumCheckoutTi 数据库联接池中,连接被某个任务所允许占用的
me 最大时间,如果超过这个时间限定,连接将被强
制收回。(毫秒)
Pool.TimeToWait 当线程试图从连接池中获取连接时,连接池中无
可用连接可供使用,此时线程将进入等待状态,
直到池中出现空闲连接。此参数设定了线程所允
许等待的最长时间。(毫秒)
Pool.PingQuery 数据库连接状态检测语句。
某些数据库在连接在某段时间持续处于空闲状态
时会将其断开。而连接池管理器将通过此语句检
测池中连接是否可用。
检测语句应该是一个最简化的无逻辑SQL 。
如“select 1 from t_user”, 如果执行此语句
成功,连接池管理器将认为此连接处于可用状态。
Pool.PingEnabled 是否允许检测连接状态。
Pool.PingConnectionsOl 对持续连接时间超过设定值(毫秒)的连接进行
derThan 检测。
Pool.PingConnectionsNo
tUsedFor
对空闲超过设定值(毫秒)的连接进行检测。
DBCP:
参数描述
Pool.MaximumWait 当线程试图从连接池中获取连接时,连接池中无
可用连接可供使用,此时线程将进入等待状态,
直到池中出现空闲连接。此参数设定了线程所允
许等待的最长时间。(毫秒)
Pool.ValidationQuery 数据库连接状态检测语句。
某些数据库在连接在某段时间持续处于空闲状态
时会将其断开。而连接池管理器将通过此语句检
测池中连接是否可用。
检测语句应该是一个最简化的无逻辑SQL 。
如“select 1 from t_user”, 如果执行此语句
成功,连接池管理器将认为此连接处于可用状态。
Pool.LogAbandoned 当数据库连接被废弃时,是否打印日志。
Pool.RemoveAbandonedTi 数据库连接被废弃的最大超时时间
meout
Pool.RemoveAbandoned 当连接空闲时间超过
RemoveAbandonedTimeout 时,是否将其废
弃。
JNDI 由于大部分配置是在应用服务器中进行,因此ibatis 中的配置相对简单,下面
是分别使用JDBC 和JTA 事务管理的JDNI 配置:
使用JDBC 事务管理的JNDI DataSource 配置
<transactionManager type="JDBC" >
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
<transactionManager type="JTA" >
<property name="UserTransaction"
value="java:/ctx/con/UserTransaction"/>
<dataSource type="JNDI">
<property name="DataSource"
value="java:comp/env/jdbc/myDataSource"/>
</dataSource>
</transactionManager>
⑷ sqlMap 节点
sqlMap 节点指定了映射文件的位置,配置中可出现多个sqlMap 节点,以指定
项目内所包含的所有映射文件。
ibatis 基础语义
XmlSqlMapClientBuilder
XmlSqlMapClientBuilder 是ibatis 2.0 之后版本新引入的组件,用以替代1.x
版本中的XmlSqlMapBuilder。其作用是根据配置文件创建SqlMapClient 实例。
SqlMapClient
SqlMapClient 是ibatis 的核心组件,提供数据操作的基础平台。SqlMapClient
可通过XmlSqlMapClientBuilder 创建:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder =
new XmlSqlMapClientBuilder();
SqlMapClient sqlMap = xmlBuilder.buildSqlMap(reader);
"com/ibatis/sample/SqlMapConfig.xml"指明了配置CLASSPATH文件在
中的相对路径。XmlSqlMapClientBuilder 通过接受一个Reader 类型的配置文
件句柄,根据配置参数,创建SqlMapClient 实例。
SqlMapClient 提供了众多数据操作方法,下面是一些常用方法的示例,具体说明
文档请参见ibatis java doc,或者ibatis 官方开发手册。
SqlMapClient 基本操作示例
以下示例摘自ibatis 官方开发手册,笔者对其进行了重新排版以获得更好的阅读效果。
例1: 数据写入操作(insert, update, delete):
sqlMap.startTransaction();
Product product = new Product();
product.setId (1);
product.setDescription (“Shih Tzu”);
int rows = sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();
例2: 数据查询(select)
sqlMap.startTransaction();
Integer key = new Integer (1);
Product product = (Product)sqlMap.queryForObject
(“getProduct”, key);
sqlMap.commitTransaction();
例3: 在指定对象中存放查询结果(select)
sqlMap.startTransaction();
Customer customer = new Customer();
sqlMap.queryForObject(“getCust”, parameterObject, customer);
sqlMap.queryForObject(“getAddr”, parameterObject, customer);
sqlMap.commitTransaction();
例4: 执行批量查询(select)
sqlMap.startTransaction();
List list = sqlMap.queryForList (“getProductList”, null);
sqlMap.commitTransaction();
例5: 关于AutoCommit
//没有预先执行startTransaction时,默认为模式
int rows = sqlMap.insert (“insertProduct”, product);
auto_commit
例6:查询指定范围内的数据
sqlMap.startTransaction();
= “”, nullsqlMap.commitTransaction();
List list sqlMap.queryForList (getProductList, 0, 40);
例7: 结合RowHandler 进行查询(select)
MyRowHandler implements RowHandler {
handleRow (Object object, List list) throws
SQLException {
Product product = (Product) object;
product.setQuantity (10000);
sqlMap.update (“updateProduct”, product);
}
}
sqlMap.startTransaction();
RowHandler rowHandler = new MyRowHandler();
List list = sqlMap.queryForList (“getProductList”, null,
rowHandler);
sqlMap.commitTransaction();
public classpublic void
例8: 分页查询(select)
PaginatedList list =
sqlMap.queryForPaginatedList (“getProductList”, null, 10);
list.nextPage();
list.previousPage();
例9: 基于Map 的批量查询(select)
sqlMap.startTransaction();
Map map = sqlMap.queryForMap (“getProductList”, null,
“productCode”);
sqlMap.commitTransaction();
Product p = (Product) map.get(“EST-93”);
OR 映射
相对Hibernate 等ORM 实现而言,ibatis 的映射配置更为简洁直接,下面是一
个典型的配置文件。
<!DOCTYPE sqlMap
PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
"http://www.ibatis.com/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<!--模块配置-->
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement=" updateUser"/>
<property name="size" value="1000" />
</cacheModel>
<!—Statement配置-->
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
<update id="updateUser"
parameterClass="user">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
</sqlMap>
可以看到,映射文件主要分为两个部分:模块配置和Statement 配置。
模块配置包括:
. typeAlias 节点:
定义了本映射文件中的别名,以避免过长变量值的反复书写,此例中通过
typeAlias 节点为类"com.ibatis.sample.User"定义了一个别名"user",
这样在本配置文件的其他部分,需要引用"com.ibatis.sample.User" 类时,
只需以其别名替代即可。
. cacheModel 节点
定义了本映射文件中使用的Cache 机制:
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
这里申明了一个名为"userCache" 的,之后可以在cacheModel
Statement 申明中对其进行引用:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user"
cacheModel="userCache"
>
这表明对通过id 为"getUser"的获取的数据,使用Select statement
cacheModel "userCache" 进行缓存。之后如果程序再次用此Statement
进行数据查询,即直接从缓存中读取查询结果,而无需再去数据库查询。
cacheModel 主要有下面几个配置点:
. flushInterval :
设定缓存有效期,如果超过此设定值,则将此CacheModel 的缓存清空。
. size:
本CacheModel 中最大容纳的数据对象数量。
. flushOnExecute:
指定执行特定Statement 时,将缓存清空。如updateUser 操作将更
新数据库中的用户信息,这将导致缓存中的数据对象与数据库中的实际
数据发生偏差,因此必须将缓存清空以避免脏数据的出现。
关于Cache 的深入探讨,请参见“高级特性”中的相关章节。
Statement 配置:
Statement 配置包含了数个与SQL Statement 相关的节点,分别为:
. statement
. insert
. delete
. update
. select
. procedure
其中,statement 最为通用,它可以替代其余的所有节点。除statement 之外
的节点各自对应了SQL 中的同名操作(procedure 对应存储过程)。
使用statement 定义所有操作固然可以达成目标,但缺乏直观性,建议在实际
开发中根据操作目的,各自选用对应的节点名加以申明。一方面,使得配置文件
更加直观,另一方面,也可借助DTD 对节点申明进行更有针对性的检查,以避免
配置上的失误。
各种类型的Statement 配置节点的参数类型基本一致,区别在于数量不同。如
insert、update、delete 节点无需返回数据类型定义(总是int)。
主要的配置项如下:
statement:
<statement id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
>
select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</statement>
select:
<select id="statementName"
[parameterClass="some.class.Name"]
[resultClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
[resultMap="nameOfResultMap"]
[cacheModel="nameOfCache"]
>
select * from t_user where sex = [?|#propertyName#]
order by [$simpleDynamic$]
</select>
Insert:
<insert id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
insert into t_user
(name,sex)
values
([?|#propertyName#],[?|#propertyName#])
</insert>
Update:
<update id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
UPDATE t_user
SET
name=[?|#propertyName#],
sex=[?|#propertyName#]
WHERE id = [?|#propertyName#]
</update>
Delete:
<delete id="statementName"
[parameterClass="some.class.Name"]
[parameterMap="nameOfParameterMap"]
>
delete from t_user
where id = [?|#propertyName#]
</delete>
其中以“[]”包围的部分为可能出现的配置栏目。
参数描述
parameterClass 参数类。指定了参数的完整类名(包括包路径)。
可通过别名避免每次重复书写冗长的类名。
resultClass 结果类。指定结果类型的完整类名(包括包路径)
可通过别名避免每次重复书写冗长的类名。
parameterMap 参数映射,需结合parameterMap 节点对映射
关系加以定义。
对于存储过程之外的statement 而言,建议使用
parameterClass 作为参数配置方式, 一方面避
免了参数映射配置工作,另一方面其性能表现也
更加出色。
resultMap 结果映射,需结合resultMap 节点对映射关系
加以定义。
cacheModel statement 对应的Cache 模块。
对于参数定义而言,尽量使用parameterClass,即直接将POJO 作为
statement 的调用参数,这样在SQL 中可以直接将POJO 的属性作为参数加以
设定,如:
<update id="updateUser"
parameterClass="com.ibatis.sample.User">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
这里将com.ibatis.sample.User 类设定为的参数,之后,update statement
我们即可在SQL 中通过#propertyName#对POJO 的属性进行引用。如上例
中的:
SET name=#name#, sex=#sex# WHERE id=#id#
运行期,将通过调用对象的、和方法获得相ibatis User getName getSex getId
应的参数值,并将其作为SQL 的参数。
如果parameterClass 中设定的是jdk 的中的简单对象类型,如String、
Integer,ibatis 会直接将其作为SQL 中的参数值。
我们也可以将包含了参数数据的Map 对象传递给Statement ,如:
<update id="updateUser"
parameterClass="java.util.Map">
UPDATE t_user
SET
name=#name#,
sex=#sex#
WHERE id = #id#
</update>
这里传入的参数就是一个对象,将以””、””、”id”从中Map ibatis key namesex
提取对应的参数值。
同样的原理,我们也可以在resultMap 中设定返回类型为map 。
<select id="getUser"
parameterClass="java.lang.String"
resultClass="java.util.Map">
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
返回的结果将以各字段名为保存在对象中返回。key Map
在SQL 中设定参数名时,可以同时指定参数类型, 如:
SET name=#name:VARCHAR#,sex=#sex:NUMERIC# WHERE
id=#id:NUMERIC#
对于返回结果而言,如果是select 语句,建议也采用resultClass 进行定义,如:
<select id="getUser"
parameterClass="java.lang.String"
resultClass="user">
<![CDATA[
select
name,
sex
from t_user
where name = #name#
]]>
</select>
会自动根据语句中的字段名,调用对应的方法设定属性ibatis select POJO set
值,如上例中,ibatis 会调用setName,setSex 方法将Select 语句返回的数据赋
予相应的POJO 实例。
有些时候,数据库表中的字段名过于晦涩,而为了使得代码更易于理解,我们
希望字段映射到POJO 时,采用比较易读的属性名,此时,我们可以通过Select
的as 字句对字段名进行转义,如(假设我们的书库中对应用户名的字段为
xingming ,对应性别的字段为xingbie):
select
xingming as name,
xingbie as sex
from t_user
where id = #id#
会根据转义后的字段名进行属性映射(即调用的方法而ibatis POJO setName
不是setXingming 方法)。
parameterMap 和resultMap 实现了POJO 到数据库字段的映射配置,下面是
一个例子:
<resultMap id="get_user_result" class="user">
<result property="name" column="xingming"
jdbcType="VARCHAR" javaType="java.lang.String"/>
<result property="sex" column="xingbie"
jdbcType="int" javaType="java.lang.Integer"/>
<result property="id" column="id"
jdbcType="int" javaType="java.lang.Integer"/>
</resultMap>
<parameterMap id="update_user_para" class="redemption" >
<parameter property="name"
jdbcType="VARCHAR"
javaType="java.lang.String"
nullValue=""
/>
<parameter property="sex"
jdbcType="int"
javaType="java.lang.Integer"
nullValue=""
/>
</parameterMap>
Parameter 的nullValue 指定了如果参数为空(null)时的默认值。
之后我们即可在statement 申明中对其进行引用,如:
<procedure id="getUserList"
resultMap="get_user_result"
>
{call sp_getUserList()}
</procedure>
<procedure id="doUserUpdate"
parameterMap="update_user_para"
>
{call sp_doUserUpdate(#id#,#name#,#sex#)}
</procedure>
一般而言,对于insert 、update 、delete 、select 语句,优先采用parameterClass
和resultClass 。
parameterMap 使用较少,而resultMap 则大多用于嵌套查询以及存储过程的
处理,之所以这样,原因是由于存储过程相对而言比较封闭(很多情况下需要调用现有
的存储过程,其参数命名和返回的数据字段命名往往不符合Java 编程中的命名习惯,
并且由于我们难以通过Select SQL 的as子句进行字段名转义,无法使其自动与POJO
中的属性名相匹配)。此时,使用resultMap 建立字段名和POJO 属性名之间的映射
关系就显得非常有效。另一方面,由于通过resultMap 指定了字段名和字段类型,
ibatis 无需再通过JDBC ResultSetMetaData 来动态获取字段信息,在一定程度
上也提升了性能表现。
ibatis 高级特性
数据关联
至此,我们讨论的都是针对独立数据的操作。在实际开发中,我们常常遇到关联数
据的情况,如User 对象拥有若干Address 对象,每个Address 对象描述了对应User 的
一个联系地址,这种情况下,我们应该如何处理?
通过单独的Statement 操作固然可以实现(通过Statement 用于读取用户数据,再手
工调用另外一个Statement 根据用户ID 返回对应的Address 信息)。不过这样未免失之
繁琐。下面我们就看看在ibatis 中,如何对关联数据进行操作。
ibatis 中,提供了Statement 嵌套支持,通过Statement 嵌套,我们即可实现关联数
据的操作。
一对多关联
下面的例子中,我们首选读取t_user 表中的所有用户记录,然后获取每个用户对应
的所有地址信息。
配置文件如下:
<sqlMap namespace="User">
<typeAlias alias="user" type="com.ibatis.sample.User"/>
<typeAlias alias="address" type="com.ibatis.sample.Address"/>
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="addresses" column="id"
select="User.getAddressByUserId"/>
</resultMap>
<select id="getUsers"
parameterClass="java.lang.String"
resultMap="get-user-result">
<![CDATA[
select
id,
name,
sex
from t_user
where id = #id#
]]>
</select>
<select id="getAddressByUserId"
parameterClass="int"
resultClass="address">
<![CDATA[
select
address,
zipcode
from t_address
where user_id = #userid#
]]>
</select>
</sqlMap>
对应代码:
String resource ="com/ibatis/sample/SqlMapConfig.xml";
Reader reader;
reader = Resources.getResourceAsReader(resource);
XmlSqlMapClientBuilder xmlBuilder= new XmlSqlMapClientBuilder();
sqlMap = xmlBuilder.buildSqlMap(reader);
//sqlMap系统初始化完毕
List userList = sqlMap.queryForList("User.getUsers", "");
for (int i = 0; i < userList.size(); i++) {
User user = (User)userList.get(i);
System.out.println("==>" + user.getName());
for (int k = 0; k < user.getAddresses().size(); k++) {
Address addr = (Address) user.getAddresses().get(k);
System.out.println(addr.getAddress());
}
}
这里通过在resultMap 中定义嵌套查询getAddressByUserId,我们实现了关联
数据的读取。
实际上,这种方式类似于前面所说的通过两条单独的Statement 进行关联数据的读
取,只是将关联关系在配置中加以描述,由ibatis 自动完成关联数据的读取。
需要注意的是,这里有一个潜在的性能问题,也就是所谓“n+1”Select 问题。
注意上面示例运行过程中的日志输出:
……
PreparedStatement -{pstm-100001} PreparedStatement: select id, name, sex from
t_user
……
PreparedStatement -{pstm-100004} PreparedStatement: select address, zipcode from
t_address where user_id = ?
……
PreparedStatement -{pstm-100007} PreparedStatement: select address,zipcode from
t_address where user_id = ?
第一条将表中的所有数据读取出来PreparedStatement t_user (目前t_user 表中有两
条测试数据),随即,通过两次Select 操作,从t_address 表中读取两个用户所关联的
Address 记录。
如果t_user 表中记录较少,不会有明显的影响,假设t_user 表中有十万条记录,那
么这样的操作将需要100000+1 条Select 语句反复执行才能获得结果,无疑,随着记录
的增长,这样的开销将无法承受。
之所以在这里提及这个问题,目的在于引起读者的注意,在系统设计中根据具体情
况,采用一些规避手段(如使用存储过程集中处理大批量关联数据),从而避免因为这
个问题而引起产品品质上的缺陷。
一对一关联
一对一关联是一对多关联的一种特例。这种情况下,如果采用上面的示例将导致
1+1 条SQL 的执行。
对于这种情况,我们可以采用一次Select 两张表的方式,避免这样的性能开销(假
设上面示例中,每个User 只有一个对应的Address 记录):
<resultMap id="get-user-result" class="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="address" column="t_address.address"/>
<result property="zipCode" column="t_address.zipcode"/>
</resultMap>
<select id="getUsers"
parameterClass="java.lang.String"
resultMap="get-user-result">
<![CDATA[
select
*
from t_user,t_address
where t_user.id=t_address.user_id
]]>
</select>
与此同时,应该保证类中包含和两个型属性。User address zipCode String
延迟加载
在运行上面的例子时,通过观察期间的日志输出顺序我们可以发现,在我们执行
sqlMap.queryForList("User.getUsers", "")时,实际上ibatis 只向数据库发送
了一条select id, name, sex from t_user SQL 。而用于获取Address 记录的SQL,只有在我
们真正访问address 对象时,才开始执行。
这也就是所谓的延迟加载(Lazy Loading)机制。即当真正需要数据的时候,才加
载数据。延迟加载机制能为我们的系统性能带来极大的提升。
试想,如果我们只需要获取用户名称和性别数据,在没有延迟加载特性的情况下,
ibatis 会一次将所有数据都从数据库取回,包括用户信息及其相关的地址数据,而此时,
关于地址数据的读取操作没有意义,也就是说,我们白白在地址数据的查询读取上浪费
了大量的系统资源。延迟加载为我们妥善的处理了性能与编码上的平衡(如果没有延迟
加载,我们为了避免无谓的性能开销,只能专门为此再增加一个不读取地址信息的用户
记录检索模块,无疑增加了编码上的工作量)。
回忆之前“ibatis 配置”中的内容:
<settings ⑴
……
enhancementEnabled="true"
lazyLoadingEnabled="true"
……
/>
节点有两个与延迟加载相关的属性lazyLoadingEnabled 和Settings
enhancementEnabled,其中lazyLoadingEnabled 设定了系统是否使用延迟加载
机制,enhancementEnabled 设定是否启用字节码强化机制(通过字节码强化机制可
以为Lazy Loading 带来性能方面的改进。
为了使用延迟加载所带来的性能优势,这两项都建议设为"true"。
动态映射
在复杂查询过程中,我们常常需要根据用户的选择决定查询条件,这里发生变化的
并不只是SQL 中的参数,包括Select 语句中所包括的字段和限定条件,都可能发生变
化。典型情况, 如在一个复杂的组合查询页面,我们必须根据用户的选择和输入决定查
询的条件组合。
一个典型的页面如下:
对于这个组合查询页面,根据用户选择填写的内容,我们应为其生成不同的查询语
句。
如用户没有填写任何信息即提交查询请求,我们应该返回所有记录:
Select * from t_user;
如用户只在页面上填写了姓名“Erica”,我们应该生成类似:
Select * from t_user where name like ‘%Erica%’ ;
的SQL 查询语句。
如用户只在页面上填写了地址“Beijing”,我们应该生成类似:
Select * from t_user where address like ‘%Beijing%”;
的SQL 。
而如果用户同时填写了姓名和地址(”Erica”&’Beijing’),则我们应生成类似:
Select * from t_user where name like ‘%Erica%’ and address like ‘%Beijing%”
的SQL 查询语句。
对于ibatis 这样需要预先指定SQL 语句的ORM 实现而言,传统的做法无非通过
if-else 语句对输入参数加以判定,然后针对用户选择调用不同的statement 定义。对于
上面这种简单的情况(两种查询条件的排列组合,共4 种情况)而言,statement 的重
复定义工作已经让人不厌其烦,而对于动辄拥有七八个查询条件,乃至十几个查询条件
的排列组合而言,琐碎反复的statement 定义实在让人不堪承受。
考虑到这个问题,ibatis 引入了动态映射机制,即在statement 定义中,根据不同的
查询参数,设定对应的SQL 语句。
还是以上面的示例为例:
<select id="getUsers"
parameterClass="user"
resultMap="get-user-result">
select
id,
name,
sex
from t_user
<dynamic prepend="WHERE">
<isNotEmpty prepend="AND" property="name">
(name like #name#)
</isNotEmpty>
<isNotEmpty prepend="AND" property="address">
(address like #address#)
</isNotEmpty>
</dynamic>
</select>
通过dynamic 节点,我们定义了一个动态的WHERE 子句。此WHERE 子句中将
可能包含两个针对name 和address 字段的判断条件。而这两个字段是否加入检索取决
于用户所提供的查询条件(字段是否为空[isNotEmpty])。
对于一个典型的Web 程序而言,我们通过HttpServletRequest 获得表单中的字段名
并将其设入查询参数,如:
user.setName(request.getParameter("name"));
user.setAddress(request.getParameter("address"));
sqlMap.queryForList("User.getUsers", user);
在执行queryForList("User.getUsers", user) 时,ibatis 即根据配置文
件中设定的SQL 动态生成规则,创建相应的SQL 语句。
上面的示例中,我们通过判定节点isNotEmpty,指定了关于name 和address 属
性的动态规则:
<isNotEmpty prepend="AND" property="name">
(name like #name#)
</isNotEmpty>
这个节点对应的语义是, 如果参数类的"name"属性非空(isNotEmpty,即非空
字符串””),则在生成的SQL Where 字句中包括判定条件(name like #name#),其
中#name#将以参数类的name 属性值填充。
Address 属性的判定生成与name 属性完全相同,这里就不再赘述。
这样,我们通过在statement 定义中引入dynamic 节点,很简单的实现了SQL 判
定子句的动态生成,对于复杂的组合查询而言,这将带来极大的便利。
判定节点的定义可以非常灵活,我们甚至可以使用嵌套的判定节点来实现复杂的动
态映射, 如:
<isNotEmpty prepend="AND" property="name">
( name=#name#
<isNotEmpty prepend="AND" property="address">
address=#address#
</isNotEmpty>
)
</isNotEmpty>
这段定义规定,只有用户提供了姓名信息时,才能结合地址数据进行查询(如果只
提供地址数据,而将姓名信息忽略,将依然被视为全检索)。
Dynamic 节点和判定节点中的prepend 属性,指明了本节点中定义的SQL 子句在
主体SQL 中出现时的前缀。
如:
<dynamic prepend="WHERE">
<isNotEmpty prepend="AND" property="name">
(name like #name#)
</isNotEmpty>
<isNotEmpty prepend="AND" property="address">
(address like #address#)
</isNotEmpty>
</dynamic>
假设"name"属性的值为“Erica”, "address"属性的值为“Beijing”,则会
生成类似下面的SQL 子句(实际运行期将生成带占位符的PreparedStatement,之
后再为其填充数据):
WHERE (name like ‘Beijing’) AND (address like ‘Beijing’)
其中WHERE 之后的语句是在dynamic 节点中所定义,因此以dynamic 节点的
prepend 设置("WHERE")作为前缀,而其中的”AND”,实际上是address 属性所对
应的isNotEmpty 节点的prepend 设定,它引领了对应节点中定义的SQL 子句。至于
name 属性对应的isNotEmpty 节点,由于ibatis 会自动判定是否需要追加prepend
前缀,这里(name like #name#)是WHERE 子句中的第一个条件子句, 无需AND 前
缀,所以自动省略。
判定节点并非仅限于isNotEmpty,ibatis 中提供了丰富的判定定义功能。
判定节点分两类:
. 一元判定
一元判定是针对属性值本身的判定,如属性是否为NULL,是否为空值等。
上面示例中isNotEmpty 就是典型的一元判定。
一元判定节点有:
节点名描述
<isPropertyAvailable> 参数类中是否提供了此属性
<isNotPropertyAvailable> 与<isPropertyAvailable> 相反
<isNull> 属性值是否为NULL
<isNotNull> 与<isNull> 相反
<isEmpty> 如果属性为Collection 或者String,其size 是否<1,
如果非以上两种类型,则通过
String.valueOf( 属性值)
获得其String 类型的值后,判断其size 是否<1
<isNotEmpty> 与<isEmpty> 相反。
. 二元判定
二元判定有两个判定参数, 一是属性名,而是判定值,如
<isGreaterThan prepend="AND" property="age"
compareValue="18">
(age=#age#)
</isGreaterThan>
其中,property="age" 指定了属性名”age”,compareValue=”18”指明
了判定值为”18”。
上面判定节点isGreaterThan 对应的语义是:如果age 属性大于
18(compareValue),则在SQL 中加入(age=#age#) 条件。
二元判定节点有:
节点名属性值与的关系
相等。
不等。
大于
大于等于
小于
小于等于
compareValues
<isEqual>
<isNotEqual>
<isGreaterThan>
<isGreaterEqual>
<isLessThan>
<isLessEqual>
事务管理
基于JDBC 的事务管理机制
ibatis 提供了自动化的JDBC 事务管理机制。
对于传统JDBC Connection 而言,我们获取Connection 实例之后,需要调用
Connection.setAutoCommit 设定事务提交模式。
在AutoCommit 为true 的情况下,JDBC 会对我们的操作进行自动提交,此时,每
个JDBC 操作都是一个独立的任务。
为了实现整体事务的原子性,我们需要将AutoCommit 设为false ,并结合
Connection.commit/rollback 方法进行事务的提交/回滚操作。
ibatis 的所谓“自动化的事务提交机制”,即ibatis 会根据当前的调用环境,自动
判断操作是否需要自动提交。
如果代码没有显式的调用SqlMapClient.startTransaction() 方法,则ibatis
会将当前的数据库操作视为自动提交模式(AutoCommit=true),如:
sqlMap = xmlBuilder.buildSqlMap(reader);
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap.update("User.updateUser", user);
User user2 = new User();
user2.setId(new Integer(2));
user2.setName("Kevin");
user2.setSex(new Integer(1));
sqlMap.update("User.updateUser", user2);
在执行的时候,会自动判定运行环境,这里操作sqlMap.update ibatis 当前的update
并没有相对应的事务范围(startTransaction 和endTransaction 代码块),于是
ibatis 将其作为一个单独的事务,并自动提交。对于上面的代码,update 执行了两次,
与其相对应,事务也提交了两次(即每个update 操作为一个单独的事务)。
不过,值得注意的是,这里的所谓“自动判定”,可能有些误导,ibatis 并没有去
检查当前是否已经有事务开启,从而判断当前数据库连接是否设定为自动提交。
实际上,在执行update 语句时,sqlMap 会检查当前的Session 是否已经关联了某个
数据库连接,如果没有,则取一个数据库连接,将其AutoCommit 属性设为true ,然后
执行update 操作,执行完之后又将这个连接释放。这样,上面两次update 操作实际上
先后获取了两个数据库连接,而不是我们通常所认为的两次update 操作都基于同一个
JDBC Connection 。这点在开发时需特别注意。
对于多条SQL 组合而成的一个JDBC 事务操作而言,必须使用
startTransaction、commit 和endTransaction 操作以实现整体事务的原子性。
如:
try{
sqlMap = xmlBuilder.buildSqlMap(reader);
sqlMap.startTransaction();
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap.update("User.updateUser", user);
User user2 = new User();
user2.setId(new Integer(2));
user2.setName("Kevin");
user2.setSex(new Integer(1));
sqlMap.update("User.updateUser", user2);
sqlMap.commitTransaction();
}finally{
sqlMap.endTransaction();
}
如果user1 或者user2 的update 操作失败,整个事务就会在endTransaction 时回
滚,从而保证了两次update 操作的原子性。
基于JTA 的事务管理机制
JTA 提供了跨数据库连接(或其他JTA 资源)的事务管理能力。这一点是与JDBC
Transaction 最大的差异。
JDBC 事务由Connnection 管理,也就是说,事务管理实际上是在JDBC Connection
中实现。事务周期限于Connection 的生命周期。同样,对于基于JDBC 的ibatis 事务管
理机制而言,事务管理在SqlMapClient 所依托的JDBC Connection 中实现,事务周期限
于SqlMapClient 的生命周期。
JTA 事务管理则由JTA 容器实现,JTA 容器对当前加入事务的众多Connection 进
行调度,实现其事务性要求。JTA 的事务周期可横跨多个JDBC Connection 生命周期。
同样,对于基于JTA 事务的ibatis 而言,JTA 事务横跨可横跨多个SqlMapClient 。
下面这幅图形象的说明了这个问题:
为了在ibatis 中使用JTA 事务管理,我们需要在配置文件中加以设定:
<transactionManager type="JTA">
……
</transactionManager>
在实际开发中,我们可能需要面对分布式事务的处理,如系统范围内包含了多个数据库,
也许还引入了JMS 上的事务管理(这在EAI 系统实现中非常常见)。我们就需要引入JTA
以实现系统范围内的全局事务,如下面示例中,我们同时将user 对象更新到两个不同的数
据库:
User user = new User();
user.setId(new Integer(1));
user.setName("Erica");
user.setSex(new Integer(0));
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
try{
sqlMap1.startTransaction();
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
sqlMap1. commitTransaction();
}finally{
sqlMap1.endTransaction();
}
上面的代码中,两个针对不同数据库的实例,在同一个事务中SqlMapClient JTA
对user 对象所对应的数据库记录进行更新。外层的sqlMap1 启动了一个全局事务,此
事务将涵盖本线程内commitTransaction 之前的所有数据库操作。只要其间发生了
异常,则整个事务都将被回滚。
外部事务管理
基于JTA 的事务管理还有另外一个特殊情况,就是利用外部事务管理机制。
对于外部事务管理,我们需要在配置文件中进行如下设定:
<transactionManager type="EXTERNAL">
……
</transactionManager>
下面是一个外部事务管理的典型示例:
UserTransaction tx = new InitialContext().lookup(“……”);
……
sqlMap1 = xmlBuilder.buildSqlMap(db1Config);
sqlMap2 = xmlBuilder.buildSqlMap(db2Config);
sqlMap1.update("User.updateUser", user);
sqlMap2.update("User.updateUser", user);
……
tx.commit();
此时,我们借助实例启动了一个全局事务。之后的操作JTA UserTransaction ibatis
( sqlMap1.update 和sqlMap2.update)全部被包含在此全局事务之中,当
UserTransaction 提交的时候,
ibatis 操作被包含在事务中提交,反之,如果UserTransaction
回滚,那么其间的ibatis 操作也将作为事务的一部分被回滚。这样,我们就实现了ibatis
外部的事务控制。
另一种外部事务管理方式是借助EJB 容器,通过EJB 的部署配置,我们可以指定
EJB 方法
java读取oracle的存储过程(转 邢红瑞's blog) java读取oracle的存储过程网上的例子不少,都太简单,没有一个返回参考游标的,返回记录集,自己写一个怕日后忘了。 oracle的PLSQL CREATE OR REPLACE package chapter_13 as end; CREATE OR REPLACE package body chapter_13 as PROCEDURE founder(oFields out rs) IS package jdbc; import java.io.*; public class TestStoredProcedures { public TestStoredProcedures() { public static void main(String[] args) throws Exception { public void process() throws SQLException { try { // *** SQL92 escape syntax *** cstmt.execute(); end = System.currentTimeMillis(); protected void finalize() throws Throwable { |
javascript实现通用表单验证函数
(转 邢红瑞)
不管是动态网站,还是其它B/S结构的系统,都离不开表单
表单做为客户端向服务器提交数据的载体担当相当重要的角色.
这就引出了一个问题,提交的数据合法吗?摆在我们面前的问题就是验证这些数据
保证所提交的数据是合法的.所以,我们写了一个大堆的验证函数.当我们开始新的一个
项目的开发时,我们又得写一大堆的验证函数,然后再调试这一大堆的函数...
本文将介绍一种方法来提高我的代码的可重用性,提高我们的开发效率.
个人以为表单的验证应该包含两部分:
第一,判断用户输入的数据是否合法.
第二,提示用户你的数据为什么是不合法的.
所以,我们的通用表单验证函数要实现的功能就是:
第一,取得用户输入的数据GetValue(el)
第二,验证用户的数据CheckForm(oForm)
IE支持自定义属性,这就是这个通用函数实现的基础
我们可以在表单元素上加入描述自身信息的属性.有点像XML吧.
check属性:该属性用于存储数据合法性的正则表达式.
warning属性:该性性用于存储出错误提示信息.
第三,返回有误的表单提示GoBack(el)
这三个步骤的触发事件是onsubmit,记住是return CheckForm(this)
搞错了就全功尽弃了 :)
<form οnsubmit="return CheckForm(this)">
写到这里,整体框架就出来了,通过取得表单元素的check属性,取得字符串,构建正则表达式.再验证其值.如果通过验证就提交,如是数据不合法则取得表单元素的warning属性,产生提示信息.并返回到该表单元素.整个的框架也比较简单.
我们要做的就是写好正则表达式!
接下来我们来分析一下所有的表单元素
按其共性,我们将它们分为三类
每类表单的特点不一样,我们的目标就是写出通用的.
1.文输入框Text
<input type="text" name="txt">
<input type="password" name="pwd">
<input type="hidden" name="hid">
<input type="file" name="myfile">
<textarea name="txts"></textarea>
2.单多选框Choose
<input type="checkbox" name="c">
<input type="checkbox" name="c">
<input type="radio" name="r">
<input type="radio" name="r">
3.单多下拉菜单Select
<select name="sel"></select>
<select name="sels" multiple></select>
讲了一堆"大道理"太抽象了,代码更有说服力!
Check.js JS函数文件
/*
*--------------- 客户端表单通用验证CheckForm(oForm) -----------------
* 功能:通用验证所有的表单元素.
* 使用:
* <form name="form1" οnsubmit="return CheckForm(this)">
* <input type="text" name="id" check="^S+$" warning="id不能为空,且不能含有空格">
* <input type="submit">
* </form>
* author:wanghr100(灰豆宝宝.net)
* email:wanghr100@126.com
* update:19:28 2004-8-23
* 注意:写正则表达式时一定要小心.不要让"有心人"有空子钻.
* 已实现功能:
* 对text,password,hidden,file,textarea,select,radio,checkbox进行合法性验证
* 待实现功能:把正则表式写成个库.
*--------------- 客户端表单通用验证CheckForm(oForm) -----------------
*/
//主函数
function CheckForm(oForm)
{
var els = oForm.elements;
//遍历所有表元素
for(var i=0;i<els.length;i++)
{
//是否需要验证
if(els[i].check)
{
//取得验证的正则字符串
var sReg = els[i].check;
//取得表单的值,用通用取值函数
var sVal = GetValue(els[i]);
//字符串->正则表达式,不区分大小写
var reg = new RegExp(sReg,"i");
if(!reg.test(sVal))
{
//验证不通过,弹出提示warning
alert(els[i].warning);
//该表单元素取得焦点,用通用返回函数
GoBack(els[i])
return false;
}
}
}
}
//通用取值函数分三类进行取值
//文本输入框,直接取值el.value
//单多选,遍历所有选项取得被选中的个数返回结果"00"表示选中两个
//单多下拉菜单,遍历所有选项取得被选中的个数返回结果"0"表示选中一个
function GetValue(el)
{
//取得表单元素的类型
var sType = el.type;
switch(sType)
{
case "text":
case "hidden":
case "password":
case "file":
case "textarea": return el.value;
case "checkbox":
case "radio": return GetValueChoose(el);
case "select-one":
case "select-multiple": return GetValueSel(el);
}
//取得radio,checkbox的选中数,用"0"来表示选中的个数,我们写正则的时候就可以通过0{1,}来表示选中个数
function GetValueChoose(el)
{
var sValue = "";
//取得第一个元素的name,搜索这个元素组
var tmpels = document.getElementsByName(el.name);
for(var i=0;i<tmpels.length;i++)
{
if(tmpels[i].checked)
{
sValue += "0";
}
}
return sValue;
}
//取得select的选中数,用"0"来表示选中的个数,我们写正则的时候就可以通过0{1,}来表示选中个数
function GetValueSel(el)
{
var sValue = "";
for(var i=0;i<el.options.length;i++)
{
//单选下拉框提示选项设置为value=""
if(el.options[i].selected && el.options[i].value!="")
{
sValue += "0";
}
}
return sValue;
}
}
//通用返回函数,验证没通过返回的效果.分三类进行取值
//文本输入框,光标定位在文本输入框的末尾
//单多选,第一选项取得焦点
//单多下拉菜单,取得焦点
function GoBack(el)
{
//取得表单元素的类型
var sType = el.type;
switch(sType)
{
case "text":
case "hidden":
case "password":
case "file":
case "textarea": el.focus();var rng = el.createTextRange(); rng.collapse(false); rng.select();
case "checkbox":
case "radio": var els = document.getElementsByName(el.name);els[0].focus();
case "select-one":
case "select-multiple":el.focus();
}
}
demo.htm 演示文件
<script language="JavaScript" src="Check.js"></script>
通用表单函数测试:
<form name="form1" οnsubmit="return CheckForm(this)">
test:<input type="text" name="test">不验证<br>
账号:<input type="text" check="^S+$" warning="账号不能为空,且不能含有空格" name="id">不能为空<br>
密码:<input type="password" check="S{6,}" warning="密码六位以上" name="id">六位以上<br>
电话:<input type="text" check="^d+$" warning="电话号码含有非法字符" name="number" value=""><br>
相片上传:<input type="file" check="(.*)(.jpg|.bmp)$" warning="相片应该为JPG,BMP格式的" name="pic" value="1"><br>
出生日期:<input type="text" check="^d{4}-d{1,2}-d{1,2}$" warning="日期格式2004-08-10" name="dt" value="">日期格式2004-08-10<br>
省份:
<select name="sel" check="^0$" warning="请选择所在省份">
<option value="">请选择
<option value="1">福建省
<option value="2">湖北省
</select>
<br>
选择你喜欢的运动:<br>
游泳<input type="checkbox" name="c" check="^0{2,}$" warning="请选择2项或以上">
篮球<input type="checkbox" name="c">
足球<input type="checkbox" name="c">
排球<input type="checkbox" name="c">
<br>
你的学历:
大学<input type="radio" name="r" check="^0$" warning="请选择一项学历">
中学<input type="radio" name="r">
小学<input type="radio" name="r">
<br>
个人介绍:
<textarea name="txts" check="^[s|S]{20,}$" warning="个人介绍不能为空,且不少于20字"></textarea>20个字以上
<input type="submit">
</form>
最简单的
function isValidDate(dateStr) {
var matchArray = dateStr.match(/^[0-9]+-[0-1][0-9]-[0-3][0-9]$/)
if (matchArray == null) {
alert("Invalid date: " + dateStr);
return false;
}
return true;
}
第二种
<script language=javascript>
String.prototype.isDate = function()
{
var r = this.match(/^(/d{1,4})(-|//)(/d{1,2})/2(/d{1,2})$/);
if(r==null)return false; var d = new Date(r[1], r[3]-1, r[4]);
return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]);
}
alert("2002-01-31".isDate());
alert("2002-01-41".isDate());
</script>
第三种,比较复杂的
<script language=javascript>
String.prototype.isTime = function()
{
var r = this.match(/^(/d{1,4})(-|//)(/d{1,2})/2(/d{1,2}) (/d{1,2}):(/d{1,2}):(/d{1,2})$/);
if(r==null)return false; var d = new Date(r[1], r[3]-1,r[4],r[5],r[6],r[7]);
return (d.getFullYear()==r[1]&&(d.getMonth()+1)==r[3]&&d.getDate()==r[4]&&d.getHours()==r[5]&&d.getMinutes()==r[6]&&d.getSeconds()==r[7]);
}
alert("2002-1-31 12:34:56".isTime());
alert("2001-2-29 12:54:56".isTime());
alert("2002-1-41 12:00:00".isTime());
</script>
判断数字用 isNaN()
数据缓存策略
(一)hibernate数据缓存策略
缓存是数据库数据在内存中的临时容器,它包含了库表数据在内存中的拷贝,位于数据库与数据访问层之间。对于查询操作相当频繁的系统(论坛,新闻发布等),良好的缓存机制显得尤为重要。
ORM在进行数据读取时,首先在缓存中查询,避免了数据库调用的性能开销。
ORM的数据缓存应包含下面几个层次:
1)事务级缓存 2)应用级缓存 3)分布式缓存
具体针对Hibernate而言,采用两级缓存策略,其过程描述:
(1)条件查询的时候,总是发出一条select * from table_name where …. 这样的SQL语句查询数据库,一次获得所有的数据对象。
(2) 把获得的所有数据对象根据ID放入到第二级缓存中。
(3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。
(4) 删除、更新、增加数据的时候,同时更新缓存。
1. 一级缓存(session level)-数据库事务级缓存
1)根据主键id加载数据时。 Session.load(), Session.iterate()方法
2)延迟加载时
Session内部维护一个数据对象集合,包括了本Session内选取的、操作的数据对象。这称为Session内部缓存,是Hibernate的第一级最快缓存,属于Hibernate的既定行为,不需要进行配置(也没有办法配置 :-)。
内部缓存正常情况下由hibernate自动维护,但也可人工干预:
1) Session.evict (): 将某个特定对象从内部缓存中清除
2)Session.clear(): 清空内部缓存
2.二级缓存(SessionFactory level)-应用级缓存
二级缓存由SessionFactory的所有session实例共享。
3. 第三方缓存实现
EHCache, OSCahe
hibernate批量查询引起的内存溢出问题
批量查询基本不适合使用现有的持久层技术来做,如CMP或hibernate,IBatis倒是可以.
因为每次调用Session.save()方法时,当前session都会将对象纳入到自身的内部缓存中。内部缓存不同于二级缓存,我们可以在二级缓存的配置中指定其最大容量。
解决方案:
1)在批处理情况下,关闭Hibernate缓存,如果关闭Hibernate缓存,那么和直接使用JDBC就没有区别。
2) 每隔一段时间清空Session内部缓存
Session实现了异步write-behind,它允许Hibernate显式地写操作的批处理。 这里,我给出Hibernate如何实现批量插入的方法: 首先,我们设置一个合理的JDBC批处理大小,hibernate.jdbc.batch_size 20。 然后在一定间隔对Session进行flush()和clear()。
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); if ( i % 20 == 0 ) { //flush 插入数据和释放内存: session.flush(); session.clear(); } } tx.commit(); session.close(); |
为了优化性能,可执行批量操作。在传统的JDBC编程中,批量操作方式如下,将数个SQL操作批量提交:
PrepareStatement ps=conn.prepareStatement("insert into users(name) values(?)"); |
在Hibernate中,可以设置hibernate.jdbc.batch_size 参数来指定每次提交的sql数量。
hibernate2和hibernate3数据批量删除机制分析
1.hibernate2
Transaction tx=session.beginTransaction();
session.delete("from users");
tx.commit();
观察日志输出:
select ... from users |
hibernate2版本会首先从数据库中查询出所有符合条件的记录,再对此记录循环删除。如果记录量过大,势必引起内存溢出和删除效率问题。ORM为什么要这么做呢?因为ORM为了自动维护内存状态,必须知道用户到底对哪些数据进行了操作。问题的解决方法:
1)内存消耗
批量删除前首先从数据库中查询出所有符合条件的记录,如果数据量过大,就会导致 OutOfMemoryError.
可以采用Session .iterate或Query.iterate方法逐条获取记录,再执行delete操作。另外,hibernate2.16后的版本提供了基于游标的数据遍历操作:
Transaction tx=session.beginTransaction(); String hql="from users"; tx.commit(); |
2)循环删除的效率问题
由于hibernate在批量删除操作过程中,需要反复调用delete SQL,存在性能问题。我们仍然可以通过调整hibernate.jdbc.batch_size参数来解决。
2.hibernate3
hibernate3 HQL中引入了 bulk delete/update操作, 即通过一条独立的sql语句来完成数据的批量操作。
Transaction tx=session.beginTransaction(); String hql="delete TUser"; tx.commit(); |
观察日志输出:
Hibernate:delete from TUser |
(二)ibatis数据缓存
相对Hibernate 等封装较为严密的ORM 实现而言(因为对数据对象的操作实现
了较为严密的封装,可以保证其作用范围内的缓存同步,而ibatis 提供的是半封闭
的封装实现,因此对缓存的操作难以做到完全的自动化同步)。
ibatis 的缓存机制使用必须特别谨慎。特别是flushOnExecute 的设定(见
“ibatis配置”一节中的相关内容),需要考虑到所有可能引起实际数据与缓存数据
不符的操作。如本模块中其他Statement对数据的更新,其他模块对数据的更新,甚
至第三方系统对数据的更新。否则,脏数据的出现将为系统的正常运行造成极大隐患。
如果不能完全确定数据更新操作的波及范围,建议避免Cache的盲目使用。
1.iBatis cache设置
sqlmap-config.xml在<sqlMapConfig>里面加入
<settings
cacheModelsEnabled="true"
enhancementEnabled="true"
lazyLoadingEnabled="true" />
maps.xml在<sqlMap>里面加入
<cacheModel id="userCache" type="LRU" readonly="true" serialize="false">
<flushInterval hours="24"/>
<flushOnExecute statement="insertTest"/>
<property name="size" value="1000" />
</cacheModel>
可以看到,Cache有如下几个比较重要的属性:readOnly,serialize,type
readOnly
readOnly值的是缓存中的数据对象是否只读。这里的只读并不是意味着数据对象一
旦放入缓存中就无法再对数据进行修改。而是当数据对象发生变化的时候,如数据对
象的某个属性发生了变化,则此数据对象就将被从缓存中废除,下次需要重新从数据
库读取数据,构造新的数据对象。
serialize
如果需要全局的数据缓存,CacheModel的serialize属性必须被设为true。否则数据缓存只对当前Session(可简单理解为当前线程)有效,局部缓存对系统的整体性能提升有限。
Cache Type:
与hibernate类似,ibatis通过缓冲接口的插件式实现,提供了多种Cache的实现机制可供选择:
1. MEMORY
2. LRU
3. FIFO
4. OSCACHE
MEMORY类型Cache与WeakReference
MEMORY 类型的Cache 实现,实际上是通过Java 对象引用进行。ibatis 中,其实现类
为com.ibatis.db.sqlmap.cache.memory.MemoryCacheController,MemoryCacheController 内部,
使用一个HashMap来保存当前需要缓存的数据对象的引用。
LRU型Cache
当Cache达到预先设定的最大容量时,ibatis会按照“最少使用”原则将使用频率最少
的对象从缓冲中清除。可配置的参数有:
flushInterval:指定了多长时间清除缓存,上例中指定每24小时强行清空缓存区的所有内容。
size
FIFO型Cache
先进先出型缓存,最先放入Cache中的数据将被最先废除。
OSCache
(三)开源数据缓存策略OSCache
可以解决的问题:
1)信息系统中需要处理的基础数据的内容短时间内是不会发生变化的,但是在一个相对长一些的时间里,它却可能是动态增加或者减少的。
2)统计报表是一个周期性的工作,可能是半个月、一个月或者更长的时间才会需要更新一次,然而统计报表通常是图形显示或者是生成pdf、word、excel等格式的文件,这些图形内容、文件的生成通常需要消耗很多的系统资源,给系统运行造成很大的负担。
OSCache是OpenSymphony组织提供的一个J2EE架构中Web应用层的缓存技术实现组件。OSCache支持对部分页面内容或者对页面级的响应内容进行缓存,编程者可以根据不同的需求、不同的环境选择不同的缓存级别。可以使用内存、硬盘空间、同时使用内存和硬盘或者提供自己的其他资源(需要自己提供适配器)作为缓存区。
使用步骤:
1. 下载、解压缩OSCache
请到OSCache的主页http://www.opensymphony.com/oscache/download.html下载Oscache的最新版本,作者下载的是OSCache的最新稳定版本2.0。
将下载后的。Zip文件解压缩到c:/oscache(后面的章节中将使用%OSCache_Home%来表示这个目录)目录下
2. 新建立一个web应用
3. 将主要组件%OSCache_Home%/oscache.jar放入WEB-INF/lib目录
4. commons-logging.jar、commons-collections.jar的处理
- OSCache组件用Jakarta Commons Logging来处理日志信息,所以需要commons-logging.jar的支持,请将%OSCache_Home%/lib/core/commons-logging.jar放入classpath(通常意味着将这个文件放入WEB-INF/lib目录)
- 如果使用JDK1.3,请将%OSCache_Home%/lib/core/commons-collections.jar放入classpath,如果使用JDK1.4或者以上版本,则不需要了
5. 将oscache.properties、oscache.tld放入WEB-INF/class目录
- %OSCache_Home%/oscache.properties包含了对OSCache运行特征值的设置信息
- %OSCache_Home%/oscache.tld包含了OSCache提供的标签库的定义内容
6. 修改web.xml文件
在web.xml文件中增加下面的内容,增加对OSCache提供的taglib的支持:
<taglib><taglib-uri>oscache</taglib-uri><taglib-location>/WEB-INF/classes/oscache.tld</taglib-location></taglib> |
7.最简单的cache标签用法
使用默认的关键字来标识cache内容,超时时间是默认的3600秒
<cache:cache><%//自己的JSP代码内容%></cache:cache> |
8. 缓存单个文件
在OSCache组件中提供了一个CacheFilter用于实现页面级的缓存,主要用于对web应用中的某些动态页面进行缓存,尤其是那些需要生成pdf格式文件/报表、图片文件等的页面,不仅减少了数据库的交互、减少数据库服务器的压力,而且对于减少web服务器的性能消耗有很显著的效果。
修改web.xml,增加如下内容,确定对/testContent.jsp页面进行缓存。
<filter> <filter-name>CacheFilter</filter-name><filter-class>com.opensymphony.oscache.web.filter. |