文章摘要(AI生成)
本文主要介绍了如何使用Java连接数据库以及JDK如何规范不同数据库驱动的实现。对于执行SQL操作,需要包括连接数据库、执行SQL、解析结果集等步骤。文章还提到了在java.sql.*中关于SQL相关的接口和工具类的作用。针对查询MySQL数据库的技术方案,包括连接MySQL服务端、执行命令、创建数据库连接、执行SQL及解析返回结果等步骤。文章还介绍了面向对象的实现方式以及数据库驱动、数据库连接、SQL执行器和结果集对象的职责。最后,文章探讨了JDK如何封装数据库驱动、数据库连接、SQL执行器和结果集,以及其他框架如何进一步封装这些对象。文章总结了类加载阶段注册驱动和基于Java socket编程连接数据库的方法。整体来看,本文详细介绍了Java连接数据库的相关知识。
本文旨在说明:
- 如何使用java连接数据库?
- jdk又是如何规范各个数据库驱动实现的?
- 执行sql需要做哪些过程?
在jdk自带的jre中,我们可以在java.sql.*
中看到一些关于sql相关的接口和工具类:
这些类都为我们做了什么呢?
假如你来出一个需求。。。
如果你是技术,来不依赖任何包实现查询mysql数据库,那么在你的技术方案中大概要实现以下几点:
- 连接mysql服务端(如:
mysql -h 182.167.12.3 -u user1 -p -P 3308
) - 执行我们构建好的命令创建数据库连接
- 在创建好的连接上执行sql
- 使用对象解析sql返回结果,创建结果集
上面这4步只是面向查询mysql数据库的过程,提出的4个实现步骤。
面向对象的实现
把上述4个步骤使用面向对象进行实现,我们首先要把上述的过程中进行对象示例化,面向对象一个很重要的思维就是:
我吃饭了 = 【发起对象:我】【执行器:吃】【目标对象:饭】
所以对于我们查询mysql数据库的这4步,我们也可以拆分为
- 构建【数据库驱动】,保存数据库连接信息
- 使用【数据库驱动】创建【数据库连接】
- 在创建好的【数据库连接】上,通过【sql执行器】执行sql
- 使用对象解析sql返回结果,创建【结果集】
基于上述过程中的对象分析,我们也可以定义出各个对象的职责:
-
【数据库驱动】:
- 保存数据库连接信息
- 提供创建数据连接的能力
-
【数据库连接】:
- sql相关的事务管理
- 连接的关闭超时设置
- 创建sql执行器
-
【sql执行器】:
- 执行sql语句
-
【结果集】:
- 解析sql返回结果
看JDK如何封装
既然我们已经清楚各个对象的职责,那么还需分析,哪些对象是可拓展的,哪些对象是过程对象无须拓展。针对上述我们梳理好的割割对象,可以看到【数据库驱动】、【数据库连接】、【sql执行器】都是可拓展对象,针对不同的数据库,完全可以有不同的实现。而不管对于何种数据库,根据驱动创建连接都是一个固定过程,所以jdk将根据驱动创建连接封装为一个【驱动管理器】
我们新抽象的【驱动管理器】职责如下:
- 维护驱动注册表,管理驱动的注册注销
- 使用数据库驱动创建数据库连接
为了增强我们sql执行器的拓展性,JDK又给我们的sql执行器划分成三种:
- 静态sql执行器,适合执行没有参数的sql语句
- 预占用sql执行器,适合执行有参数的sql语句
- 存储过程执行器,用来执行存储过程
所以对上述执行过程进行总结,我们可以得到如下过程:
结合源码我们可以对照:
名称 | 源码类名 | 类型 |
---|---|---|
驱动管理器 | java.sql.DriverManager |
类 |
数据库驱动 | java.sql.Driver |
接口 |
数据库连接 | java.sql.Connection |
接口 |
静态sql执行器 | java.sql.Statement |
接口 |
预占用sql执行器 | java.sql.PreparedStatement |
接口 |
存储过程执行器 | java.sql.CallableStatement |
接口 |
结果集 | java.sql.resultSet |
接口 |
其他框架如何进一步封装
针对数据库驱动:有对应的mysql、oracle、sql server等数据库驱动的实现
针对数据库连接:数据库连接又可以通过池化,使用连接池技术我们又有C3P0、Druid、DBCP等数据库连接池
针对执行器和结果集:这方面我们可以通过持久化技术,例如mybatis、hibernate、JPA等持久层框架进一步封装
拓展后我们可以得到如下结构图:
加亿点点技术~
类加载阶段注册驱动
注册驱动这一步对用户使用来说至关重要,如果我们能够让用户在调用驱动时就能完成驱动注册那就再好不过,如何能够让用户在调用驱动时就完成驱动注册呢?
这里我们可以通过构造方法和静态块来实现,但java规定必须使用后者来完成驱动的创建及注册:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {
// Required for Class.forName().newInstance()
}
}
这是为了在类加载的时候就完成驱动注册,所以我们可以得到一个很重要的结论:数据库驱动的加载是在类加载阶段就完成的
这样,用户只要通过Class.forName()
加载对应的驱动类,就能完成数据库驱动的注册。
socket编程
第二点,基于java socket编程,连接数据库
public <T extends Closeable> T connect(String hostname, int portNumber, PropertySet pset, int loginTimeout) throws IOException {
this.loginTimeoutCountdown = loginTimeout;
if (pset != null) {
this.host = hostname;
this.port = portNumber;
String localSocketHostname = pset.getStringProperty(PropertyKey.localSocketAddress).getValue();
InetSocketAddress localSockAddr = localSocketHostname != null && localSocketHostname.length() > 0
? new InetSocketAddress(InetAddress.getByName(localSocketHostname), 0)
: null;
int connectTimeout = pset.getIntegerProperty(PropertyKey.connectTimeout).getValue();
if (this.host != null) {
InetAddress[] possibleAddresses = InetAddress.getAllByName(this.host);
if (possibleAddresses.length == 0) {
throw new SocketException("No addresses for host");
}
// save last exception to propagate to caller if connection fails
SocketException lastException = null;
// Need to loop through all possible addresses. Name lookup may return multiple addresses including IPv4 and IPv6 addresses. Some versions of
// MySQL don't listen on the IPv6 address so we try all addresses.
for (int i = 0; i < possibleAddresses.length; i++) {
try {
this.rawSocket = createSocket(pset);
configureSocket(this.rawSocket, pset);
InetSocketAddress sockAddr = new InetSocketAddress(possibleAddresses[i], this.port);
// bind to the local port if not using the ephemeral port
if (localSockAddr != null) {
this.rawSocket.bind(localSockAddr);
}
this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout));
break;
} catch (SocketException ex) {
lastException = ex;
resetLoginTimeCountdown();
this.rawSocket = null;
}
}
if (this.rawSocket == null && lastException != null) {
throw lastException;
}
resetLoginTimeCountdown();
this.sslSocket = this.rawSocket;
return (T) this.rawSocket;
}
}
throw new SocketException("Unable to create socket");
}
评论区