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访问数据库的方式如图所示。
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的使用可以按照以下几个步骤进行:

  1. 加载数据库驱动

加载数据库驱动通常使用Class类的静态方法forName()来实现,具体实现方式如下:

Class.forName("DriverName");

在上述代码中,DriverName就是数据库驱动类所对应的字符串。例如,要加载MySQL数据库的驱动可以采用如下代码:

Class.forName("com.mysql.jdbc.Driver");

加载Oracle数据库的驱动可以采用如下代码:

Class.forName("oracle.jdbc.driver.OracleDriver");

从上面两种加载数据库驱动的代码可以看出,在加载驱动时所加载的并不是真正使用数据库的驱动类,而是数据库驱动类名的字符串

  1. 通过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可以为localhost127.0.0.1,如果在其他机器上,那么hostname为所要连接机器的IP地址),port指的是连接数据库的端口号(MySQL端口号默认为3306),databasename指的是MySQL中相应数据库的名称

  1. 通过Connection对象获取Statement对象

Connection创建Statement的方式有如下三种:

  • createStatement():创建基本的Statement对象。

  • prepareStatement(String sql):根据传递的SQL语句创建PreparedStatement对象。

  • prepareCall(String sql):根据传入的SQL语句创建CallableStatement对象。

以创建基本的Statement对象为例,其创建方式如下:

Statement stmt = conn.createStatement();
  1. 使用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);
  1. 操作ResultSet结果集

如果执行的SQL语句是查询语句执行结果将返回一个ResultSet对象,该对象里保存了SQL语句查询的结果。程序可以通过操作该ResultSet对象来取出查询结果。

  1. 关闭连接,释放资源

每次操作数据库结束后都要关闭数据库连接,释放资源,以重复利用资源。需要注意的是,通常资源的关闭顺序与打开顺序相反,顺序是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更新后的驱动包名。