数据库是后端最重要的持久化组件。JDBC 是数据库开发的基础,通过 JDBC 就可以实现对后端数据库的访问。
1、访问数据库最常见的几种方式(以 MySQL 为例):
- 图形化工具:Workbench,用户通过点击和拖拽就能实现对 MySQL 数据库的访问和操作,它的优点是页面友好、操作简单;缺点是无法满足较为复杂的查询需求。它满足了人对数据库的访问需求。
- 命令行工具:在命令行中输入完整的 SQL 语句实现更为复杂的查询需求,这就要求用户能熟练地使用 SQL,门槛相对较高。
- JDBC 驱动程序:Connector/J,使用相关驱动和 API 即可实现对数据库的访问。它满足了程序对数据库的访问需求。
本节课我们要学习的重点是第3种方式,即通过 Java 应用程序来访问数据库。
2、什么是 JDBC ?应用程序是如何与数据库连接的?
不同的数据库,所支持的连接协议也是不相同的,数据库驱动程序所提供的 API 接口也不相同,这给“跨数据库迁移”带来了诸多不便。JDBC 存在的意义,就是统一了 API 接口,从而可以方便地对任意数据库进行访问。JDBC 帮助我们屏蔽了应用程序与数据库之间交互协议的实现细节,我们只需要使用 JDBC 提供的标准 API ,即可实现对任意数据库的访问,且无需关心底层的实现方式。
对 Java 程序来讲,JDBC 就是一个普通的 Java 类库(JAR包),在应用程序中引用 JDBC JAR包所提供的类和方法,通过操作 Java对象的方式就可以操作数据库中的数据。
对 数据库厂商来讲,JDBC 就是一套接口规范,每个厂商的数据库驱动都必须实现 JDBC 中所定义的数据库接口,如此才能在 Java程序中实现数据库连接与访问。
3、JDBC 有哪些优势?
1、它使得Java程序对数据库的访问变得更加简单,一套接口即可实现对任意数据库的访问。
2、使用 JDBC,大大地提升了数据库开发的效率。
3、基于 JDBC的接口规范,使得 Java程序在面对不同的数据库时,具有了强大的跨平台可移植性。
4、JDBC 只是定义了最最基础的 API 功能,我们可以基于 JDBC 基本功能再去封装更为简单、更为便捷的框架。
4、JDBC 的体系架构是怎样的?
JDBC 的体系架构分为两层,分别是上层的 JDBC API 层,下层的 JDBC 驱动层。API 层 主要用于与数据库进行通信;驱动层主要用于与具体的数据库建立连接,这些驱动层 Driver 一般都是由数据库厂商提供的。
5、什么是 JDBC 驱动?如何搭建 JDBC 的开发环境?
JDBC 的 API 默认是已经集成在 JDK 中了,无需额外地安装。我们需要下载并安装的是 具体的数据库驱动。对 MySQL 数据库来讲,我们需要下载并安装的是 Connector/J ,这个驱动实际上是一个 JAR包,我们需要把它加入到 Java应用工程中去,或者将其添加到 CLASSPATH 环境变量中去,但是我们通常推荐使用 Maven 来管理数据库驱动程序 JAR包。
6、JDBC API 所涉及到的几个非常重要的 Java 类
这些 JDBC 的 API 类,其继承关系如下图所示,其中 Driver 是驱动程序最顶层的抽象接口,它定义了驱动程序所有必须的基本功能。
7、认识 DriverManager 类:
DriverManager 是 JDBC 驱动程序的管理类,使用 Class.forName() 可以向 DriverManager 装载并注册一个驱动程序。使用 DriverManager 对象的 getConnection() 方法就可以建立到指定数据库的连接。
getConnection() 接收三个参数,分别是 DB_URL,USERNAME,PASSWORD。下面重点解释一下连接数据库所用的 DB_URL:
什么是 DB_URL ?
它是数据库唯一的通信标识符,通过它可以确定唯一的数据库实例。DB_URL 的组成部分如下图所示:
其中“子协议”部分决定了数据库的类型。如下图是三种常用数据库的 DB_URL 格式:
8、认识 Connection 类:
Connection对象代表着 Java应用程序与后端数据库的一条物理连接,基于这条连接,我们可执行一些 SQL 语句,它最常用的方法是 conn.createStatement(),该方法返回一个 Statement 对象。
9、认识 Statement 类:
Statement 对象,可以理解成是一个 SQL 窗器,在这个窗器中可以执行 SQL 语句,以实现增删改查的功能。执行该对象的 executeQuery() 方法,可以得到一个查询结果的对象,即 ResultSet 对象。如果是执行该对象的 增、删、改的方法,返回的结果是一个 int 类型的值,这个值代表的意思是当前操作所影响到的行数。
10、认识 ResultSet 类:
ResultSet 对象代表着 SQL 查询的结果集。关系型数据库可以看成一个二元表。ResultSet 对象的内部有一个指针,用于指向结果集的行记录,默认指向第一条行记录。调用 ResultSet 对象的 next() 方法可以让指针指向下一条行记录。调用 previous() 方法让指针指向上一行。调用 absolute() 方法让指针指向指定的行。调用 beforeFirst() 方法让指针指向第一行的上一条记录。调用 afterLast() 方法,让指针指向最后一行的下一条记录。
遍历 ResultSet 对象,即可读取结果集中的每一行记录,相关方法如下表:
11、认识 SQLException 类:
在执行 SQL 语句的过程,MySQL 服务器有可能会抛出一些异常,SQLException 对象即代表着这些异常,捕获这些异常后,就是一个完整的 JDBC 工作流程了。
12、JDBC 开发的五个基本步骤
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 内存溢出。
在这种情景下,就需要使用到 JDBC 游标来解决问题了。那么什么是 JDBC 游标?它到底是如何避免“JVM 内存溢出”问题的呢?
什么是 JDBC 游标?什么是 fetch size ?
对庞大的数据记录,为了避免内存溢出,我们可以分批来读取数据库中的数据记录,这每一批的数据即为一个 fetch size。支持这种数据读取方式的技术,即为 JDBC 游标。如下图示:
如何开启 JDBC 游标呢?
在 JDBC 与数据库连接的 DB_URL 上,添加一个 useCursorFetch 属性即可开启 JDBC 游标,如下图的 DB_URL 所示:
如何使用 JDBC 游标?使用 PreparedStatement 类:
使用 PreparedStatement 对象代替 Statement 对象。PreparedStatement 实际上是继承自 Statement 的。PreparedStatement 是专门为 JDBC 游标而设计的,以解决读取大量数据记录可能导致的“内存溢出”问题。
使用 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 开发过程中不可忽视的问题。
这种情景下,我们采用的解决方案是“流方式”数据读取,即把大数据量的字段以二进制的方式进行分段读取,
使用“流方式”读取数据库数据,示例代码如下:
核心做法是, 使用 ResultSet 对象的 getBinaryStream() 方法来读取二进制流数据。
15、如何解决 JDBC 向数据库插入大量数据记录而导致速度过慢的问题?
情景描述:如果有大量数据记录需要插入到数据库,如果使用通常的一条一条的 SQL 插入语句去实现,这将导致插入速度非常慢。慢的原因是,每插入一条数据都需要向数据库发送一条 SQL 语句并执行一次 SQL 语句,如果插入成千上万条数据,那么就需要向数据库发送成千上万次 SQL,因此这样做的效率是很低的。
这种情景下,我们的解决方案是使用“批处理”插入数据,即向数据库一次同时发送多条 SQL 语句 ,同时插入多条数据。这样做,可以大大地减少向数据库发送 SQL 语句的次数,因此能够提升向数据库插入数据的效率。
批处理,所用的 API 是什么?
批处理 所使用的 JDBC API 是 Statement / PreparedStatement 对象的 addBatch() / executeBatch() / clearBatch() 方法。
什么是 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%';
在创建数据表时,还可以为数据表、字段分别设置 字符编码格式。当数据库 Server、数据库、表、字段都同时设置了字符编码时,它们优先级如下图所示,即越小的单元优先级越高。
如果数据库 Server、数据库、表、字段同时设置了字符编码格式,则为字段设置的编码格式优先起作用,依次类推。
那么该如何给 JDBC 设置字符编码格式以保证它与数据库的字符格式一致呢?
做法是,在 JDBC 与数据库连接的 DB_URL 的末尾添加上字符集属性即可,如下图所示。
public final static String DB_URL = "jdbc:mysql://localhost/world?characterEncoding=uft8";
通常为了避免中文乱码的情况,建议把数据库的编码格式和 JDBC 的编码格式都设置为 utf8。
本节完!!