很早之前就学过JDBC了,但是工作之后都是使用的对JDBC封装好的ORM框架。基本没有手写过JDBC相关的代码了,但是为了对使用的框架底层有更加深入的理解,所以重新复习总结一下JDBC技术。其实JDBC本身并不复杂,这里使用倒序的方式,先将完整步骤写出来,再对每一步进行分析讲解。个人觉得这种方式更加容易理解。
1、是什么?
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。 不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 — 面向接口编程
2、为什么?
如果不提供JDBC接口,而直接使用各个数据库产商的实现,那么程序员就必须熟悉不同产商的数据库实现,每种数据库的实现各不相同,也不便于切换。简单说就是为了解耦和可移植性。
3、怎么用?
以连接 Mysql 数据库为例,使用 JDBC 执行的步骤大体上分为7步,分别是:
// 1.导入数据库驱动的jar包(mysql-connector-java-5.1.37-bin.jar)
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 2.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 3.获取连接connection
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url, user, password);
// 4.写SQL语句
String sql = "SELECT id, name, age FROM users WHERE id = ?";
// 5.创建statement
ps = conn.prepareStatement(sql);
ps.setInt(1, 2);
// 6.执行SQL语句
rs = ps.executeQuery();
if (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
System.out.println(new User(id, name, age));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
下面,分别对每一步进行详细讲解:
(1)第一步:导入jar包:JDBC本身只是提供了一套操作数据库的接口,并没有具体的实现,要想使用某一种数据库,必须先导入该数据库的相关实现(这里即:jar包)。
(2)第二步:加载驱动:应用程序启动时,利用反射加载将对应数据库的驱动类加载到内存中。进入源码可以看到 Mysql 的驱动类中有一段静态代码块(其他数据库的驱动实现类中不一定有),当该类【com.mysql.jdbc.Driver 】被加载时,会自动加载该静态代码块的内容,从而将该驱动【com.mysql.jdbc.Driver】注册到 java 提供的驱动管理类【java.sql.DriverManager】中。
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
//注册 com.mysql.jdbc.Driver 驱动到 java.sql.DriverManager 中
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
...
}
进入 java.sql.DriverManager 类的源码可以看到,其底层使用的是一个写时复制的成员变量 registeredDrivers 集合 【CopyOnWriteArrayList<DriverInfo> 底层使用的还是数组Object[] 保存】来封装成DriverInfo并保存所有的数据库驱动。
package java.sql;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.CopyOnWriteArrayList;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
public class DriverManager {
// 存储所有JDBC驱动的集合
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static synchronized void registerDriver(java.sql.Driver driver)
throws SQLException {
//注册驱动
registerDriver(driver, null);
}
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
//如果不存在就添加到驱动集合中。即:registeredDrivers 【CopyOnWriteArrayList<DriverInfo>】类中的 Object[] array数组中
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
...
}
(3)获取连接 Connection :传入url、user、password 参数遍历 DriverManager 中注册的所有驱动,来获取对应的数据库连接。
package java.sql;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.CopyOnWriteArrayList;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
public class DriverManager {
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
@CallerSensitive
public static Connection getConnection(String url,
String user, String password) throws SQLException {
java.util.Properties info = new java.util.Properties();
if (user != null) {
info.put("user", user);
}
if (password != null) {
info.put("password", password);
}
return (getConnection(url, info, Reflection.getCallerClass()));
}
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
if(url == null) {
throw new SQLException("The url cannot be null", "08001");
}
println("DriverManager.getConnection(\"" + url + "\")");
SQLException reason = null;
//核心逻辑:通过遍历来获取对应的数据库连接
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// if we got here nobody could connect.
if (reason != null) {
println("getConnection failed: " + reason);
throw reason;
}
println("getConnection: no suitable driver found for "+ url);
throw new SQLException("No suitable driver found for "+ url, "08001");
}
...
}
此时,返回的 connection 就是 Mysql 提供的 Connection 接口的实现类。【数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。】
(4)写SQL语句:定义想要操作的数据库SQL语句。
(5)创建Statement:通过获取到的Connection来创建 prepareStatement【Statement】。这里注意的是通常都是使用PrepareStatement,而不是 Statement。两者的本质区别就是:PreparedStatement 是预编译的。这样可以防止SQL注入、批量执行时不需要重复编译,提高语句执行效率。
(6)执行SQL:Statement 是真正执行命令对象,把Sql语句发送给数据库执行。并可以处理返回的结果集 ResultSet。
(7)关闭资源:数据库连接(Connection)是非常稀有的资源,用完后必须马上释放, 如果Connection不能及时正确的关闭将导致系统宕机。Connection的使 用原则是尽量晚创建,尽量早的释放。
总结:JDBC核心就是通过 Java 提供的一套标准的 API 来屏蔽到不同数据库实现的细节,简化和加快开发过程,并且可以方便的切换不同的数据库,体现的是面向接口的编程思想。无论是JDBC 还是JAVA WEB,都是一个应用程序通过网络连接另一个应用程序,一般都会提供连接地址URL、用户名user、密码password来登录,然后获取对应的权限来执行对应的操作。
相关文章推荐:
JDBC模型—深入理解JDBC设计思想(探究Class.forName("DBDriver")): https://blog.csdn.net/daijin888888/article/details/50969621
JDBC要点总结、SQL注入示例(Statement和PreparedStatement):https://blog.csdn.net/daijin888888/article/details/50965232