深入理解JDBC

很早之前就学过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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值