JAXP案例
一、需求:一个基本的学生成绩管理系统,用xml文档模拟数据库,要求能够向数据库实时增删改查学生成绩数据
二、需求分析:
1.需要分层定义和实现,在用户层由用户输入查询信息,在底层做好数据接收,异常抛出、处理和程序测试,保证在UI层不会出现未捕获和处理的异常,并能正确处理非法输入和操作信息不存在的情况,给用户一个良好的用户体验。
2.定义学生类封装id,name,scores等数据,定义学生工具类根据查询关键字进行各种学生信息处理,适当利用容器存储多个学生对象,层层抽取简化,在用户层抽取成只是简单接收和调用。
三、文档,各层接口和所用技术
(开发时逐步更新)
四、具体流程实现
1.xml文档和简单学生类:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><exam> <student examid="222" idcard="111"> <name>张三</name> <location>沈阳</location> <grade>89</grade> </student> <student examid="444" idcard="333"> <name>李四</name> <location>大连</location> <grade>89</grade> </student> </exam>
package cn.itcast.domain; public class Student { private String examid; private String idcard; private String name; private String location; private double grade; /** * @return the examid */ String getExamid() { return examid; } /** * @param examid the examid to set */ void setExamid(String examid) { this.examid = examid; } /** * @return the idcard */ String getIdcard() { return idcard; } /** * @param idcard the idcard to set */ void setIdcard(String idcard) { this.idcard = idcard; } /** * @return the name */ String getName() { return name; } /** * @param name the name to set */ void setName(String name) { this.name = name; } /** * @return the location */ String getLocation() { return location; } /** * @param location the location to set */ void setLocation(String location) { this.location = location; } /** * @return the grade */ double getGrade() { return grade; } /** * @param grade the grade to set */ void setGrade(double grade) { this.grade = grade; } }
从学生工具类中剥离、抽取出的主要功能:各种处理需求中JAXP对xml的一些统一操作封装成工具类抽取出来,一个特殊的用于信息返回目的的自定义异常类(后面会发觉到)
抽取出操作xml文档的工具类:
public class XmlUtils { //用xml文档创建Document对象 public static Document getDocument(String filename) throws Exception{ DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); DocumentBuilder builder=factory.newDocumentBuilder(); return builder.parse(filename); } //将Document对象写回xml文档 public static void write2XML(Document document,String filename) throws Exception{ TransformerFactory factory=TransformerFactory.newInstance(); Transformer tf=factory.newTransformer(); tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream(filename))); } }
一个特殊的用于信息返回目的的自定义异常类(后面用到):
package cn.itcast.exception; public class StudentNotExistsException extends Exception { public StudentNotExistsException() { super(); // TODO Auto-generated constructor stub } public StudentNotExistsException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public StudentNotExistsException(String message) { super(message); // TODO Auto-generated constructor stub } public StudentNotExistsException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } }
package cn.itcast.dao; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import cn.itcast.domain.Student; import cn.itcast.exception.StudentNotExistsException; import cn.itcast.xmlutils.XmlUtils; public class StudentDao { private String filename = "src/exam.xml"; public StudentDao(String filename) { this.filename = filename; } /** * @param filename * the filename to set */ void setFilename(String filename) { this.filename = filename; } // 增删改查 public void add(Student s) { try { // 得到文档对象 Document document = XmlUtils.getDocument(filename); // 从文档对象创建元素节点 Element student_tag = document.createElement("student"); // 为节点添加属性 student_tag.setAttribute("idcard", s.getIdcard()); student_tag.setAttribute("examid", s.getExamid()); // 创建学生姓名,所在地,成绩元素 Element name = document.createElement("name"); Element location = document.createElement("location"); Element grade = document.createElement("grade"); // 设置文本内容 name.setTextContent(s.getName()); location.setTextContent(s.getLocation()); grade.setTextContent(s.getGrade() + "");// 转成字符串 // 挂载到学生元素 student_tag.appendChild(name); student_tag.appendChild(location); student_tag.appendChild(grade); // 学生元素挂载到根节点 document.getElementsByTagName("exam").item(0).appendChild( student_tag); // 更新内存 XmlUtils.write2XML(document, filename); } catch (Exception e) { throw new RuntimeException(e); } } public Student find(String examid) { try { Document document = XmlUtils.getDocument(filename); // 获取学生节点集合 NodeList list = document.getElementsByTagName("student"); for (int i = 0; i < list.getLength(); i++) { // item方法返回Node类型,是Element的父类,需要强转 Element student_tag = (Element) list.item(i); if (student_tag.getAttribute("examid").equals(examid)) { Student s = new Student(); s.setExamid(examid); s.setIdcard(student_tag.getAttribute("idcard")); s.setName(student_tag.getElementsByTagName("name").item(0) .getTextContent()); s.setLocation(student_tag.getElementsByTagName("location") .item(0).getTextContent()); s.setGrade(Double.parseDouble(student_tag .getElementsByTagName("grade").item(0) .getTextContent())); return s; } } return null; } catch (Exception e) { throw new RuntimeException("信息未找到"); } } // 注意上面两个都是内部抓取和处理异常,这里抛一个编译时异常,用于返回信息! public void delete(String name) throws StudentNotExistsException { try { Document document = XmlUtils.getDocument(filename); NodeList list = document.getElementsByTagName("name");// 直接找这个标签 for (int i = 0; i < list.getLength(); i++) { if (list.item(i).getTextContent().equals(name)) { list.item(i).getParentNode().getParentNode().removeChild( list.item(i).getParentNode()); // 找到删除了即更新,返回 XmlUtils.write2XML(document, filename); return; } } throw new StudentNotExistsException(name + "该生不存在"); } catch (StudentNotExistsException e) { // 再次抛出,这个异常不能在这里捕获,一定要以返回信息的目的抛给用户 throw e; } catch (Exception e) { throw new RuntimeException(e);// 这里一定用e,持续传递信息,保证异常链不会断! } } }
3.JUnit测试单元
结合设置断点Debug模式和直接运行测试,通过,其中删除功能,当删除项不存在时,抛出StudentNotExistsException,属于正常可期情况:
package cn.itcast.test; import org.junit.Test; import cn.itcast.dao.StudentDao; import cn.itcast.domain.Student; import cn.itcast.exception.StudentNotExistsException; public class JUnitTest { @Test public void testAdd() { StudentDao dao = new StudentDao(); Student s = new Student(); s.setExamid("flsfl"); s.setIdcard("3950"); s.setName("Mrd"); s.setLocation("beijing"); s.setGrade(99); dao.add(s); } @Test public void testFind() { StudentDao dao = new StudentDao(); Student s = dao.find("flsfl"); } @Test public void testDelete() throws StudentNotExistsException { StudentDao dao = new StudentDao(); dao.delete("Mrd"); } }
package cn.itcast.ui; import java.io.BufferedReader; import java.io.InputStreamReader; import cn.itcast.dao.StudentDao; import cn.itcast.domain.Student; import cn.itcast.exception.StudentNotExistsException; public class Main { public static void main(String[] args) { try { StudentDao dao = new StudentDao(); Student s; while (true) { System.out.print("请输入您的需求:a 添加 b 查询 c 删除"); BufferedReader bf = new BufferedReader(new InputStreamReader( System.in)); String ss = bf.readLine(); if ("a".equals(ss)) { s = new Student();// 不能不new,那是空指针! System.out.print("请输入准考证号:"); s.setExamid(bf.readLine()); System.out.print("请输入id号:"); s.setIdcard(bf.readLine()); System.out.print("请输入姓名:"); s.setName(bf.readLine()); System.out.print("请输入地址:"); s.setLocation(bf.readLine()); System.out.print("请输入分数:"); try { s.setGrade(Double.parseDouble(bf.readLine())); // 这两句应该放在这里,因为上面发生异常这里就不会再执行,而放在外面无论怎样都执行!!! dao.add(s); // add方法未发生异常就成功 System.out.println("添加成功"); } catch (Exception e) { // 这里不能抛,即使是运行时异常,也会被外层捕获,而这里要求输出分数非法信息,顺利结束程序 System.out.println("分数必须为数值"); } } else if ("b".equals(ss)) { System.out.print("请输入准考证号:"); s = dao.find(bf.readLine()); if (s == null) { System.out.println("未找到相关信息"); } else { System.out.println("准考证号:" + s.getExamid()); System.out.println("id号:" + s.getIdcard()); System.out.println("姓名:" + s.getName()); System.out.println("地址:" + s.getLocation()); System.out.println("成绩:" + s.getGrade()); } } else if ("c".equals(ss)) { try { System.out.print("请输入学生姓名:"); dao.delete(bf.readLine()); // 没异常就成功 System.out.println("删除成功");// 这一句确实应该放在这里,因为发生异常就会终止在这一句之前,如果放在外面,则无论怎样都输出这句! } catch (StudentNotExistsException e) { System.out.println(e.getMessage());// 只要这里捕获处理了异常,这里的程序就顺利结束,不会再输出外层的异常! } } } } catch (Exception e) { throw new RuntimeException("系统正忙,请稍后登入"); } } }
add:
请输入您的需求:a 添加 b 查询 c 删除a
请输入准考证号:3400534
请输入id号:64392
请输入姓名:xiaobai
请输入地址:beijing
请输入分数:100
添加成功
请输入您的需求:a 添加 b 查询 c 删除
find:
请输入您的需求:a 添加 b 查询 c 删除b
请输入准考证号:dsfsd
未找到相关信息
请输入您的需求:a 添加 b 查询 c 删除b
请输入准考证号:222
准考证号:222
id号:111
姓名:张三
地址:沈阳
成绩:89.0
请输入您的需求:a 添加 b 查询 c 删除
delete:
请输入您的需求:a 添加 b 查询 c 删除c
请输入学生姓名:sdfsdf
sdfsdf该生不存在
请输入您的需求:a 添加 b 查询 c 删除c
请输入学生姓名:李四
删除成功
请输入您的需求:a 添加 b 查询 c 删除
五、总结(其中更新信息暂时未做)
SAX解析案例
需求:把xml文档中的每本书封装成一个book对象,并把多个对象放到一个List集合中返回
目标xml文档:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架> <书> <书名 name="中科大">javaweb开发</书名> <作者>张孝祥</作者> <售价>99.00元</售价> </书> <书> <书名>JavaScript网页开发</书名> <作者>黎活明</作者> <售价>28.00元</售价> </书> <页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息="aaaa"/><!--实际三个属性,还有一个固定属性和一个默认属性--> </书架>
package cn.itcast.sax; public class Book { private String name; private String author; private String price; /** * @return the name */ String getName() { return name; } /** * @param name the name to set */ void setName(String name) { this.name = name; } /** * @return the author */ String getAuthor() { return author; } /** * @param author the author to set */ void setAuthor(String author) { this.author = author; } /** * @return the price */ String getPrice() { return price; } /** * @param price the price to set */ void setPrice(String price) { this.price = price; } }
class BeanListHandler extends DefaultHandler { private List list = new ArrayList(); private Book book; private String currentTag;// 保存当前标签 // 定义返回list的方法get // 不需要提供set public List getBooks() { return list; } // 复写几个方法 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { // 获取当前标签 currentTag = qName; // 如果是"书",创建一个新的对象 if ("书".equals(currentTag)) {// 注意"书"放前面,避免空指针异常 book = new Book(); } } public void characters(char[] ch, int start, int length) throws SAXException { // 处理内容,如果是"书名","作者","售价" if ("书名".equals(currentTag)) { book.setName(new String(ch, start, length)); } if ("作者".equals(currentTag)) { book.setAuthor(new String(ch, start, length)); } if ("售价".equals(currentTag)) { book.setPrice(new String(ch, start, length)); } } public void endElement(String uri, String localName, String qName) throws SAXException { // "书"结束标签 if ("书".equals(qName)) { list.add(book); book = null;// 置空 } currentTag = null;// 置空 } }
//Sax解析xml文档 public class SaxDemo { @Test public void saxcall() throws ParserConfigurationException, SAXException, IOException { // 工厂 SAXParserFactory factory = SAXParserFactory.newInstance(); // 解析器 SAXParser parser = factory.newSAXParser(); // 读取器 XMLReader reader = parser.getXMLReader(); BeanListHandler handler = new BeanListHandler(); reader.setContentHandler(handler);// 传人内容处理器 // 解析文档 reader.parse("src/book.xml"); // 返回handler中存储信息的list,使用泛型(Book元素类型) List<Book> list = handler.getBooks(); System.out.println(list); } }
获取指定标签内容--->里面的一个标签间空内容引起的问题----->必须亲自尝试,调试,反复仔细观察,不能自以为是,想当然
内容处理器:
// 获取指定标签内容 class TagValueHandler extends DefaultHandler { private String currentTag; private String result = ""; // private List list = new ArrayList();// 这里必须分配空间! private int requiredIndex = 3; private int count = 0; public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { currentTag = qName; // System.out.println(currentTag); count++; } public void characters(char[] ch, int start, int length) throws SAXException { if (count == requiredIndex) { // System.out.println("hello"); result = new String(ch, start, length); // list.add(result); // System.out.println(result); } } public void endElement(String uri, String localName, String qName) throws SAXException { } public String getResult() { // System.out.println(result); return result; // return list; } }
@Test public void saxcall() throws ParserConfigurationException, SAXException, IOException { // 工厂 SAXParserFactory factory = SAXParserFactory.newInstance(); // 解析器 SAXParser parser = factory.newSAXParser(); // 读取器 XMLReader reader = parser.getXMLReader(); TagValueHandler had = new TagValueHandler(); reader.setContentHandler(had); reader.parse("src/book.xml"); System.out.println(had.getResult()); }
用容器保存,附上xml文档,仔细看结果:
xml:
<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架> <书> <书名 name="中科大">javaweb开发</书名> <作者>张孝祥</作者> <售价>99.00元</售价> </书> <书> <书名>JavaScript网页开发</书名> <作者>黎活明</作者> <售价>28.00元</售价> </书> <页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息="aaaa"/><!--实际三个属性,还有一个固定属性和一个默认属性--> </书架>
//Sax解析xml文档 public class SaxDemo { @Test public void saxcall() throws ParserConfigurationException, SAXException, IOException { // 工厂 SAXParserFactory factory = SAXParserFactory.newInstance(); // 解析器 SAXParser parser = factory.newSAXParser(); // 读取器 XMLReader reader = parser.getXMLReader(); TagValueHandler had = new TagValueHandler(); reader.setContentHandler(had); reader.parse("src/book.xml"); System.out.println(had.getResult()); } } // 获取指定标签内容 class TagValueHandler extends DefaultHandler { private String currentTag; private String result = ""; private List list = new ArrayList();// 这里必须分配空间! private int requiredIndex = 3; private int count = 0; public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { currentTag = qName; // System.out.println(currentTag); count++; } public void characters(char[] ch, int start, int length) throws SAXException { if (count == requiredIndex) { // System.out.println("hello"); result = new String(ch, start, length); list.add(result); System.out.println(result); } } public void endElement(String uri, String localName, String qName) throws SAXException { } public List getResult() { // System.out.println(result); // return result; return list; } }
javaweb开发
[javaweb开发,
]
注意"javaweb开发"后面还有两个空行被存储,这是因为在解析完"javaweb开发"所在标签后,解析到了标签间的空行,直接执行character方法,而count仍然是上次的值,count==requiredIndex依然成立,所以空行也被赋给result,也是第一次结果为空(串)的原因。
所以在character方法中加入判断:
public void characters(char[] ch, int start, int length) throws SAXException { if (count == requiredIndex) { // System.out.println("hello"); String s = new String(ch, start, length); s = s.trim(); if (!"".equals(s)) { result = new String(ch, start, length); } // list.add(result); // System.out.println(result); } }
dom4j解析xml文档:
示例:
public static void test(){ try { SAXReader reader = new SAXReader(); Document document = reader.read(new File("src/book.xml"));//尽量用File,保证路径非法时的安全 Element root=document.getRootElement();//根节点 //achieve the second book //标签内容 Element book=(Element)root.elements("书").get(1);//强转,标签名 String value=book.elementText("书名"); System.out.println(value); //标签属性值 Element bookname=book.element("书名"); String value1=bookname.attributeValue("name");//标签属性值 System.out.println(value1); //add an element to the first book Element book0=root.element("书"); //创建并添加 book0.addElement("售价").setText("8888元"); //内存中的Document重写入文本 //编码问题:在这里默认按gb2312写入utf-8格式的xml文件(默认存储编码和xml声明编码一致)必然乱码 //所以要用转换流改码表 //测试发现:即使是gb2312的xml这里用默认的gb2312写也会乱码,用utf-8写则不会乱码,但编码变成utf-8 //这是因为不管xml文件是什么编码,(其字节码)解析到内存中的Document默认都是utf-8! XMLWriter writer = new XMLWriter(new OutputStreamWriter( new FileOutputStream( "src/book.xml" ),"utf-8") ); //所以上面的处理可以,但一般用另一种处理编码的方式保证修改前后码表一致并且不乱码: //格式化输出器(见本博关于中文编码问题的文章) writer.write( document ); writer.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
public static void test2(){ try { SAXReader reader = new SAXReader(); Document document = reader.read(new File("src/book.xml"));//尽量用File,保证路径非法时的安全 //achieve the second book Element root=document.getRootElement();//根节点 //第一本书 Element book=root.element("书");//强转,标签名 //在指定位置添加 //得到子标签集合 List list=book.elements(); //创建标签 Element price=DocumentHelper.createElement("售价"); price.setText("309元"); //加到集合中去,添加到index=2位置(index从0计) list.add(2,price); //指定格式和编码,重新写入 OutputFormat format=OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); XMLWriter writer=new XMLWriter(new FileOutputStream("src/book.xml"),format); writer.write(document); writer.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
//指定位置删除,修改 public static void test3(){ try{ SAXReader reader = new SAXReader(); Document document = reader.read(new File("src/book.xml"));//尽量用File,保证路径非法时的安全 //第一个售价节点 Element price=document.getRootElement().element("书").element("售价"); //调用父节点删除子节点 price.getParent().remove(price); //更新第二本书的作者 Element book=(Element)document.getRootElement().elements("书").get(1); book.element("作者").setText("或黎明"); OutputFormat format=OutputFormat.createPrettyPrint(); format.setEncoding("UTF-8"); XMLWriter writer=new XMLWriter(new FileOutputStream("src/book.xml"),format); writer.write(document); writer.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
XPath结合dom4j文档定位:
需求:查找文档中指定用户名密码用于用户登录
例子:
(注:和XPath相关用Node,//代表所有,这里SingleNode就是第一个)
用于查找的xml文件:
<?xml version="1.0" encoding="UTF-8"?> <users> <user id="1" username="aaa" password="123" email="[email protected]"></user> <user id="2" username="bbb" password="136" email="[email protected]"></user> </users>
//XPath查找用户名密码用于登录 public static void test5() throws Exception{ String username="aaa"; String password="123"; SAXReader reader = new SAXReader(); Document document = reader.read(new File("src/cn/itcast/dom4j/NewFile.xml")); //获取满足XPath条件的单个节点 //注意表达式中引用变量要加单引号!!! //书写表达式技巧:先在双引号中写出所有内容,包括',再用"和+来断开!!! Node node=document.selectSingleNode("//user[@username='"+username+"' and @password='"+password+"']"); if(node==null){ System.out.println("用户名或密码错误"); }else{ System.out.println("登录成功"); } }
登录成功