数据库是后端最重要的持久化组件。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 就是一套接口规范,每个厂商的数据库驱动都必须实现 JDBC 中所定义的数据库接口,如此才能在 Java程序中实现数据库连接与访问。
3、JDBC 有哪些优势?
1、它使得Java程序对数据库的访问变得更加简单,一套接口即可实现对任意数据库的访问。
2、使用 JDBC,大大地提升了数据库开发的效率。
3、基于 JDBC的接口规范,使得 Java程序在面对不同的数据库时,具有了强大的跨平台可移植性。
4、JDBC 只是定义了最最基础的 API 功能,我们可以基于 JDBC 基本功能再去封装更为简单、更为便捷的框架。
4、JDBC 的体系架构是怎样的?
JDBC 的体系架构JDBC 的体系架构分为两层,分别是上层的 JDBC API 层,下层的 JDBC 驱动层。API 层 主要用于与数据库进行通信;驱动层主要用于与具体的数据库建立连接,这些驱动层 Driver 一般都是由数据库厂商提供的。
5、什么是 JDBC 驱动?如何搭建 JDBC 的开发环境?
下载 MySQL 的 JDBC 驱动JDBC 的 API 默认是已经集成在 JDK 中了,无需额外地安装。我们需要下载并安装的是 具体的数据库驱动。对 MySQL 数据库来讲,我们需要下载并安装的是 Connector/J ,这个驱动实际上是一个 JAR包,我们需要把它加入到 Java应用工程中去,或者将其添加到 CLASSPATH 环境变量中去,但是我们通常推荐使用 Maven 来管理数据库驱动程序 JAR包。
配置 Connector/J 驱动JAR包的环境变量
6、JDBC API 所涉及到的几个非常重要的 Java 类
JDBC API 的继承关系这些 JDBC 的 API 类,其继承关系如下图所示,其中 Driver 是驱动程序最顶层的抽象接口,它定义了驱动程序所有必须的基本功能。
7、认识 DriverManager 类:
DriverManger 类DriverManager 是 JDBC 驱动程序的管理类,使用 Class.forName() 可以向 DriverManager 装载并注册一个驱动程序。使用 DriverManager 对象的 getConnection() 方法就可以建立到指定数据库的连接。
getConnection() 接收三个参数,分别是 DB_URL,USERNAME,PASSWORD。下面重点解释一下连接数据库所用的 DB_URL:
什么是 DB_URL ?
DB_URL 的组成它是数据库唯一的通信标识符,通过它可以确定唯一的数据库实例。DB_URL 的组成部分如下图所示:
其中“子协议”部分决定了数据库的类型。如下图是三种常用数据库的 DB_URL 格式:
8、认识 Connection 类:
Connection 类Connection对象代表着 Java应用程序与后端数据库的一条物理连接,基于这条连接,我们可执行一些 SQL 语句,它最常用的方法是 conn.createStatement(),该方法返回一个 Statement 对象。
9、认识 Statement 类:
Statement 类Statement 对象,可以理解成是一个 SQL 窗器,在这个窗器中可以执行 SQL 语句,以实现增删改查的功能。执行该对象的 executeQuery() 方法,可以得到一个查询结果的对象,即 ResultSet 对象。如果是执行该对象的 增、删、改的方法,返回的结果是一个 int 类型的值,这个值代表的意思是当前操作所影响到的行数。
10、认识 ResultSet 类:
ResultSet 类ResultSet 对象代表着 SQL 查询的结果集。关系型数据库可以看成一个二元表。ResultSet 对象的内部有一个指针,用于指向结果集的行记录,默认指向第一条行记录。调用 ResultSet 对象的 next() 方法可以让指针指向下一条行记录。调用 previous() 方法让指针指向上一行。调用 absolute() 方法让指针指向指定的行。调用 beforeFirst() 方法让指针指向第一行的上一条记录。调用 afterLast() 方法,让指针指向最后一行的下一条记录。
遍历 ResultSet 对象,即可读取结果集中的每一行记录,相关方法如下表:
ResultSet 类
11、认识 SQLException 类:
在执行 SQL 语句的过程,MySQL 服务器有可能会抛出一些异常,SQLException 对象即代表着这些异常,捕获这些异常后,就是一个完整的 JDBC 工作流程了。
12、JDBC 开发的五个基本步骤
JDBC 开发的 5 个基本步骤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 ?
JDBC 游标 与 fetch size对庞大的数据记录,为了避免内存溢出,我们可以分批来读取数据库中的数据记录,这每一批的数据即为一个 fetch size。支持这种数据读取方式的技术,即为 JDBC 游标。如下图示:
如何开启 JDBC 游标呢?
如何开启 JDBC 游标?在 JDBC 与数据库连接的 DB_URL 上,添加一个 useCursorFetch 属性即可开启 JDBC 游标,如下图的 DB_URL 所示:
如何使用 JDBC 游标?使用 PreparedStatement 类:
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 是什么?
使用 Statement / PreparedStatement 对象批处理 所使用的 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、数据库、表、字段都同时设置了字符编码时,它们优先级如下图所示,即越小的单元优先级越高。
如果数据库 Server、数据库、表、字段同时设置了字符编码格式,则为字段设置的编码格式优先起作用,依次类推。
那么该如何给 JDBC 设置字符编码格式以保证它与数据库的字符格式一致呢?
为 JDBC 设置字符编码做法是,在 JDBC 与数据库连接的 DB_URL 的末尾添加上字符集属性即可,如下图所示。
public final static String DB_URL = "jdbc:mysql://localhost/world?characterEncoding=uft8";
通常为了避免中文乱码的情况,建议把数据库的编码格式和 JDBC 的编码格式都设置为 utf8。
附:在 Windows系统上搭建 JDBC开发环境
在 Windows电脑上搭建 JDBC开发环境,所使用的工具有Java、Tomcat、MySQL、JDBC驱动程序。
JDBC环境搭建-Windows版
第1步:下载并安装 JavaSE,并配置相关环境变量
下载 JDK 并配置两个环境变量:
JAVA_HOME = D:\Java\JDK (JDK 所在的安装目录 )
PATH = D:\Java\JDK\bin (JRE 所在的 bin 目录)
第2步:下载并安装 Tomcat,配置 CATALINA_HOME 环境变量
下载 Tomcat 并配置环境变量
CATALINA_HOME = D:\Tomcat
使用图形化的Tomcat工具对其进行启动与停止。在 D:\Tomcat\conf\server.xml 中可以对Tomcat进行相关自定义配置。
Java程序的部署目录:D:\Tomcat\webapps\ROOT\WEB-INF
静态资源文件部署目录:D:\Tomcat\webapps\ROOT
第3步:下载并安装 MySQL
第4步:配置 Java开发的第三方JAR包
下载 servlet.jar包,下载 Connector/驱动包。
配置第三方JAR包的 CLASSPATH 环境变量:
D:\Tomcat\lib\servlet-api.jar
D:\Tomcat\lib\mysql-connector-java-8.0.12.jar
Java程序运行时,会从 CLASSPATH 环境中寻找所需要的第三方JAR包。
示例代码如下:
package cn.geekxia;
import java.util.Date;
import javax.servlet.ServletException;
import java.io.*;
import javax.servlet.http.*;
import java.sql.*;
public class User extends HttpServlet {
public static final String URL = "jdbc:mysql://localhost/world?serverTimezone=UTC&useSSL=false&user=root&password=xxxx";
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
res.setContentType("text/html;charset=UTF-8");
PrintWriter out = res.getWriter();
Connection conn = null;
Statement stmt = null;
try {
Class.forName("com.mysql.jdbc.Driver"); // 装载JDBC驱动程序
conn = DriverManager.getConnection(URL); // 创建数据库连接
stmt = conn.createStatement();
String sql = "SELECT * FROM country;"; // 执行查询语句
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
String name = rs.getString("Name");
out.println("<span> "+name+" </span>");
}
} catch(SQLException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
} finally {
try {
if (stmt != null) {
stmt.close(); // 如果存在 stmt,就关闭。以节省数据库连接资源。
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close(); // 如果存在 conn,就关闭。以节省数据库连接资源。
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
部署描述符 web.xml ,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0">
<display-name>GeekXia</display-name>
<servlet>
<servlet-name>User</servlet-name>
<servlet-class>
cn.geekxia.User
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>User</servlet-name>
<url-pattern>/User</url-pattern>
</servlet-mapping>
</web-app>
编译.java 代码,并放置至指定的 ./classes/cn/geekxia 文件目录:
javac -d ./classes/cn/geekxia User.java
重启 Tomcat,并在浏览器地址栏中输入 http://localhost:8080/User ,即可看到从 MySQL数据库中查询到的数据。
本节完!!
网友评论