Web开发|10|JDBC 数据库开发

数据库是后端最重要的持久化组件。JDBC 是数据库开发的基础,通过 JDBC 就可以实现对后端数据库的访问。

1、访问数据库最常见的几种方式(以 MySQL 为例)

  1. 图形化工具:Workbench,用户通过点击和拖拽就能实现对 MySQL 数据库的访问和操作,它的优点是页面友好、操作简单;缺点是无法满足较为复杂的查询需求。它满足了人对数据库的访问需求。
  2. 命令行工具:在命令行中输入完整的 SQL 语句实现更为复杂的查询需求,这就要求用户能熟练地使用 SQL,门槛相对较高。
  3. JDBC 驱动程序:Connector/J,使用相关驱动和 API 即可实现对数据库的访问。它满足了程序对数据库的访问需求。
Web开发|10|JDBC 数据库开发_第1张图片
三种访问数据库的方式

本节课我们要学习的重点是第3种方式,即通过 Java 应用程序来访问数据库。

2、什么是 JDBC ?应用程序是如何与数据库连接的?

不同的数据库,所支持的连接协议也是不相同的,数据库驱动程序所提供的 API 接口也不相同,这给“跨数据库迁移”带来了诸多不便。JDBC 存在的意义,就是统一了 API 接口,从而可以方便地对任意数据库进行访问。JDBC 帮助我们屏蔽了应用程序与数据库之间交互协议的实现细节,我们只需要使用 JDBC 提供的标准 API ,即可实现对任意数据库的访问,且无需关心底层的实现方式。

对 Java 程序来讲,JDBC 就是一个普通的 Java 类库(JAR包),在应用程序中引用 JDBC JAR包所提供的类和方法,通过操作 Java对象的方式就可以操作数据库中的数据。

对 数据库厂商来讲,JDBC 就是一套接口规范,每个厂商的数据库驱动都必须实现 JDBC 中所定义的数据库接口,如此才能在 Java程序中实现数据库连接与访问。

Web开发|10|JDBC 数据库开发_第2张图片
JDBC 的作用

3、JDBC 有哪些优势?

1、它使得Java程序对数据库的访问变得更加简单,一套接口即可实现对任意数据库的访问。
2、使用 JDBC,大大地提升了数据库开发的效率。
3、基于 JDBC的接口规范,使得 Java程序在面对不同的数据库时,具有了强大的跨平台可移植性。
4、JDBC 只是定义了最最基础的 API 功能,我们可以基于 JDBC 基本功能再去封装更为简单、更为便捷的框架。

4、JDBC 的体系架构是怎样的?

JDBC 的体系架构分为两层,分别是上层的 JDBC API 层,下层的 JDBC 驱动层。API 层 主要用于与数据库进行通信;驱动层主要用于与具体的数据库建立连接,这些驱动层 Driver 一般都是由数据库厂商提供的。

Web开发|10|JDBC 数据库开发_第3张图片
JDBC 的体系架构

5、什么是 JDBC 驱动?如何搭建 JDBC 的开发环境?

JDBC 的 API 默认是已经集成在 JDK 中了,无需额外地安装。我们需要下载并安装的是 具体的数据库驱动。对 MySQL 数据库来讲,我们需要下载并安装的是 Connector/J ,这个驱动实际上是一个 JAR包,我们需要把它加入到 Java应用工程中去,或者将其添加到 CLASSPATH 环境变量中去,但是我们通常推荐使用 Maven 来管理数据库驱动程序 JAR包。

Web开发|10|JDBC 数据库开发_第4张图片
下载 MySQL 的 JDBC 驱动

Web开发|10|JDBC 数据库开发_第5张图片
配置 Connector/J 驱动JAR包的环境变量

6、JDBC API 所涉及到的几个非常重要的 Java 类

这些 JDBC 的 API 类,其继承关系如下图所示,其中 Driver 是驱动程序最顶层的抽象接口,它定义了驱动程序所有必须的基本功能。

Web开发|10|JDBC 数据库开发_第6张图片
JDBC API 的继承关系

7、认识 DriverManager 类:

DriverManager 是 JDBC 驱动程序的管理类,使用 Class.forName() 可以向 DriverManager 装载并注册一个驱动程序。使用 DriverManager 对象的 getConnection() 方法就可以建立到指定数据库的连接。

Web开发|10|JDBC 数据库开发_第7张图片
DriverManger 类

getConnection() 接收三个参数,分别是 DB_URL,USERNAME,PASSWORD。下面重点解释一下连接数据库所用的 DB_URL:

什么是 DB_URL ?

它是数据库唯一的通信标识符,通过它可以确定唯一的数据库实例。DB_URL 的组成部分如下图所示:

Web开发|10|JDBC 数据库开发_第8张图片
DB_URL 的组成

其中“子协议”部分决定了数据库的类型。如下图是三种常用数据库的 DB_URL 格式:

Web开发|10|JDBC 数据库开发_第9张图片
常用数据库的 DB_URL

8、认识 Connection 类:

Connection对象代表着 Java应用程序与后端数据库的一条物理连接,基于这条连接,我们可执行一些 SQL 语句,它最常用的方法是 conn.createStatement(),该方法返回一个 Statement 对象。

Web开发|10|JDBC 数据库开发_第10张图片
Connection 类

9、认识 Statement 类:

Statement 对象,可以理解成是一个 SQL 窗器,在这个窗器中可以执行 SQL 语句,以实现增删改查的功能。执行该对象的 executeQuery() 方法,可以得到一个查询结果的对象,即 ResultSet 对象。如果是执行该对象的 增、删、改的方法,返回的结果是一个 int 类型的值,这个值代表的意思是当前操作所影响到的行数。

Web开发|10|JDBC 数据库开发_第11张图片
Statement 类

10、认识 ResultSet 类:

ResultSet 对象代表着 SQL 查询的结果集。关系型数据库可以看成一个二元表。ResultSet 对象的内部有一个指针,用于指向结果集的行记录,默认指向第一条行记录。调用 ResultSet 对象的 next() 方法可以让指针指向下一条行记录。调用 previous() 方法让指针指向上一行。调用 absolute() 方法让指针指向指定的行。调用 beforeFirst() 方法让指针指向第一行的上一条记录。调用 afterLast() 方法,让指针指向最后一行的下一条记录。

Web开发|10|JDBC 数据库开发_第12张图片
ResultSet 类

遍历 ResultSet 对象,即可读取结果集中的每一行记录,相关方法如下表:


Web开发|10|JDBC 数据库开发_第13张图片
ResultSet 类

11、认识 SQLException 类:

在执行 SQL 语句的过程,MySQL 服务器有可能会抛出一些异常,SQLException 对象即代表着这些异常,捕获这些异常后,就是一个完整的 JDBC 工作流程了。

12、JDBC 开发的五个基本步骤

JDBC 开发基本满足如下的五个步骤,对每个步骤的准确理解至关重要。尤其是 第 5步(清理环境资源)相当重要,数据库连接是非常宝贵的资源,必须在使用完毕后及时地关闭

Web开发|10|JDBC 数据库开发_第14张图片
JDBC 开发的 5 个基本步骤

JDBC 开发的 5个基本步骤, 示例如下:

package cn.geekxia;

import java.sql.*;

public class HelloMySQL {

    public final static String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    public final static String DB_URL = "jdbc:mysql://localhost/world";
    public final static String USER = "root";
    public final static String PASS = "xxxxxxxxxxxxx";
    public static void helloMysql() throws ClassNotFoundException {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        
        // 第1步:装载 JDBC 驱动程序
        Class.forName(JDBC_DRIVER);
        // 第2步:建立到 MySQL数据库的连接,这有可能出现异常,这里必须要进行相应的异常处理
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);
            // 第3步:执行 SQL 语句
            stmt = conn.createStatement();
            rs = stmt.executeQuery("select * from country");
            // 第4步:遍历 SQL 的查询结果
            while (rs.next()) {
                String name = rs.getString("Name");
                int id = rs.getInt("Code");
                System.out.println(id + " : " + name);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 第5步:清理数据库连接资源,放在 finally 中执行,以确保无论是否发生了 SQLException,都能够清理掉这些连接资源,以减少程序的性能耗费。清理资源也有可能发生异常,因此这里也需要进行异常捕获。
            try {
                if (conn != null)
                conn.close();
                if (stmt != null)
                stmt.close();
                if (rs != null)
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws ClassNotFoundException {
        helloMysql();  // 测试执行
    }
}

上述代码中,尤其要对 第5步 进行说明:清理数据库连接资源,以减少程序的性能耗费,这是非常重要的。那么为什么要放在 finally 中去执行资源清理工作呢?放在 finally 中执行,是为了确保无论是否发生了 SQLException 异常,都能够清理掉这些连接资源。清理资源的过程,也有可能发生异常,因此这里也需要进行异常捕获。



13、如何解决 JDBC 读取数据记录过多的问题?

Java程序是运行在 JVM 中的,JVM 是有内存大小限制的。在我们使用 JDBC 读取数据库中的数据时,一定要考虑 JVM 分配给 Java 程序的内存大小是否满足要求,否则将有可能报“内存溢出”的错误。

情景描述:当 SQL 过滤条件比较弱,一次 JDBC 查询读取的数据记录过多时。或者需要读取数据表中所有的记录时。这时,JDBC 读取的数据量有可能达到成千上万条,数据量如果过大,就有可能导致 JVM 内存溢出。

Web开发|10|JDBC 数据库开发_第15张图片
数据记录太多,导致内存溢出

在这种情景下,就需要使用到 JDBC 游标来解决问题了。那么什么是 JDBC 游标?它到底是如何避免“JVM 内存溢出”问题的呢?

什么是 JDBC 游标?什么是 fetch size ?

对庞大的数据记录,为了避免内存溢出,我们可以分批来读取数据库中的数据记录,这每一批的数据即为一个 fetch size。支持这种数据读取方式的技术,即为 JDBC 游标。如下图示:

Web开发|10|JDBC 数据库开发_第16张图片
JDBC 游标 与 fetch size

如何开启 JDBC 游标呢?

在 JDBC 与数据库连接的 DB_URL 上,添加一个 useCursorFetch 属性即可开启 JDBC 游标,如下图的 DB_URL 所示:

如何开启 JDBC 游标?

如何使用 JDBC 游标?使用 PreparedStatement 类:

使用 PreparedStatement 对象代替 Statement 对象。PreparedStatement 实际上是继承自 Statement 的。PreparedStatement 是专门为 JDBC 游标而设计的,以解决读取大量数据记录可能导致的“内存溢出”问题。

Web开发|10|JDBC 数据库开发_第17张图片
PreparedStatement 类

使用 JDBC 游标分批读取数据库记录,示例代码如下:

// UseCursorFetch.java
package cn.geekxia;
import java.sql.*;

public class UseCursorFetch {

    public final static String JDBC_DRIVER = "com.mysql.jdbc.Driver";

    // 开启 JDBC 游标, useCursorFetch=true
    public final static String DB_URL = "jdbc:mysql://localhost/world?useCursorFetch=true";

    public final static String USER = "root";
    public final static String PASS = "x-448914712";
    public static void helloMysql() throws ClassNotFoundException {
        Connection conn = null;
        // 使用游标
        PreparedStatement stmt = null;
        ResultSet rs = null;
        
        // 第1步:装载 JDBC 驱动程序
        Class.forName(JDBC_DRIVER);
        // 第2步:建立到 MySQL数据库的连接,这有可能出现异常,这里必须要进行相应的异常处理
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);

            // 第3步:使用 JDBC 游标 执行 SQL 语句
            stmt = conn.prepareStatement("select * from country");
            stmt.setFetchSize(10);  // 每次读取 10 条数据记录
            rs = stmt.executeQuery();

            // 第4步:遍历 SQL 的查询结果
            while (rs.next()) {
                String name = rs.getString("Name");
                int id = rs.getInt("Code");
                System.out.println(id + " : " + name);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 第5步:清理数据库连接资源,放在 finally 中执行,以确保无论是否发生了 SQLException,都能够清理掉这些连接资源,以减少程序的性能耗费。清理资源也有可能发生异常,因此这里也需要进行异常捕获。
            try {
                if (conn != null)
                conn.close();
                if (stmt != null)
                stmt.close();
                if (rs != null)
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws ClassNotFoundException {
        helloMysql();
    }
}

上述代码的核心变化有两点,一个是给 DB_URL 追加了 useCursorFetch 属性,另一个是使用 PreparedStatement 对象代替了 Statement 对象



14、如何解决 JDBC 读取大数据量 字段的问题?

情景描述:我们并不推荐在数据表中存储较大数据量的字段,比如图片、博客文章等。但是实际应用开发中,为了方便性我们还是经常会把大数据量的字段存入到数据库中去。在上一讲中,我们讨论了读取过多的数据记录有可能导致 JVM 内存溢出。事实上,即使是读取单条数据记录时,如果某个字段的数据量过大,同样会导致 JVM 内存溢出。如此看来,读取大数据量字段,也必须是我们在 JDBC 开发过程中不可忽视的问题。

Web开发|10|JDBC 数据库开发_第18张图片
单个字段数据量过大,导致内存溢出

这种情景下,我们采用的解决方案是“流方式”数据读取,即把大数据量的字段以二进制的方式进行分段读取,


Web开发|10|JDBC 数据库开发_第19张图片
流方式 读取数据量过大的字段

使用“流方式”读取数据库数据,示例代码如下:

Web开发|10|JDBC 数据库开发_第20张图片
示例代码:流方式 读取数据库数据

核心做法是, 使用 ResultSet 对象的 getBinaryStream() 方法来读取二进制流数据



15、如何解决 JDBC 向数据库插入大量数据记录而导致速度过慢的问题?

情景描述:如果有大量数据记录需要插入到数据库,如果使用通常的一条一条的 SQL 插入语句去实现,这将导致插入速度非常慢。慢的原因是,每插入一条数据都需要向数据库发送一条 SQL 语句并执行一次 SQL 语句,如果插入成千上万条数据,那么就需要向数据库发送成千上万次 SQL,因此这样做的效率是很低的。

这种情景下,我们的解决方案是使用“批处理”插入数据,即向数据库一次同时发送多条 SQL 语句 ,同时插入多条数据。这样做,可以大大地减少向数据库发送 SQL 语句的次数,因此能够提升向数据库插入数据的效率。

Web开发|10|JDBC 数据库开发_第21张图片
批处理,解决大量数据插入数据库的问题

批处理,所用的 API 是什么?

批处理 所使用的 JDBC API 是 Statement / PreparedStatement 对象的 addBatch() / executeBatch() / clearBatch() 方法。

Web开发|10|JDBC 数据库开发_第22张图片
使用 Statement / PreparedStatement 对象

什么是 batch ?

一个 batch 就是一组需要同时发送到数据库的多条 SQL 语句。

“批处理”解决大量数据插入数据库的问题,示例代码如下:

// UseBatch.java
package cn.geekxia;
import java.sql.*;

public class UseBatch {
    public final static String JDBC_DRIVER = "com.mysql.jdbc.Driver";
    public final static String DB_URL = "jdbc:mysql://localhost/world";
    public final static String USER = "root";
    public final static String PASS = "x-448914712";
    public static void helloMysql() throws ClassNotFoundException {
        Connection conn = null;
        Statement stmt = null;
        ResultSet rs = null;
        
        // 第1步:装载 JDBC 驱动程序
        Class.forName(JDBC_DRIVER);
        // 第2步:建立到 MySQL数据库的连接,这有可能出现异常,这里必须要进行相应的异常处理
        try {
            conn = DriverManager.getConnection(DB_URL, USER, PASS);

            // 第3步:批处理,同时执行多条 SQL 语句
            stmt = conn.createStatement();
            String[] users = {"name1", "name2", "name3"};
            // 把三条 SQL 语句同时添加到 batch 中去
            for (String user: users) {
                stmt.addBatch("insert into user(username) values("+user+")");
            }
            // 把三条 SQL 同时发送至数据库并执行
            stmt.executeBatch();
            // 执行完毕后,清理 batch,为下一次执行 batch 而准备
            stmt.clearBatch();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 第5步:清理数据库连接资源,放在 finally 中执行,以确保无论是否发生了 SQLException,都能够清理掉这些连接资源,以减少程序的性能耗费。清理资源也有可能发生异常,因此这里也需要进行异常捕获。
            try {
                if (conn != null)
                conn.close();
                if (stmt != null)
                stmt.close();
                if (rs != null)
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws ClassNotFoundException {
        helloMysql();
    }
}

上述代码的核心做法时,使用循环把多条 SQL 语句打包至一个 batch 中去,然后把 batch 发送至数据库一起执行,执行完 batch 后记得清理掉它即可



16、关于中文乱码的问题

如果 JDBC 中设置的字符集和数据库设置的字符集不一致,这将导致中文数据的乱码问题。

查询数据库 Servler 的字符编码列表:
show variables like '%character%';
Web开发|10|JDBC 数据库开发_第23张图片
查询数据库 Server 的字符编码列表

在创建数据表时,还可以为数据表、字段分别设置 字符编码格式。当数据库 Server、数据库、表、字段都同时设置了字符编码时,它们优先级如下图所示,即越小的单元优先级越高。

Web开发|10|JDBC 数据库开发_第24张图片
字符编码的优先级

如果数据库 Server、数据库、表、字段同时设置了字符编码格式,则为字段设置的编码格式优先起作用,依次类推

那么该如何给 JDBC 设置字符编码格式以保证它与数据库的字符格式一致呢?

做法是,在 JDBC 与数据库连接的 DB_URL 的末尾添加上字符集属性即可,如下图所示。

为 JDBC 设置字符编码
public final static String DB_URL = "jdbc:mysql://localhost/world?characterEncoding=uft8";

通常为了避免中文乱码的情况,建议把数据库的编码格式和 JDBC 的编码格式都设置为 utf8


本节完!!

你可能感兴趣的:(Web开发|10|JDBC 数据库开发)