栗乡晚秋
一凡
抹一缕天边的云霞,
深入栗乡的每一个角落,
列队的雁阵啼鸣着南飞,
满眼里,
晚秋的原野一片金黄。
一叶知秋,
凉风将一串串的落叶,
别上清凉世界的前胸,
匆匆间,
秋天就不见了踪影。
加载中…
加载中…
加载中…
加载中…| 分类: 文章欣赏 |
栗乡晚秋
一凡
抹一缕天边的云霞,
深入栗乡的每一个角落,
列队的雁阵啼鸣着南飞,
满眼里,
晚秋的原野一片金黄。
一叶知秋,
凉风将一串串的落叶,
别上清凉世界的前胸,
匆匆间,
秋天就不见了踪影。
标签:
杂谈 |
在本书的前面所介绍的内容中,所处理的都是纯文本文件。但是事实上,人们用于保存信息的文件并不是纯文本格式。现在比较流行的文件存储格式有Adobe公司的PDF和Microsoft的Word、Excel等。在处理这些文件的时候,不能简单的从文件读取字符,需要根据他们特殊的格式提取内容。本章就将对比较流行的PDF、Word和Excel格式的处理工具逐一进行介绍。
PDF全称Portable Document Format,是Adobe公司开发的电子文件格式。这种文件格式与操作系统平台无关,可以在Windows、Unix或Mac OS等操作系统上通用。
PDF文件格式将文字、字型、格式、颜色及独立于设备和分辨率的图形图像等封装在一个文件中。如果要抽取其中的文本信息,需要根据它的文件格式来进行解析。幸好目前已经有不少工具能帮助我们做这些事情。
最常见的一种PDF文本抽取工具就是PDFBox了,访问网址http://sourceforge.net/projects/pdfbox/,进入如图7-1所示的下载界面。

图7-1 PDFBox的下载页面
读者可以在该网页下载其最新的版本。本书采用的是PDFBox-0.7.3版本。PDFBox是一个开源的Java PDF库,这个库允许你访问PDF文件的各项信息。在接下来的例子中,将演示如何使用PDFBox提供的API,从一个PDF文件中提取出文本信息。
以下是在Eclipse中创建工程,并建立解析PDF文件的工具类的过程。
(1)在Eclipse的workspace中创建一个普通的Java工程:ch7。
(2)把下载的PDFBox-0.7.3.zip解压,解压后的目录结构如图7-2所示。

图7-2 解压后的PDFBox包
(3)进入external目录下,可以看到,这里包括了PDFBox所有用到的外部包。复制下面的Jar包到工程ch7的lib目录下(如还未建立lib目录,则先创建一个)。
l bcmail-jdk14-132.jar
l bcprov-jdk14-132.jar
l checkstyle-all-4.2.jar
l FontBox-0.1.0-dev.jar
l lucene-core-2.0.0.jar
然后再从PDFBox的lib目录下,复制PDFBox-0.7.3.jar到工程的lib目录下。
(4)在工程上单击右键,在弹出的快捷菜单中选择“Build Path->Config Build Path->Add Jars”命令,把工程lib目录下面的包都加入工程的Build Path。笔者机器上完整的工程目录如图7-3所示:

图7-3 工程截图
在刚刚创建的Eclipse工程中,创建一个ch7.pdfbox包,并创建一个PdfboxTest类。该类包含一个getText方法,用于从一个PDF中获取文本信息,其代码如下。
代码7.1
public void geText(String file) throws Exception {
// 是否排序
boolean sort = false;
// pdf文件名
String pdfFile = file;
// 输入文本文件名称
String textFile = null;
// 编码方式
String encoding = "UTF-8";
// 开始提取页数
int startPage = 1;
// 结束提取页数
int endPage = Integer.MAX_VALUE;
// 文件输入流,生成文本文件
Writer output = null;
// 内存中存储的PDF Document
PDDocument document = null;
try {
try {
// 首先当作一个URL来装载文件,如果得到异常再从本地文件系统//去装载文件
URL url = new URL(pdfFile);
document = PDDocument.load(url);
// 获取PDF的文件名
String fileName = url.getFile();
// 以原来PDF的名称来命名新产生的txt文件
if (fileName.length() > 4) {
File outputFile = new File(fileName.substring(0, fileName.length() - 4) + ".txt");
textFile = outputFile.getName();
}
} catch (MalformedURLException e) {
// 如果作为URL装载得到异常则从文件系统装载
document = PDDocument.load(pdfFile);
if (pdfFile.length() > 4) {
textFile = pdfFile.substring(0, pdfFile.length() - 4) + ".txt";
}
}
// 文件输入流,写入文件倒textFile
output = new OutputStreamWriter(new FileOutputStream(textFile), encoding);
// PDFTextStripper来提取文本
PDFTextStripper stripper = null;
stripper = new PDFTextStripper();
// 设置是否排序
stripper.setSortByPosition(sort);
// 设置起始页
stripper.setStartPage(startPage);
// 设置结束页
stripper.setEndPage(endPage);
// 调用PDFTextStripper的writeText提取并输出文本
stripper.writeText(document, output);
} finally {
if (output != null) {
// 关闭输出流
output.close();
}
if (document != null) {
// 关闭PDF Document
document.close();
}
}
}
在上面的代码中,getText方法接收一个String类型的参数,指定要提取的PDF文件路径。这个位置可以是一个URL或本地文件。然后函数调用PDFBox提供的PDFTextStripper类,设置提取过程中的一些属性(如起始页、是否排序等)。最后将文本提取并写入文件。
下面看一下这个函数的运行效果,在PdfboxTest加入一个main函数,其代码如下。
public static void main(String[] args) {
PdfboxTest test = new PdfboxTest();
try {
// 取得C盘下的index.pdf的内容
test.geText("C:\\index.pdf");
} catch (Exception e) {
e.printStackTrace();
}
}
这里要处理一个index.pdf文件,该PDF文件的内容如图7-4所示。

图7-4 要解析的PDF文档内容
通过PdfboxTest处理后的文本文件如图7-5所示。

图7-5 处理的结果
可以看到,PDF中的文本已经被提取出来,保存于文本文件中了。其中第4行的超链接部分“POI News WebBlog”,在文本文件中已经被替换成了普通的纯文本。读者可以根据PDFBox所提供的API文档进一步查询其他功能。
PDFBox还提供和Lucene的集成,它提供了一套简单的方法把PDF Documents加入到Lucene的索引中去,请看以下代码:
Document lucenedocument = LucenePDFDocument.getDocument(…);
其中,LucenePDFDocument是PDFBox中提供的一个类,它的getDocument被重载为3个方法,分别接收一个File对象、InputStream对象或者URL对象作为参数,然后从该参数传递进来的PDF文件中,提取并生成Lucene的Document对象。
当通过PDFBox从一个PDF文档中得到一个Lucene Document后,可以直接使用IndexWriter把它加到Lucene的index中。LucenePDFDocument自动从PDF文件中提取各种元数据Field,并把它们加入到Document中。它提取的信息如表7-1所示。
表7-1 PDFBox生成的Lucene Document格式
|
Lucene Field名称 |
说明 |
|
path |
文件系统路径(如果文档是从文件装载) |
|
url |
URL地址(如果文档是从网络装载) |
|
contents |
整个Document的内容,索引但不存储 |
|
summary |
Document的内容前500个字符 |
|
modified |
最后修改时间 |
|
uid |
Document的惟一ID |
|
CreationDate |
从PDF的meta-data获取 |
|
Creator |
从PDF的meta-data获取 |
|
Keywords |
从PDF的meta-data获取 |
|
ModificationDate |
从PDF的meta-data获取 |
|
Producer |
从PDF的meta-data获取 |
|
Subject |
从PDF的meta-data获取 |
|
Trapped |
从PDF的meta-data获取 |
下面通过LucenePDFDocument,直接对PDF建立索引,在ch7.pdfbox包下面新建一个PdfLuceneTest类,该类的代码如下。
代码7.2
public class PdfLuceneTest {
public static void main(String[] args) {
try {
// IndexWriter存放索引到d:\index下
IndexWriter writer = new IndexWriter("d:\\index",
new StandardAnalyzer(), true);
// LucenePDFDocument返回由PDF产生的Lucene Docuement
Document d = LucenePDFDocument
.getDocument(new File("C:\\index.pdf"));
// 写入索引
writer.addDocument(d);
// 关闭索引文件流
writer.close();
// 读取d:\index下的索引文件建立IndexSearcher
IndexSearcher searcher = new IndexSearcher("d:\\index");
// 对索引的contents Field进行查找关键词poi
Term t = new Term("contents", "poi");
// 根据Term生成Query
Query q = new TermQuery(t);
// 搜索返回结果集
Hits hits = searcher.search(q);
// 打印结果集
for (int i = 0; i < hits.length(); i++) {
System.out.println(hits.doc(i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
函数利用LucenePDFDocument的getDocument函数,从一个PDF文件直接返回一个Lucene的Document,其中包含有path、url、modified、contents、summary等Field,把它们直接写入index,然后创建一个IndexSearcher,对contents字段经行检索,查找关键词“poi”(注意必须是小写),程序的执行结果如图7-6所示。

图7-6 搜索代码的运行结果
标签:
it |
第六章 二进制、八进制、十六进制这是一节“前不着村后不着店”的课。不同进制之间的转换纯粹是数学上的计算。不过,你不必担心会有么复杂,无非是乘或除的计算。 生活中其实很多地方的计数方法都多少有点不同进制的影子。 比如我们最常用的10进制,其实起源于人有10个指头。如果我们的祖先始终没有摆脱手脚不分的境况,我想我们现在一定是在使用20进制。 至于二进制……没有袜子称为0只袜子,有一只袜子称为1只袜子,但若有两袜子,则我们常说的是:1双袜子。 生活中还有:七进制,比如星期。十六进制,比如小时或“一打”,六十进制,比如分钟或角度……
(该版课程的内容更新及订正均已停止) ---------------------------------- 的“最新2008年版 白话C++”课程,请点击!] (另有: 博客版)
6.1 为什么需要八进制和十六进制?
编程中,我们常用的还是10进制……必竟C/C++是高级语言。 比如: int a = 100,b = 99; 不过,由于数据在计算机中的表示,最终以二进制的形式存在,所以有时候使用二进制,可以更直观地解决问题。 但,二进制数太长了。比如int 类型占用4个字节,32位。比如100,用int类型的二进制数表达将是: 0000 0000 0000 0000 0110 0100 面对这么长的数进行思考或操作,没有人会喜欢。因此,C,C++ 没有提供在代码直接写二进制数的方法。
用16进制或8进制可以解决这个问题。因为,进制越大,数的表达长度也就越短。不过,为什么偏偏是16或8进制,而不其它的,诸如9或20进制呢? 2、8、16,分别是2的1次方,3次方,4次方。这一点使得三种进制之间可以非常直接地互相转换。8进制或16进制缩短了二进制数,但保持了二进制数的表达特点。在下面的关于进制转换的课程中,你可以发现这一点。
6.2 二、八、十六进制数转换到十进制数6.2.1 二进制数转换为十进制数二进制数第0位的权值是2的0次方,第1位的权值是2的1次方…… 所以,设有一个二进制数:0110 0100,转换为10进制为: 下面是竖式:
0110 0100 换算成 十进制
第0位 0 * 20 = 0 第1位 0 * 21 = 0 第2位 1 * 22 = 4 第3位 0 * 23 = 0 第4位 0 * 24 = 0 第5位 1 * 25 = 32 第6位 1 * 26 = 64 第7位 0 * 27 = 0 + --------------------------- 100
用横式计算为: 0 * 20 + 0 * 21 + 1 * 22 + 1 * 23 + 0 * 24 + 1 * 25 + 1 * 26 + 0 * 27 = 100
0乘以多少都是0,所以我们也可以直接跳过值为0的位: 1 * 22 + 1 * 23 + 1 * 25 + 1 * 26 = 100
6.2.2 八进制数转换为十进制数八进制就是逢8进1。 八进制数采用 0~7这八数来表达一个数。 八进制数第0位的权值为8的0次方,第1位权值为8的1次方,第2位权值为8的2次方…… 所以,设有一个八进制数:1507,转换为十进制为: 用竖式表示:
1507换算成十进制。
第0位 7 * 80 = 7 第1位 0 * 81 = 0 第2位 5 * 82 = 320 第3位 1 * 83 = 512 + -------------------------- 839 同样,我们也可以用横式直接计算: 7 * 80 + 0 * 81 + 5 * 82 + 1 * 83 = 839
结果是,八进制数 1507 转换成十进制数为 839
6.2.3 八进制数的表达方法C,C++语言中,如何表达一个八进制数呢?如果这个数是 876,我们可以断定它不是八进制数,因为八进制数中不可能出7以上的阿拉伯数字。但如果这个数是123、是567,或12345670,那么它是八进制数还是10进制数,都有可能。 所以,C,C++规定,一个数如果要指明它采用八进制,必须在它前面加上一个0,如:123是十进制,但0123则表示采用八进制。这就是八进制数在C、C++中的表达方法。 由于C和C++都没有提供二进制数的表达方法,所以,这里所学的八进制是我们学习的,CtC++语言的数值表达的第二种进制法。 现在,对于同样一个数,比如是100,我们在代码中可以用平常的10进制表达,例如在变量初始化时:
int a = 100; 我们也可以这样写: int a = 0144; //0144是八进制的100;一个10进制数如何转成8进制,我们后面会学到。
千万记住,用八进制表达时,你不能少了最前的那个0。否则计算机会通通当成10进制。不过,有一个地方使用八进制数时,却不能使用加0,那就是我们前面学的用于表达字符的“转义符”表达法。
6.2.4 八进制数在转义符中的使用我们学过用一个转义符'\'加上一个特殊字母来表示某个字符的方法,如:'\n'表示换行(line),而'\t'表示Tab字符,'\''则表示单引号。今天我们又学习了一种使用转义符的方法:转义符'\'后面接一个八进制数,用于表示ASCII码等于该值的字符。 比如,查一下第5章中的ASCII码表,我们找到问号字符(?)的ASCII值是63,那么我们可以把它转换为八进值:77,然后用 '\77'来表示'?'。由于是八进制,所以本应写成 '\077',但因为C,C++规定不允许使用斜杠加10进制数来表示字符,所以这里的0可以不写。 事实上我们很少在实际编程中非要用转义符加八进制数来表示一个字符,所以,6.2.4小节的内容,大家仅仅了解就行。
6.2.5 十六进制数转换成十进制数2进制,用两个阿拉伯数字:0、1; 8进制,用八个阿拉伯数字:0、1、2、3、4、5、6、7; 10进制,用十个阿拉伯数字:0到9; 16进制,用十六个阿拉伯数字……等等,阿拉伯人或说是印度人,只发明了10个数字啊?
16进制就是逢16进1,但我们只有0~9这十个数字,所以我们用A,B,C,D,E,F这五个字母来分别表示10,11,12,13,14,15。字母不区分大小写。 十六进制数的第0位的权值为16的0次方,第1位的权值为16的1次方,第2位的权值为16的2次方…… 所以,在第N(N从0开始)位上,如果是是数 X (X 大于等于0,并且X小于等于 15,即:F)表示的大小为 X * 16的N次方。 假设有一个十六进数 2AF5, 那么如何换算成10进制呢?
用竖式计算:
2AF5换算成10进制:
第0位: 5 * 160 = 5 第1位: F * 161 = 240 第2位: A * 162 = 2560 第3位: 2 * 163 = 8192 + ------------------------------------- 10997 直接计算就是: 5 * 160 + F * 161 + A * 162 + 2 * 163 = 10997 (别忘了,在上面的计算中,A表示10,而F表示15)
现在可以看出,所有进制换算成10进制,关键在于各自的权值不同。 假设有人问你,十进数 1234 为什么是 一千二百三十四?你尽可以给他这么一个算式: 1234 = 1 * 103 + 2 * 102 + 3 * 101 + 4 * 100
6.2.6 十六进制数的表达方法如果不使用特殊的书写形式,16进制数也会和10进制相混。随便一个数:9876,就看不出它是16进制或10进制。 C,C++规定,16进制数必须以 0x开头。比如 0x1表示一个16进制数。而1则表示一个十进制。另外如:0xff,0xFF,0X102A,等等。其中的x也也不区分大小写。(注意:0x中的0是数字0,而不是字母O) 以下是一些用法示例:
int a = 0x100F; int b = 0x70 + a;
至此,我们学完了所有进制:10进制,8进制,16进制数的表达方式。最后一点很重要,C/C++中,10进制数有正负之分,比如12表示正12,而-12表示负12,;但8进制和16进制只能用达无符号的正整数,如果你在代码中里:-078,或者写:-0xF2,C,C++并不把它当成一个负数。
6.2.7 十六进制数在转义符中的使用
转义符也可以接一个16进制数来表示一个字符。如在6.2.4小节中说的 '?' 字符,可以有以下表达方式:
'?' //直接输入字符 '\77' //用八进制,此时可以省略开头的0 '\0x3F' //用十六进制
同样,这一小节只用于了解。除了空字符用八进制数 '\0' 表示以外,我们很少用后两种方法表示一个字符。
6.3 十进制数转换到二、八、十六进制数6.3.1 10进制数转换为2进制数
给你一个十进制,比如:6,如果将它转换成二进制数呢?
10进制数转换成二进制数,这是一个连续除2的过程: 把要转换的数,除以2,得到商和余数, 将商继续除以2,直到商为0。最后将所有余数倒序排列,得到数就是转换结果。
听起来有些糊涂?我们结合例子来说明。比如要转换6为二进制数。
“把要转换的数,除以2,得到商和余数”。 那么: 要转换的数是6, 6 ÷ 2,得到商是3,余数是0。 (不要告诉我你不会计算6÷3!)
“将商继续除以2,直到商为0……” 现在商是3,还不是0,所以继续除以2。 那就: 3 ÷ 2, 得到商是1,余数是1。
“将商继续除以2,直到商为0……” 现在商是1,还不是0,所以继续除以2。 那就: 1 ÷ 2, 得到商是0,余数是1 (拿笔纸算一下,1÷2是不是商0余1!)
“将商继续除以2,直到商为0……最后将所有余数倒序排列” 好极!现在商已经是0。 我们三次计算依次得到余数分别是:0、1、1,将所有余数倒序排列,那就是:110了!
6转换成二进制,结果是110。
把上面的一段改成用表格来表示,则为:
(在计算机中,÷用 / 来表示)
如果是在考试时,我们要画这样表还是有点费时间,所更常见的换算过程是使用下图的连除:
(图:1) 请大家对照图,表,及文字说明,并且自已拿笔计算一遍如何将6转换为二进制数。 说了半天,我们的转换结果对吗?二进制数110是6吗?你已经学会如何将二进制数转换成10进制数了,所以请现在就计算一下110换成10进制是否就是6。
6.3.2 10进制数转换为8、16进制数
非常开心,10进制数转换成8进制的方法,和转换为2进制的方法类似,惟一变化:除数由2变成8。
来看一个例子,如何将十进制数120转换成八进制数。
用表格表示:
120转换为8进制,结果为:170。
非常非常开心,10进制数转换成16进制的方法,和转换为2进制的方法类似,惟一变化:除数由2变成16。
同样是120,转换成16进制则为:
120转换为16进制,结果为:78。
请拿笔纸,采用(图:1)的形式,演算上面两个表的过程。
6.4 二、十六进制数互相转换
二进制和十六进制的互相转换比较重要。不过这二者的转换却不用计算,每个C,C++程序员都能做到看见二进制数,直接就能转换为十六进制数,反之亦然。 我们也一样,只要学完这一小节,就能做到。 首先我们来看一个二进制数:1111,它是多少呢? 你可能还要这样计算:1 * 20 + 1 * 21 + 1 * 22 + 1 * 23 = 1 * 1 + 1 * 2 + 1 * 4 + 1 * 8 = 15。 然而,由于1111才4位,所以我们必须直接记住它每一位的权值,并且是从高位往低位记,:8、4、2、1。即,最高位的权值为23 = 8,然后依次是 22 = 4,21=2, 20 = 1。
记住8421,对于任意一个4位的二进制数,我们都可以很快算出它对应的10进制值。
下面列出四位二进制数 xxxx 所有可能的值(中间略过部分)
仅4位的2进制数 快速计算方法 十进制值 十六进值 1111 = 8 + 4 + 2 + 1 = 15 F 1110 = 8 + 4 + 2 + 0 = 14 E 1101 = 8 + 4 + 0 + 1 = 13 D 1100 = 8 + 4 + 0 + 0 = 12 C 1011 = 8 + 4 + 0 + 1 = 11 B 1010 = 8 + 0 + 2 + 0 = 10 A 1001 = 8 + 0 + 0 + 1 = 10 9 .... 0001 = 0 + 0 + 0 + 1 = 1 1 0000 = 0 + 0 + 0 + 0 = 0 0
二进制数要转换为十六进制,就是以4位一段,分别转换为十六进制。 如(上行为二制数,下面为对应的十六进制):
1111 1101 , 1010 0101 , 1001 1011 F D , A 5 , 9 B
反过来,当我们看到 FD时,如何迅速将它转换为二进制数呢? 先转换F: 看到F,我们需知道它是15(可能你还不熟悉A~F这五个数),然后15如何用8421凑呢?应该是8 + 4 + 2 + 1,所以四位全为1 :1111。 接着转换 D: 看到D,知道它是13,13如何用8421凑呢?应该是:8 + 2 + 1,即:1011。 所以,FD转换为二进制数,为: 1111 1011
由于十六进制转换成二进制相当直接,所以,我们需要将一个十进制数转换成2进制数时,也可以先转换成16进制,然后再转换成2进制。 比如,十进制数 1234转换成二制数,如果要一直除以2,直接得到2进制数,需要计算较多次数。所以我们可以先除以16,得到16进制数:
结果16进制为: 0x4D2
然后我们可直接写出0x4D2的二进制形式: 0100 1011 0010。 其中对映关系为: 0100 -- 4 1011 -- D 0010 -- 2
同样,如果一个二进制数很长,我们需要将它转换成10进制数时,除了前面学过的方法是,我们还可以先将这个二进制转换成16进制,然后再转换为10进制。 下面举例一个int类型的二进制数: 01101101 11100101 10101111 00011011 我们按四位一组转换为16进制: 6D E5 AF 1B
6.5 原码、反码、补码
结束了各种进制的转换,我们来谈谈另一个话题:原码、反码、补码。
我们已经知道计算机中,所有数据最终都是使用二进制数表达。 我们也已经学会如何将一个10进制数如何转换为二进制数。 不过,我们仍然没有学习一个负数如何用二进制表达。
比如,假设有一 int 类型的数,值为5,那么,我们知道它在计算机中表示为: 00000000 00000000 00000000 00000101 5转换成二制是101,不过int类型的数占用4字节(32位),所以前面填了一堆0。 现在想知道,-5在计算机中如何表示?
在计算机中,负数以其正值的补码形式表达。 什么叫补码呢?这得从原码,反码说起。
原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。 比如 00000000 00000000 00000000 00000101 是 5的 原码。
反码:将二进制数按位取反,所得的新二进制数称为原二进制数的反码。 取反操作指:原为1,得0;原为0,得1。(1变0; 0变1) 比如:将00000000 00000000 00000000 00000101每一位取反,得11111111 11111111 11111111 11111010。 称:11111111 11111111 11111111 11111010 是 00000000 00000000 00000000 00000101 的反码。 反码是相互的,所以也可称: 11111111 11111111 11111111 11111010 和 00000000 00000000 00000000 00000101 互为反码。
补码:反码加1称为补码。 也就是说,要得到一个数的补码,先得到反码,然后将反码加上1,所得数称为补码。 比如:00000000 00000000 00000000 00000101 的反码是:11111111 11111111 11111111 11111010。 那么,补码为: 11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
所以,-5 在计算机中表达为:11111111 11111111 11111111 11111011。转换为十六进制:0xFFFFFFFB。
再举一例,我们来看整数-1在计算机中如何表示。 假设这也是一个int类型,那么:
1、先取1的原码:00000000 00000000 00000000 00000001 2、得反码: 11111111 11111111 11111111 11111110 3、得补码: 11111111 11111111 11111111 11111111
可见,-1在计算机里用二进制表达就是全1。16进制为:0xFFFFFF。
一切都是纸上说的……说-1在计算机里表达为0xFFFFFF,我能不能亲眼看一看呢?当然可以。利用C++ Builder的调试功能,我们可以看到每个变量的16进制值。
6.6 通过调试查看变量的值下面我们来动手完成一个小小的实验,通过调试,观察变量的值。 我们在代码中声明两个int 变量,并分别初始化为5和-5。然后我们通过CB提供的调试手段,可以查看到程序运行时,这两个变量的十进制值和十六进制值。 首先新建一个控制台工程。加入以下黑体部分(就一行):
//--------------------------------------------------------------------------- #pragma hdrstop //--------------------------------------------------------------------------- #pragma argsused int main(int argc, char* argv[]) { int aaaa = 5, bbbbb = -5; return 0; }
//--------------------------------------------------------------------------- 没有我们熟悉的的那一行: getchar(); 所以,如果全速运行这个程序,将只是DOS窗口一闪而过。不过今天我们将通过设置断点,来使用程序在我们需要的地儿停下来。 设置断点:最常用的调试方法之一,使用程序在运行时,暂停在某一代码位置,
在CB里,设置断点的方法是在某一行代码上按F5或在行首栏内单击鼠标。 如下图:
在上图中,我们在return 0;这一行上设置断点。断点所在行将被CB以红色显示。
接着,运行程序(F9),程序将在断点处停下来。
(请注意两张图的不同,前面的图是运行之前,后面这张是运行中,左边的箭头表示运行运行到哪一行)
当程序停在断点的时,我们可以观察当前代码片段内,可见的变量。观察变量的方法很多种,这里我们学习使用Debug Inspector (调试期检视),来全面观察一个变量。 以下是调出观察某一变量的 Debug Inspector 窗口的方法:
先确保代码窗口是活动窗口。(用鼠标点一下代码窗口) 按下Ctrl键,然后将鼠标挪到变量 aaaa 上面,你会发现代码中的aaaa变蓝,并且出现下划线,效果如网页中的超链接,而鼠标也变成了小手状:
点击鼠标,将出现变量aaaa的检视窗口:
(笔者使用的操作系统为WindowsXP,窗口的外观与Win9X有所不同) 从该窗口,我可以看到: aaaa :变量名 int :变量的数据类型 0012FF88:变量的内存地址,请参看5.2 变量与内存地址;地址总是使用十六进制表达 5 : 这是变量的值,即aaaa = 5; 0x00000005 :同样是变量的值,但采用16进制表示。因为是int类型,所以占用4字节。
首先先关闭前面的用于观察变量aaaa的Debug Inspector窗口。 现在,我们用同样的方法来观察变量bbbb,它的值为-5,负数在计算机中使用补码表示。
正如我们所想,-5的补码为:0xFFFFFFFB。
再按一次F9,程序将从断点继续运行,然后结束。 6.7 本章小结很难学的一章? 来看看我们主要学了什么:
1)我们学会了如何将二、八、十六进制数转换为十进制数。 三种转换方法是一样的,都是使用乘法。
2)我们学会了如何将十进制数转换为二、八、十六进制数。 方法也都一样,采用除法。
3)我们学会了如何快速的地互换二进制数和十六进制数。 要诀就在于对二进制数按四位一组地转换成十六进制数。 在学习十六进制数后,我们会在很多地方采用十六进制数来替代二进制数。
4)我们学习了原码、反码、补码。 把原码的0变1,1变0,就得到反码。要得到补码,则先得反码,然后加1。 以前我们只知道正整数在计算机里是如何表达,现在我们还知道负数在计算机里使用其绝对值的补码表达。 比如,-5在计算机中如何表达?回答是:5的补码。
5)最后我们在上机实验中,这会了如何设置断点,如何调出Debug Inspector窗口观察变量。 以后我们会学到更多的调试方法。 |
|
今天在做一个struts2的小程序时,遇到一个问题就是该网站不能被反问,分两步把这个问题人解决啦!!! 第一步: 去看tomcat的webapps,里边也部署上了。接着用昨天刚学的一招必杀,呵呵!去看C:\Program Files\Apache Software Foundation\Tomcat 5.0\logs下的日志,显示javax.xml.transform.TransformerFactoryConfigurationError Provider org.apache.xalan.processor.TransformerFactoryImpl not found 后来上网搜了一下:找到了一篇文章,说该问题是由于tomcat 里C:\Program Files\Apache Software Foundation\Tomcat 5.0\common\endorsed下的两个有关xml的jar文件和JDK里的xml解析器冲突造成的:原文如下: http://localhost:8080/testdwr/dwr 访问 我按上面介绍的第二种方法,删除了endorsed下面的两个jar文件,再重启tomcat,查看日志,果然没报上面的那个错误,但一运行,还是不能显示该项目。
第二步: 查看MyEclipse下console控制台,发现有 严重: Error filterstart错误 上网查了一下:原文如下: 最近使用上了Tomcat 5.5,只是这东西在你最需要的时候,往往令你很失望。 严重: Error filterStart
信息,当时就检查初始化Struts2的Filter以及初始化对应的Servlet-api.jar,没有发现什么问题。最后实在没有办法,直接使用Tomcat
6,其启动时候抛出的异常告诉我无法加载在struts.xml配置的Java 类。这时候,我才明白,原来问题所在。 最后教训,一旦在struts.xml配置的类,若丢失的话,会影响整个站点的运行。 建议大家可以直接把站点直接放在Tocmat 6下面,用于取代tomat 5.5。Tomcat 5.5的错误日志太让人寒心了~ ”其启动时候抛出的异常告诉我无法加载在struts.xml配置的Java 类“ 这名话是关键,说的很明确说是找不着java类,在我的程序里就是LoginAction这个类,怎么会找不到呢,上struts.xml文件里一看,天啊,应该是class="com.test.action.LoginAction",而我写的是"com.test.LoginAction",改过以后,运行,好使!!! |
标签:
it |
package cn;
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
public class Arithmetic {
static Key key;
public static void getKey(String strKey) {
try {
KeyGenerator _generator =
KeyGenerator.getInstance("DES");
_generator.init(new
SecureRandom(strKey.getBytes()));
key =
_generator.generateKey();
_generator = null;
} catch (Exception e) {
e.printStackTrace();
}
}
public static String getEncString(String strMing)
{
byte[] byteMi = null;
byte[] byteMing = null;
String strMi = "";
try {
return
byte2hex(getEncCode(strMing.getBytes()));
// byteMing =
strMing.getBytes("UTF8");
// byteMi =
this.getEncCode(byteMing);
// strMi = new String(
byteMi,"UTF8");
} catch (Exception e) {
e.printStackTrace();
} finally {
byteMing = null;
byteMi = null;
}
return strMi;
}
public static String getDesString(String strMi)
{
byte[] byteMing = null;
byte[] byteMi = null;
String strMing = "";
try {
return new
String(getDesCode(hex2byte(strMi.getBytes())));
// byteMing =
this.getDesCode(byteMi);
// strMing = new
String(byteMing,"UTF8");
} catch (Exception e) {
e.printStackTrace();
} finally {
byteMing = null;
byteMi = null;
}
return strMing;
}
private static byte[] getEncCode(byte[] byteS)
{
byte[] byteFina = null;
Cipher cipher;
try {
cipher =
Cipher.getInstance("DES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byteFina =
cipher.doFinal(byteS);
} catch (Exception e) {
e.printStackTrace();
} finally {
cipher = null;
}
return byteFina;
}
private static byte[] getDesCode(byte[] byteD)
{
Cipher cipher;
byte[] byteFina = null;
try {
cipher =
Cipher.getInstance("DES");
cipher.init(Cipher.DECRYPT_MODE, key);
byteFina =
cipher.doFinal(byteD);
} catch (Exception e) {
e.printStackTrace();
} finally {
cipher = null;
}
return byteFina;
}
public static String byte2hex(byte[] b) { //
一个字节的数,
// 转成16进制字符串
String hs = "";
String stmp = "";
for (int n = 0; n < b.length;
n++) {
// 整数转成十六进制表示
stmp =
(java.lang.Integer.toHexString(b[n] & 0XFF));
if (stmp.length() == 1)
hs = hs +
"0" + stmp;
else
hs = hs +
stmp;
}
return hs.toUpperCase(); // 转成大写
}
public static byte[] hex2byte(byte[] b)
{
if ((b.length % 2) != 0)
throw new
IllegalArgumentException("长度不是偶数");
byte[] b2 = new byte[b.length / 2];
for (int n = 0; n < b.length; n
+= 2) {
String item = new String(b, n,
2);
//
两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个进制字节
b2[n / 2] = (byte)
Integer.parseInt(item, 16);
}
return b2;
}
public static void main(String[] args) {
Arithmetic des = new Arithmetic();//
实例化一个对像
des.getKey("aadd");// 生成密匙
String strEnc = des.getEncString("樊振华");//
加密字符串,返回String的密文
System.out.println(strEnc);
String strDes = des.getDesString(strEnc);//
把String 类型的密文解密
System.out.println(strDes);
}
}
文章出处:http://www.diybl.com/course/3_program/java/javajs/2008924/145171.html
Hibernate的乐观锁与悲观锁
文章转自网上好像是玉米田的,忘记了
锁( locking )
业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算处理中,
悲观锁( Pessimistic Locking )
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
一个典型的倚赖数据库的悲观锁调用:
select * from account where name=”Erica” for update
这条 sql 语句锁定了 account
表中所有符合检索条件(name=”Erica”)的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。Hibernate
的悲观锁,也是基于数据库的锁机制实现。
下面的代码实现了对查询记录的加锁:
String hqlStr
="from TUser as user where user.name='Erica'";
Query query = session.createQuery(hqlStr);
query.setLockMode("user",LockMode.UPGRADE); // 加锁
List userList = query.list();//
执行查询,获取数据
query.setLockMode 对查询语句中,特定别名所对应的记录进行加锁(我们为TUser 类指定了一个别名 “user”
),这里也就是对返回的所有 user 记录进行加锁。
观察运行期 Hibernate 生成的 SQL 语句:
select tuser0_.id as id, tuser0_.name as name,
tuser0_.group_id
as group_id, tuser0_.user_type as user_type, tuser0_.sex as
sex
from t_user tuser0_ where (tuser0_.name='Erica' ) for
update
这里 Hibernate 通过使用数据库的 for update 子句实现了悲观锁机制。
Hibernate 的加锁模式有:
LockMode.NONE : 无锁机制。
LockMode.WRITE : Hibernate 在 Insert 和 Update
记录的时候会自动获取。
LockMode.READ : Hibernate 在读取记录的时候会自动获取。
以上这三种锁机制一般由 Hibernate 内部使用,如 Hibernate 为了保证 Update过程中对象不会被外界修改,会在
save 方法实现中自动为目标对象加上 WRITE 锁。
LockMode.UPGRADE :利用数据库的 for update 子句加锁。
LockMode. UPGRADE_NOWAIT : Oracle 的特定实现,利用 Oracle
的 for update nowait 子句实现加锁。
上面这两种锁机制是我们在应用层较为常用的,加锁一般通过以下方法实现:
Criteria.setLockMode
Query.setLockMode
Session.lock
注意,只有在查询开始之前(也就是 Hiberate 生成 SQL
之前)设定加锁,才会真正通过数据库的锁机制进行加锁处理,否则,数据已经通过不包含 for update 子句的 Select SQL
加载进来,所谓数据库加锁也就无从谈起。
乐观锁( Optimistic Locking )
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时如更改用户帐户余额),如果
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一个version 字段,当前值为 1
;而当前帐户余额字段(balance)为 $100 。
1 操作员 A 此时将其读出(version=1),并从其帐户余额中扣除 $50($100-$50)。
2 在操作员 A 操作的过程中,操作员 B 也读入此用户信息(version=1),并从其帐户余额中扣除 $20
($100-$20)。
3 操作员 A 完成了修改
4 操作员 B
完成了操作,也将版本号加一(version=2)试图向数据库提交数据(balance=$80),但此时比对数据库记录版本时发现,操作员
B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足“ 提交版本必须大于记录当前版本才能执行更新“
的乐观锁策略,因此,操作员 B 的提交被驳回。这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员 A
的操作结果的可能。
从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员 A 和操作员 B
操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
Hibernate 在其数据访问
Hibernate 中可以通过 class 描述符的 optimistic-lock 属性结合 version描述符指定。
现在,我们为之前示例中的 TUser 加上乐观锁机制。
1 . 首先为 TUser 的 class 描述符添加 optimistic-lock 属性:
<hibernate-mapping>
<class name="org.hibernate.sample.TUser"
table="t_user" dynamic-update="true"
dynamic-insert="true"
optimistic-lock="version">
……
</class>
</hibernate-mapping>
optimistic-lock
属性有如下可选取值:
none:无乐观锁
version:通过版本机制实现乐观锁
dirty:通过检查发生变动过的属性实现乐观锁
all:通过检查所有属性实现乐观锁
其中通过 version 实现的乐观锁机制是 Hibernate 官方推荐的乐观锁实现,同时也是 Hibernate
中,
2 . 添加一个 Version 属性描述符
<hibernate-mapping>
<class name="org.hibernate.sample.TUser"
table="t_user" dynamic-update="true" dynamic-insert="true"
optimistic-lock="version">
<id name="id" column="id"
type="java.lang.Integer">
<generator class="native">
</generator>
</id>
<version column="version" name="version"
type="java.lang.Integer"/>
……
</class>
</hibernate-mapping>
注意
version 节点必须出现在 ID 节点之后。这里我们声明了一个 version 属性,用于存放用户的版本信息,保存在 TUser
表的version 字段中。
此时如果我们尝试编写一段代码,更新 TUser 表中记录数据,如:
Criteria criteria =
session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
List userList = criteria.list();
TUser user =(TUser)userList.get(0);
Transaction tx = session.beginTransaction();
user.setUserType(1); // 更新 UserType 字段
tx.commit();
每次对 TUser 进行更新的时候,我们可以发现,数据库中的 version 都在递增。而如果我们尝试在 tx.commit
之前,启动另外一个 Session ,对名为 Erica 的用户进行操作,以模拟并发更新时的情形:
Session session= getSession();
Criteria criteria = session.createCriteria(TUser.class);
criteria.add(Expression.eq("name","Erica"));
Session session2 = getSession();
Criteria criteria2 = session2.createCriteria(TUser.class);
criteria2.add(Expression.eq("name","Erica"));
List userList = criteria.list();
List userList2 = criteria2.list();TUser user
=(TUser)userList.get(0);
TUser user2 =(TUser)userList2.get(0);
Transaction tx = session.beginTransaction();
Transaction tx2 = session2.beginTransaction();
user2.setUserType(99);
tx2.commit();
user.setUserType(1);
tx.commit();
执行以上代码,代码将在 tx.commit() 处抛出
StaleObjectStateException
|
不管把鼠标放在哪里--桌面上、图标上都是这种情况:
解决:我的电脑—右键—属性—高级—性能—设置—视觉效果—淡入淡出或滑动菜单到视图,将其前面的复选框的钩去掉。 |
标签:
it |
分类: Spring Framework 开发参 |
<bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransactionManager">
<property name="sessionFactory">
<ref local="sessionFactory" />
</property>
</bean>
配置一个事务代理模板,其他需要事务管理的bean可以继承它
<bean id="baseTransactionProxy" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED, readOnly</prop>
</props>
</property>
</bean>
所有save开头的方法都纳入事务管理,其他方法都作为只读事务
如果有个userManager需要事务管理的话,可以这样配置
<bean id="userManager" parent="baseTransactionProxy">
<property name="target">
<bean class="some.package.UserManagerImpl">
<property name="userDAO">
<ref bean="userDAO"/>
</property>
</bean>
</property>
</bean>
target属性中用了内部匿名bean的语法
先看HibernateTransactionManager。按照api里的解释:HibernateTransactionManager为每个SessionFactory产生的Session绑定到一个线程上。SessionFactoryUtils和HibernateTemplate直接使用当前线程里的Session(ThreadLocal模式?),自动参与到事务中。
这里有疑问,session如何绑定到线程,具体何时打开何时关闭
HibernateTemplate.execute()操作Session的3个步骤: 1.get Session
如果当前线程在事务中,获取为当前线程绑定的Session。否则创建新Session,并绑定到当前线程。(放入SessionHolder,并把他以当前线程的SessionFactory实例为key放入TransactionSynchronizationManager的ThreadLocal变量中)
2.使用Session 3.release Session
如果在事务中,则不关闭Session
ibatis基础代码包括:
1.
ibatis实例配置
一个典型的配置文件如下(具体配置项目的含义见后):
<? xml
version="1.0"
encoding="UTF-8"
?>
<! DOCTYPE sqlMapConfig
PUBLIC "-//iBATIS.com//DTD SQL Map Config
2.0/
"http://www.ibatis.com/dtd/sql-map-config-2.dt
<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"
value ="user" />
< property
name
="JDBC.Password"
value ="mypass" />
< property
name
="Pool.MaximumActiveConnections"
value ="10" />
< property
name
="Pool.MaximumIdleConnections"
value ="5" />
< property
name
="Pool.MaximumCheckoutTime"
value ="120000" />
< property
name
="Pool.TimeToWait"
value ="500" />
< property
name
="Pool.PingQuery"
value ="select 1 from ACCOUNT"
/>
< property
name
="Pool.PingEnabled"
value ="false" />
< property
name
="Pool.PingConnectionsOlderThan"
value ="1" />
< property
name
="Pool.PingConnectionsNotUsedFor"
value ="1" />
</ dataSource >
</ transactionManager
>
< sqlMap
resource ="com/ibatis/sample/User.xml"
/>
</ sqlMapConfig
>
⑴ Settings 节点
cacheModelsEnabled
是否启用SqlMapClient上的缓存机制。 建议设为"true"
enhancementEnabled
是否针对POJO启用字节码增强机getter/setter的调用效能,避免Reflect所带来的性能开销。同时,这也为Lazy
Loading带来提升。 建议设为"true"
errorTracingEnabled
是否启用错误日志,在开发期间建议设为"true" 以方便调试
lazyLoadingEnabled
是否启用延迟加载机制,建议设为"true"
maxRequests
最大并发请求数(Statement并发数)
maxTransactions
最大并发事务数
maxSessions
最大Session数。即当前最大允许的并发SqlMapClient数。
useStatementNamespaces
是否使用Statement命名空间。
这里的命名空间指的是映射文件中,sqlMap节的namespace属性,如在上例中针对t_use
表的映射文件sqlMap节点: <sqlMap namespace="User">
这里,指定了此sqlMap节点下定义的操作均属于"User"命名空间。
在useStatementNamespaces="true"的情况下,Statement调用需追加命名空间,如:sqlMap.update("User.updateUser",user);
否则直接通过Statement名称调用即可,如:
sqlMap.update("updateUser",user);
但请注意此时需要保证所有映射文件中,Statement定义无重名。
transactionManager节点
transactionManager节点定义了ibatis的事务管理器,目前提供了以下几种选择:
JDBC
通过传统JDBC
Connection.commit/rollback实现事务支持。
JTA
使用容器提供的JTA服务实现全局事务管理。
EXTERNAL
外部事务管理,如在EJB中使用ibatis,通过EJB的部署配置即可实现自
动的事务管理机制。此时ibatis将把所有事务委托给外部容器进行管理。
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.JndiDataSourceFactory。
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.MaximumActiveConnections
数据库连接池可维持的最大容量。
Pool.MaximumIdleConnections
数据库连接池中允许的挂起(idle)连接数。
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
>
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时,默认为auto_commit模式
int
rows
=
sqlMap.insert (“insertProduct”,
product);
例6:查询指定范围内的数据
sqlMap.startTransaction();
List list
=
sqlMap.queryForList
(“getProductList”,
null , 0
, 40 );
sqlMap.commitTransaction();
例7: 结合RowHandler进行查询(select)
public
class
MyRowHandler
implements
RowHandler
{
public
void
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();
// 例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 ”);
下载压缩文件包:iBATIS_DBL-2.1.7.597.zip需用的:ibatis-common-2.jar
ibatis-dao-2.jar
ibatis-sqlmap-2.jar
1.2. SqlMapConfig.xml
<xml version="1.0" encoding="UTF-8"
>
<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="org.gjt.mm.mysql.Driver"/>
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://localhost/ibatissample"/>
<property name="JDBC.Username" value="root"/>
<property name="JDBC.Password" value="chenyan"/>
<property name="Pool.MaximumActiveConnections"
value="10"/>
<property name="Pool.MaximumIdleConnections"
value="5"/>
<property name="Pool.MaximumCheckoutTime"
value="120000"/>
<property name="Pool.TimeToWait" value="500"/>
<property name="Pool.PingQuery" value="select 1 from
t_user"/>
<property name="Pool.PingEnabled" value="false"/>
<property name="Pool.PingConnectionsOlderThan"
value="1"/>
<property name="Pool.PingConnectionsNotUsedFor"
value="1"/>
</dataSource>
</transactionManager>
<sqlMap resource="hs/com/ibatis/User.xml"/>
</sqlMapConfig>
注意点:1. maxRequests >= maxSessions > maxTransactions
2. useStatementNamespaces节点:对应于映射文件中的namespace属性,当此节点为true的时候,Statement调用需要追加命名空间。当此节点为false的时候,则需保证所有的映射文件中,Statement定义无重名。
1.3. POJO文件
package hs.com.ibatis.POJO;
import java.io.Serializable;
public class User implements Serializable {
private Integer id;
private String name;
private Integer sex;
/** 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;}
}
1.4. 映射文件
<xml version="1.0"
encoding="UTF-8">
<sqlMap namespace="User">
<typeAlias alias="user"
type="hs.com.ibatis.POJO.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>
二.iBatis基础组件与操作
1. XmlSqlMapClientBuilder
2.0版本后引入,以替代XmlSqlMapBuilder。作用是根据配置文件创建SqlMapClient实例。2.7版本中好像找不到了!
2. SqlMapClient
提供数据操作的基础平台,线程安全
String resource
="hs/com/ibatis/SqlMapConfig.xml";
Reader reader = Resources.getResourceAsReader(resource);
//XmlSqlMapClientBuilder xmlBuilder = new
XmlSqlMapClientBuilder();
SqlMapClient sqlMap =
SqlMapClientBuilder.buildSqlMapClient(reader);
SqlMapClient基本操作:
数据写入(Insert,Update,Delete)
数据查询
在指定对象中存放查询结果
执行批量查询
查询指定范围内的数据
结合RowHandler查询
分页查询
基于Map的批量查询
数据写入(Insert):
sqlMap.startTransaction();
Product product = new Product();
product.setId (1);
product.setDescription (“Arom_chen”);
int rows = sqlMap.insert (“insertProduct”, product);
sqlMap.commitTransaction();
数据查询:
sqlMap.startTransaction();
Integer key = new Integer (1);
Product product = (Product)sqlMap.queryForObject
(“getProduct”, key);
sqlMap.commitTransaction();
其他的操作也是基本上根据上面两种操作变化而来!
三.iBatis高级特性
3.1. 数据关联
3.2. 延迟加载
3.3. 动态映射
<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>
注意:在第一个条件成立的时候不会加AND前缀。
3.4. 事务管理
附(mysql表):
CREATE TABLE t_address (id int(10) not
null,
country varchar(100) not null,
city varchar(100),
town varchar(100),
number int(10),
PRIMARY KEY(id))
TYPE=myisam;
CREATE TABLE t_user (id int(10) not null,
name varchar(100) not null,
sex int(10),
PRIMARY KEY(id))
TYPE=myisam;