JavaWeb
JDBC案例
初始化数据库
创建数据库
create database javaweb;
创建表
-- ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 存储引擎设置为 InnoDB,支持 事务 (Transaction),保证数据安全性(如转账操作要么全部成功,要么全部失败)。支持 行级锁,并发性能好。支持 外键约束,表与表之间可以建立外键关系。
-- utf8mb4 是 MySQL 里真正的 完整 UTF-8 实现,支持所有 Unicode 字符
CREATE TABLE goods (
id INT AUTO_INCREMENT PRIMARY KEY COMMENT '唯一ID',
fname VARCHAR(100) NOT NULL COMMENT '名字',
price DECIMAL(10,2) NOT NULL COMMENT '价格',
fcount INT NOT NULL COMMENT '库存数量',
remark VARCHAR(255) COMMENT '描述'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
插入数据
INSERT INTO goods (fname, price, fcont, remark) VALUES
('苹果', 3.50, 100, '新鲜的红富士苹果'),
('梨', 2.80, 80, '香甜多汁的雪梨'),
('桃子', 5.20, 60, '水蜜桃,入口即化'),
('香蕉', 4.00, 120, '进口香蕉,口感香甜'),
('橙子', 3.80, 90, '维C丰富的橙子'),
('西瓜', 15.00, 30, '大西瓜,清凉解暑');
数据库连接池
druid使用步骤:
方式1:手动导jar包
1 把druid-1.1.10.jar包放在libs文件夹中
2 选中libs文件夹 右键 add as library
3 选择要使用的范围
4 这样就可以下拉查看里边的class文件了
方式2:使用maven管理,在pom.xml上添加
以后再使用
新建jdbc.properties,位于 src/jdbc.properties
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/yourdb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username=root
password=123456
# 连接池参数
initialSize=5
maxActive=20
minIdle=5
maxWait=60000
修改 DBUtil.java
,使用 Druid,位于 src/com.fruit.utils.DButil.java
package com.example.project.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class DBUtil {
private static DataSource dataSource;
// 初始化连接池
static {
try {
Properties prop = new Properties();
InputStream in = DBUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
prop.load(in);
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws Exception {
return dataSource.getConnection();
}
// 关闭资源
public static void close(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close(); // 归还连接到连接池
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试类
package com.example.project.test;
import com.example.project.utils.DBUtil;
import java.sql.Connection;
public class TestDB {
public static void main(String[] args) throws Exception {
Connection conn = DBUtil.getConnection();
System.out.println("获取连接成功: " + conn);
conn.close(); // 归还到池
}
}
创建JDBC
在根目录创建libs文件夹,导入 druid 和 mysql 驱动包。把libs作为 Add as Library
创建dao目录(src/dao),pojo目录(src/pojo),utils目录(src/utils),test目录(test/)。
创建 数据库配置文件 jdbc.properties (src/jdbc.properties)。
封装mysql数据库中表的实体类:src/pojo/Fruit.java
package com.fruit.pojo;
import java.math.BigDecimal;
public class Fruit {
// 私有属性
private Integer id;
private String fname;
private BigDecimal price; // 用 BigDecimal 存金额
private Integer fcount;
private String remark;
public Fruit() {
}
public Fruit(Integer id, String fname, BigDecimal price, Integer fcount, String remark) {
this.id = id;
this.fname = fname;
this.price = price;
this.fcount = fcount;
this.remark = remark;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFname() {
return fname;
}
public void setFname(String fname) {
this.fname = fname;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getFcount() {
return fcount;
}
public void setFcount(Integer fcount) {
this.fcount = fcount;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@Override
public String toString() {
return "Fruit{" +
"id=" + id +
", fname='" + fname + '\'' +
", price=" + price +
", fcount=" + fcount +
", remark='" + remark + '\'' +
'}';
}
}
封装数据库连接池工具类:src/utils/DButil.java
package com.fruit.utils;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class DButil {
// 定义静态数据
private static String DRIVER;
private static String URL;
private static String USER;
private static String PWD;
// 定义静态的 数据库连接池对象
private static DruidDataSource dataSource;
// 静态代码块,在类加载时读取配置
static {
try {
// 创建Properties Map集合类
Properties prop = new Properties();
// 获取当前类加载器,获取 jdbc的读取流
InputStream in = DButil.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 加载配置文件
prop.load(in);
// 获取数据库连接池对象(方式1)
// 方式 1:DruidDataSourceFactory.createDataSource(prop)
// 直接用 工厂方法 根据 Properties 配置生成一个 DruidDataSource 对象
// 配置集中在 jdbc.properties 文件里,支持 Druid 的各种高级配置
// dataSource = DruidDataSourceFactory.createDataSource(prop);
// 创建数据库连接池对象(方式2)
// 手动创建 Druid 连接池对象,然后逐个设置属性
dataSource = new DruidDataSource();
// 获取properties文件中的值
DRIVER = prop.getProperty("DRIVER");
URL = prop.getProperty("URL");
USER = prop.getProperty("USER");
PWD = prop.getProperty("PWD");
// 加载mysql驱动(数据库连接池 Druid会自 动加载mysql驱动)
// Class.forName(DRIVER);
// 设置用户名,密码
dataSource.setDriverClassName(DRIVER);
dataSource.setUrl(URL);
dataSource.setUsername(USER);
dataSource.setPassword(PWD);
} catch (Exception e) {
e.printStackTrace();
}
}
// 获取连接,返回一个连接对象
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// 关闭连接
public static void close(Connection conn, Statement stmt, ResultSet rs) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
封装反射的工具类 ClassUtil.java (src/utils/ClassUtil)
package com.fruit.utils;
import java.lang.reflect.Field;
public class ClassUtil {
// 获取 Class对象
public static Class getEntityClass(String entityName){
try {
return Class.forName(entityName);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
// 通过反射创建Class对象的实例
public static Object createInstance(String entityName){
try {
return getEntityClass(entityName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
// 通过反射给实例的属性赋值
public static void setProperty(Object obj,String propertyName,Object propertyValue){
try {
Field field = obj.getClass().getDeclaredField(propertyName);
// 忽略警告
field.setAccessible(true);
// 赋值
field.set(obj,propertyValue);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
封装通用 BaseDao (src/dao/BaseDao.java)
package com.fruit.dao;
import com.fruit.utils.ClassUtil;
import com.fruit.utils.DButil;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
// 定义BaseDao为一个抽象类
public abstract class BaseDao<T> {
// 定义泛型的名称
private String entityClassName;
// 定义ResultSet结果集
private ResultSet rs;
// 连接池对象
Connection connection = null;
// sql语句对象
PreparedStatement pstm = null;
// 在无参构造中,获取泛型类型,子类调用构造,默认调用父类的无参构造
public BaseDao(){
// 调用
getEntityClassName();
}
// 获取子类实例给父类泛型T传入的名称
private void getEntityClassName(){
// 通过子类实例对象,获取父类(自己)的泛型T的实际名称
// 此处的this代表的是FruitDaoImpl实例,而不是BaseDao
// this.getClass()得到的就是FruitDaoImpl的Class对象
// getGenericSuperclass() 获取带有泛型的父类,因此可以获取到 BaseDao<Fruit>
// 因为我们是这样定义的:class FruitDaoImpl extends BaseDao<Fruit>,所以泛型父类是: BaseDao<Fruit>
Type genericSuperclass = this.getClass().getGenericSuperclass();
// 把父类的泛型信息,从通用的 Type 强转为 ParameterizedType,以便后续获取实际的泛型参数。
// 强转为ParameterizedType类型
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
// getActualTypeArguments 获取实际的类型参数
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
// 因为当前BaseDao<T>后面只有一个泛型位置,所以此处我们使用的是[0]
// getTypeName() 获取类型名称
// getTypeName() 返回完整类名,例如 "com.xxx.pojo.Fruit"
String typeName = actualTypeArguments[0].getTypeName();
entityClassName = typeName;
}
// 定义设置参数的方法
private void setParams(PreparedStatement psmt , Object... params) throws SQLException {
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
psmt.setObject(i+1,params[i]);
}
}
}
// 执行增删改的操作
protected int executeUpdate(String sql,Object ...params){
// 去除空格,并转为小写
sql = sql.trim().toUpperCase();
// 设置标记是否是插入语句
boolean insertFlag = sql.startsWith("INSERT INTO");
// 获取连接对象
connection = DButil.getConnection();
try {
// 判断是否是插入语句
if (insertFlag){
// 获取sql执行语句对象,插入语句
pstm = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
}else{ // 非插入语句
pstm = connection.prepareStatement(sql);
}
// 给sql语句传入参数
setParams(pstm,params);
// 执行sql
int resRow = pstm.executeUpdate();
// 返回
if(insertFlag) { // 如果是插入语句
// 获取自增id
rs = pstm.getGeneratedKeys();
// 如果返回有值
if(rs.next()){
// 获取第一列数据
return (rs.getInt(1));
}
}else{
return resRow; // 返回默认受影响行数
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭连接
DButil.close(connection,pstm,rs);
}
return 0;
}
// 查询的方法
protected List<T> executeQuery(String sql,Object ...params){
List<T> list = new ArrayList<>();
connection = DButil.getConnection();
try {
// 获取statement对象
pstm = connection.prepareStatement(sql);
// 设置SQL参数
setParams(pstm,params);
// 执行SQL
rs = pstm.executeQuery();
// 方式1:通过反射来处理
// 方式2:通过数据解析器来处理(见JDBC章节)
// 获取结果集的元数据,也就是每一行的数据
ResultSetMetaData metaData = rs.getMetaData();
// 获取元数据的列数
int columnCount = metaData.getColumnCount();
// 遍历结果集
while(rs.next()){
// 通过反射获取实体类的Class对象
Class entityClass = ClassUtil.getEntityClass(entityClassName);
// 通过反射创建实例,强转为T类型
T instance = (T)ClassUtil.createInstance(entityClassName);
// 遍历
for (int i = 0; i < columnCount; i++) {
// 读取列名
String columnName = metaData.getColumnName(i + 1);
// 获取当前行指定列的值
Object columnValue = rs.getObject(i + 1);
// 给实例赋值
ClassUtil.setProperty(instance,columnName,columnValue);
}
// 集合中添加元素
list.add(instance);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DButil.close(connection,pstm,rs);
}
return list;
}
}
定义具体类的DAO接口:src/dao/FruitDao.java
package com.fruit.dao;
import com.fruit.pojo.Fruit;
import java.util.List;
public interface FruitDao {
// 获取所有的库存记录
List<Fruit> getFruitList();
// 添加新库存
void addFruit(Fruit fruit);
// 删除指定的库存记录
void delFruit(Integer id);
// 获取指定的库存记录
Fruit getFruit(Integer id);
Fruit getFruit(String name);
// 修改库存记录
void updateFruit(Fruit fruit);
}
定义实体类DAO接口的实现类 src/dao/impl/FruitDaoImpl.java
package com.fruit.dao.impl;
import com.fruit.dao.BaseDao;
import com.fruit.dao.FruitDao;
import com.fruit.pojo.Fruit;
import java.util.List;
public class FruitDaoImpl extends BaseDao<Fruit> implements FruitDao {
@Override
public List<Fruit> getFruitList() {
String sql = "select * from goods";
List<Fruit> fruits = this.executeQuery(sql, null);
return fruits;
}
@Override
public void addFruit(Fruit fruit) {
String sql = "insert into goods values(0,?,?,?,?)";
int resRow = this.executeUpdate(sql, fruit.getFname(), fruit.getPrice(), fruit.getFcount(), fruit.getRemark());
// 打印受影响行数
System.out.println("resRow = " + resRow);
}
@Override
public void delFruit(Integer id) {
}
@Override
public Fruit getFruit(Integer id) {
return null;
}
@Override
public Fruit getFruit(String name) {
return null;
}
@Override
public void updateFruit(Fruit fruit) {
}
}
定义 JDBC配置文件:src/jdbc.properties
DRIVER=com.mysql.cj.jdbc.Driver
URL=jdbc:mysql://192.168.1.38:3306/javaweb?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
USER=hanser
PWD=234234
测试类:test/TestFruit.java
import com.fruit.dao.FruitDao;
import com.fruit.dao.impl.FruitDaoImpl;
import com.fruit.pojo.Fruit;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.List;
public class TestFruit {
// 创建fruitDao实例
private FruitDao fruitDao = new FruitDaoImpl();
@Test
public void test01 (){
BigDecimal bigDecimal = new BigDecimal("6.00");
Fruit fruit = new Fruit(10,"黄桃",bigDecimal,60,"黄桃很好吃");
fruitDao.addFruit(fruit);
}
@Test
public void test02 (){
List<Fruit> fruitList = fruitDao.getFruitList();
for (Fruit fruit : fruitList) {
System.out.println("fruit = " + fruit);
}
}
}
相关API接口
DataSource接口
DataSource
是 JDBC 规范接口,它是 DriverManager
的替代方案,用于获取数据库连接。
// 简单理解:DataSource 提供数据库连接
DataSource ds = ...;
Connection conn = ds.getConnection();
方法
getConnection()
作用:获取数据库连接
参数:无
返回值:Connection对象
示例:
Connection getConnection() throws SQLException; // 获取数据库连接
public interface DataSource {
Connection getConnection() throws SQLException; // 获取数据库连接
Connection getConnection(String username, String password) throws SQLException; // 指定用户名密码
PrintWriter getLogWriter() throws SQLException; // 获取日志输出
void setLogWriter(PrintWriter out) throws SQLException; // 设置日志输出
void setLoginTimeout(int seconds) throws SQLException; // 设置超时时间
int getLoginTimeout() throws SQLException; // 获取超时时间
java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException; // 日志
}
DruidDataSource接口
构造方法
// 无参构造
public DruidDataSource()
// 有参构造(常见写法是先 new 再 set,不太用到有参构造)
public DruidDataSource(boolean lockFair)
// lockFair: 公平锁标志,一般默认 false 即可
常用方法
// 基础配置
setDriverClassName(String driverClassName) // 设置 JDBC 驱动类
setUrl(String url) // 设置数据库连接 URL
setUsername(String username) // 设置数据库用户名
setPassword(String password) // 设置数据库密码
// 连接池管理
setInitialSize(int size) // 设置初始化时创建的连接数
setMaxActive(int maxActive) // 设置最大连接数
setMinIdle(int minIdle) // 设置最小空闲连接数
setMaxWait(long millis) // 获取连接时最大等待时间(毫秒)
// 超时与检测
setValidationQuery(String sql) // 用来检测连接是否有效的 SQL,如 "SELECT 1"
setTestOnBorrow(boolean flag) // 从连接池取连接时,是否检测有效性
setTestOnReturn(boolean flag) // 还回连接时,是否检测有效性
setTestWhileIdle(boolean flag) // 空闲时是否检测有效性
setTimeBetweenEvictionRunsMillis(long millis) // 检测线程运行间隔
setMinEvictableIdleTimeMillis(long millis) // 连接保持空闲而不被驱逐的最小时间
// 获取连接
Connection getConnection()
Connection getConnection(String username, String password)
// 关闭
close() // 关闭数据源,释放资源
DruidDataSourceFactory工厂类
DruidDataSourceFactory
并不是接口,而是阿里 Druid 提供的一个 工厂类,用于通过配置(Properties
)快速创建 DruidDataSource
对象。
构造方法
// 私有构造(工具类,不允许实例化)
private DruidDataSourceFactory()
// 所以你不能 new DruidDataSourceFactory(),只能直接调用它的 静态方法
常用方法
createDataSource(Properties properties)
作用:
根据传入的配置(Properties 对象),创建并返回一个 DruidDataSource 数据源。
参数:
properties —— 数据源配置信息,常见 key 包括:
driverClassName 数据库驱动类
url 数据库连接地址
username 用户名
password 密码
initialSize 初始化连接数
maxActive 最大连接数
minIdle 最小空闲连接数
maxWait 最大等待时间
validationQuery 用于检测连接是否可用的 SQL
返回值:
DataSource —— 返回一个配置好的 DruidDataSource 对象
示例:
Properties props = new Properties();
props.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/test");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource ds = DruidDataSourceFactory.createDataSource(props);
Connection conn = ds.getConnection();
System.out.println("连接成功:" + conn);
conn.close();
Java DOM 解析核心类
以下都是 都是 Java 标准库 JAXP (Java API for XML Processing) 中 DOM 解析 的核心类。
🔖 DocumentBuilderFactory工厂类
- 包名:
javax.xml.parsers
- 作用:工厂模式类,用来创建
DocumentBuilder
实例。 - 特点:
单例工厂,不直接 new。可以配置一些解析特性,比如是否校验 DTD、是否忽略空白节点。
- 使用示例:
// 创建factory工厂类实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 忽略空白
factory.setIgnoringElementContentWhitespace(true);
// 创建DocumentBuilder实例
DocumentBuilder builder = factory.newDocumentBuilder();
🔖 DocumentBuilder接口
-
包名:
javax.xml.parsers
-
作用:解析 XML 文件并生成 DOM 树(即
Document
对象)。 -
特点:不能直接 new,只能通过
DocumentBuilderFactory
获取。 -
方法
parse()
作用:把 XML 文件转换成内存中的 DOM 对象。
参数:InputStream输入流对象
返回值:dom对象
示例:
// 获取xml文件的输入流对象
InputStream in = TestXml.class.getClassLoader().getResourceAsStream("students.xml");
// 创建dom工厂对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 创建documentBuilder对象
DocumentBuilder builder = factory.newDocumentBuilder();
// 创建dom对象
Document dom = builder.parse(in);
🔖 Document类
- 包名:
org.w3c.dom
- 作用:表示整个 XML 文档,是 DOM 树的根节点(所有内容的入口)。
- 特点:
- 相当于“内存里的 XML 文档对象”。
- 可以通过它获取根元素、创建元素、操作节点。
- 通过此类可以获取XML文件的元素DOM对象
- 常用方法:
Element element = getDocumentElement()
获取根元素
Element newStudent = createElement(String tagName)
创建新元素
Node node = createTextNode(String data)
创建文本节点
NodeList nodeList = getElementsByTagName(String tagName);
获取元素
// 获取xml文件的输入流对象
InputStream in = TestXml.class.getClassLoader().getResourceAsStream("students.xml");
// 创建dom工厂对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 创建documentBuilder对象
DocumentBuilder builder = factory.newDocumentBuilder();
// 创建dom对象
Document dom = builder.parse(in);
// 获取元素
NodeList studentNodeList = dom.getElementsByTagName("student");
// 1. 获取根元素
Element root = dom.getDocumentElement(); // <students>
// 2. 创建一个新 <student> 元素
Element newStudent = dom.createElement("student");
newStudent.setAttribute("sid", "s003");
// 3. 创建 <sname> 元素并加上文本
Element sname = dom.createElement("sname");
sname.appendChild(dom.createTextNode("Tom"));
newStudent.appendChild(sname);
// 4. 挂到根元素上
root.appendChild(newStudent);
// 5. 获取所有 student 节点
NodeList studentList = dom.getElementsByTagName("student");
System.out.println("总共有 " + studentList.getLength() + " 个学生");
🔖 Element类
包名:org.w3c.dom
作用:表示 XML 文档中的 元素节点(带标签的部分,如 <student>
)。
特点:
Element
是Node
的子接口,专门用来操作标签。- 可以获取元素名、子元素、属性等。
常用方法:
getTagName()
获取标签名
getAttribute(String name)
获取属性值
getAttributes()
获取属性值列表
setAttribute(String name, String value)
设置属性
getElementsByTagName(String name)
获取子元素列表
getTextContent()
获取元素内的文本内容
使用示例:
Element student = (Element) root.getElementsByTagName("student").item(0);
String sid = student.getAttribute("sid");
String name = student.getElementsByTagName("sname").item(0).getTextContent();
🔖 Node类
包名:org.w3c.dom
作用:XML 文档中所有 DOM 节点的父接口。
特点:
- 一个 XML 文档里的所有部分都是
Node
(元素、文本、注释等)。 Element类
、Text 类
、Comment类
都继承自Node
。
常用方法:
getNodeName()
节点名(元素名、属性名等)
getNodeValue()
节点值(通常文本节点才有值)
getNodeType()
节点类型(如元素=1,属性=2,文本=3,注释=8)
getChildNodes()
获取子节点列表
getParentNode()
获取父节点
getFirstChild()
获取第一个子节点
示例:
NodeList students = root.getChildNodes();
for (int i = 0; i < students.getLength(); i++) {
Node node = students.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
System.out.println("元素节点: " + node.getNodeName());
}
}
🔖 NodeList类
-
包名:
org.w3c.dom
-
作用:表示 节点列表,即某个节点的子节点集合。
-
特点:
-
NodeList
不是List
或ArrayList
,更像一个“只读数组”。
-
通过索引来访问里面的节点。
-
由 DOM 解析器返回,不能直接 new 出来。
-
常见场景:通过
getElementsByTagName()
、getChildNodes()
得到。
-
-
方法
int getLength()
返回列表中节点的数量
Node item(int index)
根据索引获取节点(从 0 开始)
// 获取所有 <student> 元素
NodeList students = dom.getElementsByTagName("student");
for (int i = 0; i < students.getLength(); i++) {
Node node = students.item(i); // 获取单个 student 节点
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element student = (Element) node;
String sid = student.getAttribute("sid");
String name = student.getElementsByTagName("sname").item(0).getTextContent();
String age = student.getElementsByTagName("age").item(0).getTextContent();
String address = student.getElementsByTagName("address").item(0).getTextContent();
System.out.println("学号:" + sid + ", 姓名:" + name + ", 年龄:" + age + ", 地址:" + address);
}
}
🔖 总结:
DocumentBuilderFactory → 工厂,生产解析器。
DocumentBuilder → 解析器,负责把 XML 文件读进来。
Document → 整个 XML 文档(根对象)。
Element → XML 元素(标签,如 <student>)。
Node → 所有节点的通用接口,Element/Text/Comment 都继承它。
Java 的
org.w3c.dom
包 和 浏览器里的 JavaScript DOM API 都是基于 W3C DOM 标准 实现的,所以有很多方法名、概念是一一对应的。不同点在于:
- Java 常用于 后端/工具类应用,解析 XML 文件。
- JavaScript 常用于 前端,操作 HTML 页面。
XML语法
HTML语言是xml语言的子集。xml 的标签属性和html 的标签属性是非常类似的,一个标签上可以书写多个属性。每个属性的值必须使用引号引起来。
XML组成
(1) XML 声明(Declaration)
固定写法,永远放在第一行。指明 XML 版本、文档编码方式。
<?xml version="1.0" encoding="UTF-8"?>
(2) 文档类型定义(DTD, Document Type Definition)
作用:约束和规范 XML 文档中元素、属性的写法。
位置:XML 声明之后,XML 正文之前。
可选:可以没有。
(3) XML 正文(Content / Document Body)
XML 的主要内容,必须有 唯一根元素
<note>
<to>Tom</to>
<from>Jerry</from>
<heading>Reminder</heading>
<body>Don’t forget XML homework!</body>
</note>
XML 可以省略 DTD
完整写法(声明 + DTD + 正文)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note SYSTEM "note.dtd">
<note> ... </note>
简写(只有声明 + 正文)
<?xml version="1.0" encoding="UTF-8"?>
<note> ... </note>
示例:
<?xml version="1.0" encoding="UTF-8"?>
<!-- DOCTYPE : 表示开始进行文档类型定义
students 表示当前文档的根元素是students
ELEMENT : 表示开始定义元素
students (student*) 表示 students这个元素内部有0~n个子元素student
student (sname ,age , address) 表示student里面依次有且仅有一个sname,age,address
#PCDATA 相当于String,相当于数据库中的varchar,表示字符串,文本
ATTLIST student sid ID #REQUIRED ATTLIST表示属性列表,表示student的属性sid
ID相当于数据库的主键(唯一)
#REQUIRED 相当于数据库的not null , 表示必须的,不能没有
IDREF 表示这一列的取值必须来源于某一个ID的值
#IMPLIED 表示这个属性可有可无
-->
<!DOCTYPE students[
<!ELEMENT students (student*)>
<!ELEMENT student (sname ,age , address)>
<!ELEMENT sname (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!ATTLIST student sid ID #REQUIRED>
<!ATTLIST student gid IDREF #IMPLIED>
]>
<students>
<student sid="s001">
<sname>jim</sname>
<age>18</age>
<address>USA</address>
</student>
<student sid="s002" gid="s001">
<sname>鸠摩智</sname>
<age>30</age>
<address>吐蕃</address>
</student>
</students>
语法规则
- 必须有一个根元素(唯一)。
- 标签必须成对出现,大小写敏感。
- 属性值必须加引号(单双引号均可)。
- 必须正确嵌套(不允许交叉)。
<a><b></b></a> <!-- 正确 -->
<a><b></a></b> <!-- 错误 -->
- 特殊符号必须转义
<、>、&
- 注释格式:
<!-- 注释内容 -->
- 可以包含 CDATA 区块,原样保存内容:
<![CDATA[ <username>Tom & Jerry</username> ]]>
两种规范
(1) 格式良好(Well-Formed)
- 必须满足 XML 基本语法规则
- 只要语法没错,就算格式良好
(2) 有效(Valid)
- 在 格式良好 的基础上,还必须 符合 DTD 或 XML Schema 的约束。
- 简单理解就是 : 有效 = 语法正确 + 内容符合规则
解析XML
在 Java 里解析 XML 文件的方式有很多,常见的有三大类:DOM、SAX、StAX。另外还有一些常用的第三方库(JDOM、dom4j、Jackson、FastXML 等)
把xml文件放置在 src 的同级目录下 resources
(没有则创建),然后把这个文件夹定义为Resources Root
文件夹,把students.xml文件放在里边。resources/students.xml
<!-- DOCTYPE : 表示开始进行文档类型定义
students 表示当前文档的根元素是students
ELEMENT : 表示开始定义元素
students (student*) 表示 students这个元素内部有0~n个子元素student
student (sname ,age , address) 表示student里面依次有且仅有一个sname,age,address
#PCDATA 相当于String,相当于数据库中的varchar,表示字符串,文本
ATTLIST student sid ID #REQUIRED ATTLIST表示属性列表,表示student的属性sid
ID相当于数据库的主键(唯一)
#REQUIRED 相当于数据库的not null , 表示必须的,不能没有
IDREF 表示这一列的取值必须来源于某一个ID的值
#IMPLIED 表示这个属性可有可无
-->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE students[
<!ELEMENT students (student*)>
<!ELEMENT student (sname ,age , address)>
<!ELEMENT sname (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!ATTLIST student sid ID #REQUIRED>
<!ATTLIST student gid IDREF #IMPLIED>
]>
<students>
<student sid="s001">
<sname>jim</sname>
<age>18</age>
<address>USA</address>
</student>
<student sid="s002" gid="s001">
<sname>鸠摩智</sname>
<age>30</age>
<address>吐蕃</address>
</student>
</students>
DOM解析
把整个 XML 文档一次性加载到内存,构造成树形结构(Document
对象)。适合小型 XML,操作方便
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.io.File;
public class DOMParseExample {
public static void main(String[] args) {
try {
// 1. 创建 DocumentBuilderFactory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 2. 创建 DocumentBuilder
DocumentBuilder builder = factory.newDocumentBuilder();
// 3. 解析 XML 文件
Document document = builder.parse(new File("students.xml"));
// 4. 获取根元素
Element root = document.getDocumentElement();
System.out.println("根元素: " + root.getNodeName());
// 5. 获取所有 student 节点
NodeList studentList = root.getElementsByTagName("student");
for (int i = 0; i < studentList.getLength(); i++) {
Element student = (Element) studentList.item(i);
String sid = student.getAttribute("sid");
String name = student.getElementsByTagName("sname").item(0).getTextContent();
String age = student.getElementsByTagName("age").item(0).getTextContent();
String address = student.getElementsByTagName("address").item(0).getTextContent();
System.out.println(sid + " | " + name + " | " + age + " | " + address);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
示例2:
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
public class TestXml {
@Test
public void TestXml() throws ParserConfigurationException, IOException, SAXException {
// 获取xml文件的输入流对象
InputStream in = TestXml.class.getClassLoader().getResourceAsStream("students.xml");
// 创建dom工厂对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 创建documentBuilder对象
DocumentBuilder builder = factory.newDocumentBuilder();
// 创建DOM对象
Document dom = builder.parse(in);
// 获取元素
NodeList studentNodeList = dom.getElementsByTagName("student");
for (int i = 0; i < studentNodeList.getLength(); i++) {
// 获取NodeList中的每一个元素节点
Node nodeItem = studentNodeList.item(i);
// 把Node类型 强转为 Element类型
Element eleItem = (Element) nodeItem;
// 使用Element中的 方法,获取标签中的属性
String sid = eleItem.getAttribute("sid");
String gid = eleItem.getAttribute("gid");
// 打印
System.out.println("sid 和 gid 分别是 " + sid + gid);
// 获取标签内的子标签
NodeList childNodes = eleItem.getChildNodes();
// 遍历子标签节点
for (int j = 0; j < childNodes.getLength(); j++) {
// 获取子标签内的节点元素
Node nodeChildItem = childNodes.item(j);
// 判断子标签内节点类型是否是元素类型
if (nodeChildItem.getNodeType() == Node.ELEMENT_NODE){
// 获取子节点的内的第一个子节点
Node firstChild = nodeChildItem.getFirstChild();
// 如果第一个子节点的子节点是文本节点则读取值
if (firstChild.getNodeType() == Node.TEXT_NODE){
String nodeValue = firstChild.getNodeValue();
System.out.println("nodeValue = " + nodeValue);
}
}
}
}
}
}
第三方库
在实际开发中,很多人用第三方库来解析 XML,操作更简洁:
- JDOM:比 DOM/SAX 简单。
- dom4j:功能强大,Hibernate、Spring 早期大量使用。
- Jackson / Gson:支持 XML, JSON 转换。
实际开发:更多用 dom4j 或者直接转 JSON。