首页 > 编程笔记 > Java笔记 阅读:3

Java JDBC数据库连接的用法(非常详细,附带大量实例)

Java 程序在运行中产生的各类变量,从本质上来说存储在计算机内存中。当应用程序结束或意外退出时,这些变量及它们存储的数据将被清除,即没有持久化保存。

但在现实中,Java 程序对持久化存储的数据有很强的依赖性,需要使用持久化存储的数据作为计算的输入,并将计算结果写入持久化存储中。例如,在用户登录实例中,需要使用用户注册时提交的用户名和密码来验证本次提交的信息是否正确,用户登录成功后,需要将用户本次登录操作记录到日志中。

前面章节中介绍了 Java 的输入/输出流 API,能够实现将 Java 程序的数据写入本地文件系统中。但是,这些数据在文件系统中是以纯文本或字节码的形式保存的,缺少高效的组织和管理方法,不利于应用程序的快速查询和更新。

在实际生产中,Java 程序一般和数据库管理系统配合使用,以满足 Java 程序对数据持久化存储和高效访问的需求。

早期,开发者想要在 Java 程序中实现数据库访问会面临诸多挑战,其中涉及网络访问协议、数据封装方式和数据库接口调用等。同时,不同厂商的数据库采用各自独立的设计,大大增加了开发者的工作量和数据库迁移的难度。

为了解决上述问题,Java 在 JDK 1.1 中提供了 JDBC(Java Database Connectivity,Java 数据库连接),有效降低了 Java 程序访问数据库的编程难度和工作量。由于 JDBC 发布时(1997 年)主流的商用数据库均是关系型的,因此 JDBC 的设计是面向关系型数据库的。目前,,也有一些非关系型数据库和中间件支持 JDBC 的访问。

JDBC概述

JDBC 是 Java 的应用程序编程接口,定义了客户端访问数据库的方法。

JDBC 是一种“开放”的方案,为数据库应用开发者、数据库前台工具开发者提供了一种标准的应用程序设计接口,使开发者可以用纯 Java 编程语言编写完整的数据库应用程序。

JDBC 提供了两种 API,分别是面向开发者的 API 和面向底层的驱动程序 API,底层主要通过直接的 JDBC 驱动和 JDBC-ODBC 桥驱动实现与数据库的连接。

Java 程序使用 JDBC 访问数据库的示意图如下图所示:


图 1 Java程序使用 JDBC 访问数据库的示意图

JDBC 的设计使 Java 程序可以使用一套 API 访问不同厂商提供的关系型数据库。JDBC 提供了一种机制,用于动态加载正确的驱动程序并将它们注册到 JDBC 驱动程序管理器中。

JDBC的编程步骤

接下来将通过一个具体的案例来介绍使用 JDBC 访问数据库的步骤,其中包含 JDBC API 中常用类、接口和方法的内容。

在编写 Java 程序之前,需要先完成两步准备工作,分别是下载数据库连接驱动和为项目添加驱动。

1) 下载数据库连接驱动

可以从目标数据库的官方网站或 MVNRepository 网站上下载数据库连接驱动。

本案例以访问 MySQL 数据库为例进行演示。可以在 MySQL 官方网站的下载区下载 Connector/J,以及 Java 的连接驱动。MySQL 连接驱动的下载页面如下图所示:


图 2 MySQL连接驱动的下载页面

在下载数据库连接驱动时,需要注意驱动的版本。在一般情况下,驱动版本与数据库版本相匹配,需要根据要访问的数据库来决定下载的驱动版本。在大多数情况下,高版本的驱动可以兼容低版本的驱动,但这并不是绝对的,而是取决于实际要访问的数据库类型及版本。

以访问 MySQL 数据库为例,目前使用频率较高的是 5.x 版本和 8.x 版本。对应的连接驱动也是 5.x 版本和 8.x 版本。图 2 中显示的 8.0.30 是连接驱动的版本,根据官方说明,该驱动可以同时支持 MySQL 8.x 和 MySQL 5.7。

在实际下载时,单击下载页面中的任意一个 Download 按钮均可,区别仅仅是压缩包的格式不同,压缩包中实际提供的连接驱动的文件是相同的,均为 mysql-connector-java-8.0.30.jar 文件,也就是开发者常说的 .jar 文件或 jar 包。

2) 为项目添加驱动

在下载了数据库连接驱动的文件后,还需要将该文件添加到正在开发的 Java 项目中,只有这样,项目在运行过程中才能动态地加载驱动文件包含的内容。为项目添加驱动 .jar 文件的操作相同,一般都是通过 IDE 工具完成的。

以 IDEA 为例,具体的步骤如下:

图 3 在 IDEA 中添加 MySQL 连接驱动

为项目添加驱动后,可以在项目中基于 JDBC 编程的规范开发数据库访问程序。JDBC 编程的 6 个步骤如从下图所示。


图 4 JDBC 编程的 6 个步骤

① 加载驱动

通常使用 Class.forName(String className) 将驱动类动态加载到程序的内存中,其中 className 位置传入驱动类的包名和类名。

例如,加载 MySQL 的连接驱动可以写成如下形式:
Class.forName("com.mysql.jdbc.Driver");

② 建立连接

当驱动加载完成后,可以调用驱动管理器 DriverManager的getConnection(String url, String user, String password) 方法,建立 Java 程序与目标数据库的连接。其中,参数 url 是目标数据库的访问地址,参数 user 是数据库访问用户名,参数 password 是数据库访问密码。该方法会返回一个代表数据库连接的 Connection 对象,开发者可以基于该对象实现对数据库的各类操作。

以 MySQL 为例,参数 url 的格式如下:
jdbc:mysql://数据库服务器主机名(或IP地址):端口号/数据库名[?连接参数]
如果要访问运行在当前主机的 3306 端口上的数据库中的 ex 库,那么参数 url 可以写成如下形式:
jdbc:mysql://localhost:3306/ex
连接参数部分用于在创建数据库连接时传递约定的信息或进行特定的配置。例如,在 MySQL 8.x 的连接驱动中约定,建立连接时需要指定时区信息。此时,参数 url 需要写成如下形式:
jdbc:mysql://localhost:3306/ex?serverTimezone=Asia/Shanghai

③ 创建SQL语句的执行器

在建立了 Java 程序与目标数据库的连接之后,Java 程序需要将要执行的 SQL 语句发送给数据库执行。该操作是通过 SQL 语句的执行器来实现的。

可以通过 Connection 对象的 createStatement() 方法创建一个 Statement 对象,用来表示 SQL 语句的执行器。Statement 对象具有多个执行 SQL 语句的实例方法可供使用。

④ 执行SQL语句

可以调用 Statement 对象的 execute(String sql) 方法来执行 SQL 语句,该方法适用于各类操作。

另外,Statement 对象还提供了专门适用于查询操作的 executeQuery(String sql) 方法和适用于其他操作的 executeUpdate(String sql) 方法。

⑤ 对结果集进行操作

如果 Statement 对象执行的是查询语句,那么返回一个 ResultSet 对象,存储查询结果。ResultSet 对象实际上是由查询结果组成的表,是一个通道式数据集,由统一形式的数据行组成,一行对应一条查询记录。ResultSet 对象中隐含了一个游标,一次只能获得游标当前所指的数据行,使用 next() 方法可以获取下一个数据行。

ResultSet 对象的设计如下图所示。


图 5 ResultSet对象的设计

如果 Statement 对象执行的是其他类型的操作,如增、删、改,那么不会返回 ResultSet 对象。因此,本步骤并不会在每个 JDBC 操作中都出现。

⑥ 关闭连接,释放资源

JDBC 操作建立了 Java 程序到数据库管理系统的网络连接,通过该连接进行 SQL 语句和数据的交互,维持连接需要持续消耗系统的资源。在 JDBC 操作完成后,需要主动关闭连接,释放资源。通过调用 Connection 对象、Statement 对象和 ResultSet 对象的 close() 方法可以关闭连接。

Java 7 提供了 try-with-resource 语法,可以用来简化关闭连接的编码,具体内容将在后续的编程实例中介绍。

下面通过具体的编程实例来演示访问 JDBC 的具体操作。在开发编程实例前,需要先在 MySQL 的 ex 库中创建一张表名为 info 的数据表,作为本次操作的目标表。info 表中有 3 个字段,分别是 id(int 型)、name(varchar 型)和 gender(varchar 型)。

MySQL 中的建库、建表语句的结构如下:
# 创建 ex 库
create database ex;
# 使用 ex 库
use ex;
# 创建 info 表
create table info(
    id int primary key auto_increment comment '用户 id',
    name varchar(50) comment '用户姓名',
    gender char(1) comment '用户性别'
);

info 表的结构为:
MariaDB [ex]> desc info;
+-------+--------------+------+-----+-------------------+----------------+
| Field | Type         | Null | Key | Default           | Extra          |
+-------+--------------+------+-----+-------------------+----------------+
| id    | int(11)      | NO   | PRI | NULL              | auto_increment |
| name  | varchar(50)  | YES  |     | NULL              |                |
| gender| char(1)      | YES  |     | NULL              |                |
+-------+--------------+------+-----+-------------------+----------------+
3 rows in set (0.01 sec)

接下来向 info 表中插入两条测试数据,SQL 语句如下:
insert into info values(1,'Tom','M'), (2,'Jerry','F');
当测试数据插入完成后,进入 Java 编程环节。需要注意的是,此时项目中应该已添加了 MySQL 的 .jar 文件。

【实例】查询表中全部的数据。
import java.sql.*;

public class Example11_1 {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/ex?serverTimezone=Asia/Shanghai";
        // 数据库用户名
        String user = "root";
        // 数据库密码
        String password = "root";
        // 声明要执行的SQL语句
        String sql = "select * from info";
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1.加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 2.建立连接
            conn = DriverManager.getConnection(url, user, password);
            // 3.创建SQL语句的执行器
            stmt = conn.createStatement();
            // 4.执行SQL语句
            rs = stmt.executeQuery(sql);
            // 5.对结果集进行操作
            // 调用rs.next()方法不断移动游标,判断是否有结果
            while (rs.next()) {
                // 获取第一列的值
                int id = rs.getInt(1);
                // 获取第二列的值
                String name = rs.getString(2);
                // 获取第三列的值
                String gender = rs.getString(3);
                // 调用rs.getxxx()方法获取记录游标执行的行和列的值
                System.out.println(id + "\t" + name + "\t" + gender);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 6.关闭连接,释放资源
                // 关闭ResultSet对象
                if (rs != null) rs.close();
                // 关闭Statement对象
                if (stmt != null) stmt.close();
                // 关闭Connection对象
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
运行结果为:

1    Tom    M
2    Jerry  F

由上面的代码可以发现,管理连接的代码不但与应用逻辑无关,而且是重复的。

可以使用 try-with-resource 语法对这部分代码进行优化。如果在 try() 方法中声明并初始化 Connection 对象和 Statement 对象,那么该语法会在 try-catch 语句块执行完后,自动调用 Connection 对象和 Statement 对象的 close() 方法。根据 JDBC 的设计,当 Statement 对象关闭后,它对应的 ResultSet 对象也会被关闭,不需要再显式调用 ResultSet 对象的 close() 方法。

下面演示基于 JDBC 对数据库表中的数据进行增、改和删。

【实例】向表中插入数据。
import java.sql.*;

public class Example11_2 {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/ex?serverTimezone=Asia/Shanghai";
        // 数据库用户名
        String user = "root";
        // 数据库密码
        String password = "root";
        // 声明要执行的SQL语句
        String sql = "insert into info(id,name,gender)values(null,'Lucy','女')";

        // 1.加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 2.建立连接
        try (
            Connection conn = DriverManager.getConnection(url, user, password);
            // 3.创建 SQL语句的执行器
            Statement stmt = conn.createStatement();
        ) {
            // 4.执行 SQL语句的更新操作,返回受影响数据的行数
            int rows = stmt.executeUpdate(sql);
            // 输出受影响的行数
            System.out.println("受影响的行数为:" + rows);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
运行结果为:

受影响的行数为:1

由上面的代码中执行的 SQL 语句可以发现,语句中对应 id 列传入的参数为 null。这是因为在创建 info 表时将 id 列设置为自增(auto_increment),当传入的参数为 null 时,数据库会根据自增规则,自动为该列生成 id 值。

执行插入操作后,在 MySQL 终端中使用 SQL 语句查询 info 表中的数据,结果如下:
MariaDB [ex]> select * from info;
+----+------+--------+
| id | name | gender |
+----+------+--------+
|  1 | Tom  | M      |
|  2 | Jerry| F      |
|  3 | Lucy | 女     |
+----+------+--------+
3 rows in set (0.00 sec)
可以看到,插入记录的实际 id 值为 3。

【实例】更新表中的数据。
import java.sql.*;

public class Example11_3 {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/ex?serverTimezone=Asia/Shanghai";
        // 数据库用户名
        String user = "root";
        // 数据库密码
        String password = "root";
        // 声明要执行的SQL语句
        String sql1 = "update info set name='Lily' where id = 3";
        String sql2 = "select name from info where id = 3";

        // 1.加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 2.建立连接
        try (
            Connection conn = DriverManager.getConnection(url, user, password);
            // 3.创建SQL语句的执行器
            Statement stmt = conn.createStatement();
        ) {
            // 4.执行SQL语句的更新操作,返回受影响数据的行数
            int rows = stmt.executeUpdate(sql1);
            // 输出受影响的行数
            System.out.println("受影响的行数为:" + rows);

            // 查询更新后的结果
            ResultSet rs = stmt.executeQuery(sql2);
            // 将游标移动到第一行
            rs.next();
            String name = rs.getString(1);
            System.out.println("id为3的用户的名称修改为: " + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
运行结果为:

受影响的行数为:1
id为3的用户的名称修改为:Lily

实例的代码中有一个关于 ResultSet 对象的访问细节值得注意。实例所执行的查询 SQL 语句中,没有使用“*”来查询所有列,而是使用 name 字段指定仅查询姓名这一列的值。因此,查询操作返回的 ResultSet 中仅包含一列的内容。

在调用 rs.getString() 方法时,想要获取 name 列的数据需要传入的参数是 1,而不是像第一个实例中那样传入参数 2。这说明,getString() 方法中传入的列的编号需要与 SQL 语句中的查询结果相匹配,并不一定与数据库表中的列的顺序一致。

【实例】删除表中的数据。
import java.sql.*;

public class Example11_4 {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/ex?serverTimezone=Asia/Shanghai";
        // 数据库用户名
        String user = "root";
        // 数据库密码
        String password = "root";
        // 声明要执行的SQL语句
        String sql1 = "delete from info where id = 3";
        String sql2 = "select * from info where id = 3";

        // 1.加载驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 2.建立连接
        try (
            Connection conn = DriverManager.getConnection(url, user, password);
            // 3.创建SQL语句的执行器
            Statement stmt = conn.createStatement();
        ) {
            // 4.执行SQL语句的更新操作,返回受影响数据的行数
            int rows = stmt.executeUpdate(sql1);
            // 输出受影响的行数
            System.out.println("受影响的行数为: " + rows);
            // 查询更新后的结果
            ResultSet rs = stmt.executeQuery(sql2);
            // 判断是否查询到数据
            if (rs.next()) {
                System.out.println("查询到id为3的记录");
            } else {
                System.out.println("未查询到 id为 3的记录");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
运行结果为:

受影响的行数为: 1
未查询到 id为 3的记录

由上面的代码可知,当未查询到目标数据时,Statement 对象的 executeQuery() 方法仍会返回 ResultSet 对象而非 null。可以通过调用 ResultSet 对象的 next() 方法来判断能否查询到结果。如果返回 true,那么表示游标成功向下移动一位,即查询到了至少一行数据;如果返回 false,那么表示游标移动失败,即 ResultSet 对象中不包含任何数据。

JDBC常用API

JDBC API 定义了一系列抽象的 Java 接口,可以使开发者连接指定的数据库,以及执行 SQL 语句和处理返回结果。

JDBC API 主要用于 java.sql 包中,该包定义了一系列访问数据库的接口类。

1) DriverManager类

DriverManager 是 java.sql 包中用于管理数据库驱动程序的类,主要负责处理驱动程序的加载和建立新数据库连接。

通常,应用程序只使用 DriverManager 类的 getConnection() 静态方法,用来建立与数据库的连接,返回代表连接的 Connection 对象。

指定数据库的 URL、用户名和密码来创建 Connection 对象:
static Connection getConnection(String url, String username, String password)
URL 的语法格式如下:
jdbc:<数据库的连接机制>:<ODBC数据库名>

2) Connection接口

Connection 是 java.sql 包中用于处理与特定数据库连接的接口。

Connection 实现类的对象(简称 Connection 对象)用来表示数据库连接,Java 程序对数据库的操作都是基于 Connection 对象实现的。

Connection 对象的主要方法如下:

3) Statement接口

Statement 是 java.sql 包中用于在指定的连接中处理 SQL 语句的接口,用于执行静态 SQL 语句并返回它产生的结果。可以使用 Connection 对象的 createStatement() 方法获取一个用于向当前连接的数据库发送 SQL 语句的 Statement 对象。

Statement 类的主要方法如下:

4) ResultSet接口

ResultSet 接口用于表示数据库的结果集,通常通过执行查询数据库的语句产生。

ResultSet 对象实际上是由查询结果数据组成的表,是一个通道式数据集,由统一形式的数据行组成,一行对应一条查询记录。在 ResultSet 对象中隐含着一个游标,一次只能获取游标当前所指的数据行,用 next() 方法可以获取下一个数据行。用数据行的字段(列)名称或位置索引(自 1 开始)调用形如 getXXX() 的方法可以获得记录的字段值。

以下是 ResultSet 对象的部分方法:
以上方法中的 columnIndex 是位置索引,用于指定获取哪一列的数据,索引值从 1 开始,代表结果集中的第一列。除了 columnIndex,还可以使用字段名 columnName。在明确知道结果集的列顺序的情况下,使用 columnIndex 会更简捷。但是使用 columnIndex 会对代码的可维护性造成影响,因此,在大部分情况下,推荐使用 columnName。

用户需要在查询结果集上浏览,或者前后移动,或者显示结果集的指定记录,这称为可滚动结果集。程序要获得一个可滚动结果集,只要在获得SQL语句的对象时增加指定结果集的两个参数即可。

例如:
Statement stmt = con.createStatement(type, concurrency);
ResultSet rs = stmt.executeQuery(SQL语句);
使用语句对象 stmt 的 SQL 语句进行查询就能得到相应类型的结果集。

int 型参数 type 决定了可滚动集的滚动方式:
int 型参数 concurrency 决定了数据库是否与可滚动集同步更新:
例如,以下代码利用连接对象 connect 创建 Statement 对象 stmt,指定结果集可滚动,并以只读方式读数据库:
stmt = connect.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
可滚动集上另外一些常用的方法如下:

相关文章