9. Java-JDBC
JDBC
每天学习新知识,每天进步一点点。
在实际开发中,项目中的数据通常是存储在数据库中的,要想使用其中的数据,就i必须编写程序连接到数据库对数据进行相关操作。Java对数据库的操作提供了相应的支持,它提供了一套可以执行SQL语句的API,即为JDBC
1. 什么是JDBC
JDBC(Java Database Connectivity,Java数据库连接),它是一套用于执行SQL语句的Java API。应用程序可通过这套API连接到关系型数据库,并使用SQL语句来完成对数据库中数据的查询、新增、更新和删除等操作。
不同的数据库(如MySQL、Oracle等)在其内部处理数据的方式是不同的,因此每一个数据库厂商都提供了自己数据库的访问接口。如果直接使用数据库厂商提供的访问接口操作数据库,应用程序的可移植性就会变得很差。例如,用户在当前项目中使用的是MySQL提供的接口操作数据库,如果想要换成Oracle数据库,就需要在项目中重新使用Oracle数据库提供的接口,这样代码的改动量会非常大。有了JDBC后,这种情况就不存在了,因为它要求各个数据库厂商按照统一的规范来提供数据库驱动,在程序中由JDBC和具体的数据库驱动联系,这样应用程序就不必直接与底层的数据库交互,从而使得代码的通用性更强。
应用程序使用JDBC访问数据库的方式如图所示。

图1 应用程序使用JDBC访问数据库方式
2.JDBC常用API
在正式介绍使用JDBC开发数据库应用之前,需要先了解JDBC常用的API。JDBC API主要位于
java.sql包,该包定义了一系列访问数据库的接口与类。
2.1 Dirver 接口
Driver接口是所有JDBC驱动程序必须实现的接口,该接口专门提供给数据库厂商使用。需要注意的是,在编写JDBC程序时,必须要把所使用的数据库驱动程序或类库加载到项目的classpath中(这里指数据库的驱动JAR包)。
2.2 DriverManager 类
DriverManager类用于加载JDBC驱动并且创建与数据库的连接。在DriverManager类中,定义了两个比较重要的静态方法,如表所示。
表2-2 DriverManager类的重要方法
| 方法声明 | 功能描述 |
|---|---|
| static synchronized void registerDriver(Driver driver) | 该方法用于向DriverManager中注册给定的JDBC驱动程序 |
| static Connection getConnection(String url,String user,String pwd) | 该方法用于建立和数据库的连接,并返回表示连接的Connection对象 |
需要注意的是,在实际开发中,通常不使用DriverManager.registerDriver(Driver driver)这种方式注册驱动,因为选择要注册的JDBC驱动类com.mysql.jdbc.Driver中有一段静态代码块,是向DriverManager注册一个Driver实例,当再次执行DriverManager.registerDriver(new Driver())的时候,静态代码块也已经执行了,相当于是实例化了两个Driver对象,因此在加载数据库驱动时通常使用Class类的静态方法forName()来实现,后面会详细介绍。
2.3 Connection 接口
Connection接口代表Java程序和数据库的连接对象,只有获得该连接对象后,才能访问数据库,并操作数据表。在Connection接口中,定义了一系列方法,其常用方法如表所示。
表2-3 Connection接口中的常用方法
| 方法声明 | 功能描述 |
|---|---|
| Statement createStatement() | 该方法用于返回一个向数据库发送语句的Statement对象 |
| PreparedStatement prepareStatement(String sql) | 该方法用于返回一个PreparedStatement对象,该对象用于向数据库发送参数化的SQL语句 |
| CallableStatement prepareCall(String sql) | 该方法用于返回一个CallableStatement对象,该对象用于调用数据库中的存储过程 |
2.4 Statement 接口
Statement是Java执行数据库操作的一个重要接口,它用于
执行静态的SQL语句,并返回一个结果对象。Statement接口对象可以通过Connection实例的createStatement()方法获得,该对象会把静态的SQL语句发送到数据库中编译执行,然后返回数据库的处理结果。
在Statement接口中,提供了3个常用的执行SQL语句的方法,具体如表所示。
表2-4 Statement接口中的常用方法
| 方法声明 | 功能描述 |
|---|---|
| boolean execute(String sql) | 用于执行各种SQL语句,返回一个boolean类型的值,如果为true,表示所执行的SQL语句有查询结果,可通过Statement的getResultSet()方法获得查询结果;如果为false,表示所执行的SQL语句没有查询结果,可通过Statement的getUpdateCount()方法获得受影响的记录条数 |
| int executeUpdate(String sql) | 用于执行SQL中的insert、update和delete语句。该方法返回一个int类型的值,表示数据库中受该SQL语句影响的记录条数 |
| ResultSet executeQuery(String sql) | 用于执行SQL中的select语句,该方法返回一个表示查询结果的ResultSet对象 |
2.5 PreparedStatement 接口
Statement接口封装了JDBC执行SQL语句的方法,虽然可以完成Java程序执行SQL语句的操作,但是在实际开发过程中往往需要将程序中的变量作为SQL语句的查询条件,而使用Statement接口操作这些SQL语句会过于繁琐,并且存在安全方面的问题。针对这一问题,JDBC API 中提供了扩展的PreparedStatement接口。
PreparedStatement是Statement的子接口,用于执行预编译的SQL语句。该接口扩展了带有参数SQL语句的执行操作,应用接口中的SQL语句可以使用占位符“?”来代替其参数,然后通过setXxx()方法为SQL语句的参数赋值。
在PreparedStatement接口中,提供了一些常用方法,具体如表所示。
表2-5 PreparedStatement 接口中的常用方法
| 方法声明 | 功能描述 |
|---|---|
| int executeUpdate() | 在此PreparedStatement对象中执行 SQL 语句,该语句必须是一个DML语句或者是无返回内容的SQL 语句,如 DDL 语句 |
| ResultSet executeQuery() | 在此PreparedStatement对象中执行SQL查询,该方法返回的是ResultSet对象 |
| void setInt(int parameterIndex, int x) | 将指定参数设置为给定的int值 |
| void setFloat(int parameterIndex, float x) | 将指定参数设置为给定的float值 |
| void setString(int parameterIndex, String x) | 将指定参数设置为给定的String值 |
| void setDate(int parameterIndex, Date x) | 将指定参数设置为给定的Date值 |
| void addBatch() | 将一组参数添加到此PreparedStatement对象的批处理命令中 |
| void setCharacterStream(int parameterIndex, java.io.Reader reader, int length) | 将指定的输入流写入数据库的文本字段 |
| void setBinaryStream(int parameterIndex, java.io.InputStream x, int length) | 将二进制的输入流数据写入到二进制字段中 |
需要注意的是,表2-5中的setDate()方法可以设置日期内容,但参数Date的类型必须是java.sql.Date,而不是java.util.Date。
在为SQL语句中的参数赋值时,可以通过输入参数与SQL类型相匹配的setXxx()方法,例如字段的数据类型为int或Integer,那么应该使用setInt()方法,也可以通过setObject()方法设置多种类型的输入参数。具体如下所示:
// 假设users表中字段id、name、email类型分别是int、varchar、varchar
String sql = "INSERT INTO users(id,name,email) VALUES(?,?,?)";
PreparedStatement preStmt = conn.prepareStatement(sql);
preStmt.setInt(1, 1); //使用参数与SQL类型相匹配的方法
preStmt.setString(2, "zhangsan"); //使用参数与SQL类型相匹配的方法
preStmt.setObject(3, "zs@sina.com"); //使用setObject()方法设置参数
preStmt.executeUpdate();
2.6 ResultSet 接口
ResultSet接口用于
保存JDBC执行查询时返回的结果集,该结果集封装在一个逻辑表格中。在ResultSet接口内部有一个指向表格数据行的游标(或指针),ResultSet对象初始化时,游标在表格的第一行之前,调用next()方法可将游标移动到下一行。如果下一行没有数据,则返回false。在应用程序中经常使用next()方法作为while循环的条件来迭代ResultSet结果集。
ResultSet接口中的常用方法如表所示。
表2-6 ResultSet接口中的常用方法
| 方法声明 | 功能描述 |
|---|---|
| String getString(int columnIndex) | 用于获取指定字段的String类型的值,参数columnIndex代表字段的索引 |
| String getString(String columnName) | 用于获取指定字段的String类型的值,参数columnName代表字段的名称 |
| int getInt(int columnIndex) | 用于获取指定字段的int类型的值,参数columnIndex代表字段的索引 |
| int getInt(String columnName) | 用于获取指定字段的int类型的值,参数columnName代表字段的名称 |
| Date getDate(int columnIndex) | 用于获取指定字段的Date类型的值,参数columnIndex代表字段的索引 |
| Date getDate(String columnName) | 用于获取指定字段的Date类型的值,参数columnName代表字段的名称 |
| boolean next() | 将游标从当前位置向下移一行 |
| boolean absolute(int row) | 将游标移动到此 ResultSet 对象的指定行 |
| void afterLast() | 将游标移动到此 ResultSet 对象的末尾,即最后一行之后 |
| void beforeFirst() | 将游标移动到此 ResultSet 对象的开头,即第一行之前 |
| boolean previous() | 将游标移动到此 ResultSet 对象的上一行 |
| boolean last() | 将游标移动到此 ResultSet 对象的最后一行 |
从表中可以看出,ResultSet接口中定义了大量的getXxx()方法,而采用哪种getXxx()方法取决于字段的数据类型。程序既可以通过字段的名称来获取指定数据,也可以通过字段的索引来获取指定的数据,字段的索引是从1开始编号的。例如,假设数据表的第1列字段名为id,字段类型为int,那么既可以使用getInt("id")获取该列的值,也可以使用getInt(1)获取该列的值。
3. JDBC 编程
3.1 JDBC的编程步骤
通常情况下,JDBC的使用可以按照以下几个步骤进行:
- 加载数据库驱动
加载数据库驱动通常使用Class类的静态方法forName()来实现,具体实现方式如下:
Class.forName("DriverName");
在上述代码中,DriverName就是数据库驱动类所对应的字符串。例如,要加载MySQL数据库的驱动可以采用如下代码:
Class.forName("com.mysql.jdbc.Driver");
加载Oracle数据库的驱动可以采用如下代码:
Class.forName("oracle.jdbc.driver.OracleDriver");
从上面两种加载数据库驱动的代码可以看出,在加载驱动时所加载的并不是真正使用数据库的驱动类,而是数据库驱动类名的字符串。
- 通过
DriverManager获取数据库连接
DriverManager中提供了一个getConnection()方法来获取数据库连接,获取方式如下:
Connection conn = DriverManager.getConnection(String url,
String user, String pwd);
从上述代码可以看出,getConnection()方法中有3个参数,它们分别表示连接数据库的URL、登录数据库的用户名和密码。其中用户名和密码通常由数据库管理员设置,而连接数据库的URL则遵循一定的写法。以MySQL数据库为例,其地址的书写格式如下:
jdbc:mysql://hostname:port/databasename
上面代码中,jdbc:mysql:是固定的写法,mysql指的是MySQL数据库。hostname指的是主机的名称(如果数据库在本机上,hostname可以为localhost或127.0.0.1,如果在其他机器上,那么hostname为所要连接机器的IP地址),port指的是连接数据库的端口号(MySQL端口号默认为3306),databasename指的是MySQL中相应数据库的名称。
- 通过
Connection对象获取Statement对象
Connection创建Statement的方式有如下三种:
-
createStatement():创建基本的Statement对象。
-
prepareStatement(String sql):根据传递的SQL语句创建PreparedStatement对象。
-
prepareCall(String sql):根据传入的SQL语句创建CallableStatement对象。
以创建基本的Statement对象为例,其创建方式如下:
Statement stmt = conn.createStatement();
- 使用
Statement执行SQL语句
所有的Statement都有如下三种执行SQL语句的方法:
-
execute(String sql):用于执行
任意的SQL语句。 -
executeQuery(String sql):用于
执行查询语句,返回一个ResultSet结果集对象。 -
executeUpdate(String sql):主要用于执行
DML(数据操作语言)和DDL(数据定义语言)语句。执行DML语句(INSERT、UPDATE或DELETE)时,会返回受SQL语句影响的行数,执行DDL(CREATE、ALTER)语句返回0。
以executeQuery()方法为例,其使用方式如下:
// 执行SQL语句,获取结果集ResultSet
ResultSet rs = stmt.executeQuery(sql);
- 操作ResultSet结果集
如果执行的SQL语句是查询语句,执行结果将返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。程序可以通过操作该ResultSet对象来取出查询结果。
- 关闭连接,释放资源
每次操作数据库结束后都要关闭数据库连接,释放资源,以重复利用资源。需要注意的是,通常资源的关闭顺序与打开顺序相反,顺序是ResultSet、Statement(或PreparedStatement)和Connection。为了保证在异常情况下也能关闭资源,需要在try...catch的finally代码块中统一关闭资源。或者直接使用try-with-resources语法来自动关闭资源。
至此,JDBC程序的大致实现步骤已经讲解完成。
3.2 批处理
批处理(Batch processing)是指将多个SQL语句一次性提交给数据库执行,而不是一条一条提交。批处理可以提高数据库操作效率,减少网络通信次数,提升数据库处理能力。
在JDBC中,批处理主要通过PreparedStatement对象的addBatch()方法来实现。在调用PreparedStatement对象的executeUpdate()方法时,如果之前调用过addBatch()方法,则会将之前添加的SQL语句批量提交给数据库执行。
使用用法如下:
// 创建 PreparedStatement 对象
PreparedStatement pstmt = conn.prepareStatement("insert into tb_user values(null,?,?,?,?)");
pstmt.setString(1, "张三");
pstmt.setString(2, "男");
pstmt.setString(3, "1376630362@qq.com");
pstmt.setDate(4, new Date(System.currentTimeMillis()));
// 添加 SQL 语句到批处理中
pstmt.addBatch();
pstmt.setString(1, "汪芜");
pstmt.setString(2, "女");
pstmt.setString(3, "132312312@qq.com");
pstmt.setDate(4, new Date(System.currentTimeMillis()));
// 添加另一个 SQL 语句到批处理中
pstmt.addBatch();
// 执行批处理
pstmt.executeBatch();
3.3 实现第一个JDBC程序
熟悉了JDBC的编程步骤后,接下来通过一个案例并依照上一小节所讲解的步骤来演示JDBC的使用。此案例会从tb_user表中读取数据,并将结果打印在控制台。
需要说明的是,Java中的JDBC是用来连接数据库从而执行相关数据相关操作的,因此在使用JDBC时,一定要确保安装有数据库。常用的关系型数据库有MySQL和Oracle,此次以连接MySQL数据库为例,使用JDBC执行相关操作。
3.3.1 配置数据库环境
在MySQL数据库中创建一个名称为jdbc的数据库,然后在该数据库中创建一个名称为tb_user的表,创建数据库和表的SQL语句如下:
-- 创建数据库 jdbc
CREATE DATABASE jdbc;
-- 使用 jdbc 数据库
USE jdbc;
-- 创建表 tb_user
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(10) NOT NULL,
sex VARCHAR(2) NOT NULL,
email VARCHAR(60),
birthday DATE
)
上述创建tb_user表时添加了id、NAME、sex、email和birthday共5个字段,其中name字段在MySQL数据库中属于关键字,所以为了正常使用,使用 **``**区分。
数据库和表创建成功后,再向tb_user表中插入3条数据,插入的SQL语句如下所示:
-- 插入3条数据
INSERT INTO tb_user VALUES
(NULL,"十一月","男","571497983@qq.com","1998-06-04"),
(NULL,"悦悦","女","3214023251@qq.com","2000-08-12"),
(NULL,"魅影","男","1376630362@qq.com","1996-10-24")
插入数据直接使用了批量插入,id字段的位置填写NULL,因为我们设置了主键自增,所以会由数据库自动生成。
3.3.2 编写JDBC程序
首先,打开IDEA,创建一个新的MAVEN项目,并在pom.xml文件中添加如下依赖:
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
</dependencies>
注意自己的版本,我使用的是5.7以下版本的mysql
然后,在src/main/java目录下创建com.demo.jdbc包,在该包下创建Demo.java类,并添加如下代码:
package com.demo.jdbc; // 定义包名
import java.sql.*; // 导入 JDBC 相关的类
public class Demo {
public static void main(String[] args) throws SQLException {
// 声明数据库连接、语句和结果集变量
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 加载 MySQL JDBC 驱动
Class.forName("com.mysql.jdbc.Driver");
// 建立数据库连接 url 帐号 密码 因为我没有设置密码,所以直接写空串
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "");
// 创建一个用于发送 SQL 语句的 Statement 对象
stmt = conn.createStatement();
// 声明结果集变量
rs = null;
// 定义 SQL 查询语句
String sql = "select * from tb_user";
// 执行 SQL 查询,并将结果存储在 ResultSet 中
rs = stmt.executeQuery(sql);
// 打印表头
System.out.println("Id\t\t\tName\t\t\tSex\t\t\tEmail\t\t\tBirthDay");
// 遍历 ResultSet,对每一行数据进行处理
while (rs.next()) {
// 获取每一列的数据并打印
System.out.println(
rs.getInt(1) + "\t\t\t" + // 第一列:Id
rs.getString(2) + "\t\t\t" + // 第二列:Name
rs.getString(3) + "\t\t\t" + // 第三列:Sex
rs.getString(4) + "\t\t\t" + // 第四列:Email
rs.getDate(5) // 第五列:BirthDay
);
}
} catch (Exception e) {
// 捕获并打印异常信息
e.printStackTrace();
} finally {
// 关闭结果集、语句和连接,避免内存泄漏
if (rs != null) rs.close(); // 关闭结果集
if (stmt != null) stmt.close(); // 关闭 Statement 对象
if (conn != null) conn.close(); // 关闭数据库连接
}
}
}
运行结果:
Id Name Sex Email BirthDay
1 十一月 男 571497983@qq.com 1998-06-04
Id Name Sex Email BirthDay
2 悦悦 女 3214023251@qq.com 2000-08-12
Id Name Sex Email BirthDay
3 魅影 男 1376630362@qq.com 1996-10-24
上述代码中,首先加载MySQL JDBC驱动,然后建立数据库连接,创建Statement对象,定义SQL查询语句,执行SQL查询,并将结果存储在ResultSet对象中。
tip:如果使用的mysql为8.0版本以上,加载驱动的字符串更改为com.mysql.cj.jdbc.Driver,这是mysql更新后的驱动包名。