项目研发部J2EE开发指南


1 / 181 博微 JAVA EE 开发指南 江西博微新技术有限公司 2012 年 9 月 2 / 181 第一篇:编码规范 3 / 181 1 适用范围 公司所有从事J2EE开发的人员; 本规范是为了使J2EE开发过程有章可循,保证软件开发的质量,加强技术的管理,开发 团队更专业及更有效率而制定。 2 命名规范 定义这个规范的目的是增强代码的可读性,使不同的开发人员能快速掌握系统代码结 构。 2.1 package 的命名 命名规则:package 的名字都是由小写单词组成,各个单词间以英文点(.)分隔,一般以 com+公司英文名称+项目英文简称开头+子模块简称。 示例: 北京投资分析管理系统: com.booway.inverst; 规划:  com.booway.inverst 为产品包前缀;(默认为 com.booway.xxx)  plan/project 为子系统级包名;  action - 控制层;biz – 逻辑层;dao - 数据库层; 2.2 class 命名 命名规则:类名是一个名词,采用大小写混合的方式,每个单词的首字母大写。尽量使 你的类名简洁而富于描述。使用完整单词,避免缩写词(除非该缩写词被广泛使用,如 URL, HTML 等)。 4 / 181 示例:ProjectList 2.3 class 方法命名 命名规则: 1) 方法名的首字母小写,随后的单词的首字母都应大写。 2) 方法名应能基本表达该方法的含义,应尽量避免缩写(约定俗成的缩写除外)。 3) 对于包含缩写词的方法名称,以动词加名字组成为原则,比如 getProjectList,不需 要对缩写词小写,如 toURI,而不是 toUri。 4) 存取属性的方法采用 setter 和 getter 方法,动作方法采用动词和动宾结构。 格式: 动词 + 名词 is + 布尔属性名() set + 属性名() 动词() 动词 + 宾语() 示例: public String getType(); public boolean isFinished(); public void setStatus(boolean); public void show(); public void addKeyListener(Listener); 2.4 class 类属性变量命名 命名规则:变量的名字必须用一个小写字母开头,后面的单词用大写字母开头。对应的 VO 类属性,应该跟数据库字段名保持一致,命名规则:去掉字段名中的表名前缀及字段中 的下划线,下划线后的第一个字母大写。 示例:数据库字段 project_id,属性名 projectId; 注意:为了保证平台兼容性 Id 不允许用“ID”命名 2.5 static final 变量命名 命名规则:static final 变量的名字应该都大写,单词间用下划线隔开,并且指出完整含 义。 示例: public final static int MAX_VALUE = 1000; 5 / 181 public final static String DEFAULT_START_DATE = "2001-12-08"; 2.6 数组命名 命名规则:将[]跟在定义类型后面。 示例: byte[] buffer; 不允许: byte buffer[]; 说明:使用byte buffer[] 格式使程序的可读性较差 2.7 方法参数命名 命名规则:使用有意义的参数命名,尽量使用与赋值的字段一样的名字: 示例: public void setCounter(int size) { this.size = size; } 2.8 方法内变量及返回值的命名规则 命名规则: 1) 变量名称应该短而有意义,可以使用类名为对象进行命名; 2) 返回值不论什么类型定为 result,get 方法的返回值除外 3) 临时变量可以使用 i,j,k 等简单命名。 示例: 1) Object object = null; Service service = null; 2) public int service() { int result = 0; …… return result; } 6 / 181 2.9 类,方法,变量命名约定: 常用组件类的命名以组件名加上组件类型名结尾。 示例:  Application 类型的,命名以App 结尾——MainApp  Frame 类型的,命名以Frame 结尾——TopoFrame  Panel 类型的,建议命名以Panel 结尾——CreateCircuitPanel  Bean 类型的,建议命名以Bean 结尾——DataAccessBean  EJB 类型的,建议命名以EJB 结尾——DBProxyEJB  Applet 类型的,建议命名以Applet 结尾——PictureShowApplet 如果函数名超过15 个字母,可采用以去掉元音字母的方法或者以行业内约定俗 成的缩写方式缩写函数名。 示例: getCustomerInformation() 改为 getCustomerInfo() 准确地确定成员函数的存取控制符号,不是必须使用 public 属性的,请使用 protected,不是必须使用 protected, 请使用 private。 示例: protected void setUserName() 作用范围package private void calculateRate() 作用范围inner class 含有集合意义的属性命名,尽量包含其复数的意义。 示例: customers, orderItems 2.10 JSP/HTML 命名: 命名规则:文件名全部小写,单词间用下划线隔开; 数据/内容显示页名词,多个单词用下划线分隔,要求能说明显示内容的信息,为避免 冲突,可加上 “_list”。 示例:new_message.html 或 my_file_list.jsp 操作处理页 命名格式:名词_下划线_动词, 示例:file_delete.jsp。 html头一般需要遵循以下格式: 7 / 181 some title 注意:必须指定一个有意义的,严禁出现“Untitled”或“未命名”之类的<title>。 页面中JS代码除了特殊情况,必须写在<head></head>中 所有html标签使用小写,标签注意关闭。 示例:<input type="submit" name="submit" value="确定"/> 2.11 JavaScript 脚本命名 JavaScript 脚本方法和变量命名同 JAVA 命名规范。 3 java 源文件样式 3.1 package/imports 规则: 1) package 行要在 import 行之前。 2) import 具体到每一个使用到的类,不允许出现 import java.io.*,而应该是 import java.io.File。 (eclipse 支持自动导包 ctrl+shift+o) 3) import 顺序:  jdk标准包  java扩展包(例如servlet,javamail,jce等)  使用的外部库的包(例如struts2 servlet) 8 / 181  使用的项目的公共包  使用的模块的其他包 建议:每一类import后面加一个换行。 示例: //jdk标准包 import java.io.File; //java扩展包 import javax.servlet.http.HttpServletResponse; //使用的外部库的包 import org.apache.struts2.ServletActionContext; //使用的项目的公共包 import com.booway.invest.util.ActionUtils; //使用的模块的其他包 import com.booway.invest.bussiness.project.ProjectService; 4 类定义 规则:把 extends 和 implements 定义在同一行,如果超长的话,把 extends、implements 分写在不同行上。先定义类的继承 extends,然后定义类的实现 implements。 示例 public class CounterSet extends Observable implements Cloneable 建议:类属性和类方法不要交叉放置,不同存取范围的属性或者方法也尽量不 要交叉放置。 格式: 类定义 { 类的公有属性定义 类的保护属性定义 类的私有属性定义 类的公有方法定义 类的保护方法定义 类的私有方法定义 } 9 / 181 4.1 构造函数 规则:采用递增的方式书写(参数多的写在后面)。访问类型 ("public", "private" 等) 和 任何 "static", "final" 或 "synchronized" 应该在一行中,并且方法和参数另写一行,这样可 以使方法和参数更易读。 建议:每一个类都添加一个默认的空构造函数。 示例: public CounterSet(int size) { this.size = size; } 4.2 类方法 规则: 每个方法的入参数量建议不超过 5 个,如果超过 5 个,请为这个入参参数集合构建一个 简单的 javabean 作为该方法的入参参数。 每个方法做业务操作之前,检查该方法的入参参数是否合理,如果不合理,请抛出 NPE (NullPointerException)或者 IllegalArguemntException 或者 IllegalStateException 等 java 的 runtime exception。 一个方法的代码长度不能超过 200 行,如果超过 200 行,请分开到多个方法。 示例: /** * Set the packet counters * (such as when restoring from a database) */ protected final void setArray(int[] r1, int[] r2, int[] r3, int[] r4) throws IllegalArgumentException { // Ensure the arrays are of equal size if (r1.length != r2.length || r1.length != r3.length || r1.length != r4.length) throw new IllegalArgumentException("Arrays must be of the same size"); System.arraycopy(r1, 0, r3, 0, r1.length); System.arraycopy(r2, 0, r4, 0, r1.length); } 10 / 181 4.3 toString 方法 规则:尽量在类中定义 toString 方法。 说明:父类如果实现了比较合理的toString() ,子类可以继承不必再重写。 public String toString(){ String result = "CounterSet:"; for (int i = 0; i < data.length(); i++) { result += data.bytes.toString(); result += data.packets.toString(); } return result; } } 4.4 main 方法 如果类包含 main(String[]) 方法,应该放在类的底部。 4.5 建议 源程序中关系较为紧密的代码应尽可能相邻 4.6 语句 4.7 简单语句 规则:每行至多包含一条语句: 示例 argv++; // 正确 argc--; // 正确 argv++; argc--; // 错误 4.8 复合语句 规则: 1) 复合语句是包含在大括号中的语句序列,形如"{ 语句 }"。 2) 被括中的语句应该较之复合语句缩进一个层次。 11 / 181 3) 左大括号"{"应位于复合语句起始行的行尾;右大括号"}"应另起一行并与复合语句 首行对齐。 示例: public Date stringToDate(String input){ if (input == null || input.equals("")) { return null; } Try { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date date1 = format.parse(input); format = null; return date1; } catch(Exception e) { (new SysException(e)).log(); } return null; } 4.9 返回语句 规则:一个带返回值的 return 语句不使用括号"()",除非它们以某种方式使返回值更为 显见。 示例: return; // 正确,返回类型为 void return MyDisk.size(); // 正确 return (size > 0 ? size :defaultSize); // 错误 4.10 if,if-else,if else-if else 语句 if-else 语句应该具有如下格式: if (condition) { statements; } if (condition) { statements; } else { statements; } 12 / 181 if (condition) { statements; } else if (condition) { statements; } else { statements; } 注意:if 语句总是用"{"和"}"括起来,避免使用如下容易引起错误的格式: if (condition) statement; // 尽量避免 4.11 while 语句 一个 while 语句应该具有如下格式: do { statements; } while (condition); 4.12 switch 语句 一个 switch 语句应该具有如下格式: switch (condition) { case ABC: statements; /* falls through */ case DEF: statements; break; case XYZ: statements; break; default: statements; break; } 13 / 181 case 顺着往下执行时(因为没有 break 语句),通常应在 break 语句的位置添加注释。 上面的示例代码中就包含注释/* falls through */。 4.13 try-catch 语句 一个 try-catch 语句应该具有如下格式: try { statements; } catch (ExceptionClass e) { statements; } 一个 try-catch 语句后面也可能跟着一个 finally 语句,不论 try 代码块是否顺利执行 完,它都会被执行。 try { statements; } catch (ExceptionClass e) { statements; } finally { statements; } 注意:数据库操作、IO操作等需要使用结束close()的对象必须在try-catch-finally 的finally中close()。 异常捕获后,如果不对该异常进行处理,则应该纪录日志或者 ex.printStackTrace() 。 说明:若有特殊原因必须用注释加以说明。 自己抛出的异常必须要填写详细的描述信息。 示例: throw new IOException("Writing data error! Data: " + data.toString()); 5 注释 良好的注释有助于增加程序的可读性。 14 / 181 Java 程序有两类注释:实现注释(implementation comments)和文档注释(document comments)。实现注释是那些在 C++中见过的,使用/*...*/和//界定的注释。文档注释(被称为 "docment comments")是 Java 独有的,并由/**...*/界定。文档注释可以通过 javadoc 工具转换 成 HTML 文件。 实现注释用以注释代码或者实现细节。文档注释从实现自由(implementation-free)的角度 描述代码的规范。它可以被那些手头没有源码的开发人员读懂。 注释应被用来给出代码的总括,并提供代码自身没有提供的附加信息。注释应该仅包含 与阅读和理解程序有关的信息。例如,相应的包如何被建立或位于哪个目录下之类的信息不 应包括在注释中。 在注释里,对设计决策中重要的或者不是显而易见的地方进行说明是可以的,但应避免 提供代码中己清晰表达出来的重复信息。多余的的注释很容易过时,通常应避免那些代码更 新就可能过时的注释。 注意:频繁的注释有时反映出代码的低质量。当你觉得被迫要加注释的时候,考虑一下 重写代码使其更清晰。 注释不应写在用星号或其他字符画出来的大框里。注释不应包括诸如制表符和回退符之 类的特殊字符。 5.1 注释的格式 JAVA 程序可以有 4 种注释的格式,分别是:块(block)注释、单行(single-line)注释、尾端 (trailing)注释和行末(end-of-line)注释。 5.2 块注释 块注释通常用于提供对文件,方法,数据结构和算法的描述。块注释被置于每个文件的 开始处以及每个方法之前。它们也可以被用于其他地方,比如方法内部。在功能和方法内部 的块注释应该和它们所描述的代码具有一样的缩进格式。 块注释之首应该有一个空行,用于把块注释和代码分割开来。 示例: /* * Here is a block comment. */ 5.3 单行注释 单行注释可以显示在一行内,并与其后的代码具有一样的缩进层级。如果一个注释不能 在一行内写完,就该采用块注释。单行注释之前应该有一个空行。以下是一个 JAVA 代码中 单行注释的例子: if (condition) { /* Handle the condition. */ ... 15 / 181 } 5.4 行末注释 注释界定符"//",可以注释掉整行或者一行中的一部分。它一般不用于连续多行的注释 文本;然而,它可以用来注释掉连续多行的代码段。以下是所有三种风格的例子: if (foo > 1) { // Do a double-flip. ... } else { return false; // Explain why here. } //if (bar > 1) { // // // Do a triple-flip. // ... //} //else { // return false; //} 5.5 尾端注释 极短的注释可以与它们所要描述的代码同一行,但是要有足有空白来分开代码和注释。 若有多个短注释出现在大段代码中,它们应该具有相同的缩进。 示例: if (foo > 1) { statement; /* comment */ } else { statement; /* comment */ } 16 / 181 5.6 行末注释 5.7 类和接口注释 类和接口的注释,用来解释类和接口的功能。 在注释里面添加@author 以及@version 两个说明。 @author 按以下格式填写: @author <a href=mailto:your mail address>your name</a> 类注释举例如下: /** * A class representing a set of packet and byte counters * It is observable to allow it to be watched, but only * reports changes when the current set is complete * * @author <a href=mailto:address@example.com>author</a> * @version 1.0 */ 5.8 方法注释 为了让别人能够很快看懂该方法的作用、传入参数、返回值,方法注释语包括四部分。 1) 方法的描述:该方法有什么作用 2) 参数介绍 3) 方法返回值 4) 抛出异常 示例: /** * 把指定的 SessionBean 部署到 Mock 的容器环境中。 * * @param jndi JNDI 名称 * @param home 主接口 * @param remote 远程接口 * @param bean 实现类 * @return void 17 / 181 * @throws Exception */ public void deploySessionBean (String jndi, Class home, Class remote, Class bean) throws Exception{ SessionBeanDescriptor descriptor = new SessionBeanDescriptor(jndi, home, remote, bean); mockContainer.deploy(descriptor); } 5.9 成员变量 public 的成员变量必须有文档(javadoc)注释,protected、private 和 package 类型的 成员变量如果名字含义明确的话,可以没有注释。 /** * Packet counters */ protected int[] packets; 5.10 特殊注释 在注释中使用// TODO 来标识某些未实现但可以工作的内容。用// FIXME 来标识某些假 的和错误的内容。 5.11 JSP 注释 jsp/html页面顶部必须存在一个基本描述注释,包含功能描述、参数列表和历史修改信 息。 示例: <%-- /** * purpose: 下载文件提示 * author:作者 * parameters: * history : * 2002/04/05 作者 修改内容 * 2002/06/07 dingw 增加强制下在功能 18 / 181 */ --%> jsp头部一般需要遵循以下格式: <%@ page contentType="text/html;charset=gb2312" %> <%@ page import="java.io.*" %> // jdk 标准包 <%@ page import="javax.mail.*" %> // java 扩展包 <%@ page import="org.apache.xml.*" %> //使用的外部库的包 <%@ page import="com.sunrise..*" %> //使用的项目的公共包 <%@ page import=" com.sunrise.applications.*" %> //使用的模块的 其他包 <%@ include file="some.jsp" %> //include 其他的 jsp <% response.setHeader("Pragma","No-cache"); response.setHeader("Cache-Control","no-cache"); response.setHeader("Expires","0"); %> //一般 jsp 都需要防止缓存 5.12 XML 注释 XML 文件需要加上必要的注释用 <!-- 注释内容 --> Struts 的 Action ,Spring 配置文件的 BEAN 和 SQLMAP 的操作结点请加上必要的注释: 示例: <!-- *********************** 资 金 计 划 申 报 ********************************* --> <package name="bankrollplandeclare" namespace="/bankrollplandeclare" extends="struts-default"> <!-- 资金计划申报 --> <action name="bankrollplandeclare_*" class="bankrollPlanDeclareAction" method="{1}"> <result name="declareFill" type="dispatcher"> <param name="location">/bankrollplan/bankroll_declare_fill.jsp</param> </result> </action> </package> 19 / 181 6 排版 6.1 缩进 缩进应该是每行 4 个空格。建议不要在源文件中保存 Tab 字符。在使用不同的源代码管 理工具时 Tab 字符将因为用户设置的不同而扩展为不同的宽度。 如果你使用 UltraEdit 作为你的 JAVA 源代码编辑器的话,你可以通过如下操作来禁止 保存 Tab 字符, 方法是通过 UltraEdit 中先设定 Tab 使用的长度室 4 个空格,然后用 Format|Tabs to Spaces 菜单将 Tab 转换为空格。 6.2 页宽 页宽应该设置为 80 个字符。源代码一般不会超过这个宽度, 并导致无法完整显示, 但这 一设置也可以灵活调整。在任何情况下, 超长的语句应该在一个逗号或者一个操作符后折 行。一条语句折行后,应该比原来的语句再缩进 4 个字符。 6.3 换行 当一个表达式无法容纳在一行内时,可以依据如下一般规则断开之: 1) 在一个逗号后面断开 2) 在一个操作符前面断开 3) 宁可选择较高级别(higher-level)的断开,而非较低级别(lower-level)的断开 4) 新的一行应该与上一行同一级别表达式的开头处对齐 以下是两个断开算术表达式的例子。前者更好,因为断开处位于括号表达式的外边,这 是个较高级别的断开。 longName1 = longName2 * (longName3 + longName4 - longName5) + 4 * longname6; //PREFFER longName1 = longName2 * (longName3 + longName4 - longName5) + 4 * longname6; // 反例 以下是两个缩进方法声明的例子。前者是常规情形。后者若使用常规的缩进方式将会使 第二行和第三行移得很靠右,所以代之以缩进 8 个空格。 //CONVENTIONAL INDENTATION someMethod(int anArg, Object anotherArg, String yetAnotherArg, Object andStillAnother) { ... } 20 / 181 //INDENT 8 SPACES TO AVOID VERY DEEP INDENTS private static synchronized horkingLongMethodName(int anArg, Object anotherArg, String yetAnotherArg, Object andStillAnother) { ... } if 语句的换行通常使用 8 个空格的规则,因为常规缩进(4 个空格)会使语句体看起来比 较费劲。比如: //DON'T USE THIS INDENTATION if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { //BAD WRAPS doSomethingAboutIt(); //MAKE THIS LINE EASY TO MISS } //USE THIS INDENTATION INSTEAD if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } //OR USE THIS if ((condition1 && condition2) || (condition3 && condition4) ||!(condition5 && condition6)) { doSomethingAboutIt(); } 这里有三种可行的方法用于处理三元运算表达式: alpha = (aLongBooleanExpression) ? beta : gamma; alpha = (aLongBooleanExpression) ? beta : gamma; alpha = (aLongBooleanExpression) ? beta 21 / 181 : gamma; 6.4 {}对 {} 中的语句应该单独作为一行。例如, 下面的第 1 行是错误的, 第 2 行是正确的 if (i > 0) { i ++ }; // 错误, { 和 } 在同一行 if (I > 0) { i ++ }; // 正确, { 单独作为一行 }语句永远单独作为一行。如果}语句应该缩进到与其相对应的{那一行相对齐的位置。 6.5 括号 左括号和后一个字符之间不应该出现空格, 同样, 右括号和前一个字符之间也不应该出 现空格。下面的例子说明括号和空格的错误及正确使用: callProc( AParameter ); // 错误 callProc(aParameter); // 正确 不要在语句中使用无意义的括号。括号只应该为达到某种目的而出现在源代码中。下面 的例子说明错误和正确的用法: if ((i) = 42) { // 错误 - 括号毫无意义 if ((i == 42) || (j == 42)) // 正确 - 的确需要括号 6.6 空格 下列情况应该使用空格: 1) 一个紧跟着括号的关键字应该被空格分开,例如: while (true) { ... } 注意:空格不应该置于方法名与其左括号之间。这将有助于区分关键字和方法调用。 2) 空白应该位于参数列表中逗号的后面 3) 所有的二元运算符,除了".",应该使用空格将之与操作数分开。一元操作符和操 作数之间不因该加空格,比如:负号("-")、自增("++")和自减("--")。例如: a += c + d; 22 / 181 a = (a + b) / (c * d); while (d++ = s++) { n++; } printSize("size is " + foo + "\n"); 4) for 语句中的表达式应该被空格分开,例如: for (expr1; expr2; expr3); 5) 强制转型后应该跟一个空格,例如: myMethod((byte) aNum, (Object) x); myMethod((int) (cp + 5), ((int) (i + 3)) + 1); 6.7 空行 空行将逻辑相关的代码段分隔开,以提高可读性。 下列情况应该总是使用两个空行: 1) 一个源文件的两个片段(section)之间 2) 类声明和接口声明之间 下列情况应该总是使用一个空行: 1) 两个方法之间 2) 方法内的局部变量和方法的第一条语句之间 3) 块注释或单行注释 4) 一个方法内的两个逻辑段之间,用以提高可读性 7 程序编写注意项 7.1 exit() exit 除了在 main 中可以被调用外,其他的地方不应该调用。因为这样做不给任何代码 机会来截获退出。一个类似后台服务的程序不应该因为某一个库模块决定了要退出就退出。 7.2 异常  声明的错误应该抛出一个 RuntimeException 或者派生的异常。  顶层的 main()函数应该截获所有的异常,并且打印(或者记录在日志中)在屏幕上。  在程序中使用异常处理还是使用错误返回码处理,根据是否有利于程序结 构来确定,并且异常和错误码不应该混合使用,推荐使用异常。 23 / 181  记录异常不要保存exception.getMessage(),而要记录exception.toString()。示 例:NullPointException抛出时常常描述为空,这样往往看不出是出了什么错。  一个方法不应抛出太多类型的异常。 说明: 如果程序中需要分类处理,则将异常根据分类组织成继承关系。如果确 实有很多异常类型首先考虑用异常描述来区别,throws/exception子句标明的异常最 好不要超过三个。  异常捕获尽量不要直接 catch (Exception ex),应该把异常细分处理。 7.3 final 类 绝对不要因为性能的原因将类定义为 final 的(除非程序的框架要求),如果一个类还没 有准备好被继承,最好在类文档中注明,而不要将它定义为 final 的。这是因为没有人可以 保证会不会由于什么原因需要继承它。 访问类的成员变量大部分的类成员变量应该定义为 protected 的来防止继承类使用他 们。 public void setPackets(int[] packets) { this.packets = packets; } CounterSet(int size){ this.size = size; } 7.4 避免使用不易理解的数字 避免使用不易理解的数字,用有意义的标识来替代。涉及物理状态或者含 有物理意义的常量,不应直接使用数字,必须用有意义的静态变量来代替。 示例: private final static int TRUNK_IDLE = 0; private final static int TRUNK_BUSY = 1; private final static int TRUNK_UNKNOWN = -1; if (state == TRUNK_IDLE) { state = TRUNK_BUSY; ... // program code 24 / 181 } 7.5 避免使用技巧性很高的语句 不要使用难懂的技巧性很高的语句,除非很有必要时。 说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。 7.6 Form 输入域的 maxlength 在表单对应 text 类型的输入域,必须根据数据库字段的长度设置相应的 maxlength, 例如数据库类型是 VARCHAR(64),那么 maxlength 是 32(因为中文浏览器对于中文 也认为是一个字符)。 25 / 181 第二篇:JDK 配置相关 26 / 181 1. 准备配置界面 我的电脑点右键,选择“属性”,选择“高级”标签,进入环境变量设置,分别设置如 下三个环境变量。 2. 设置 JAVA_HOME: 一是为了方便引用,比如,JDK安装在C:\jdk1.6.0目录里,则设置JAVA_HOME为该目 录路径, 那么以后要使用这个路径的时候, 只需输入%JAVA_HOME%即可, 避免每次引用都输 入很长的路径串; 二则是归一原则, 当JDK路径改变的时候, 仅需更改JAVA_HOME的变量值即可, 否则, 就要更改任何用绝对路径引用JDK目录的文档, 要是万一没有改全, 某个程序找不到JDK, 后果是可想而知的----系统崩溃! 三则是第三方软件会引用约定好的JAVA_HOME变量, 不然, 你不能正常使用该软件. 在系统环境变量那一栏中点-新建JAVA_HOME (JAVA_HOME指向的是JDK的安装路径) 变量名: JAVA_HOME 变量值: C:\jdk1.6.0 如图:(找到java jdk的安装路径) 3. 设置好 path 变量: 使得我们能够在系统中的任何地方运行java应用程序,比如javac、java、javah等等, 这就要找到我们安装JDK 的目录,比如我们的JDK安装在C:\jdk1.6.0目录下,那么在 C:\jdk1.6.0\bin目录下就是我们常用的java应用程序,我们就需要 把C:\jdk1.6.0\bin这 个目录加到path环境变量里面。 在系统变量里找到path变量,选择编辑;(里面已经有很多的变量值,是在变量值的最前 面加上;%JAVA_HOME%/bin;%JAVA_HOME%/jre/bin) 变量名: path 27 / 181 变量值: ;%JAVA_HOME%/bin;%JAVA_HOME%/jre/bin 如图(后面是jdk安装的bin路径) 4. classpath 环境变量: 是当我们在开发java程序时需要引用别人写好的类时,要让java解释器知道到哪里去找 这个类。通常,sun为我们 提供了一些额外的丰富的类包,一个是dt.jar,一个是tools.jar, 这两个jar包都位于C:\jdk1.6.0\lib目录下,所以通常我们 都会把这两个jar包加到我们的 classpath环境变量中set classpath=.;C:\jdk1.6.0\lib\tools.jar;C:\jdk1.6.0\lib\dt.jar。 在系统环境变量那一栏中点-新建classpath 变量名: classpath 变量值: .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意,CLASSPATH最前面是 有个 “.”的,表示当前目录,这样当我们运行java AClass的时候,系统就会先在当前目 录寻找AClass文件了。);如图 把该配置的环境变量配置好之后,可以正常使用jdk和tomcat 28 / 181 第三篇:Web 服务器介绍 29 / 181 1 Tomcat 服务器 1.1 1.1 TOMCAT 的安装 根据不同的系统,下载不同的版本 下面介绍 WINDOWS 系统安装 TOMCAT 的过程, WINDOWS 下有 2 种格式的安装包,从 tomcat 网站下载 jakarta-tomcat-4.0.1.exe,按照一般 的 windows 程序安装步骤即可安装好 tomcat,安装时它会自动寻找你的 jdk 和 jre 的位置,另 外一种直接解压 zip 包就可以。 1.2 目录文件的作用 Bin 目录: 包括 TOMCAT 的 可 执 行 文 件 , 在 windows 系 统 下 startup.bat 文件 启动 tomcat,shutdown.bat 文件停止 tomcat. 启动后可以在浏览 器中输入 http://localhost:8080/测试。 Conf 目录: TOMCAT 的各种配置文件 ,server.xml(Tomcat 的主要配置文件)和 web.xml,以 及 tomcat-users.xml Log 目录: 记录 TOMCAT 运行时的日志文件 Webapps 目录: 放置 TOMCAT 下各种 WEB 应用程序 ,以后你要部署的应用程序也要放到 此目录。 Work 目录: TOMCAT 的工作目录 ,用于存放 jsp 编译后产生的 class 文件。 配置文件 server.xml 解析: 1,Connector (表示客户端和 service 之间的连接) port 指定服务器端要创建的端口号,并在这个断口监听 来自客户端的请求。redirectPort 指定服务器正在处理 http 请求时收到了一个 SSL 传输请求 后重定向的端口号;connectionTimeout 指定超时的时间数(以毫秒为单位) 2,Engine (表示指定 service 中的请求处理机,接收和处理来自 Connector 的请求) defaultHost 指定缺 省的处理请求的主机名,它至少与其中的一个 host 元素的 name 属性值是一样的。 3,Realm 30 / 181 (表示存放用户名,密码及 role 的数据库) className 指定 Realm 使用的类名,此类必须实现 org.apache.catalina.Realm 接口 4,host (表示一个虚拟主机) name 指定主机名;appBase 应用程序基本目录,即存放应用程序的目录;unpackWARs 如 果为 true,则 tomcat 会自动将 WAR 文件解压,否则不解压,直接从 WAR 文件中运行应用 程序 5,reloadable 这个属性非常重要,如果为 true,则 tomcat 会自动检测应用程序的 /WEB-INF/lib 和/WEB-INF/classes 目录的变化,自动装载新的应用程序,我们可以在不重起 tomcat 的情况下改变应用程序 配置文件 tomcat-users.xml 文件: 1, 主要是用来设置用户权限以及登陆用户名和密码的,参考管理员角色例子如下: <user name=”oyy" password="oyy" roles="manager"/>当启动 tomcat 服务器之后点击 TomcatManager 超链接进入用户登陆窗口,此时输入用户名以及秘密,可以登陆。 备注:更具体的配置信息见 tomcat 的文档 1.3 环境变量的配置 1, 解压缩版:的环境变量设置和 JDK 环境变量设置一样也在我的电脑(属性(高级(环境变量 里设置。新建个环境变量,在变量名输入:CATALINA_HOME 变量值为解压目录 C:\Program Files\apache-tomcat-6.0.18 在环境变量中查找 path 变量,编辑变量在变量值 后加入 C:\Program Files\apache-tomcat-6.0.18\bin 点击确定 ,这样我们的环境变量就设 置完成。下面是检测是否配置成功。 打开运行输入 cmd 输入 startup 运行后,这时会 自动弹出个 dos 窗口,显示启动信息。启动完成后,打开 IE 输入 http://localhost:8080/ 、 http;//127.0.0.1:8080 会出现 tomcat 的管理页面,显示代表 tomcat 环境变量设置成功。 2, EXE 版 :TOMCAT 完全是由 JAVA 代码写成的,所以安装前必须先安装 JDK 下面说明下 解压缩版的环境变量设置和之前一样都在我的电脑里面设置,新建个环境变量,变量名 输入:TOMCAT_HOME 变 量 值 为 安 装 目 录 C:\Program Files\Apache Software Foundation\Tomcat 6.0 在变量中查找 CLASSPATH, 编 辑 变 量 在 后 加 入%TOMCAT_HOME%\BIN 这样 exe 安装的环境变量也设置好了。 下面检测是否成功。 31 / 181 进入安装目录下的 bin 目录运行 tomcat6.exe 文件。 打开 IE,输入 http://127.0.0.1:8080 会出现 tomcat 的管理页面,显示代表 tomcat 环境变量设置成功。 1.4 项目的部署 1, 第一种方法:利用 eclipse 插 件 启 动 tomcat 和 发 布 项 目 , 进 入 eclipse->windows->preference->Server->RuntimeEnvironments 然后添加,选择相应的版本, 以及 tomcat 安装路径,最后完成。然后在 Server 控制台右键 tomcat 服务器选择 add and remove 来部署 web 项目,然后启动服务器,部署完成。 2, 第二种方法:在浏览器中输入 http://localhost:8080 之后打开 tomcat 管理页面,在这里 可以查看当前发布了的项目,也可以在底部 deploy(发布),找到 war 包路径点击发布 就 OK 了; 2 Weblogic 服务器: 2.1 步骤 1:配置 Domain 创建 weblogic 域 1. 点击“开始”菜单,所有程序,选取“BEA Products / Tools / Configuration Wizard”,打 开配置向导。 32 / 181 2. 选择创建新的 weblogic 域,下一步 , 3. 选择生成一个自动配置的域来支持一下产品,下一步, 33 / 181 4. 设置 weblogic 域的名字以及安装路径,下一步, 5. 设置用户名,密码,下一步, 34 / 181 6. 选择开发模式并选择可用 JDK 下的 Sun SDK 1.6.0,下一步, 7. 可以不选直接下一步,有兴趣可以每种都选择一下试试 35 / 181 8. 点击创建 9 点击完成,完成 weglogic 域的创建 以后就可以在开始菜单“BEA Products / User Projects / base_domain / Start Admin Server for Weblogic Server Domain”,打开服务器。 36 / 181 2.2 步骤 2:配置数据源 打开服务器之后 1. 用浏览器打开 http://localhost:7001/console ,登录后主页面如下, 输入之前设置的用户名以及密码登陆进入如下页面: 2. 选择 服务/ JDBC / 数据源 进入, 37 / 181 3. 点 新建按钮新建数据源, 4. JNDI Name 填 ACCOUNT,注意大写,Database Type 选择 Oracle ,Database Driver 选 择 *Oracle’s Driver (Thin) Versions:9.0.1,9.2.0,10 , 下一步, 38 / 181 5. 默认,下一步, 6. 进入 Connection Properties 连接池配置部分,下一步 Database Name 填数据库实例名 ,Host Name 填 主机地址(localhost) ,Database Username / Password 填数据库用户名和密码(根据所需填写开发库或者测试库的用户名密 码),Next, 39 / 181 7. 先点测试配置按钮测试一下, 8. 如果出现如上提示,可以继续,注意点下一步,不要完成, 40 / 181 9. 勾上 AdminServer 前面的复选框,最后 Finish。 10. 回到数据源列表页面, 11. 直接点击刚才配好的数据源, 12. 选择 配置 标签下的 连接池 选项,将页面拉到最下面, 41 / 181 13. 点击 高级 超链接, 14. 勾上 保留时测试连接 前的复选框, 15. 在 Init SQL 栏中填写:SQL alter session set nls_date_format = 'YYYY-MM-DD' , 16. Save 一下 , 17. 最后点击左上角 查看更改和重新启动 按钮完成。 18. 配置完 Data Source 最好测试一下,再次进入 服务 / JDBC / 数据源 42 / 181 19. 点击数据源名进入配置页面, 20. 选择 监视 标签下的 测试 选项, 43 / 181 选上 AdminServer 然后点 测试数据源 按钮,如果出现如上绿字测试成功的提示,就表示数 据源正常工作了。 2.3 步骤 3:部署应用程序 1. 点击 部署 进入部署页面,点击安装 ,将出现如下页面 44 / 181 2. 根据本机路径选择 web 项目, 3. 选上 web 项目,然后下一步 , 45 / 181 4. 默认,下一步, 5. 6. 选择 adminServer 下一步, 46 / 181 7. 选择,Finish,最后点击左上角的查看更改和重启 按钮完成。 8. 再次进入 部署 页面, 9. 勾上要部署的项目前的复选框,点击 Start 按钮, 47 / 181 10. 选择为所有请求提供服务 11. 一切大功告成! 12. 接下来可以在浏览器输入地址访问了。 48 / 181 第四篇:ORM 框架及 hibernate 学习 49 / 181 1. Hibernate 简介 2.1 什么是 Hibernate 简单的说 Hibernate 是一个开源框架,他的作用就是封装了数据库的连接对象。对于 Hibernate 的称呼有很多,比如工具、技术、框架以及解决方案等,这些都可以,重要 的是大家要知道它的作用。在这里我习惯性称它为框架,它是一种能实现 ORM 的框架。 能实现 ORM 这个功能的框架有很多,Hibernate 可以说是这些框架中最流行、最受开发 者关注的,甚至连 JBoss 公司也把它吸收进来,利用它在自己的项目中实现 Hibernate ORM 功能。 也许这样讲对于很多人来讲还是比较抽象的,大家不用担心,当大家学完整个 Hibernate 课程之后,对它肯定就会有一个比较全的认识。到目前为止,大家只要在心中有这样 一个概念,Hibernate 其实就是用来做数据持久化的,只不过是对 JDBC 作了一个轻量级 的封装,也就是说它就是用来操作数据库的。 2.2 Hibernate API 简介 应用程序可以通过 Hibernate API 直接访问数据库,Hibernate API 中的 API 大致可以分为 以下几类:  用于配置 Hibernate 应用的接口,如:Configuration 等。  提供访问数据数据的操作的接口,如:Session,Transaction,Query 等。  应用程序接收 Hibernate 内部发生的事件,并作出相应响应的回调接口,如: Interceptor,Lifecycle 等。  用于自身扩展 Hibernate 功能的接口,如:UserType,IdentifierGenerator 等。 Hibernate 内部封装了 JDBC(Java Data Base Connectivity),JTA(Java Transaction API) 和 JNDI(Java Naming and Directory Interface)。所以 Hibernate 的 API 比较全,也比较庞 大。本节只对各接口的作用作简单介绍,更详细的应用会在稍后的章节中讲到。 2.3 Hibernate 的核心接口 Hibernate 官方发布 Hibernate 的时,随着也给我们提供了相关的 API 帮助文档,整个个 Hibernate 的 API 还是比较庞大的,但对于我们 Hibernate 应用开发人员来讲,并不难,我们 只要搞明白与我们开发相关的常用接口就可以了。所有的 Hibernate 应用程序都会使用到如 图 1-1 中的六个核心的接口。 50 / 181 (1) Configuration 接口 Configuration 接口负责配置并启动 Hibernate,创建 SessionFactory 对象。在 Hibernate 的启动的过程中,Configuration 类的实例首先定位映射文档位置、读取配置,然后创建 SessionFactory 对象。 (2) SessionFactory 接口 SessionFactroy 接口负责初始化 Hibernate。它充当数据存储源的代理,并负责创建 Session 对象。需要注意的是 SessionFactory 并不是轻量级的,因为一般情况下,一个应用通常只需 要一个 SessionFactory 就够了,当需要操作多个数据库时,可以为每个数据库指定一个 SessionFactory。 (3) Session 接口 Session 接口负责执行被持久化对象的 CRUD 操作(CRUD 的任务是完成与数据库的交流, 包含了很多常见的 SQL 语句。)。但需要注意的是 Session 对象是非线程安全的。 (4) Transaction 接口 Transaction 接口是 Hibernate 的数据库事务接口,负责事务相关的操作,它对底层的事 务接口做了封装,底层事务接口包括:  JDBC API  JTA(Java Transaction API)  CORBA(Common Object Requet Broker Architecture)API Hibernate 应用可通过一致的 Transaction 接口来声明事务边界,这有助于应用在不同的 环境容器中移植。尽管应用也可以绕过 Transaction 接口,直接访问底层的事务接口,这种 方法不值得推荐,因为它不利于应用在不同的环境移植。但它是可选的,可发人员也可以设 计编写自己的底层事务处理代码。 (5) Query 接口与 Criteria 接口 Query 和 Criteria 接口是 Hibernate 的查询接口,用于向数据库查询对象,以及控制执行 查询的过程。Query 实例包装了一个 HQL 查询语句,HQL 查询语句和 SQL 查询语句有些相似, 但 HQL 查询语句是面向对象的,它引用类句及类的属性句,而不是表句及表的字段句。Criteria 接口完全封装了基于字符串的查询语句,比 Query 接口更加面向对象,Criteria 接口擅长执 行动态查询。 图 1-1 51 / 181 2.4 Hibernate 框架的安装 1.4.1 获得 Hibernate 目前 Hibernate 最新版本是 hibernate3.3,可以登录 https://www.hibernate.org 网址进行 下载。目前本文使用的还是最稳定版本 Hibernate3.2。将下载的文件解压缩后,目录结构如 图 1-2 所示: 图 1-2 现就主要目录文件说明如下: doc:存放的是与 hibernate 相关的一些帮助文档,如 API。 eg:官方提供的 Hiberante 基本案例,可以通过这些例子来学习 Hibernate。 etc:与 Hibearnate 有关的一些配置文件的模板。 lib:Hiberbate 依赖的第三方 Jar 包。 src:Hibernate 的源文件。 hibernate3.jar:Hibernate 的核心类库,是所有 Hibernate 应用核心包。 1.4.2 配置 Hibernate 开发环境 Hibernate 流行的另一个原因就是其独立运行环境,Hibernate 应用不需要任何容器支持, 所以配置 Hibernate 开发环境也是异常简单,几乎跟开发一个普通的 JDBC 应用一样,根本 不用作过多额外工作。我们只需要将下载的 Hiberante 核心库(hibernate3.jar)和第三方支 持包全部导入到我们的开发环境中即可。 52 / 181 2. Hibernate 应用开发 2.1 Hibernate 应用的开发步骤 Hibernate 是 Java 应用和关系数据库之间的桥梁,要在应用中使用 Hibernate 完成持久 化,其实只要搭起一座桥梁就行,我们都知道,现实社会中建立一座桥梁是需要按照一定的 工程步骤才能完成的,那么我们先来总体了解一下建立 Hibernate 这座桥梁的步骤又如何 呢? Hibernate 应用开发的步骤可以总结为如下几步: 第一步:设计 一般首先进行领域对象的设计。因为在 Hibernate 中,我们的领域对象可以直接充当持 久化类。 第二步:映射 定义 Hibernate 的映射文件,实现持久化类和数据库之间的映射。 第三步:配置 Hibernate 是连接数据库的应用层,连接用的信息需要配置。 第四步:应用 使用 Hibernate 提供的 API,实现具体的持久化业务。 在下一节,我们将通过一个实际的案例,来体验一下 Hibernate 开发。 2.2 一个简单的 Hibernate 开发案例 目的:熟悉 Hibernate 开发过程 功能:实现最简单的单表添加操作(用户数据的增加)。 环境:数据库:Oracle 开发工具:Eclipse 操作系统:Windows 7 下面我们就按上面总结的四个步骤来完成这个简单的 Hibernate 应用。 2.2.1 案例开发详解 下面我们就按上面总结的四个步骤来完成上述 Hibernate 开发案例。 第一步:打开 Eclipse,新建名为 UserManagementHibernate 的 Java 工程,然后在 53 / 181 UserManagementHibernate 中引入之前我们下载的 jar 包( Hiberante 核心库 hibernate3.jar 和第三方支持包),另外由于这是一个基于 Oracle 数据库的应用,所以毫无疑问需要 Oracle 数据库驱动包()。 1)创建用户数据表。在 Oracle 中创建一个名为 Test 的数据库,在 Test 数据库中建立 一个数据表,名为 User,包含 id、name、password 三个字段,建表语句如下: CREATE TABLE `User` ( `id` int(11) NOT NULL auto_increment, `name` varchar(100) NOT NULL default '', `password` varchar(100) NOT NULL default '', PRIMARY KEY (`id`) ); 2)根据设计的数据库表,新建如下实体类: package com.oyy.model; public class User implements Serializable { private int id; private String name; private String password; // 省略 get(),set()方法; }; 第二步:每一个实体类都对应着一个映射文件,这里,我们为 User 实体类编辑如下 映射文件: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.oyy.model.User" table="USER"> <cache usage="read-only"/> <id name="id" type="int" column="ID"> <generator class="native"/> </id> <property name="name" column="NAME"/> <property name="password" column="PASSWORD"/> </class> </hibernate-mapping> 54 / 181 这个映射文件(mapping file)被保存为 User.hbm.xml,和我们的 User 源文件放在同一 个目录下。 第三步:建立 Hibernate 配置文件 hibernate.cfg.xml,此文件放在项目的 classpath 下(即 src 目录下), Hibernate 在启动时会自动在它的根目录开始寻找名为 hibernate.cfg.xml 的配 置文件。 hibernate.cfg.xml 配置文件内容如下: <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- local connection properties --> <property name="hibernate.connection.url"> jdbc:oracle:thin:@192.168.6.60:1521:Test </property> <property name="hibernate.connection.driver_class"> oracle.jdbc.driver.OracleDriver </property> <property name="hibernate.connection.username">scott</property> <property name="hibernate.connection.password">tiger</property> <!-- property name="hibernate.connection.pool_size"></property --> <!-- dialect for Oracle 9 --> <property name="dialect"> org.hibernate.dialect.Oracle9Dialect </property> <property name="hibernate.show_sql">false</property> <property name="hibernate.transaction.factory_class"> org.hibernate.transaction.JDBCTransactionFactory </property> <mapping resource="com/oyy/model/User.hbm.xml" /> </session-factory> </hibernate-configuration> 第四步:使用 Hibernate 提供的 API,实现具体的持久化业务: 使用 Hibernate API 编写数据访问层代码,来对 User 完成添加操作。 由于 SessionFactory 是一个重量级对象,创建它需要比较多的系统开销,所以在一个应 用中没有必要多次创建 SessionFactory,所以我们可以将获得 Session 的工作整理成一个工具 类。 工具类 HibernateUtil 的代码清单如下: 55 / 181 package com.oyy.model; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static SessionFactory sessionFactory = null; static {// 只执行一次,对于 一个应用来讲,也只创建了一个SessionFactory try { Configuration cfg = new Configuration(); sessionFactory = cfg.configure().buildSessionFactory(); } catch (Throwable e) { throw new ExceptionInInitializerError(e); } } /** * 获得当前应用SessionFactory实例 */ public static SessionFactory getSessionFactory() { return sessionFactory; } /** * 获得一个新的Session实例 */ public static Session getSession() { return sessionFactory.openSession(); } } UserDao.java 的源代码如下: Package com.oyy.model; public class UserDao { public void save(User user){ Session session = null; Transaction trans = null; try { session = HibernateUtil.getSession();// 获得session对象 trans = session.beginTransaction();// 开启事务 56 / 181 session.save(user); trans.commit(); // 事务提交 } catch (HibernateException e) { // TODO Auto-generated catch block e.printStackTrace(); trans.rollback();//事务回滚 }finally{ //关闭资源 if (session != null) { session.close(); } } } public static void main(String[] args){ User user=new User(); user.setId(3); user.setName("bb"); user.setPassword("bb"); UserDao dao=new UserDao(); dao.save(user); } } 成功运行此代码后,进入 Oracle 的 Test 数据库查询 User 表,发现新增了一条记录。至 此,Hibernate 应用开发的步骤完成。 2.3 案例剖析 2.3.1 配置文件说明 对于 HelloWorld 应用中的 hibernate.cfg.xml 文件称为 Hibernate 的配置文件,它是 Hibernate 应用启动的不可缺少的一个文件,Hibernate 配置文件主要用来配置数据库连接参 数,例如数据库的驱动程序 URL,用户名,密码等。 Hibernate 提供了两种格式的配置文件:hibernate.properties 和 hibernate.cfg.xml。一般 情况下,hibernate.cfg.xml 是 Hibernate 的默认配置文件,classpath 作为配置文件的默认存放 位置。在我们的案例应用中使用的是 xml 的配置方式。 由于 Hibernate 是为了能在各种不同环境下工作而设计的,因此存在着大量的配置参数, 幸运的是多数配置参数都有比较直观的默认值,并且随 Hibernate 发布而一同分发了的配置 样例 hibernate.properties( 位于 etc/) 来展示各种配置选项,同时也分发了一个 hibernate.cfg.xml 的配置模板,我们所需要做的仅仅是将这个配置模板复制到类路径 (classpath)下并做一些自定义的修改。 下面对 hibernate.cfg.xml 文件的常用属性进行说明。 属性 说明 57 / 181 必 须 connection.driver_class 指定数据库驱动程序 connection.url 指定数据库的 URL connection.username 指定数据库的用户名 connection.password 指定数据库的密码 dialect 指定数据数据方言,用于配置不同的数据库 可 选 show_sql 在控制台输出后台执行的 SQL 语句,用于调试 format_sql 对输出的 SQL 语句进行格式化,提高可读性 hbm2ddl.auto 根据映射文件自动生成 DDL 语句 mapping 指定对象关系映射文件的存放位置 以上属性是前面的例子中提到过的,也是经常用到的,其实 Hibernate 配置文件的属性 远不止这些,都可以在 etc 目录下的 hibernate.properties 文件中找到,其中都有相关说明和 配置方法。在这里就不一一列举,以后用到了再查阅相关文档。 Hibernate 的配置文件默认文件名是 hibernate.cfg.xml,默认存入位置是 classpath 下,但 这都不是必须的,你可以根据需要来指定,但会给代码带来一些没有必要的麻烦,如果没有 特殊需要,建议遵循 Hibernate 默认方式。 2.3.2 持久化类说明 在应用程序中,用来实现业务问题实体的(如在电子商务应用程序中的 Customer 和 Order)类就是持久化类,其实持久化类通常都是域模型中的实体类。如果这些持久化类遵 循一些简单的规则,Hibernate 能够工作得更好,这些规则也被称作简单传 Java 对象 (POJO:Plain Old Java Object)编程模型。但是这些规则并不是必需的。实际上,Hibernate3 对 于你的持久化类几乎不做任何设想。那么 Hiberante 的持久化类又应该具备哪些要求呢? Hibernate 官方文档中已经给出了说明,我们大致可以总结出以下几点: 1. 持久化类应符合 JavaBean 规范,具有一些属性,和与之对应的 set 方法和 get 方法。 set 方法与 get 方法必须遵循特定的命名规则,set 和 get 后紧跟相应的属性名,并 将属性的第一个字母改为大写。 2. 必须提供无参的构造方法。因为 Hibernate 是通过反射调用持久类的无参的构造方 法来实例化持久对象的。 3. 每个持久化类最好提供一个 id 属性,用来作为持久化类对象的唯一标识。在面向 对象的术语中,这个 id 称作对象标识符(OID,Object Identifier),通常都是整数表示, 但不是必须的。 4. 持久化类最好是 public 和 protected 的访问权限,不可以是 private,默认或 final 修 饰,否则 Hibernate 的一些特性将变得不可用,如懒加载特性。 Hiberante 并不要求持久化类必须实现 java.io.Serializable 接口,但如果你的应用是一个 分布式应用或此持久化类的实例对象需要存入在 HttpSession 中,则该类必须实现序列 化接口。 2.3.3 映射文件说明 案例应用中的 User.hbm.xml 我们就称之为对称关系映射文件,它的作用在于指定持久 类的属性与关系数据中表的字段一一对应关系。 对象和关系数据库之间的映射通常是用一个 XML 文档(XML document)来定义的。这个 58 / 181 映射文档被设计为易读的,并且可以手工修改。映射语言是以 Java 为中心,这意味着映射 文档是按照持久化类的定义来创建的,而非表的定义。 Hibernate 的映射文件也有一些约定俗成的要求(都不是必须的):  一个持久化类对应一个映射文件  映射文件名与类名相同  XML 文件的命名形式为 XXX.hbm.xml  通常与持久化类放在相同目录下 下面对 Hibernate 映射文件的常用配置进行说明: (1) hibernate-mapping 节点,映射文件根节点。  package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定的类名, 就 使用这个作为包名。  schema (可选): 数据库 schema 的名称。  catalog (可选): 数据库 catalog 的名称。  default-cascade (可选 - 默认为 none): 默认的级联风格。  default-lazy (可选 - 默认为 true): 指定了未明确注明 lazy 属性的 Java 属性和集合 类, Hibernate 会采取什么样的默认加载风格。  auto-import (可选 - 默认为 true): 指定我们是否可以在查询语言中使用非全限定 的类名(仅限于本映射文件中的类)。 (2) class 节点,用来定义一个持久化类。  name (可选): 持久化类(或者接口)的 Java 全限定名。 如果这个属性不存在, Hibernate 将假定这是一个非 POJO 的实体映射。  table (可选 - 默认是类的非全限定名): 对应的数据库表名。  schema (可选): 覆盖在根<hibernate-mapping>元素中指定的 schema 名字。  catalog (可选): 覆盖在根<hibernate-mapping>元素中指定的 catalog 名字。  dynamic-update (可选, 默认为 false): 指定用于 UPDATE 的 SQL 将会在运行时动态 生成,并且只更新那些改变过的字段。  dynamic-insert (可选, 默认为 false): 指定用于 INSERT 的 SQL 将会在运行时动态 生成,并且只包含那些非空值字段。  batch-size (可选,默认是 1) 指定一个用于 根据标识符(identifier)抓取实例时使用 的"batch size"(批次抓取数量)。  optimistic-lock(乐观锁定) (可选,默认是 version): 决定乐观锁定的策略。  lazy (可选): 通过设置 lazy="false", 所有的延迟加载(Lazy fetching)功能将被全 部禁用(disabled)。  abstract (可选): 用于在<union-subclass>的继承结构 (hierarchies)中标识抽象超 类。 (3) id 节点,被映射的类必须定义对应数据库表主键字段。大多数类有一个 JavaBean 风 格的属性, 为每一个实例包含唯一的标识。<id> 元素定义了该属性到数据库表 主键字段的映射。  name (可选): 标识属性的名字。  column (可选 - 默认为属性名): 主键字段的名字。  type (可选): 标识 Hibernate 类型的名字。 (4) generator 节点,可选的<generator>子元素是一个 Java 类的名字, 用来为该持 久化类的实例生成唯一的标识。如果这个生成器实例需要某些配置值或者初始化参 数, 用<param>元素来传递。 59 / 181 (5) property 节点,指定持久化类的属性。  name 属性:指定持久化类的属性。  type 属性:指定 hibernate 映射的数据类型。对应 Java 数据类型。  column 属性:通过 name 属性指定其对应的数据库表的字段名。  length 属性:通过 name 属性指定其对应的数据库表的字段名的长度。  not-null 属性:通过 name 属性指定其对应的数据库表的字段名是否为空。  update:若为 false 则在更新数据时,不会更新该字段。默认为 true.即更新。  insert:若为 false 则在插入数据时,不会插入该字段。默认为 true.即插入。 2.3.4 数据操作代码说明 如果不使用我们编写的工具类 HibernateUtil,数据操作的代码应该为如下形式: import com.oyy.model.User; public class UserDao { public void save(User user){ Session session = null; Transaction trans = null; try { // 一个org.hibernate.cfg.Configuration实例代表了一个应用程序 中Java类型到数据库映射的完整集合. Configuration被用来构建一个(不可变的 (immutable))SessionFactory. 映射定义则由不同的XML映射定义文件编译而来. // Configuration实例被设计成启动期间(startup-time)对象,一旦 SessionFactory创建完成它就被丢弃了. Configuration cfg = new Configuration(); //使用默认路径下的Hibernate配置文件来创建一个SessionFactory SessionFactory sessionFactory = cfg.configure() .buildSessionFactory(); //使用SessionFactory实例来创建一个session session = sessionFactory.openSession(); //开始事务,Hibernate中默认的事务提交方式是手动提交 trans = session.beginTransaction(); //保存持久化对象 session.save(account); //提交事务 trans.commit(); } catch (HibernateException e) { //异常情况下需要回滚事务 trans.rollback(); e.printStackTrace(); } finally { //关闭资源 if (session != null) { session.close(); 60 / 181 } } } public static void main(String[] args){ User user=new User(); user.setId(3); user.setName("bb"); user.setPassword("bb"); UserDao dao=new UserDao(); dao.save(user); } } 分析上面这段典型的 Hibernate 代码,我们可以总结出进行 Hiberante 持久层开发一般 步骤: (1) 创建 Configuration 实例 (2) 加载 Hibernate 配置文件并取得 SessionFacotroy 实例 (3) 打开会话 Session (4) 开启本次事务 (5) 执行持久化操作 (6) 结束本次事务 (7) 关闭资源 其实进行 Hibernate 应用开发只要遵照上面一般步骤即可,实际上这段代码就是一个基 本的 Hibernate 持久化操作,我们只需要稍加修改,它就可以成为我们 Hibernate 应用的一 个开发模板。但对于一个应用而言,读取 Hibernate 配置文件和创建与数据库对应的 SessionFactory 只需要进行一次就可以了,没有必要在每个持久化操作中重复进行,而实际 上完成对象持久化操作的核心接口只有 Session 接口,而这正是我们编写工具类 HibernateUtil 的目的。 看完上面的注释,其实我们对使用 Hibernate 的 API 进行持久化操作应该有一定的了解, 下面我们再具体学习一下在这段代码中涉及到的几个 Hibernate 接口以及一些 Hibernate 常 用接口。 (1) Configuration 接口 Configuration 是 Hibernate 的入口,在新建一个 Configuration 的实例的时候,Hibernate 会在 classpath 里面查找 Hibernate 的默认配置文件。它的作用是对 Hibernate 进行配置,以 及对它进行启动,在 Hibernate 的启动过程中。 Configuration 对象可以创建一个 SessionFactory 对象,当 SessionFactory 对象创建成功后, Configuration 对象就没有用了,你可以简单地抛弃它,虽然 Configuration 接口在整个 Hibernate 项目中只扮演着一个很小的角色,但它是启动 hibernate 时你所遇到的第一个对象。 启动 Hibernate 方式如下: 实例代码: // 创建实例 Configuration config = new Configuration(); // 加载 hibernate 默认的配置文件,如果你的 Hibernate 配置文件没有遵循 Hibernate 默 认配置,则你需要调用 configure(String path)方法来指定配置文件 61 / 181 Config.configure(); (2) SessionFactory 接口 这里用到了一个设计模式――工厂模式,用户程序从工厂类 SessionFactory 中取得持久 化操作接口 Session 的实例。SessionFactory 并不是轻量级的,实际上它的设计者的意图是让 它能在整个应用中共享。简单地来说,一个项目通常只需要一个 SessionFactory 就够了,因 为随意地创建 SessionFactory 实例会占用大量内存空间。但是当你的项目要操作多个数据库 时,那你必须为每个数据库指定一个 SessionFactory。 获得数据存储源 SessionFactory 的对像的方式如下: 实例代码: // 创建实例 Configuration config = new Configuration(); // 加载 hibernate 默认的配置文件,如果你的 Hibernate 配置文件没有遵循 Hibernate 默 认配置,则你需要调用 configure(String path)方法来指定配置文件 Config.configure(); // 获得数据存储源对象 SessionFactory sessionFactory = config.buildSessionFactory(); SessionFactory 在 Hibernate 中实际起到了一个缓冲区的作用,它缓冲了 Hibernate 自动 生成的 SQL 语句和一些其它的映射数据,还缓冲了一些将来有可能重复利用的数据。 (3) Transaction 接口 Transaction 接口是一个可选的API,你可以选择不使用这个接口,取而代之的是Hibernate 的设计者自己写的底层事务处理代码。Transaction 接口是对实际事务实现的一个抽象,这些 实现包括 JDBC 的事务、JTA 中的 UserTransaction、甚至可以是 CORBA 事务。之所以这样设 计是能让开发者能够使用一个统一事务的操作界面,使得自己的项目可以在不同的环境和容 器之间方便地移值。Hibernate 默认的事务采用的是 JDBC 事务。 涉及 Hibernate 事务的代码如下: 实例代码: Transaction trans = session.beginTransaction();//打开事务 trans.commit();//提交事务 trans.rollback();//回滚事务 (4) Session 接口 Session 接口对于 Hibernate 开发人员来说是一个最重要的接口,Hibernate 的持久化功 能大多集中在 Session 接口中。然而在 Hibernate 中,实例化的 Session 是一个轻量级的类, 创建和销毁它都不会占用很多资源。这在实际项目中确实很重要,因为在客户程序中,可能 会不断地创建以及销毁 Session 对象,如果 Session 的开销太大,会给系统带来不良影响。但 值得注意的是 Session 对象是非线程安全的,因此在你的设计中,最好是一个线程只创建一 个 Session 对象。 创建和销毁 Session 实例对象的方式如下: 实例代码: Session session = sessionFacotry.openSession(); // 创建 session session.close(); // 关闭 session Session 接口提供了各种操作数据库的方法,通过这些方法能够完成基本的 CRUD 操作。  save()和 persist():把 Java 对象保存到数据库中 62 / 181  update()和 merge():对数据库中的对象进行更新  delete():用于删除数据库中的对象  get()和 load():用于从数据库中查询对象 下面以案例中用户信息对象(User)为例,使用工具类 HibernateUtil,来详细了解 这些方法的应用: 一、保存用户信息 实例代码: Session session = null; Transaction trans = null; try{ session = HibernateUtil.getSession(); trans = session.beginTransaction(); session.save(user); // 可以换成session.persist(user),user为用户信息对象 实例 trans.commit(); }catch(HibernateException e){ if(trans!= null) trans.rollback(); throw e; }finally{ if(session != null) session.close(); } 二、删除用户信息 实例代码: Session session = null; Transaction trans = null; try{ session = HibernateUtil.getSession(); trans = session.beginTransaction(); session.delete(user);// 可以换成session.persist(user),user为用户信息对象 实例 trans.commit(); }catch(HibernateException e){ if(trans != null) trans.rollback(); throw e; }finally{ if(session != null) session.close(); } 三、查询用户信息 实例代码: Session session = null; Transaction trans = null; User user = null; 63 / 181 try{ session = HibernateUtil.getSession(); trans = session.beginTransaction(); user = (User) session.get(User.class, id); // 可以换成 session.load(Account.class,id),id为User的id字段 trans.commit(); }catch(HibernateException e){ if(trans != null) trans.rollback(); throw e; }finally{ if(session != null) session.close(); } 四、修改用户信息 实例代码: Session session = null; Transaction trans = null; try{ session = HibernateUtil.getSession(); trans = session.beginTransaction(); session.update(user); // 可以换成session.merge(user),user为用户信息对 象实例 trans.commit(); }catch(HibernateException e){ if(trans != null) trans.rollback(); throw e; }finally{ if(session != null) session.close(); } 2.3.5 HQL(Hibernate Quert language) HQL 是面向对象的查询语言,它和 Sql 查询语言有些相似。所谓面向对象,就是它不同 于 sql 语言直接对数据表中字段进行查询,而是通过 hibernate 的反射机制对对象以及对象 中的属性进行操作。之前在配置 XX.hbm.xml 的时候就提及到对象名对应数据表的名字,而 对象的属性名则对应表中字段名。看个例子: public void select(){ Configuration cfg=new Configuration().configure(); SessionFactory sf=cfg.buildSessionFactory(); Session session=sf.openSession(); try { 64 / 181 session.beginTransaction(); session.createQuery("from User as u where c.id=1").list(); session.getTransaction().commit(); } catch (HibernateException e) { // TODO Auto-generated catch block session.getTransaction().rollback(); e.printStackTrace(); }finally{ session.close(); } } 这条语句的意思是在 User 对象对应的 user 表中查找字段 id 值为 1 的数据列表;并且通过 反射机制返回为一个 List 对象,User 是 List 对象中的一个子元素,通过调用 list.get(i)方法 可以获得 User 对象。 3. Hibernate 基础知识 3.1 事务(Transaction) 3.1.1 什么是数据库事务 数据库事务是指由一个或者多个 SQL 语句组成的工作单元。这个工作单元中的 SQL 语句相 互依赖,如果有一个 SQL 语句执行失败,就必须撤销整个工作单元。 现实生活中的事务是指一组相互依赖的操作行为。例如:TOM 到银行办理转账事务,要把 100 元钱转到 jack 的账号上,这个事务包含一下操作: 从 tom 账户上扣除 100 元; 往 jacd 账户上增加 100 元; 显然以上两个操作是不可分割的否则这件事就没完成。 数据库事务是对现实生活的模拟,它是由一组在业务逻辑上的相互依赖的 SQL 语句组成。 以上转账事务对应一下 SQL 语句: 1, update accounts set balance=900 where id=1; 2, update accounts set balance=1100 where id=2; 这两条语句都执行完成,表示整个事务成功。只要有一条失败,accounts 表中的数据就必 65 / 181 须退回到最初状态。 3.1.2 数据库事务四大特性(ACID) 原子性(Atomic);一致性(Consistency);隔离性(Isolation);持久性(Durablility); 3.1.3 声明事务边界的方式 数据库系统的客户程序只要想数据库系统声明了一个事务,数据库系统就会自动保证事务 的 ACID 特性。声明事务包含以下内容: 1,事务的开始边界(BEGIN):开始事务 2,事务的正常结束边界(COMMIT):提交事务,永久保存被事务更新后的数据库状态; 3,事务的异常结束边界(ROLLBACK):撤销事务,使数据库退回到执行事务前的初始状态; 3.1.3.1 JAVA 应用通过 JDBC API 声明 JDBC 事务 在 JDBC API 中,jaca.sql.Connection 类代表一个数据库连接。以下程序用于创建一个 Connection 类的实例: Class.forName(“com.mysql.jdbc.Driver”); DriverManager.regiseterDriver(new com.mysql.jdbc.Driver()); Stting dburl=”jdbc:mysql://localhost:3306/SAMPLEDB?useUnicode=true&characterE ncoding=gb2312”; String dbuser=”root”; String dbpwd=”1234”; Connection con=java.sql.DriverManager.getConnection(dburl,dbuser,dbpwd); 对于新建的 Connection 类在默认情况下是自动提交事务,可以通过 setAutoCommit(false)方 法来设置手动提交事务: Try{ Connection con=java.sql.DriverManager.getConnection(dburl,dbuser,dbpwd); Con.setAutoCommit(false);//设置手动提交事务 Stmt=con.createStatement(); Stmt.executeUpdate(“update accounts set balance=900 where id=1;”); Stmt.executeUpdate(“update accounts set balance=900 where id=1;”); Con.commit();//提交事务 }catch(){ 66 / 181 Try{ Con.rollback();//操作不成功撤销事务 } } 3.1.3.2 JAVA 应用通过 Hibernate API 声明 JDBC 事务 只要让 Hibernate 配置文件中的 hibernate.transacion.factory_class 事务工厂属性取默认值 org.hibernate.transaction.JDBCTransactionFactoy,那么通过 Hibernate Api 声明的事务就是 JDBC 事务: Session session=sessionFactory.openSession(); Transaction tx=session.beginTransaction(); ………. Tx.commit();持久操作结束后提交事务 或者 Tx.roolback();//在持久操作出现异常的时候撤销事务 备注:从 Hibernate3 版本开始,当调用 session.beginTranaction();方法开始一个新事务时, session 会自动从数据库连接池中获得一个新的连接,当调用 tansaction.commit()方法提交事 务时,session 会自动释放当前的数据库连接。 3.1.3.3 JAVA 应用通过 Hibernate API 声明 JTA 事务 在 Hibernate 配 置 文 件 中 配 置 hibernate.connection.dataresource 属性, hibernate.transaction.factory_class 属性和 hibernate.transaction.manageer_lookup_class 属性 hibernate.connection.dataresource=java:comp/env/jdbc/SAMPLEDB hibernate.transacion.factory_class=org.hibernate.transaction.JTATransactionFactoy hibernate.transaction.manageer_lookup_class=org.hibernate.transaction.jbossTransactionManager Lookup; 通过 Hibernate Api 声明 JTA 事务与声明 JDBC 事务的程序代码一样,不同之处是:对于 JDBC 事务,当事务开始时,session 被分配一个数据库连接,提交时释放连接。而 JTA 事务是在执 行一个 SQL 操作的时候获得数据库连接,SQL 语句结束时候,释放数据库连接。JTA 会保证 同一个事务中的所有 SQL 操作用的是同一个数据库连接。 67 / 181 3.1.3.4 JAVA 应用通过 JTA API 声明 JTA 事务 在 Hibernate 配 置 文 件 中 配 置 hibernate.transaction.factory_class 属性和 hibernate.transaction.manageer_lookup_class 属性: hibernate.transacion.factory_class=org.hibernate.transaction.JTATransactionFactoy hibernate.transaction.manageer_lookup_class=org.hibernate.transaction.jbossTransactionManager Lookup; 事务声明代码: UserTransaction utx=( UserTransaction)new initialContext().lookup(“UserTransaction”); Seession session1=null; Seession session2=null; Try{ Utx.begin(); Session1=sessionFactoryBeiJing.openSession(); Session2=sessionFactoryShanghai.openSession(); Account account1=(Account)session1.get(Account.class, new long(1)); Account1.setBalance(account1.getBalance()-100); Account account2=(Account)session2.get(Account.class, new long(2)); Account1.setBalance(account2.getBalance()+100); Session1.flush();//清理 session1 缓存 Sesion2.flush(); Utx.commit(); }catch(){ Utx.roolback();//撤销事务 } Finally{ Session1.close(); Session2.close(); } 备注:在 Hibernate 配置文件中不必配置数据库连接池,因为 UserTransaction 会直接从受管 理运行环境或者支持 JTA 的不受管理运行时环境中获得数据库连接池。 68 / 181 3.2 数据源&JNDI 3.2.1 什么是数据库连接池(数据源) 所有的 java 应用最终都必须通过 JDBC API 访问数据库,当执行数据库事务时,必须先 获得 JDBC Connection 实例,这个 Connection 实例就代表数据库连接。建立数据库连接需要 消耗大量系统资源,频繁的创建数据库连接会大大消弱应用性能。为了解决这一问提,数据 库连接池应运而生。数据库连接池的基本实现原理:事先建立一定数量的数据库连接,这些 连接存放在连接池中,当 java 应用执行一个数据库事务时,只需从连接池中取出空闲状态 的数据库连接;当 java 执行完事务,再将数据库连接放回连接池。 获得连接池的方法: 第一种:从头到尾实现自己的连接池。 第二种:使用第三方提供的连接池产品,流行的有(poolman,DBCP,C3PO,JDBCPOOL 等)。 标准的 javax.sql.DataSource 接口: Sun 公司制定了标准的 javax.sql.DataSource 接口,它用于封装各种不同的连接池实现。凡是 实现了 DataSource 接口的连接池都被看做是标准的数据源。可以发布到 javaee 应用服务器 中。 3.2.2 JNDI(java Naming and Directory Interface) 可以简单的理解为一种将对象和名字绑定的技术,对象工厂负责生产出对象,这些对象都和 唯一的 JNDI 名字绑定,外部程序通过 JNDI 名字获得某个对象的引用。例如容器发布了一个 名为:jdbc/SAMPLEDB 的数据源,java 应用通过 JNDI API 中的 javax.naming.Context 接口获 得这个数据源的引用: Context ctx=new InitialContext(); DataSource ds=(DataSource)ctx.lookup(“java:comp/env/jdbc/SAMPLEDB”); 程序得到了 DataSource 对象的引用之后,就可以通过 DataSource 的 getConnection()方 法获得数据库连接对象了。 Cnnection con=ds.getConnection(); 对于使用了 Hibernate 的 java 应用,Hibernate 对 JDBV API 进行了封装,Java 应用可以 完全通过 Hibernate API 来访问数据库。然而 Java 应用不会直接访问数据库而是访问数据库 69 / 181 连接池, 3.2.3 hibernate 访问数据库连接池的方法 第一种:使用默认的数据库连接池 Hibernate 提供了默认的连接池实现,它的实现类为 DriverManagerConnnectionProvider.如果 在 Hibernate 配置文件中没有明确配置任何连接池,hibernate 会默认使用这个连接池。 第二种:使用配置文件指定的数据库连接池 例如:在 hibernate.cfg.xml 中配置如下 Hibernate.c3p0.min_size=5 Hibernate.c3p0.max_size=20 Hibernate.c3p0.timeout=300 Hibernate.c3p0.max_statement=50 Hibernate.c3p0.idle_text_period=3000 此处是配置了第三方数据源产品 c3p0 数据源。具体属性的含义请读者自己查资料。 第三种:从容器中获得数据源 在受管理环境中之前介绍了 JNDI,容器负责构造数据源然后把它发布为 JNDI 资源然后通过 之前的方法获得数据源。 在不受管理环境中有些servlet容器例如:tomcat服务器也能负责构造数据源。首先,在tomcat 安装目录的 conf 下的 server.xml 文件中配置数据源: <Resource name=”jdbc/SAMPLEDB” Auth=”Container” Type=”javax.sql.DataSource” maxActive=”100” maxIdle=”30” maxWait=”10000” username=”root” password=”root” driverClassName=”com.mysql.jdbc.Driver” url=”jdbc:mysql://localhost:3306/SAMPLEDB?autoReconnect=true”/> 在 tomcat 容器中配置好数据源之后,到 Hibernate 配置文件里面配置如下: Hibernate。Dailect=org.hiberate.dialect.MySQLDialect Hibernate.connection.dataresouce=java:comp/evn/jdbc/SAMPLEDB Hibernate.show_sql=true 在指定数据源时,必须提供完整的 JNDI 名字。此外,由于 hibernate 直接从容器中获得 了数据源,因此在 hibernate 的配置文件中,可以不用配置连接 url,username,password 等数 70 / 181 据源属性。 3.3 关联映射 3.3.1 Many –to-one 多对一单向关联 以 Customer 和 orders 两个实体类为例,一个客户可以有多条订单,而每一个订单只属于一 个客户,这就是典型的一对多关联关系,反过来说订单和客户是多对一的。 实体类 Customer Class Customer{ Private long id; Private String name; ```````省略 get() set()方法; } 实体类 Order Clsss Order{ Private long id; Private String orderNumber; Private Customer customer;//实体类 Customer 在这里作为 Orders 类的一个属性; ```````省略 get() set()方法; } Customer 类的所有属性和 CUSTOMERS 表的字段一一对应,因此把 Customer 类映射到 CUSTOMERS 表非常简单,就是普通的映射。 <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.oyy.model.Customer" table="CUSTOMER"> <id name="id" type="long" column="ID"> <generator class="native"/> </id> <property name="name" column="NAME"/> </class> </hibernate-mapping> 71 / 181 然而 Orders 类的 customer 属性是 Customer 类型的而 ORDERS 表中的 CUSTOMER_ID 是整形 的,显然不匹配所以不能用<property>元素来映射 customer 属性,而要使用<many-to-one> 元素,Order.hbn.xml 配置如下属性: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.oyy.model.Order" table="ORDERS"> <id name="id" type="long" column="ID"> <generator class="native"/> </id> <property name="orderNumber" column="ORDERNUMBER"/> <many-to-one name="customer" column="CUSTOMER_ID" class="com.oyy.model.Customer" not-null="true" lazy="false" /> </class> </hibernate-mapping> 如此<many-to-one>元素建立了 customer 属性和 ORDERS 表的外键 CUSTOMER_ID 之间的映 射。 3.3.2 One-to-many 一对多双向关联 实体类 Customer Class Customer{ Private long id; Private String name; Private Set orders;//实体类 Orders 集合在这里作为 Customer 类的一个属性; ```````省略 get() set()方法; } 实体类 Order Clsss Order{ Private long id; Private String orderNumber; Private Customer customer;//实体类 Customer 在这里作为 Orders 类的一个属性; ```````省略 get() set()方法; } Customer 类的所有属性和 CUSTOMERS 表的字段现在不是对应了,因此把 Customer 类映 射到 CUSTOMERS 表变的更加复杂,因此需要加入<one-to-many>元素在映射 XML 文件中声 72 / 181 明。Orders 类的 customer 属性是 Customer 类型的而 ORDERS 表中的 CUSTOMER_ID 是整形的, 显然不匹配所以仍然不能用<property>元素来映射 customer 属性,而要使用<many-to-one> 元素: <many-to-one> Name=”customer” Colum=”CUSTOMER_ID” Class=”mypack.Customer” Not-null=”true” Lazy=”false” /> 如此<many-to-one>元素建立了 customer 属性和 ORDERS 表的外键 CUSTOMER_ID 之间的映 射。 然而,Customer 类的映射文件将要在之前的基础之上添加如下: <set Name=”oders” Cascade=”save-update”> <key colum=”CUSTOMERS_ID” /> <one-to-many class=”mypack.Orders” /> </set> 解析:<key>元素设定与所关联的持久化类对应的表的外键,此处为 ORDERS 表中的 SUSTOMER_ID 字段,<one-to-many>元素设定所关联的持久化类,此处为 Orders 类。 现在类和类之间建立了关联,就可以方便的从一个对象导航到另外一个或一组与它关联的对 象。例如,对于给定的 Order 对象。如果想获得与他关联的 Customer 对象只需要调用如下 方法: Customer customer=order.getCustomer(); 然而对于给定的 Customer 对象如果想获得与它关联的 Orders 对象组, Set orders=customer.getOrders(); 小结:对象位于内存中,在内存中从一个对象导航到另外一个对象显然比到数据库中查询速 度快,但是复杂的关联关系也会给编程带来麻烦,随意修改一个对象可能牵一发而动全身, 必须调整许多与之关联的对象之间的关系。所以到底使用双向关联还是单向关联要看业务需 求而定。 3.3.3 Many-to-many 多对多双向关联 假如有 Category(商品类别)类和 Item(商品)类,他们就是多对多的关系,一个商品类别里面 73 / 181 有很多商品,然而一个商品又属于很多种商品类别。这样就构成了多对多的关联关系。 实体类 Category Class Category{ Private String name; Private Set items; ……省略 get() set()方法; } 实体类 Item Class Item{ Private String name; Private double price; ……省略 get() set()方法; Private Set categorys; } 对应的 Category.hbm.xml 中,映射 Category 类的 items 属性的代码如下: <set name=”items” table=”CATEGORY_ITEM” Lazy=”true” Cascade=”save-update” <key colum=”CATEGORY_ID” /> <many-to-many class=”mypack.Item” colum=”ITEM_ID” /> </set> 对应的 Item.hbm.xml 文件中,映射 Item 类的 categorys 属性的代码如下: <set name=” categorys” table=”CATEGORY_ITEM” Lazy=”true” Cascade=”save-update” <key colum=” ITEM_ID” /> <many-to-many class=”mypack.Category” colum=” CATEGORY_ID” /> </set> 这里可以发现多了一张 CATEGORY_ITEM 表,其实处理多对多关联关系的时候就会引入第三 张中间表 CATEGORY_ITEM,存放的是 ITEM_ID 和 CATEGORY_ID 这两个字段作为联合主键共 同标识,同时都作为·外键各自参照 CATEGORY 表和 ITEM 表。 One-to –one 其实一对一只是多对一的特殊情况,其单向双向关联这里就不介绍了。 74 / 181 第五篇:spring 范围及用法学习 75 / 181 1 Spring 简介 Spring 是一个开源框架,是为了解决企业应用开发的复杂性而创建的,它即是一个容器 又是一个框架: 容器——Spring 包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器, 你可以配置你的每个 bean 如何被创建——基于一个可配置原型(prototype),你的 bean 可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关 联的。 框架——Spring 提供了很多基础的与业务逻辑无关的功能,比如:事务管理、持久化 框架集成等等,使用 Spring 框架,开发人员可以专注于业务逻辑开发,这个意义上讲它是 一个框架。 简单来说,Spring 是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。 2 Spring 框架的安装 2.1 Spring 框架 jar 包 Spring 的 jar 包 有 两 种 : spring-framework-1.1.4-with-dependencies.zip 和 spring-framework-1.1.4.zip,建议下载前者(下载主页 http://www.springframework.org/),因为 前者比后者多了一些 Spring 要用到的第三方包,如 hibernate、j2ee、dom4j、aopalliance、 jakarta-commons 等。下载包名称的 dependencies 就是“依赖”的意思。 1、解压后的目录结构如图 1.1 所示: 76 / 181 图 1.1 Spring 目录结构 现就主要目录文件说明如下: dist:Spring 自已的核心库。 docs: 存放的是与 Spring 相关的一些帮助文档,如 API。 lib:Spring 依赖的第三方 Jar 包。 samples: 官方提供的 Spring 基本案例,可以通过这些例子来学习 Spring。 src:Spring 的源文件。 test:测试用例。 2.2 配置 Spring 开发环境 我们只需要将下载的 Spring 自已的核心库(dist 目录下的 jar)和第三方支持包(lib 目录下的 jar)全部导入到我们的开发环境中即可。 3 Spring 的 IOC IOC(Inversion of control)即反向控制。IOC 即使用容器来控制程序之间的依赖关系, 而非用程序代码来控制。控制权由原来的代码转移到到了容器,所以也称之为反转控制。 IOC 又名,依赖注入,DI(Dependency Injection)。组件之间的依赖关系,由容器运行 77 / 181 时动态的注入,而非编写代码给定。例如:没有 Spring 之前,我们的代码,对象实例通过 new 一个对象来产生。然而通过采用 Spring 框架的依赖注入,我们可以不再使用 new,而只 需要在指定的 xml 文件中按一定格式进行相应的配置,便可以达到相同的效果。对象之间的 依赖关系转变为一个配置文件,开发人员不再需要在代码中关注对象与对象之间的依赖关 系。 3.1 Spring 配置文件示例 配置文件一般存放在类路径中,以 applicationContext.xml 命名, Spring 允许有多个 配置文件。 配置文件结构示例: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" > // 注册bean及依赖 </beans> Spring 通过该配置文件描述 Bean 及 Bean 之间的依赖关系。 Spring 中 Bean 实例的注册及依赖注入示例: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" > // 注册了一个com.Bar的实例bar <bean id="bar" class="com.Bar"> </bean> // 注册一个com.Foo的实例foo <bean id="foo" class="com.Foo"> // 注入bar的属性,属性值引用已注册的bar实例 <property name="bar" ref="bar"></property> </bean> </beans> 78 / 181 配置文件中用到的 Bean 代码: Package com; public class Foo { private Bar bar; public Foo(Bar bar) { this.bar = bar; } // 属性注入时将调用该set方法 Public void setBar(Bar bar){ this.bar = bar; } } 3.2 如何获取注入的类 1)通过 BeanFactory 获取 BeanFactory 为 Spring 核心容器的实现接口。 实例代码:建立 BeanFactory 对象实例 BeanFactory bf = new XmlBeanFactory( new ClassPathResource("applicationContext.xml") ); 实例代码:从当前 BeanFactory 中获取 Bean 实例 Foo foo = (Foo)bf.getBean("foo"); 2)通过 Spring 上下文(ApplicationContext)获取 ApplicationContext 是 Spring 中相当重要的类,它是 BeanFactory 的扩展,功能得到 了进一步增强,比如更易与 Spring AOP 集成、消息资源处理(国际化处理)、事件传递及各 种不同应用层的 context 实现(如针对 Web 应用的 WebApplicationContext,它继承了 ApplicationContext)。 Spring 提供了三种类型的上下文,分别用于不同的应用环境: 1) ClassPathXmlApplicationContext: 通 过 引 用 类 路 径 中 的 配 置 文 件 来 建 立 ApplicationContext 实例。 2) FileSystemXmlApplicationContext: 通过引用文件系统中的配置文件来建立 ApplicationContext 实例。 3) XmlWebApplicationContext:从 Web 应用的环境中获取配置文件 建立 Spring 上下文实例,并获取注册的 Bean: 79 / 181 ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); Foo foo = (Foo)app.getBean("foo"); 在 web 项 目 中 , 通 常 在 web.xml 中 初 始 化 bean 的 配 置 文 件 , 来 实 例 化 ApplicationContext(上下文)。 实例代码:web.xml 中初始化 bean 的配置文件 <context-param> <param-name>contextConfigLocation</param-name><param-value> classpath:resource/spring/applicationContext.xml</param-value> </context-param>//配置Spring配置文件 <listener>//配置监听以实例化Spring上下文 <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> 然后在 servlet 中,由 WebAppliCationContextUtil 得到 ApplicationContext(上下 文),从而通过上下文对象获取注入类。 实例代码: ApplicationContext ctx = WebApplicationContextUtils. getRequiredWebApplicationContext(ServletContext sc); // 或者ApplicationContext ctx = WebApplicationContextUtils. getWebApplicationContext(ServletContext sc); Foo foo = (Foo) ctx.getBean("foo"); 其中 servletContext sc 可以具体换成 servlet.getServletContext() 或者 this.getServletContext() 或者 request.getSession().getServletContext(); Web 项目中,获取注入类的方法很多,这里就不一一举例说明了。 推荐方法(web 项目): 通过实现 ApplicationContextAware 接口,来获取注入类。 实例代码: package com.corejsf; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class SpringContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; // Spring应用上下文环境 /** 80 / 181 * 实现ApplicationContextAware接口的回调方法,设置上下文环境 * @param applicationContext * @throws BeansException */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextUtil.applicationContext = applicationContext; } /** * @return ApplicationContext */ public static ApplicationContext getApplicationContext() { return applicationContext; } public static Object getBean(String name){ return applicationContext.getBean(name); } } 在配置文件 applicationContext.xml 中添加以下文段: <bean id="SpringContextUtil" class="com.corejsf.SpringContextUtil" singleton="true" /> 这样,当对 SpringContextUtil 实例时就会自动设置 applicationContext,以便后来可 直接用 applicationContext。 实例代码:调用 SpringContextUtil 获取注入类 Foo foo = (Foo) SpringContextUtil.getBean("foo"); 3.3 Spring 依赖注入实例 本节,我们将通过一个实例来了解 spring 的 IOC。以用户信息的保存为例。 用户对象代码(User.java): package com.oyy.model; public class User { private String id; private String name; private String password; // 省略get(),set()方法 } 用户操作对象代码(UserDao.java)。采用了 Hibernate 实现用户信息的保持,具体配 置在前一章节已经介绍过了,不详述。 81 / 181 package com.corejsf; import org.hibernate.HibernateException; import org.hibernate.Session; import org.hibernate.Transaction; public class UserDao { public void save(User user){ Session session = null; Transaction trans = null; try { session = HibernateUtil.getSession();// 获得session对象 trans = session.beginTransaction();// 开启事务 session.save(user); trans.commit(); // 事务提交 } catch (HibernateException e) { // TODO Auto-generated catch block e.printStackTrace(); trans.rollback();//事务回滚 }finally{ //关闭资源 if (session != null) { session.close(); } } } } 一、不采用 IOC package com.corejsf; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class TestSpring { private UserDao userDao = null; public UserDao getUserDao() { //未使用IOC,代码新建用户对象的实例 userDao = new UserDao(); return userDao; } public void saveUser(User user){ 82 / 181 //获取Dao,调用Dao的方法保存用户信息 getUserDao().save(user); } public static void main(String[] args){ TestSpring test = new TestSpring(); User user=new User(); user.setId(3); user.setName("bb"); user.setPassword("bb"); test.saveUser(user); } } 二、采用 IOC package com.corejsf; import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; public class TestSpring { private UserDao userDao = null; // 容器自动调用该方法注入当前对象依赖的UserDao public void setUserDao(UserDao userDao) { this.userDao = userDao; } public UserDao getUserDao() { // 这里不需要再采用new来构建对象实例了 return userDao; } public void saveUser(User user){ //获取Dao,调用Dao的方法保存用户信息 getUserDao().save(user); } public static void main(String[] args){ ApplicationContext ctx = new FileSystemXmlApplicationContext( "D:/workspace/testspring/WebContent/WEB-INF/applicationContext.xm l"); TestSpring test = (TestSpring) ctx.getBean("TestSpring"); User user=new User(); user.setId(3); user.setName("bb"); user.setPassword("bb"); 83 / 181 test.saveUser(user); } } applicationContext.xml 配置文件内容 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- the application context definition for the springapp DispatcherServlet --> <bean id="UserDao" class="com.corejsf.UserDao"/> <!--使用依赖注入完成变量值设定--> <bean id="TestSpring" class="com.corejsf.TestSpring"> <property name="userDao"> <ref bean = "UserDao"/> </property> </bean> </beans> 通过 applicationContext.xml 配置 文件 ,将 UserDao 对象 通过 TestSpring 的 setUserDao 方法注入到 TestSpring 的 userDao 变量。最后在代码中调用 UserDao 的 save 方法完成用户信息的保存。 3.4 依赖注入的方式 1)Set 注入 利用属性的 set 方法注入其依赖的对象 使用<property >的 ref 注入已注册的其它的 Bean 实例 Set 注入示例: <bean id="orderSrv" class="com.srv.OrderSrv"> <property name="dao" ref="orderDao"/> </bean> <bean id="orderDao" class="com.dao.OrderDao"> </bean> 2)构造注入 84 / 181 在构建 Bean 的实例时,同时注入 Bean 的依赖对象。 Spring 可通过构造函数来注入 Bean 的依赖,Bean 可以对应多个构造函数,而每个构造 函数的参数不一样,Spring 通过配置的参数来决定调用那一个构造函数。 使用<constructor-arg>进行构造注入示例: 示例类代码: public class Foo { private Bar bar; public Foo(Bar bar) { this.bar = bar; } } 3)构造注入配置: <bean id="bar" class="com.Bar" scope=""> <bean id="foo" class="com.Foo" scope=""> <constructor-arg index="0" ref="bar"/> </bean> 3)自动注入 在 Spring 构建 Bean 的实例后,自动搜索与 Bean 属性名或类型相匹配的其它 Bean 实例 并注入。 如果设置 Bean 为自动注入,Spring 构建 Bean 实例后,会自动设置 Bean 的属性值。 示例类代码: public class OrderSrv { private OrderDao orderDao; private CustomerDao cusDao; public OrderDao getOrderDao() { return orderDao; } public void setOrderDao(OrderDao orderDao) { this.orderDao = orderDao; } public CustomerDao getCusDao() { return cusDao; } public void setCusDao(CustomerDao cusDao) { this.cusDao = cusDao; } } 自动注入配置: 85 / 181 <bean id="orderDao" class="com.OrderDao"> </bean> <bean id="cusDao" class="com.CustomerDao"> </bean> <bean id="orderSrv" class="com.OrderSrv" autowire="byName"> <property name="orderDao" ref="orderDao"></property> </bean> orderSrv 采用自动注入,由于 orderSrv 中含有 cusDao 这一属性,虽然配置中未进行 显示的注入,但 cusDao 仍将被容器自动注入到 orderSrv。orderDao 属性使了显示的 Set 注入,也将被注入到 orderSrv。 自动注入的方式有: 1)byName,根据属性的名称自动从容器中寻找与之匹配的 bean 的定义进行装配。 2)byType,根据属性的类型自动从容器中寻找与之匹配的 bean 的定义进行装配。 3)constructor,用于构造函数自动装配,从容器中查找与构造参数类型一致的 bean 进行自动装配。 4)autodetect,首先使用 constructor 装配,再使用 byType 进行装配。 给出的例子中采用的是第一种“byName”。 4 Spring 的 AOP AOP(Aspect Oriented Programming),中文意思是面向切面编程,它可以将不应该集合 在一起的功能从业务操作模块代码中分离并提取出来,使用依赖注入的方式注入到业务代码 中,降低了代码的耦合度,提高了代码的重用率。 4.1 AOP 概念  切面(Aspect): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管 理是 J2EE 应用中一个关于横切关注点的很好的例子。 在 Spring AOP 中,切面可以 使用通用类(基于模式的风格) 或者在普通类中以 @Aspect 注解(@AspectJ 风格) 来实现。  连接点(Joinpoint): 在程序执行过程中某个特定的点,比如某方法调用的时候 或者处理异常的时候。 在 Spring AOP 中,一个连接点 总是 代表一个方法的执行。 通过声明一个 org.aspectj.lang.JoinPoint 类型的参数可以使通知(Advice)的主体部 分获得连接点信息。 86 / 181  通知(Advice): 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有 各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分 进行讨论。许多 AOP 框架,包括 Spring,都是以拦截器做通知模型, 并维护一个 以连接点为中心的拦截器链。  切入点(Pointcut): 匹配连接点(Joinpoint)的断言。通知和一个切入点表达式 关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。 切入点表达式如何和连接点匹配是 AOP 的核心:Spring 缺省使用 AspectJ 切入点语 法。  引入(Introduction): (也被称为内部类型声明(inter-type declaration))。声 明额外的方法或者某个类型的字段。 Spring 允许引入新的接口(以及一个对应的实 现)到任何被代理的对象。 例如,你可以使用一个引入来使 bean 实现 IsModified 接 口,以便简化缓存机制。  目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的 对象。也有人把它叫做 被通知(advised) 对象。 既然 Spring AOP 是通过运行时 代理实现的,这个对象永远是一个 被代理(proxied) 对象。  AOP 代理(AOP Proxy): AOP 框架创建的对象,用来实现切面契约(aspect contract) (包括通知方法执行等功能)。 在 Spring 中,AOP 代理可以是 JDK 动态代理或者 CGLIB 代理。 注意:Spring 2.0 最新引入的基于模式(schema-based)风格和@AspectJ 注解风格的切面声明,对于使用这些风格的用户来说,代理的创建是透明的。  织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创 建一个被通知(advised)的对象。 这些可以在编译时(例如使用 AspectJ 编译器), 类加载时和运行时完成。 Spring 和其他纯 Java AOP 框架一样,在运行时完成织入。 通知的类型:  前置通知(Before advice): 在某连接点(join point)之前执行的通知,但这个 通知不能阻止连接点前的执行(除非它抛出一个异常)。  返回后通知(After returning advice): 在某连接点(join point)正常完成后执 行的通知:例如,一个方法没有抛出任何异常,正常返回。  抛出异常后通知(After throwing advice): 在方法抛出异常退出时执行的通知。  后通知(After (finally) advice): 当某连接点退出的时候执行的通知(不论是 正常返回还是异常退出)。  环绕通知(Around Advice): 包围一个连接点(join point)的通知,如方法调用。 这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它 也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执 行。 4.2 Spring 对 AOP 的支持: Spring AOP 用纯 Java 实现。它不需要专门的编译过程。Spring AOP 不需要控制类装载 器层次,因此它适用于 J2EE web 容器或应用服务器。 Spring AOP 通常与 Spring IOC 一起使用,AOP 的处理采用最普遍的 Bean 方法定义,使 用 Spring IOC 容器来管理采用 Bean 定义的 AOP 处理,这是 Spring AOP 与其他 AOP 实现品 最重要的区别。 87 / 181 事务配置: Spring 配置文件中关于事务配置总是由三个组成部分,分别是 DataSource、 TransactionManager 和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制 这部分。 DataSource、TransactionManager 这两部分只是会根据数据访问方式有所变化,比如 使用 Hibernate 进行数据访问时,DataSource 实际为 SessionFactory,TransactionManager 实现为 HibernateTransactionManager。 5 DataSource 的配置 5.1 数据源信息配置: 这里利用 c3p0 数据连接池来配置数据源,需要有 c3p0 的 jar 包的支持。 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> //数据源信息的配置,这里选择的是 c3po 数据源 <bean id="test_DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"></property> <property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.6.64:1521:orcl"></property> <property name="user" value="oyy"></property> <property name="password" value="oyy"></property> <property name="maxPoolSize" value="30"></property> <property name="initialPoolSize" value="10"></property> <property name="maxIdleTime" value="60"></property> <property name="acquireIncrement" value="5"></property> <property name="maxStatements" value="0"></property> <property name="idleConnectionTestPeriod" value="60"></property> <property name="acquireRetryAttempts" value="30"></property> <property name="breakAfterAcquireFailure" value="true"></property> <property name="testConnectionOnCheckout" value="false"></property> </bean> 88 / 181 5.2 注入到 SessionFactory 使用 Hibernate 进行数据访问,需把数据源注入到 Session 工厂里面。 <!-- 配置sessionFactory --> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean "> <property name="dataSource" ref="test_DataSource"> </property> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> <prop key="hibernate.jdbc.batch_size">20</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.connection.release_mode">after_transaction</prop> </props> </property> <property name="mappingLocations"> <list> <value>classpath:com/oyy/model/*.hbm.xml</value> </list> </property> </bean> 其中<property name="mappingLocations">….</property-name>是配置持久层映射地址, 找到对应的.hbm.xml 映射文件。 89 / 181 6 TransactionManager 部分 6.1 事务管理器分类: PlatformTransactionManagemer 以上就是事务管理器的几种常用实现 1) 如果你在应用程序中仅仅必须处理一个数据源并且用 JDBC 进 行 访 问 , DataSourceTransactionManager 应该符合你的需求; 2) 如果你使用 JTA 进行 Java EE 应用服务器上的事务管理,就应该使用 JtaTransactionManager 从应用服务器上寻找事务; 3) 如果你使用 对象/关系 映射框架访问数据库,应该选用该框架对应的事务管理器, 例如 HibernateTransactionManager 和 JpaTransactionManager。下面介绍一下结合使用 Hibernate 的事务管理器配置。 定义事务管理器配置: <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager " > <property name="sessionFactory" ref="test_sessionFactory" /> </bean> AbstractPlatformTransactionManage r JtaTransactionManager JpaTransactionManager DataSourceTransactionManager HibernateTransactionManager JdoTransactionManager JmsTransactionManager 90 / 181 7 代理机制配置 根据代理机制的不同,总结了四种 Spring 事务的配置方式,配置文件如下: (注意:如果需要用到 bean 的命名空间,则需引入) 7.1 每个 Bean 都有一个代理: 配置如下: <bean id="userDaoTarget" class="org.springframework.transaction.interceptor.TransactionPro xyFactoryBean"> 配置事务管理器 <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="userDao" /> 此处声明一个代理接口用于处理事务,接口暂时没写 <property name="proxyInterfaces" ref="" /> 定义传播规则 <property name="transactionAttributes"> <props> 对所有方法进行事务处理 <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> 7.2 所有 Bean 共享一个代理基类: <bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionPro xyFactoryBean" lazy-init="true" abstract="true"> 配置事务管理器 <property name="transactionManager" ref="transactionManager" /> 配置事务属性 <property name="transactionAttributes"> <props> 对所有方法进行事务处理 <prop key="*">PROPAGATION_REQUIRED</prop> </props> 91 / 181 </property> </bean> 7.3 使用拦截器: <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInt erceptor"> 配置事务管理器 <property name="transactionManager" ref="transactionManager" /> 配置事务属性 <property name="transactionAttributes"> <props> 对所有方法进行事务处理 <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> 配置与需要使用的事务管理的类 <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoPr oxyCreator"> 匹配所有DAO后缀的类 <property name="beanNames"> <list> <value>*Dao</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean> 7.4 使用 tx 标签配置的拦截器: <aop:config proxy-target-class="true"> <!-- 拦截com.booway.dao.impl包下,所有类型文件内所有方法 --> <aop:advisor pointcut="execution(* com.booway.dao.impl.*.*(..))" 92 / 181 advice-ref="functionSettingDispatchAdvice"/> </aop:config> <!-- 定义拦截规则 --> <tx:advice id="functionSettingDispatchAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 对sava、move、update、delete开头的方法进行拦截并进行事务操 作,如出现回滚则抛出异常 --> <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception" /> <tx:method name="move*" propagation="REQUIRED" rollback-for="Exception" /> <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception" /> <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception" /> <!--对其他方法不做事务处理 --> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> <!--<tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes> </tx:advice> 93 / 181 第六篇:JSH 实战篇 (Jsf + spring + hibernate) 94 / 181 1 引言 1.1 什么是 JSH? 我们所说的 JSH 就是 jsf 框架,spring 框架以及 hibernate 框架的结合。 1.2 为什么采用 JSH? JSF、 Spring 与 Hibernate 有各自的优点与不足,将这三个框架有效整合在一起,能够 有效搭建三层或多层系统,保证了清晰的职责划分以及可维护性和可扩展性。三层或多层系 统已经被证明比没有业务逻辑层的客户-服务器系统具有更多的可升级性和柔韧性。 1.3 JSH 中各框架的角色 通常多层架构系统划分为:客户端、表现层、业务逻辑层和数据持久层。 1)JSF 应用于表现层 表现层的功能是收集用户的输入、展示数据、控制页面导航并将用户的输入传递给业 务逻辑层,表示层同时需要验证用户的输入以及维护应用的 session 状态。JSF 是 Web 应用 的服务器端用户组件框架,它包含以下 API:表示 UI 组件、管理它们的状态、处理事件、 服务器端验证、数据转换、 定义页面导航、支持国际化,并为这些特性提供扩展能力。它 同时包括两个 JSP 的 tag 库以在 JSP 页面中表示 UI 组件,以及将组件 wire 为服务器端对象。 JSF 非常适合于基于 MVC 的表示层架构,它为行为和表示之间提供了清晰的分离。 2)Spring 应用于业务逻辑层 业务逻辑层提供了应用客户所需要的业务服务,它包含有业务数据和业务逻辑。通常 对应用请求的大部分业务集中在这一层处理。它从表示层接受请求,基于请求处 理业务逻 辑,作为访问数据资源层中介。Spring 是基于 IoC 概念的框架,在事务管理和依赖注射方面 有着独到之处。Spring 能有效地组织中间层对 象,利用 AOP(Aspect-Oriented Programming) 实现垂直处理,避免一个实体功能的分解,促进面向对象设计的实现。 3)Hibernate 应用于数据持久层 选用 Hibernate 作为数据持久层的实现技术。Hibernate 是一个开源 O/R 映射框架,对 95 / 181 JDBC 进行了轻量级的对象封装。它提供数据恢复和更新的工具、事务管理、数据连接池, 声明实体关系的管理等。支持所有主流的 SQL 数据库管理系统。Hibernate 的 HQL(Hibernate Query Language)查询数据语言是 SQL 面向对象的最小的扩展来设计的,在对象和关系领 域间提供了一个交互的桥。 下面的章节,我们将通过一个例子来体验一下 JSH 三框架在实际项目中的应用,以及 每种框架在项目中扮演的角色。通过这个例子你可以: 了解项目开发过程过程中一个模块的实现流程,以及业务流程; 掌握 hibernate 的实际应用; 掌握 spring 整合 hibernate 的具体方法; 了解 spring 对 hibernate 的控制方法; 了解 spring 事务在 JSH 整合框架中的应用。 2 JSH 框架整合案例实现用户管理模块 2.1 用户管理模块功能介绍 实现用户登陆,权限检查,以及管理员级别用户对所有用户的管理,包括:增加用户(注 册),删除用户,查询用户。 2.2 运行环境 操作系统:Windows XP; 服务器:Tomcat Web 服务器; 数据库:Oracle 数据库; 2.3 模块架构以及业务代码 2.3.1 代码整体架构以及需要导入的 jar 包 代码整体架构如图 1.1 所示: 96 / 181 需要导入的 jar 包如图 1.2 所示: 图 1.2 需要导入的 jar 包 97 / 181 2.3.2 源文件代码展示和介绍 1) 如图 1.1 建立各种源文件的 Package,然后依次编写后台代码,首先编写 User.java 文件,这是一个实体类。 package com.oyy.model; public class User { private String id; private String userName; private String userPassword; private String quanxian; 省略get(),set()方法 } 然后在该文件同级目录下编写 User.hbm.xml 的映射文件如下: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2012-4-16 14:09:00 by Hibernate Tools 3.3.0.GA --> <hibernate-mapping> <class name="com.oyy.model.User" table="users"> <id name="id"> <column name="ID" /> <generator class="assigned"></generator> </id> <property name="userPassword"> <column name="PASSWORD"/> </property> <property name="userName"> <column name="NAME"/> </property> <property name="quanxian"> <column name="QUANXIAN"/> </property> </class> </hibernate-mapping> 2) 编写 dao 层接口,UserDao.java 这个接口提供了 user 对象的增删改等方法。 package com.oyy.dao; import java.util.List; 98 / 181 import com.oyy.model.User; public interface UserDao { public void register(User user); public List<User> getAllUsers(); public void modify(User user); public User getUser(String id); public String selectquanxian(User user); public void del(String id); public List<User> check(User user); } 3)编写 dao 层接口的实现类,UserDaoImpl.java 该类中实现了接口中的所有方法,并且 继承了 HibernateDaoSupport 类,之后可以调用父类的中的 getHibernateTemplate()方法来做 持久化操作。 package com.oyy.daoimpl; import java.util.List; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; import com.oyy.dao.UserDao; import com.oyy.model.User; public class UserDaoImpl extends HibernateDaoSupport implements UserDao { @SuppressWarnings("unchecked") @Override public List<User> getAllUsers() { // TODO Auto-generated method stub List<User> list=getHibernateTemplate().find("from User"); return list; } @Override public void del(String id) { // TODO Auto-generated method stub User user=this.getUser(id); getHibernateTemplate().delete(user); } @Override public String selectquanxian(User user) { // TODO Auto-generated method stub User user1=(User)getHibernateTemplate().get(User.class, user.getId()); 99 / 181 return user1.getQuanxian(); } @Override public void modify(User user) { // TODO Auto-generated method stub getHibernateTemplate().update(user); } @Override public User getUser(String id) { // TODO Auto-generated method stub User user=(User)getHibernateTemplate().get(User.class, id); return user; } @Override public void register(User user) { // TODO Auto-generated method stub getHibernateTemplate().save(user); } @SuppressWarnings("unchecked") @Override public List<User>check(User user) { // TODO Auto-generated method stub List<User> list=getHibernateTemplate().find("from User u where u.userName='"+user.getUserName()+"' and u.userPassword='"+user.getUserPassword()+"'"); return list; } } 4) 编写 UserService 接口: package com.oyy.service; import java.util.List; import com.oyy.model.User; public interface UserService { public void register(User user); public List<User> getAllUsers(); public void modify(User user); public User getUser(String id); public String selectquanxian(User user); public void del(String id); public List<User> check(User user); 100 / 181 } 5) 编写接口 UserService 的实现类 UserServiceImpl.java,实现接口中的所有方法,并且添 加 UserDao 属性,以及其 get(),set()方法。 package com.oyy.serviceimpl; import java.util.List; import com.oyy.dao.UserDao; import com.oyy.model.User; import com.oyy.service.UserService; public class UserServiceImpl implements UserService { private UserDao userDao; @Override public List<User> getAllUsers() { // TODO Auto-generated method stub return userDao.getAllUsers(); } public UserDao getUserDao() { return userDao; } public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public User getUser(String id) { // TODO Auto-generated method stub return userDao.getUser(id); } @Override public void modify(User user) { // TODO Auto-generated method stub userDao.modify(user); } @Override public void register(User user) { // TODO Auto-generated method stub userDao.register(user); } @Override public void del(String id) { // TODO Auto-generated method stub userDao.del(id); } 101 / 181 @Override public String selectquanxian(User user) { // TODO Auto-generated method stub return userDao.selectquanxian(user); } @Override public List<User> check(User user) { // TODO Auto-generated method stub return userDao.check(user); } } 6) 编写工具类 GetBean.java,该类的静态方法 getBean()可以获得 appliactionContext.xml 文件中 id=name 的类的实例。 package com.oyy.tool; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; public class GetBean implements ApplicationContextAware{ private static ApplicationContext context; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { GetBean.context = context; } public static Object getBean(String name) { return context.getBean(name); } } 编写工具类 GetParemeter.java,该类负责获得前台页面传到后台的参数。例如 id。 package com.oyy.tool; import javax.faces.context.FacesContext; 102 / 181 import javax.servlet.http.HttpServletRequest; public class GetParameter { public final HttpServletRequest getRequest() { FacesContext context = FacesContext.getCurrentInstance(); return (HttpServletRequest) context.getExternalContext().getRequest(); } public final String getParameter(String param) { FacesContext context = FacesContext.getCurrentInstance(); String value = (String) context.getExternalContext().getRequestParameterMap().get(param); if (null == value) value = (String) getRequest().getAttribute(param); return value; } } 7) 编写 acion 包下的 UserBean.java 文件,该类负责从页面获得参数,以及根据后台的 处理返回相应的字符串,该类的返回值对应着 jsf 配置文件中的导航规则。 package com.oyy.action; import java.io.Serializable; import java.util.List; import com.oyy.model.User; import com.oyy.service.UserService; import com.oyy.tool.GetBean; import com.oyy.tool.GetParameter; @SuppressWarnings("serial") public class UserBean implements Serializable { private List<User> list; private String message; private User user; //创建构造函数 public UserBean(){ setUser(new User()); } //删除用户信息 public String delete(){ UserService service=(UserService)GetBean.getBean("userService"); GetParameter gp=(GetParameter)GetBean.getBean("getparameter"); String id=(String)gp.getParameter("id"); 103 / 181 service.del(id); message="删除成功"; return "message"; } //获得所有用户 public String getAll(){ UserService service=(UserService)GetBean.getBean("userService"); list=service.getAllUsers(); if(list.isEmpty()){ message="没有用户!"; return "message"; }else{ return "list"; } } public List<User> getList() { return list; } public String getMessage() { return message; } public User getUser() { return user; } //用户登陆 public String logon(){ UserService service=(UserService)GetBean.getBean("userService"); List<User> list=service.check(user); if(list.isEmpty()){ message="该用户不存在"; return "message"; }else{ if(list.get(0).getQuanxian().equals("admin")){ return "admin"; }else if(list.get(0).getQuanxian().equals("visitor")){ return "visitor"; }else{ return null; } } } //修改用户前获得当前用户信息 public String modifybefore(){ UserService service=(UserService)GetBean.getBean("userService"); 104 / 181 GetParameter gp=(GetParameter)GetBean.getBean("getparameter"); String id=(String)gp.getParameter("id"); User user1=service.getUser(id); if(user1!=null){ this.user=user1; return "beforemodify"; }else{ return "message"; } } //注册用户 public String register(){ UserService service=(UserService)GetBean.getBean("userService");// 实例化 userService String id=user.getId(); System.out.println(id); User user1=service.getUser(id); if(user1==null){ service.register(this.user); message="成功注册"; return "message"; }else{ message="注册失败"; return "message"; } } public void setList(List<User> list) { this.list = list; } public void setMessage(String message) { this.message = message; } public void setUser(User user) { this.user = user; } //后台修改用户信息 public String update(){ UserService service=(UserService)GetBean.getBean("userService"); service.modify(user); message="修改成功!"; return "message"; } 105 / 181 } 2.1 JSH 整合框架的基础配置 2.4.1 jsf 配置文件 faces-config.xml 该配置文件配置了页面导航规则以及 bean 的配置如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE faces-config PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN" "http://java.sun.com/dtd/web-facesconfig_1_0.dtd"> <faces-config> <navigation-rule> <from-view-id>/*</from-view-id> <navigation-case> <from-outcome>admin</from-outcome> <to-view-id>/admin.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>visitor</from-outcome> <to-view-id>/visitor.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>list</from-outcome> <to-view-id>/list.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>beforemodify</from-outcome> <to-view-id>/modify.jsp</to-view-id> </navigation-case> <navigation-case> <from-outcome>message</from-outcome> <to-view-id>/message.jsp</to-view-id> </navigation-case> </navigation-rule> <managed-bean> <managed-bean-name>userbean</managed-bean-name> <managed-bean-class> com.oyy.action.UserBean </managed-bean-class> 106 / 181 <managed-bean-scope>request</managed-bean-scope> </managed-bean> </faces-config> 2.4.2 Spring 配置文件 applicationContext.xml 这个配置文件将 Spring 和 Hibernate 进行有效的整合。SessionFactory 底层的 DataSource 使用 Spring 的 IoC 注入,之后再将 SessionFactory 至注入相依的对象之中。然后,在 Spring 的配置文件中注入 Hibernate 的 SessionFactory。完成后配置文件 applicationContext.xml 文件 内容如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> // 数据源的配置,这里选择的是 c3po 数据源 <bean id="test_DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"></property> <property name="jdbcUrl" value="jdbc:oracle:thin:@192.168.6.64:1521:orcl"></property> <property name="user" value="oyy"></property> <property name="password" value="oyy"></property> <property name="maxPoolSize" value="30"></property> <property name="initialPoolSize" value="10"></property> <property name="maxIdleTime" value="60"></property> <property name="acquireIncrement" value="5"></property> <property name="maxStatements" value="0"></property> <property name="idleConnectionTestPeriod" value="60"></property> <property name="acquireRetryAttempts" value="30"></property> <property name="breakAfterAcquireFailure" value="true"></property> <property name="testConnectionOnCheckout" value="false"></property> </bean> <!-- 配置sessionFactory --> // 配置文件中注 入Hibernate的SessionFactory <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> // SessionFactory 底层的 DataSource 使用 Spring 的 IoC 注入 <property name="dataSource" ref="test_DataSource"> </property> <property name="hibernateProperties"> <props> 107 / 181 <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> <prop key="hibernate.jdbc.batch_size">20</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">true</prop> <prop key="hibernate.connection.release_mode">after_transaction</prop> </props> </property> <property name="mappingLocations"> <list> <value>classpath:com/oyy/model/User.hbm.xml</value> </list> </property> </bean> <!-- spring注入相关配置 --> <bean id="userDao" class="com.oyy.daoimpl.UserDaoImpl"> // 注入SessionFactory至相依的对象之中 <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean id="userService" class="com.oyy.serviceimpl.UserServiceImpl"> <property name="userDao" ref="userDao" /> </bean> <bean id="getparameter" class="com.oyy.tool.GetParameter"></bean> <bean class="com.oyy.tool.GetBean"></bean> 这个配置文件里面配置了 c3p0 数据源连接到了本地 oracle 数据库。 2.4.3 applicationContext.xml 文件的拓展配置 在 spring 配置文件中除了可以配置数据源 datesource,sessionFactory,以及 bean 的配置之 外,还可以配置事务例如一下几种配置事务的方式: <!-- 定义事务管理器(声明式的事务) --> <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" > <property name="sessionFactory" ref="test_sessionFactory" /> </bean> <!-- 事务相关配置(五种方式)--> <!-- 第一种方式:每个Bean都有一个代理 --> <!-- <bean id="userDaoTarget" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 配置事务管理器 <property name="transactionManager" ref="transactionManager" /> 108 / 181 <property name="target" ref="userDao" /> 此处声明一个代理接口用于处理事务,接口暂时没写 <property name="proxyInterfaces" ref="" /> 定义传播规则 <property name="transactionAttributes"> <props> 对所有方法进行事务处理 <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>--> <!-- 第二种方式:所有Bean共享一个代理基类 --> <!-- <bean id="transactionBase" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" lazy-init="true" abstract="true"> 配置事务管理器 <property name="transactionManager" ref="transactionManager" /> 配置事务属性 <property name="transactionAttributes"> <props> 对所有方法进行事务处理 <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean>--> <!-- 第三种方式:使用拦截器 --> <!-- <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor"> 配置事务管理器 <property name="transactionManager" ref="transactionManager" /> 配置事务属性 <property name="transactionAttributes"> <props> 对所有方法进行事务处理 <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> 配置与需要使用的事务管理的类 <bean 109 / 181 class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 匹配所有DAO后缀的类 <property name="beanNames"> <list> <value>*Dao</value> </list> </property> <property name="interceptorNames"> <list> <value>transactionInterceptor</value> </list> </property> </bean>--> <!-- 第四种方式:使用tx标签配置的拦截器 --> <aop:config proxy-target-class="true"> <!-- 拦截com.booway.dao.impl包下,所有类型文件内所有方法 --> <aop:advisor pointcut="execution(* com.booway.dao.impl.*.*(..))" advice-ref="functionSettingDispatchAdvice"/> </aop:config> <!-- 定义拦截规则 --> <tx:advice id="functionSettingDispatchAdvice" transaction-manager="transactionManager"> <tx:attributes> <!-- 对sava、move、update、delete开头的方法进行拦截并进行事务操作,如 出现回滚则抛出异常 --> <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception" /> <tx:method name="move*" propagation="REQUIRED" rollback-for="Exception" /> <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception" /> <tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception" /> <!--对其他方法不做事务处理 --> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> <!--<tx:attributes> <tx:method name="*" propagation="REQUIRED" /> </tx:attributes>--> </tx:advice> 110 / 181 <!-- <aop:config> <aop:pointcut id="interceptorr" expression="execution(* com.booway.dao.impl.*.*(..))"/> <aop:advisor advice-ref="functionSettingDispatchAdvice" pointcut-ref="interceptorr" /> </aop:config> --> 2.4.4 部署文件 web.xml 文件 在 jsf 应用的 web 部署描述符中(也就是 web.xml),你必须注册 jsf servlet facessServlet 处理 web 请求,你可以将这个 servlet 映射到 URL 模式*.faces。为了在启动时加载 spring 的 应用上下文,你还必须注册 servlet 监听器 ContextLoaderListener。鉴于 MyFaces 组件在 jsf 应用中的重要地位,文件中添加了对 MyFaces 的支持配置。 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>test_jsh</display-name> <!-- jsf 配置 --> <context-param> <param-name>javax.faces.CONFIG_FILES</param-name> <param-value>/WEB-INF/faces-config.xml</param-value> </context-param> <!-- 注册FacessServlet --> <servlet> <servlet-name>Faces Servlet</servlet-name> <servlet-class>javax.faces.webapp.FacesServlet</servlet-class> </servlet> <servlet-mapping> <!-- servlet映射到URL模式*.faces --> <servlet-name>Faces Servlet</servlet-name> <url-pattern>*.faces</url-pattern> </servlet-mapping> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> 111 / 181 </context-param> <listener> <!-- 注册servlet监听器ContextLoaderListener --> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Myfaces配置 --> <context-param> <param-name>org.apache.myfaces.NUMBER_OF_VIEWS_IN_SESSION</param-name> <param-value>20</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.ALLOW_JAVASCRIPT</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.DETECT_JAVASCRIPT</param-name> <param-value>false</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.PRETTY_HTML</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.AUTO_SCROLL</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.ADD_RESOURCE_CLASS</param-name> <param-value> org.apache.myfaces.renderkit.html.util.DefaultAddResource </param-value> </context-param> <context-param> <param-name>org.apache.myfaces.CHECK_EXTENSIONS_FILTER</param-name> <param-value>true</param-value> 112 / 181 </context-param> <filter> <filter-name>MyFacesExtensionsFilter</filter-name> <filter-class>org.apache.myfaces.webapp.filter.ExtensionsFilter</filter-class> <init-param> <param-name>maxFileSize</param-name> <param-value>20m</param-value> </init-param> </filter> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> 到现在为止项目的代码以及配置全部写完了,接下来编写页面代码,首先我们来编写用 户登陆界面 login.jsp。 <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=GB18030"> <title>登陆 用户名: 密码: 启动服务器,发布项目之后在浏览器中输入 http://localhost:8080/UserManagementJSH/login.faces 显示如下: 113 / 181 注册页面:reg.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> 第一个JSF程序

请输入用户信息

编号: 用户名: 密码 : 身份:
114 / 181 消息显示页面:message.jsp 负责显示从后台返回的信息 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> Insert title here 管理员页面 admin.jsp <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> 115 / 181 管理员页面 点击之后进入用户列表页面 <%@ page language="java" contentType="text/html; charset=gb2312" pageEncoding="utf-8"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> Insert title here 116 / 181 显示结果如下: 修改用户页面信息 modify.jsp <%@ page language="java" contentType="text/html; charset=GB18030" pageEncoding="GB18030"%> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%> <%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%> 117 / 181 <%@ taglib uri="http://myfaces.apache.org/tomahawk" prefix="t"%> 修改用户信息

请输入用户信息

ID: 用户名: 密码 : 身份:
在 list.jsp 中点击修改某条记录的时候显示如下修改页面,可以在原先数据的基础上进 行修改。 到此为止,JSH 框架用户管理模块的后台代码以及前台页面展示已经全部展示完毕。 118 / 181 第七篇:WebService 基础和 CXF 实现 119 / 181 1 引言 1.1 什么是 WebService? 所谓 webservice 就是定义了一套标准的调用过程: a 服务器首先用一套标准的方法向外界描述它所提供的服务的内容,就属于 WSDL b 客户端需要以一种标准的协议来调用此服务,这属于 SOAP. c 服务提供者将服务内容放在一个公共的网址让大家查询,就属于 UDDI. 简单的说 webservice 就是一些站点开放一些服务出来,也可以是你自己开发的 service, 也就是一些方法,通过 URL,指定某一个方法名,发出请求,站点的这个服务(方法),接收 请求后,根据传入的参数做一些处理,然后将处理后的结果以 XML 形式返回给你,你的程 序就解析这些 XML 参数,然后显示出来或做其他操作。 例如:很多大的站点提供有天气预报的 webservice、查询某网站的数据的 webservice, 只要你发送请求过来,它就返回天气预报、某网站的数据,然后你把结果显示处来。 1.2 WebService 的用途及好处: WebService 的主要目标是跨平台的可互操作性,为了达到这个目标,WebService 完全 基于 XML(可扩展标记语言)、XSD(XMLSchema)等独立于平台、独立于软件供应商的标 准,是创建可互操作行、分布式应用程序的新平台。以往我们学到的项目,大部分需要的接 口调用都是调用本身项目中的接口,那么如果我们需要调用其它项目中的接口服务的话,就 需要用到 WebService 了。 下面的章节,我们将通过一个简单的 WebService 案例来体验一下它的实际应用。 2 WebService 开发案例 2.1 功能介绍 在一个项目中(我们所谓的 server 端)我们编写一个用户管理模块的后台代码,并且提 供接口供外部程序调用。在另外一个项目(我们所谓的 client 端)我们编写程序调用 server 120 / 181 端提供给外部的接口,从 client 端实现对用户的管理包括增加,删除,修改用户的功能。 2.3 服务端 2.3.1 创建工程导入 jar 包 建立 Server 端,工程取名 UserManagementWebserviceServer,同时引入需要用到的 jar 包,如图 1.1 所示: 图 1.1 工程结构与相关 jar 包 2.3.2 配置 web.xml 文件 WebserviceServer 121 / 181 contextConfigLocation classpath:applicationContext.xml org.springframework.web.context.ContextLoaderL istener CXFServlet org.apache.cxf.transport.servlet.CXFServlet 3 CXFServlet /services/* 2.3.3 编写用户管理的业务操作 用户管理实例代码: package com.oyy.model; public class User { private String userName; private String userPassword; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPassword() { return userPassword; } public void setUserPassword(String userPassword) { this.userPassword = userPassword; } } 122 / 181 接口类:UserDao.java package com.oyy.dao; import java.sql.SQLException; import java.util.List; import com.oyy.model.User; public interface UserDao { public void saveUser(User user) throws SQLException; public void deleteUser(User user) throws SQLException; public ListgetUsers() throws SQLException; } 实现类:UseDaoImpl.java package com.oyy.dao.impl; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import com.oyy.dao.UserDao; import com.oyy.model.User; import com.oyy.tool.DB; public class UserDaoImpl implements UserDao { private DB db=null; private ResultSet rs=null; private PreparedStatement pre=null; @Override public void deleteUser(User user) throws SQLException { // 删除用户 db=new DB(); String sql="delete user_web where userName=?"; pre=db.getConnection().prepareStatement(sql); pre.setString(1, user.getUserName()); pre.executeUpdate(); pre.close(); db.close(); } @Override public List getUsers() throws SQLException { //获得用户列表 db=new DB(); List list=new ArrayList(); rs=db.getSet(); 123 / 181 String sql="select * from user_web"; pre=db.getConnection().prepareStatement(sql); rs=pre.executeQuery(); while(rs.next()){ User user=new User(); user.setUserName(rs.getString(1)); user.setUserPassword(rs.getString(2)); list.add(user); } return list; } @Override public void saveUser(User user) throws SQLException { // 注册用户 db=new DB(); String sql="insert into user_web(userName,userPassword) values(?,?)"; pre=db.getConnection().prepareStatement(sql); pre.setString(1, user.getUserName()); pre.setString(2,user.getUserPassword()); pre.executeUpdate(); pre.close(); db.close(); } } Service 接口:UserServiec.java package com.oyy.service; import java.sql.SQLException; import java.util.List; import javax.jws.WebService; import com.oyy.model.User; @WebService public interface UserService { public void saveUser(User user) throws SQLException; public void deleteUser(User user) throws SQLException; public ListgetUsers() throws SQLException; } 在这个接口类之前必须加注释@WebService,表示这个接口是对外部提供的 webservice,外 部可以调用它。 实现类: package com.oyy.service.impl; 124 / 181 import java.sql.SQLException; import java.util.List; import com.oyy.dao.UserDao; import com.oyy.model.User; import com.oyy.service.UserService; public class UserServiceImpl implements UserService { private UserDao dao; public UserDao getDao() { return dao; } public void setDao(UserDao dao) { this.dao = dao; } @Override public void deleteUser(User user) throws SQLException { // TODO Auto-generated method stub dao.deleteUser(user);// 删除用户 } @Override public List getUsers() throws SQLException { // TODO Auto-generated method stub return dao.getUsers();// 获得用户列表 } @Override public void saveUser(User user) throws SQLException { // TODO Auto-generated method stub dao.saveUser(user);// 添加用户 } } 到这里 Server 端的业务代码就写完了,接下来编写 spring 配置文件. 2.3.4 编写 applicationContext.xml 文件 2.4 客户端 2.4.1 新建工程导入 jar 包 新建一个名为 UserManagementWebserviceClient 的工程,同时引入相关 jar 包,如图 1.2 所示: 126 / 181 图 1.2 工程结构与相关 jar 包 其中 fromUserManagementWebserviceServer.jar 是从服务端中 UserService 接口包打包 出去的 jar 包;为的是在客户端引入 UserService 接口。 2.4.2 编写客户端程序 客户端调用服务端接口实例代码: package com.oyy.user; import java.sql.SQLException; import java.util.List; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import com.oyy.model.User; import com.oyy.service.UserService; public class UserManager { private static String serviceUrl = "http://localhost:8080/UserManagementWebserviceServer/services/"; private static UserService userService = null; public static UserService getUserService() { JaxWsProxyFactoryBean bean = new JaxWsProxyFactoryBean(); bean.setAddress(serviceUrl + "UserWebService"); bean.setServiceClass(UserService.class); userService = (UserService) bean.create(); return userService; } //编写main方法查询所有用户信息 127 / 181 public static void main(String[] args) throws SQLException{ UserService service=UserManager.getUserService(); System.out.println(service); List list=service.getUsers(); for(int i=0;iLicense ,设置 License Key ( 注 册 号 ) : E6WB3-P5639-B54MD-EJQMT-FW884。如图 1.4 所示: 131 / 181 图 1.4 注册 Teamprise 第四步:退出 Eclipse ,用“com.teamprise.core.jar” 覆盖 “eclipse\plugins\com.teamprise.core_3.2.2.10365R3”下的同名文件; 第五步: 创 建 文 本 文 档 “license.txt”,打开编辑该文档,输入 “0:E6WB3-P5639-B54MD-EJQMT-FW884”,保存该文档,然后去除该文本文档 后 缀 名 ( “license.txt” 变为“license ”),最 后 将 “license” 文 件 拷 贝 至 目 录 “C:\Documents and Settings\Administrator\Local Settings\Application Data\Teampris e\2.0\common”; 第六步:把日期调回原时间,启动 Eclpise。 1.2 VSTS 在 Eclipse 中的使用配置: VSTS 插件安装完成后,点击 Window-->Preference,在弹出的对话框中选择 Source Control,勾选“Check Out”, 这样每次对 VSTS 中文件签出进行签出时, 默认会选择该项,阻止其他人签出修改该文件;勾选“Automatically get the latest version befort check out”, 每次签出时自动更新获取最新版;勾选“Confirm before checking in”, 每次签入的时候弹出确认提示。设置如图 1.5 中所示: 132 / 181 图 1.5 VSTS 使用配置 点击 Window-->Show View-->Other,在弹出的对话框中选择 Team Explorer, 如图 1.6 所示: 图 1.6 选择 Team Explorer 进入项目的添加页面,点击添加项目按钮,在弹出的对话框中对服务器地址, 133 / 181 用户名、密码进行设置,完成 VSTS 的配置。服务器地址为: http://192.168.0.20:8080,用户名和密码由部门分配,勾选“Save password”。如 图 1.7 所示: 图 1.7 TFS 登录条件设置 2 VSTS 操作 2.1 添加文件 选中需要添加文件的目录,点击右键-->New,在弹出菜单中选择文件的类 型进行添加。如图 1.8 所示: 134 / 181 图 1.8 添加文件 该操作仅在本地对文件进行了添加,若要将其上传到服务器的的相应项目, 则必须进行签入(Check In):选中添加的文件或该文件所在目录,点击右键 -->Team-->Check In。 2.2 编辑文件 在对文件进行编辑前,务必签出(check out)该文件。 选中需要编辑的文件,点击右键-->Team-->Check Out For Edit ,如图 1.9 所 示: 图 1.9 签出文件 135 / 181 在弹出的菜单中选中“Check Out-Prevent other users from checking out and checking in”单选框,如图 1.10 所示: 图 1.10 签出方式设置 该方式将阻止其他用户对这个文件进行编辑,从而避免因多个用户同时对同 一文件进行编辑而造成的冲突。 编辑完成保存后,可以进行签入(Check In),将该操作结果上传到服务器: 选中编辑的文件或该文件所在目录,点击右键-->Team-->Check In。 2.3 删除文件 选中需要删除的文件,点击右键-->Delete 或直接使用键盘上的 Delete 键进 行删除,该操作仅在本地对文件进行了删除,若要将服务器相应文件也删除,必 须进行签入(Check In):选中删除文件所在目录,点击右键-->Team-->Check In。 在进行签入操作之前,如果发现文件删除错误,可以通过 Pending Change 中的 Undo 操作来恢复该文件: 点击 Eclipse 左下角的 Show View as a fast view 图标按钮,在弹出菜单中选 择 other,在弹出对话框中选择 Teamprise-->Pending Change,这时会弹出 136 / 181 PendingChange 窗口,里面包含所有未签入的修改操作,勾选需要恢复的操作, 点击右键,在弹出菜单中选择 Undo。如图 1.11 所示: 图 1.11 Undo 操作 2.4 重命名文件 选中需要重命名的文件,必须签出与所有该文件有关联的文件,然后点击右 键-->Refactor-->Rename 或直接使用键盘上的 F2 键,在弹出的对话框中设置 New name 进行重命名,如图 1.12 所示: 137 / 181 图 1.12 文件重命名 设置完成后,点击 Finish 完成文件的重命名。 重命名完成后,可以进行签入(Check In),将该操作结果上传到服务器: 选中所有因文件重命名而导致内容被修改的文件或直接选中文件根目录,点击右 键-->Team-->Check In。 2.5 问号文件要与服务器同步怎么处理? 可分为两种情况处理: 1)服务器上存在该文件: 第一步:选中项目中的问号文件,将其删除; 第二步:选中问号文件所在目录,点击右键--> Team-->Get Latest Version (Recursive),重新从服务器上获取该文件,进行同步。 2)服务器上不存在该文件: 第一步:选中项目中的问号文件,将其复制到任意目录; 第二步:删除刚才选中的问号文件; 第三步:将第一步复制的问号文件复制回其在项目的相应目录,刷新该项目, 使得新添加文件状态为星号; 第四步:进行签入(Check In)。 2.6 怎么样让文件参与到自己项目中,但又不进 VSTS? 在项目中添加一个文件,选中刚刚添加的文件,点击右键--> Team--> Undo Pending Change,这个时候,我们发现这个文件变为一个问号文件,如图 1.13 和 图 1.14 所示: 138 / 181 图 1.13 进行“Undo Pending Change”操作前 图 1.14 进行“Undo Pending Change”操作后 这样的话,这个文件只能在自己的项目中使用,而不会进入 VSTS。 2.7 使用 VSTS 的注意事项 1、换计算机名前签入所有文件或撤销不要的文件 如果换计算机名前不对签出文件或撤销文件进行签入,任何用户都将无法对 其进行操作。 2、如果换计算名后发现有文件被以前的计算机名冲突怎么处理? 将计算机名改回文件冲突之前的名称,对冲突文件进行处理签入后,然后再 修改计算机名。 139 / 181 如果不记得之前的计算机名,我们可以通过以下操作来获取之前的计算机 名: 点击右键-->Team-->Check In,对该文件进行签出操作。由于该文件已经被 签出,这时 eclipse 将弹出已被签出的一个提示信息,信息中包括签出该文件的 计算机名与用户名,如图 1.15 所示: 图 1.15 签出提示信息 2.8 使用 VSTS 的良好习惯 1、每天下班前签入文件: 每个人的工作时间不完全一样,签出的文件如果不进行签入,其他组员将无 法对其进行操作,从而导致其工作无法正常进行。 2、工作项有个成果,则应该签入文件: 首先,这保证了我们的工作成果;其次,一个文件可能不止一个组员使用, 签出的文件进行签入后,其他组员才能对其进行操作,有利于提高工作效率。 需要注意的是:必须尽量保证每一个工作项准确无误的完成后,再签入文件, 避免其他组员因更新获取到功能实现有误的文件而导致其工作难以正常进行的 问题。 3、写的代码多按 Ctrl+S 键保存: 每时每刻注意保存自己的工作成果,避免因意外的突发事故(如:停电,电 脑死机等)导致的工作成果的丢失。 140 / 181 第九篇:java 项目服务器打包配置教程 141 / 181 第十篇:XML 读取和构造样例 142 / 181 1 XmlDocument 1.1 XmlDocument 介绍 XmlDocument 是一个 Java 的 XML 管理类,其功能类似于 dom4j,用来读写 XML 文件。 XmlDocument 是一个非常优秀的 Java XML 读写工具,具有性能优异、功能强大和极端易 用使用的特点。(源代码见 1.1.5 附录) 1.2 使用 XmlDocument 创建内存 Xml 对象 XMLDocument 有几个重要的方法: initRoot : 添加根节点; apendChildNode : 添加子元素; apendNodeAttr : 添加属性; 方法使用方式如下: XMLDocument document = new XMLDocument(); // XML 文档对象。 Element root = document.initRoot("根节点名称"); // 添加一个 XML 根元素。 Element element = document.apendChildNode(root, "节点名称", "节点值"); // 给元素添加 一个子元素 document.apendNodeAttr(element, "属性名称", "属性值");// 给元素添加属性及属性值。 实例代码:创建 xml 文档 public static void main(String[] args) { // 创建文档 XMLDocument document = new XMLDocument(); // 文档增加节点,即根节点,一个文档只能有一个根节点,多加出错 Element root = document.initRoot("skills"); // 根节点下添加节点 Element child_One = document.apendChildNode(root, "skill", ""); // 节点下添加属性 document.apendNodeAttr(child_One, "name", "独孤九剑"); 143 / 181 // 节点下添加节点 document.apendChildNode(child_One, "info", "为独孤求败所创,变化万千,凌厉无比。 其传人主要有风清扬、令狐冲。"); // 同理增加其他节点,内容,属性等 Element child_Two = document.apendChildNode(root, "skill", ""); // 节点下添加属性 document.apendNodeAttr(child_Two, "name", "北冥神功"); // 节点下添加节点 child = document.apendChildNode(child_Two, "info", "逍遥派的顶级内功之一,能吸 人内力转化为自己所有,威力无穷。"); // 输出xml内容 System.out.println(document.asXML()); } 生成 XML 文件内容如下: 为独孤求败所创,变化万千,凌厉无比。其传人主要有风清扬、令狐冲。 逍遥派的顶级内功之一,能吸人内力转化为自己所有,威力无穷。 144 / 181 1.3 使用 XmlDocument 解析 Xml 文档 构建 Dom 树 XmlDocument 能从不同的输入源构建 Dom 树。根据输入源的不同,可以分为以下三种 方式: 一、由 xml 文件绝对路径构建 Dom 树 实例代码: XMLDocument document = new XMLDocument(); // 创建文档 document.loadFromFile("d:/skills.xml"); // xml文件绝对路径构建Dom树 二、由 Xml 文件内容字符串构建 Dom 树 实例代码: XMLDocument document = new XMLDocument(); // 创建文档 document.loadFromXml("xml字符串"); // Xml文件内容字符串构建Dom树 三、由 Xml 文件流构建 Dom 树 实例代码: XMLDocument document = new XMLDocument(); // 创建文档 InputStream stream = new FileInputStream(File); document.load(stream); // Xml文件流构建Dom树 获取节点 获得 Dom 树之后,可以根据 Dom 树获取节点。首先获取根节点,然后根据根节点获取 其子节点。 实例代码:访问根节点 Element root = document.getRootElement (); 实例代码:访问所有某个元素的所有子元素,如 root List childList = document.getChildList(root); 实例代码:访问指定名称的节点,如访问名称为“skill”的全部节点 NodeList skills = root. getElementsByTagName ("skill"); // 获取名称为“skill”的全部节点 145 / 181 for (int i = 0; i < skills.getLength(); i++) // for循环遍历名称为skill所有节点 { Element e = (Element) skills .item(i); // 获取节点对象实例 } 获取属性 获取节点后,可以根据节点获取属性,获得方式与获取节点类似。 实例代码:获取指定名称的元素的属性 Element root = document.getRootElement (); Attr attr = root.getAttributeNode ("name"); // 获取属性对象实例 String name = attr.getNodeName(); // 获取属性名称 String value = attr.getNodeValue(); // 获取属性值 实例代码:获取某节点下全部属性 NamedNodeMap map = root.getAttributes(); // 获取节点下所有属性 if(map != null) { for(int i=0; i < map.getLength(); i++) // for循环遍历属性 { String name = map.item(i).getNodeName(); // 获取属性名称 String value = map.item(i).getNodeValue(); // 获取属性值 } } 使用 XmlDocument 编辑 Xml 文档 编辑 xml 文件,需要先获取 Dom 树,通常欲编辑节点需要先获得该节点或其父节点, 欲编辑属性,需要先获得该属性所在的节点和该属性。 一、增加操作: 实例代码:某节点下增加子节点(以根节点为例) 146 / 181 Element root = document.getRootElement(); Element skill = document. apendChildNode(root, "节点名称","节点值"); 实例代码:某节点增加属性 Element root = document.getRootElement(); Element skill = document. apendNodeAttr(root, "属性名称","属性值"); 二、删除操作: 实例代码:删除某节点 Element root = document.getRootElement(); Element skill = root. getElementsByTagName ("节点名称").item(0); root. removeChild(skill); 实例代码:删除节点指定名称的属性 Element root = document.getRootElement(); Element skill = document. apendNodeAttr(root, "属性名称","属性值"); root.removeAttribute("属性名称"); 三、修改操作: 实例代码:修改节点值 Element root = document.getRootElement(); root .setNodeValue("节点值"); 实例代码:修改某节点属性值 Element root = document.getRootElement(); root. setAttribute ("节点名称","节点值"); 属性名称无法修改,欲修改属性名称,可以先删除旧属性,再增加新属性。 字符串转化 一、把 xml 文档转化成字符串,使用 asXML()方法。 实例代码: String docXmlText = document.asXML(); 二、把字符串转换为文档,使用 loadFromXml()方法。注意引号需要转义 实例代码: 147 / 181 String xml = "字符串转化"; XmlDocument document = new XmlDocument(); document.loadFromXml(xml); 1.4 常用方法 XmlDocument API Method Comment initRoot () 创建根节点 getRootElement () 获取 XML 根节点对象 getChildList () 获取某节点子节点集合 loadFromFile () 文件加载为内存 XML 对象 loadFromXml () XML 字符串加载为内存对象 load () 输入流加载为内存对象 apendChildNode () 根据父节点创建子节点 apendNodeAttr () 给某个节点添加属性 asXML () 将当前 XML 内存对象输出为 XML 字符串 XMLWriter API Method Comment writeNode() 递归遍历 xml 文件节点并将其写入 Write flush () 释放 Write 流对象 附录 XmlDocument 源代码: import java.io.ByteArrayInputStream; import java.io.FileInputStream; 148 / 181 import java.io.InputStream; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * 内存 XML 管理对象 * * @author shijiaokun * */ public class XMLDocument { private Document doc; /** * 创建根节点 * @param rootName 根节点名称 * @return 根节点对象 149 / 181 */ public Element initRoot(String rootName) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = null; try { builder = factory.newDocumentBuilder(); } catch (ParserConfigurationException e) { return null; } doc = builder.newDocument(); Element e = doc.createElement(rootName); doc.appendChild(e); return e; } public Element getElementByXpath(Element node, String xpath) { String[] pathArray = xpath.split("/"); for (String path : pathArray) { 150 / 181 node = this.getElement(node, path); if (node == null) return null; } return node; } private Element getElement(Element node, String xpath) { NodeList list = node.getChildNodes(); if (list == null) return null; if (list.getLength() <= 0) return null; for (int i = 0; i < list.getLength(); i++) { if (list.item(i) instanceof Element) { if (list.item(i).getNodeName().equals(xpath)) { return (Element) list.item(i); } } } 151 / 181 return null; } public Node getElement(String xpath) { NodeList list = doc.getDocumentElement().getChildNodes(); if (list == null) return null; if (list.getLength() <= 0) return null; return list.item(0); } /** * 获取根节点 * @return */ public Element getRootElement() { if (doc == null) return null; return doc.getDocumentElement(); } /** * 获取子节点集合 152 / 181 * @param e * @return */ public List getChildList(Element e) { List childs = new ArrayList(); if (doc == null) return childs; NodeList list = e.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { if (list.item(i) instanceof Element) { childs.add((Element) list.item(i)); } } return childs; } /** * 从文件加载为内存XML对象 * @param path 文件路径 * @throws Exception 初始化失败 */ public void loadFromFile(String path) throws Exception { try 153 / 181 { FileInputStream stream = new FileInputStream(path); this.load(stream); } catch (Exception e) { throw new Exception("初始化 XML 文件失败!"); } } /** * 从 XML 字符串加载为内存对象 * @param xml XML字符串 * @throws Exception 初始化失败 */ public void loadFromXml(String xml) throws Exception { try { ByteArrayInputStream stream = new ByteArrayInputStream(xml.getBytes("utf-8")); this.load(stream); } catch (Exception e) { throw new Exception("初始化 XML 文件失败!"); } } public void load(InputStream stream) throws Exception 154 / 181 { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); doc = builder.parse(stream); doc.normalize(); } public void load(Document document) throws Exception { doc = document; doc.normalize(); } /** * 根据父节点创建子节点 * @param parentNode 父节点 * @param childName 子节点名称 * @param childValue 子节点值 * @return */ public Element apendChildNode(Element parentNode, String childName, String childValue) { if (doc == null) return null; Element child = doc.createElement(childName); child.setTextContent(childValue); 155 / 181 parentNode.appendChild(child); return child; } /** * 给某个节点添加属性 * @param node 节点 * @param attrName 属性名 * @param attrValue 属性值 * @return 添加成功的属性对象 */ public Attr apendNodeAttr(Element node, String attrName, String attrValue) { if (doc == null) return null; attrName = attrName.replace("(", "").replace(")", ""); attrName = attrName.replace("(", "").replace(")", ""); Attr attr = doc.createAttribute(attrName.trim()); attr.setTextContent(attrValue); node.setAttributeNode(attr); return attr; } /** * 将当前XML内存对象输出为 XML 字符串 156 / 181 * @returnXML 字符串 */ public String asXML() { StringWriter out = new StringWriter(); try { XMLWriter writer = new XMLWriter(out); writer.writeNode(doc.getDocumentElement()); writer.flush(); } catch (Exception e) { return ""; } return out.toString(); } XMLWrite 源代码: package com.booway.tools.xml; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; 157 / 181 /** * XML 写对象 * @author shijiaokun * */ public class XMLWriter { private Writer writer; //节点深度(用于格式化xml字符串) private int level=0; public XMLWriter() throws IOException { this.writer = new BufferedWriter(new OutputStreamWriter(System.out)); this.writer.write("\n"); } public XMLWriter(Writer writer) throws IOException { this.writer = writer; this.writer.write("\n"); } public void writeNode(Node node) throws Exception { if(writer == null) return; 158 / 181 if(node.getNodeType() == Node.ELEMENT_NODE) { //输出格式化缩进 for(int i=0;i\n"); } NodeList childs = node.getChildNodes(); 159 / 181 // 递归输出子节点 for(int i = 0; i < childs.getLength(); i++) { if(childs.item(i).getNodeType() == Node.ELEMENT_NODE) { level++; this.writeNode(childs.item(i)); } else if(childs.item(i).getNodeType() == Node.TEXT_NODE) { if(!childs.item(i).getTextContent().equals("")){ //输出格式化缩进 for(int k=0;k<(level+1)*4;k++){ this.writer.write(" "); } this.writer.write(childs.item(i).getTextContent()+"\n"); } } } // 输出结束符 if(node.getNodeType() == Node.ELEMENT_NODE) { //输出格式化缩进 for(int i=0;i\n"); if(level>0) level--; } } public void flush() throws IOException { this.writer.flush(); } } 2 Dom4j 2.1 dom4j 介绍 dom4j 是一个 Java 的 XML API,类似于 jdom,用来读写 XML 文件的。dom4j 是一个非 常非常优秀的 Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也 是一个开放源代码的软件,可以在 SourceForge 上找到它。在 IBM developerWorks 上面可以 找到一篇文章,对主流的 Java XML API 进行的性能、功能和易用性的评测,dom4j 无论在 那个方面都是非常出色的。如今你可以看到越来越多的 Java 软件都在使用 dom4j 来读写 XML,特别值得一提的是连 Sun 的 JAXM 也在用 dom4j。这是必须使用的 jar 包, Hibernate 用它来读写配置文件。我们只需要导入 dom4j 的开发包(dom4j-1.6.1.jar),便可以使用 dom4j 对 xml 进行操作。 2.2 使用 dom4j 创建 xml 文档 Document document = DocumentHelper.createDocument(); 通过这句定义一个 XML 文档对象。 161 / 181 Element root = document.addElement("根节点名称"); 通过这句定义一个 XML 元素,这里添加的是根节点。 Element 有几个重要的方法: addComment : 添加注释; addAttribute : 添加属性; addElement : 添加子元素; 最后通过 XMLWriter 生成物理文件,默认生成的 XML 文件排版格式比较乱,可以通 过 OutputFormat 类格式化输出,默认采用 createCompactFormat()显示比较紧凑,最好使 用 createPrettyPrint()。 实例代码: public static void main(String[] args) { // 创建文档。 Document document = DocumentHelper.createDocument(); // 设置文档DocType,这里为了举例,添加hibernate的DocType document.addDocType("hibernate-configuration", "-//Hibernate/Hibernate Configuration DTD 3.0//EN", "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"); // 文档增加节点,即根节点,一个文档只能有一个根节点,多加出错 Element root = document.addElement("skills"); // 添加注释 root.addComment("第一个技能"); // 根节点下添加节点 Element first = root.addElement("skill"); // 节点添加属性 first.addAttribute("name", "独孤九剑"); // 节点下添加节点 Element info = first.addElement("info"); // 节点设置内容数据 info.setText("为独孤求败所创,变化万千,凌厉无比。其传人主要有风清扬、令狐冲。 162 / 181 "); /** * 第二种方法增加节点,内容,属性等。先创建节点,属性,然后使用add加入。 */ // 创建节点 Element third = DocumentHelper.createElement("skill"); // 将节点加入到根节点中 root.add(third); // 创建属性,第一个参数指定了拥有者,也可以为null,指定拥有者 Attribute name = DocumentHelper.createAttribute(third, "name", "北冥神功"); // 将属性加入到节点上 third.add(name); // 创建子节点并加入到节点中 Element info2 = DocumentHelper.createElement("info"); info2.setText("逍遥派的顶级内功之一,能吸人内力转化为自己所有,威力无穷。"); third.add(info2); try { // 创建格式化类 OutputFormat format = OutputFormat.createPrettyPrint(); // 设置编码格式,默认UTF-8 format.setEncoding("UTF-8"); // 创建输出流,此处要使用Writer,需要指定输入编码格式,使用OutputStream 则不用 FileOutputStream fos = new FileOutputStream("d:/skills.xml"); // 创建xml输出流 XMLWriter writer = new XMLWriter(fos, format); // 生成xml文件 163 / 181 writer.write(document); writer.close(); } catch (Exception e) { e.printStackTrace(); } } 生成 XML 文件内容如下: 为独孤求败所创,变化万千,凌厉无比。其传人主要有风清扬、令狐冲。 逍遥派的顶级内功之一,能吸人内力转化为自己所有,威力无穷。 2.3 使用 dom4j 解析 xml 文件 构建 dom4j 树 org.dom4j.io 提供了两个类:SAXReader 和 DOMReader,DOMReader 只能一个现有的 w3c DOM 树构建 dom4j 树 , 即 只 能 从 一 个 org.w3c.dom.Document 中构建 org.dom4j.Document 树,而 SAXReader 则使用 SAX 解析器,从不同的输入源构建 dom4j 树,如可以从 xml 文件中读取并构建 dom4j 树。 实例代码:使用 SAXReader 解析 164 / 181 SAXReader reader = new SAXReader(); Document document = reader.read(new File("d:/skills.xml")); 实例代码:使用 DOMReader 解析 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); File file = new File("d:/skills.xml"); org.w3c.dom.Document domDocument = db.parse(file); DOMReader reader = new DOMReader(); org.dom4j.Document document = reader.read(domDocument); 获取节点 获得 dom4j 树之后,可以根据 dom4j 树获取节点。首先获取根节点,然后根据根节点 获取其子节点。 实例代码:访问根节点 Element root = document.getRootElement(); 实例代码:访问所有子节点 List skills = root.elements(); for (Iterator it = skills.iterator(); it.hasNext();) { Element e = (Element) it.next(); } 实例代码:访问指定名称的节点,如访问名称为“skill”的全部节点 List skills = root.elements("skill"); for (Iterator it = skills.iterator(); it.hasNext();) { Element e = (Element) it.next(); } 实例代码:访问指定名称的第一个节点 Element skill = root.element("skill"); 实例代码:迭代某个元素的所有子元素,如迭代 root for(Iterator it = root.elementIterator();it.hasNext();){ 165 / 181 Element e = it.next(); } 获取属性 获取节点后,可以根据节点获取属性,获得方式与获取节点类似。 实例代码:获取指定名称的元素 Element skill = root.element("skill"); Attribute attr1 = skill.attribute("name"); 实例代码:按照属性顺序获取属性 Element skill = root.element("skill"); Attribute attr2 = skill.attribute(0); 实例代码:获取某节点下全部属性 Element skill = root.element("skill"); List list = skill.attributes(); for (Iterator it = list.iterator(); it.hasNext();) { Attribute attr = it.next(); } 实例代码:获取某节点下全部属性 Element skill = root.element("skill"); for (Iterator it = skill.attributeIterator(); it.hasNext();) { Attribute attr = it.next(); } 使用 XPath 获取节点和属性 Dom4j 中集成了对 XPath 的支持。在选择节点时,可以直接使用 XPath 表达式。这种 方式更加方便,简洁,官方文档中推荐使用该种方式。 实例代码:要选择所有的元素的 name 属性 List list = document.selectNodes("//skills/skill/@name"); 166 / 181 for (Iterator it = list.iterator(); it.hasNext();) { Attribute attr = (Attribute) it.next(); } 注意:为了能够编译执行上述使用 XPath 表达式的代码,需要配置 dom4j 安装包中自带 的 jaxen 包,你也可以从 http://sourceforge.net/products/jaxen/上下载 jaxen。jaxen 是一个用 java 开发的 XPath 引擎,用于配合各种基于 XML 的对象模型,如 DOM,dom4j 和 JDOM。在 dom4-1.6.1 目录下,有一个 lib 子目录,其中有个 jaxen-1.1-beta-6.jar 文件,需要在 classpath 环境变量中配置该文件的全路径名。 使用 dom4j 编辑 xml 文件 修改 xml 文件,需要先获取 dom4j 树(即 Document),通常欲修改节点需要先获得该节 点或其父节点,欲修改属性,需要先获得该属性所在的节点和该属性。 一、增加操作:参照前文。 二、删除操作: 实例代码:删除某节点 Element root = document.getRootElement(); Element skill = root.element("skill"); root.remove(skill); 实例代码:删除指定名称的属性 Element skill = root.element("skill"); skill.remove(skill.attribute("name")); 三、修改操作: 实例代码:修改节点名称和节点值 Element skill = root.element("skill"); skill.setName("new_skill"); skill.setText("你好"); 实例代码:修改属性值 Attribute attr = skill.attribute("name"); attr.setText("newName"); 167 / 181 属性名称无法修改,欲修改属性名称,可以先删除旧属性,再增加新属性 字符串转化 实例代码:把节点,属性,文档等转化成字符串,使用 asXML()方法。 String docXmlText = document.asXML(); String rootXmlText = root.asXML(); 实例代码:把字符串转换为文档,注意引号需要转义 String skillString = "神龙摆尾"; Document d = DocumentHelper.parseText(skillString); 2.4 常用方法 Element 元素 API Method Comment getQName() 元素的 QName 对象 getNamespace() 元素所属的 Namespace 对象 getNamespacePrefix() 元素所属的 Namespace 对象的 prefix getNamespaceURI() 元素所属的 Namespace 对象的 URI getName() 元素的 local name getQualifiedName() 元素的 qualified name getText() 元素所含有的 text 内容,如果内容为空则返 回一个空字符串而不是 null getTextTrim() 元素所含有的 text 内容,其中连续的空格被 转化为单个空格,该方法不会返回 null attributeIterator() 元素属性的 iterator,其中每个元素都是 Attribute 对象 attributeValue() 元素的某个指定属性所含的值 168 / 181 elementIterator() 元素的子元素的 iterator,其中每个元素都 是 Element 对象 element() 元素的某个指定(qualified name 或者 local name)的子元素 elementText() 元素的某个指定(qualified name 或者 local name)的子元素中的 text 信息 getParent 元素的父元素 getPath() 元素的 XPath 表 达 式 , 其 中 父 元 素 的 qualified name 和子元素的 qualified name 之间使 用"/"分隔 isTextOnly() 是否该元素只含有 text 或是空元素 isRootElement() 是否该元素是 XML 树的根节点 Attribute 属性 API Method Comment getQName() 属性的 QName 对象 getNamespace() 属性所属的 Namespace 对象 getNamespacePrefix() 属性所属的 Namespace 对象的 prefix getNamespaceURI() 属性所属的 Namespace 对象的 URI getName() 属性的 local name getQualifiedName() 属性的 qualified name getValue() 属性的值 命名空间(Namespace)操作 dom4j 的名称空间信息 api 常用的方法有 8 个。 dom4j在Element和Attribute 接口中定义了获取名称空间信息的方法,这些方法和JDOM 中的方法相同。如下所示: public java.lang.String getNamespacePrefix()该方法返回元素(属性)的名称空间前缀 169 / 181 public java.lang.String getNamespaceURI()该方法返回元素(属性)的名称空间 URI public java.lang.String getName()该方法返回元素(属性)的本地名 public java.lang.String getQualifiedName()该方法返回元素(属性)的限定名 public Namespace getNamespace()该方法返回元素本身的名称空间 public java.util.List additionalNamespaces()返回某元素上附加的名称空间声明列表,列表 中的每一个对象都是 Namespace 类型。 这个类的方法提供了两个方法分别获得名称空间前缀和本地名。如下: public java.lang.String getPrefix()该方法返回名称空间前缀。 public java.lang.String getURI()该方法返回名称空间的 URI。 170 / 181 第十一篇:POI 应用规范 171 / 181 1 POI 简介 Jakarta POI 是 apache 的子项目,目标是处理 ole2 对象。它提供了一组操纵 Windows 文档的 Java API 。使用 Java 语言读写 Microsoft Office,提供了下面这几种类型: HSSF-提供读写 Microsoft Excel XLS 格式档案的功能。 XSSF-提供读写 Microsoft Excel OOXML XLSX 格式档案的功能。 HWPF-提供读写 Microsoft Word DOC 格式档案的功能。 HSLF- 供读写 Microsoft PowerPoint 格式档案的功能。 HDGF-提供读 Microsoft Visio 格式档案的功能。 HPBF-提供读 Microsoft Publisher 格式档案的功能。 2 POI 操作 Excel 本章主要讲述的是 POI 对 Excel 格式文档的解析,主要涉及 HSSF(Excel 97-2003)与 XSSF(Excel 2007)两个类型。 POI 可以到 www.apache.org 下载到。实际运行时,需要有 poi 包就可以了: 开发包: dom4j-1.6.1.jar junit-3.8.1.jar poi-3.8-20120326.jar poi-ooxml-3.8-20120326.jar poi-ooxml-schemas-3.8-20120326.jar xmlbeans-2.3.0.jar POI 很好的采用了面向接口编程的思想,通过面向接口编程,生成 Excel2003 和生成 Excel2007 的代码多数可以复用。在本章中,均采用接口操作 Excel。以下是 HSSF 与 XSSF 相关对象继承的接口: Workbook :excel 文档(工作薄)接口 Sheet: excel 的表单(工作表)接口 Row: excel 的行接口 Cell: excel 的格子单元接口 Font: excel 字体接口 Name: 区域命名接口 DataFormat: 日期格式接口 Header:sheet 头接口 Footer:sheet 尾接口 CellStyle:cell 样式接口 辅助操作包括 DateUtil:日期接口 PrintSetup :打印接口 ErrorConstants:错误信息接口 结合接口,我们来理解一下一个 Excel 的文件的大概的组织形式,一个 Excel 文件对应 172 / 181 于一个 Workbook(工作薄),一个 Workbook 可以有多个 Sheet(工作表)组成,一个 Sheet 是由多个 Row(行)组成,一个 Row 是由多个 Cell(单元格)组成。 3 创建工作簿 (WORKBOOK) 一、创建一个 Excel 2003 工作薄 调用方法:HSSFWorkbook() 实例代码: Workbook wb = new HSSFWorkbook(); FileOutputStream fileOut = new FileOutputStream("workbook.xls"); wb.write(fileOut); fileOut.close(); 二、创建一个 Excel 2007 工作薄 调用方法:XSSFWorkbook() 实例代码: Workbook wb = new XSSFWorkbook(); FileOutputStream fileOut = new FileOutputStream("workbook.xlsx"); wb.write(fileOut); fileOut.close(); 三、通过 Excel(2003 或 2007)文件流对象创建工作薄 调用方法:WorkbookFactory.create(参数); // 参数为 Excel 文件输入流对象。 实例代码: InputStream inp = new FileInputStream("workbook.xls"); //InputStream inp = new FileInputStream("workbook.xlsx"); Workbook wb = WorkbookFactory.create(inp); 4 POI 解析 Excel 4.1 获取工作表 一、通过工作表名称获取 调用方法:getSheet(参数); // 参数为工作表名(String 类型) 实例代码:获取指定名称的工作表 InputStream inp = new FileInputStream("workbook.xls"); //InputStream inp = new FileInputStream("workbook.xlsx"); Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb. getSheet("工作表名"); 二、通过工作表索引号获取 调用方法:getSheetAt(参数); // 参数为工作表索引号(int 类型) 实例代码:获取指定索引号的工作表 InputStream inp = new FileInputStream("workbook.xls"); 173 / 181 //InputStream inp = new FileInputStream("workbook.xlsx"); Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb. getSheetAt(0); 三、遍历工作表 调用方法:getNumberOfSheets() 实例代码: InputStream inp = new FileInputStream("workbook.xls"); // InputStream inp = new FileInputStream("workbook.xlsx"); Workbook wb = WorkbookFactory.create(inp); // 获取工作表数量 int number = wb.getNumberOfSheets(); for(int i = 0; i < number; i++) // for 循环遍历工作表 { Sheet sheet = wb.getSheetAt(i); // Do something here } 4.2 获取行 一、通过行索引号获取行 调用方法:getRow(参数); // 参数为行索引号(int 类型) 实例代码: Sheet sheet = wb.getSheetAt(0); if(sheet != null) { Row row = sheet.getRow(0);// 按行索引号获取行对象实例 // Do something here } 二、遍历行 很多情况下,我们希望遍历一个 sheet 页中所有的行。Sheet 对象提供了一个 rowIterator() 方法对所有行进行遍历。 实例代码: Sheet sheet = wb.getSheetAt(0); // 获取工作表 for (Iterator rit = sheet.rowIterator(); rit.hasNext(); ) // for 循环遍历行 { Row row = rit.next(); // Do something here } 4.3 获取单元格 一、通过当前行单元格索引号获取 调用方法:getRow(参数); // 参数为单元格索引号(int 类型) 174 / 181 实例代码: Sheet sheet = wb.getSheetAt(0); // 获取工作表 if(sheet != null) { Row row = sheet.getRow(0); // 按行索引号获取行对象实例 if(row != null) { Cell cell = row.getCell(0); //通过当前行单元格索引号获取单元格对象实例 // Do something here } } 二、遍历单元格: 很多情况下,我们希望遍历一个 sheet 页中所有的单元格。这可以用一个简单的 for 循 环来实现。Row 对象定义了一个 CellIterator 内部类用来处理单元格的遍历。 实例代码: Sheet sheet = wb.getSheetAt(0); // 获取工作表 for (Iterator rit = sheet.rowIterator(); rit.hasNext(); ) // for 循环遍历行 { Row row = rit.next(); for (Iterator cit = row.cellIterator(); cit.hasNext(); ) // for 循环遍历单元格 { Cell cell = cit.next(); // Do something here } } 除此之外,Sheet 和 Row 对象都实现了 Iterable 接口,因此如果您用的是 Java 1.5 及以 上版本,那么这将十分的方便,因为它支持新的 foreach 循环,我们可以简单的调用内置的 “foreach”来实现。 实例代码: Sheet sheet = wb.getSheetAt(0); // 获取工作表 for (Row row : sheet) // foreach 循环遍历行 { for (Cell cell : row) // foreach 循环遍历单元格 { // Do something here } } 4.4 获得单元格内的内容 要想获得单元格内的内容,您首先要要知道单元格内容的格式(比如您想获得从字符格 式的单元格内获得一个数字,您将得到的是 NumberFormatException 错误)。因此,您将希 望能够切换到该单元格格式,然后为获得该单元格内容调用合适的 getter 方法。 在以下的代码中我们将对一个 sheet 页中的所有单元格进行遍历,并打印出单元格的引 用和内容。 175 / 181 实例代码 Sheet sheet1 = wb.getSheetAt(0); // 获取工作表 for (Row row : sheet1) { for (Cell cell : row) { CellReference cellRef = new CellReference(row.getRowNum(), cell.getCellNum()); System.out.print(cellRef.formatAsString()); System.out.print(" -"); switch(cell.getCellType()) { case Cell.CELL_TYPE_STRING: System.out.println(cell.getRichStringCellValue().getString()); break; case Cell.CELL_TYPE_NUMERIC: if(DateUtil.isCellDateFormatted(cell)) { System.out.println(cell.getDateCellValue()); } else { System.out.println(cell.getNumericCellValue()); } break; case Cell.CELL_TYPE_BOOLEAN: System.out.println(cell.getBooleanCellValue()); break; case Cell.CELL_TYPE_FORMULA: System.out.println(cell.getCellFormula()); break; default: System.out.println(); } } } 5 POI 编辑 Excel 5.1 增加操作 1)增加工作表(SHEET) 调用方法:createSheet(参数); // 参数为工作表名称(String 类型) 实例代码: Workbook wb = new HSSFWorkbook(); // 或者是 new XSSFWorkbook(); // 创建工作表一 Sheet sheet1 = wb.createSheet("new sheet"); // 创建工作表二 Sheet sheet2 = wb.createSheet("second sheet"); // 将 sheet1 页设定为默认选中 176 / 181 Sheet1.setSelected(true); 2)增加单元格(CELL) 调用方法:createRow(参数) ; // 参数为行索引号(int 类型),由 0 开始; createCell(参数) ; // 参数为列索引号(int 类型),由 0 开始; setCellvalue(参数); // 参数为单元格值(可以为 boolean、String、double、 Calendar、Date 和 RichTextString 类型)。 首先,在工作表中创建一行,然后在其中加入多个单元格,行索引号从 0 开始,单元格 的索引号也是从 0 开始,最后给单元格设值。 实例代码: Workbook wb = new HSSFWorkbook(); // 或者是 new XSSFWorkbook(); CreationHelper createHelper = wb.getCreationHelper(); Sheet sheet = wb.createSheet("new sheet"); // 在 sheet 里创建一行,参数为行号(第一行,此处可想象成数组) Row row = sheet.createRow((short)0); // 在 row 里建立新 cell(单元格),参数为列号(第一列) Cell cell = row.createCell(0); // 在单元格其中加入内容. cell.setCellValue(1); // 上面的多行代码也可以采用下面的一行代码的方式完成 row.createCell((short)1).setCellvalue(1.2); // 设置 cell 浮点类型的值 row.createCell((short)2).setCellvalue("test"); // 设置 cell 字符类型的值 row.createCell((short)3).setCellvalue(true); // 设置 cell 布尔类型的值 CellStyle cellStyle = wb.createCellStyle(); // 建立新的 cell 样式 cellStyle.setDataFormat(HSSFDataFormat. getBuiltinFormat("m/d/yy h:mm")); // 设置 cell 样式为定制的日期格式 Cell dCell =row.createCell((short)4); dCell.setCellvalue(new Date()); // 设置 cell 为日期类型的值 dCell.setCellStyle(cellStyle); // 设置该 cell 日期的显示格式 Cell csCell =row.createCell((short)5); csCell.setEncoding(Cell.ENCODING_UTF_16); // 设置 cell 编码解决中文高位字节截断 csCell.setCellvalue("中文测试_Chinese Words Test"); // 设置中西文结合字符串 row.createCell((short)6).setCellType(Cell.CELL_TYPE_ERROR); // 建立错误 cell 3)合并单元格 调用方法: CellRangeAddress(参数 1,参数 2,参数 3,参数 4); // 参数 1 为合并单元格起始行, 参数 2 为终止行,参数 3 为起始列,参数 4 为终止列 addMergedRegion(参数); // 参数为 CellRangeAddress 类型对象 实例代码: Workbook wb = new HSSFWorkbook(); // 或者是 new XSSFWorkbook(); Sheet sheet = wb.createSheet("new sheet"); Row row = sheet.createRow((short) 1); Cell cell = row.createCell((short) 1); cell.setCellValue("This is a test of merging"); sheet.addMergedRegion(new CellRangeAddress( 1, //first row (0-based) 1, //last row (0-based) 177 / 181 1, //first column (0-based) 2 //last column (0-based) )); 5.2 删除操作 1)删除工作表(SHEET) 调用方法:removeSheetAt(参数); //参数为工作表索引号(int 类型) 实例代码: InputStream inp = new FileInputStream("workbook.xls"); // InputStream inp = new FileInputStream("workbook.xlsx"); // 自动判断文件是 2003 还是 2007 版并返回相应的对象实例 Workbook wb = WorkbookFactory.create(inp); // 删除工作表,参数为工作表索引号(int 类型),由 0 开始 wb.removeSheetAt(0); 2)删除行(ROW) 调用方法:removeRow(参数); // 参数为行索引号(int 类型) 实例代码: // 获取 Excel 文件输入流对象实例 InputStream inp = new FileInputStream("workbook.xls"); // InputStream inp = new FileInputStream("workbook.xlsx"); Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb.getSheetAt(0); // 获取行类型对象实例,参数为行索引号(int 类型),由 0 开始 Row row = sheet.getRow(0); // 删除行,参数为行类型对象实例 wb.removeRow(row); 3)删除单元格(CELL) 调用方法:removeCell(参数); //参数为单元格类型对象实例 实例代码: InputStream inp = new FileInputStream("workbook.xls"); // InputStream inp = new FileInputStream("workbook.xlsx"); Workbook wb = WorkbookFactory.create(inp); // 获取行类型对象实例,参数为行索引号(int 类型),由 0 开始 Row row = sheet.getRow(0); // 获取单元格类型对象实例,参数为行索引号(int 类型),由 0 开始 Cell cell = row.getCell(0); // 删除单元格,参数为单元格类型对象实例 row.removeCell(cell); 178 / 181 5.3 修改操作 1)获取 Excel 单元格对象,修改单元格的值 实例代码: // 获取 Excel 文件输入流对象实例 InputStream inp = new FileInputStream("workbook.xls"); // InputStream inp = new FileInputStream("workbook.xlsx"); // 自动判断文件是 2003 还是 2007 版并返回相应的对象实例 Workbook wb = WorkbookFactory.create(inp); Sheet sheet = wb.getSheetAt(0); if(sheet == null) { sheet = wb.createSheet("new sheet"); Row row = sheet.getRow(2); if(row == null) { row = sheet.createRow (2); } Cell cell = row.getCell(3); if (cell == null) { cell = row.createCell(3); } cell.setCellType(Cell.CELL_TYPE_STRING); // 设置单元格的值 cell.setCellValue("a test"); } 2)根据内容调整单元格的宽度 调用方法:autoSizeColumn(); 实例代码: Sheet sheet = workbook.getSheetAt(0); sheet.autoSizeColumn((short)0); // 调整第一列的宽度 sheet.autoSizeColumn((short)1); // 调整第二列的宽度 6 常用方法 6.1 Workbook 工作薄 API Method Comment addPicture(byte[] pictureData, int format) 在工作薄中添加图片 cloneSheet(int sheetNum) 克隆工作薄中一个已存在的工作表 createCellStyle() 创建一个新的单元格样式 179 / 181 createDataFormat() 创建一个日期格式化对象实例 createFont() 创建一个新的字形 createName() 为工作薄创建一个默认名称 createSheet(String sheetname) 创建一个工作表 findFont(short boldWeight, short color, short fontHeight, String name, boolean italic, boolean strikeout, short typeOffset, byte underline) 条件查找字体对象 getActiveSheetIndex() 获取当前活动工作表 getAllPictures() 获取工作薄中所有图片 getCellStyleAt(short idx) 获取单元格样式对象 getFontAt(short idx) 按索引获取字体 getName(String name) 按名称获取区域命名对象 getNameAt(int nameIndex) 按索引获取区域命名对象 getNameIndex(String name) 按名称获取区域命名索引 getNumberOfFonts() 获取字体数量 getNumberOfNames() 获取区域命名数量 getNumberOfSheets() 获取工作薄中工作表的数量 getNumCellStyles() 获取工作薄中包含的样式数量 getPrintArea(int sheetIndex) 获取打印区域 getSheet(String name) 按工作表名称获取工作表 getSheetAt(int index) 按工作表索引号获取工作表 getSheetIndex(Sheet sheet) 获取工作表索引号 getSheetName(int sheet) 获取工作表名称 isSheetHidden(int sheetIx) 判断工作表是否隐藏 isSheetVeryHidden(int sheetIx) 判断工作表是否完全隐藏 removeName(int index) 按指定索引删除命名区域名称 removeName(String name) 删除命名区域名称 removePrintArea(int sheetIndex) 删除指定工作表的打印区域 removeSheetAt(int index) 按工作表索引号删除工作表 setActiveSheet(int sheetIndex) 设置指定工作表为活动状态 setPrintArea(int sheetIndex,int startColumn, int endColumn, int startRow, int endRow) 设置打印区域 setPrintArea(int sheetIndex, String reference) 为给定工作表设置打印区域 setSheetHidden(int sheetIx, boolean hidden) 隐藏指定工作表 setSheetName(int sheet, String name) 设置工作表名称 setSheetOrder(String sheetname, int pos) 设置工作表索引号 write(OutputStream stream) 将当前工作薄写入输出流 6.2 Sheet 工作表 API Method Comment addMergedRegion(CellRangeAddress region) 添加合并单元格 autoSizeColumn(int column) 根据内容调整单元格的宽度 180 / 181 createFreezePane(int colSplit, int rowSplit) 创建冻结窗格 createRow(int rownum) 为工作表创建行 createSplitPane(int xSplitPos,int ySplitPos, int leftmostColumn,int topRow, int activePane) 创建拆分窗格 getCellComment(int row, int column) 获取注释 getColumnStyle(int column) 获取单元格样式 getColumnWidth(int columnIndex) 获取单元格宽度 getDefaultColumnWidth() 获取默认单元格宽度 getDefaultRowHeight() 获取默认行高 getDefaultRowHeightInPoints() 获取默认行高(单位:像素) getFirstRowNum() 获取工作表第一行 getFooter() 获取页脚 getHeader() 获取页眉 getLastRowNum() 获取工作表最后一行 getMergedRegion(int index) 获取合并单元格 getNumMergedRegions() 获取合并单元格数量 getPaneInformation() 获取当前窗格信息(拆分或冻结) getPhysicalNumberOfRows() 获得实际存在的行的总数 getPrintSetup() 获取打印设置对象 getRow(int rownum) 获取工作表行对象 getSheetName() 获取工作表名称 getWorkbook() 获取工作表所属工作薄 isColumnHidden(int columnIndex) 判断某列是否隐藏 removeMergedRegion(int index) 从工作表中删除合并单元格 removeRow(Row row) 从工作表中删除行 rowIterator() 工作表物理行列表 setColumnWidth(int columnIndex, int width) 设置工作表列宽 setSelected(boolean value) 设置工作表选中标识 6.3 Row 行 API Method Comment cellIterator() 行包含的单元格列表 createCell(int column) 在当前行创建新单元格 getCell(int cellnum) 获取单元格对象 getFirstCellNum() 获取当前行第一个单元格的索引号 getHeight() 获取单元格高 getHeightInPoints() 获取单元格高(单位:像素) getLastCellNum() 获取当前行最后一个单元格的索引号 getRowNum() 获取当前行的行索引号 getSheet() 获取当前行所属工作表 removeCell(Cell cell) 删除单元格 setHeight(short height) 设置行高 setHeightInPoints(float height) 设置行高(单位:像素) 181 / 181 setRowNum(int rowNum) 设置当前行的行索引号 6.4 Cell 单元格 API Method Comment getBooleanCellValue() 获取单元格值(布尔类型) getCellComment() 获取单元格注释 getCellFormula() 获取单元格值(公式) getCellStyle() 获取单元格样式 getCellType() 获取单元格类型 getColumnIndex() 获取单元格列索引号 getDateCellValue() 获取单元格值(日期类型) getHyperlink() 获取超链接 getNumericCellValue() 获取单元格值(数值类型) getRichStringCellValue() 获取单元格值(RichTextString 类型) getRowIndex() 获取单元格行索引号 getSheet() 获取单元格所属工作表 getStringCellValue() 获取单元格值(字符类型) removeCellComment() 删除单元格注释 setAsActiveCell() 设置当前单元格为活动单元格 setCellComment(Comment comment) 为当前单元格设置注释 setCellFormula(String formula) 为当前单元格设置公式 setCellStyle(CellStyle style) 为当前单元格设置样式 setCellType(int cellType) 为当前单元格设置类型 setCellValue(boolean value) 为当前单元格设置值 setHyperlink(Hyperlink link) 为当前单元格设置超级链接
还剩180页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

23531271

贡献于2014-07-17

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf