JDBC初步

mybatis基于JDBC进行的封装,在了解mybatis之前,先了解mybatis的内核JDBC

首先我们先来看connection的获取。Connection获取实现的核心类DriverManager,负责管理加载到jvm中的所有驱动实现类,也是网传典型的桥接模式。

首先说明DriverManager是如何加载驱动类的。以MySQL驱动类com.mysql.cj.jdbc.Driver为例

php 复制代码
// Register ourselves with the DriverManager
static {
    try {
        java.sql.DriverManager.registerDriver(new Driver());
    } catch (SQLException E) {
        throw new RuntimeException("Can't register driver!");
    }
}

驱动类的注册在静态代码块中完成,而驱动类的加载则是采用了java的spi服务发现技术。在DriverManager

vbnet 复制代码
// If the driver is packaged as a Service Provider, load it.
// Get all the drivers through the classloader
// exposed as a java.sql.Driver.class service.
// ServiceLoader.load() replaces the sun.misc.Providers()
​
AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Void run() {
​
        ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
        Iterator<Driver> driversIterator = loadedDrivers.iterator();
​
        /* Load these drivers, so that they can be instantiated.
                 * It may be the case that the driver class may not be there
                 * i.e. there may be a packaged driver with the service class
                 * as implementation of java.sql.Driver but the actual class
                 * may be missing. In that case a java.util.ServiceConfigurationError
                 * will be thrown at runtime by the VM trying to locate
                 * and load the service.
                 *
                 * Adding a try catch block to catch those runtime errors
                 * if driver not available in classpath but it's
                 * packaged as service and that service is there in classpath.
                 */
        try{
            while(driversIterator.hasNext()) {
                driversIterator.next();
            }
        } catch(Throwable t) {
            // Do nothing
        }
        return null;
    }
});
​

这部分同样位于静态代码块中,通过spi自动加载java.sql.Driver实现类。根据spi技术规范,我们也可以在驱动包中找到驱动的注册信息,位于META-INF/services/java.sql.Driver文件中,文件中的全限定类名为接口java.sql.Driver的实现类,以MySQL驱动包mysql-connector-java:8.0.27为例

复制代码
com.mysql.cj.jdbc.Driver

再回到驱动类的注册过程上,在DriverManager

java 复制代码
public static synchronized void registerDriver(java.sql.Driver driver,
                                               DriverAction da)
    throws SQLException {
    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        // This is for compatibility with the original DriverManager
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);
}

显然,对于已经完成注册的驱动类,是一个读多写少的场景,因此这里的registeredDrivers用的CopyOnWrite容器保证线程安全。

然后是getConnection方法。DriverManager不负责实现具体连接逻辑,将实体实现委托给驱动类。至于驱动类如何实现,这里不做过多介绍。有空填坑

scss 复制代码
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());
    }
​
}

Connection之后是Statement。Statement是用于表示一条sql的接口,可供静态sql执行以及返回执行结果,共有三种类型:

  • Statement 对应静态无参sql

  • PreparedStatement Extends Statement 对应预编译sql,可以携带参数,防止sql injection。在一条sql多次执行的情况下,比Statement快,因此PreparedStatement不需要在DBMS中重复编译。

    PreparedStatement如何防止sql注入?

    首先,sql注入简单来说是数据库引擎执行了意料之外的语义,执行范围超过了原始sql改动范围。PreparedStatement之所以能够防止sql注入,引用oracle自己的话就是:Prepared statements always treat client-supplied data as content of a parameter and never as a part of an SQL statement.

    PreparedStatement和Statement不同,在初始化时PreparedStatement就确定了sql内容,其所持有的是DBMS parser解析之后sql,涉及的表和列都已经确定不会更改(因为不会再次编译),提供的参数仅仅作为纯文本的输入,而不会改动sql的语义。所以这也是为什么不能把table和column当作参数的原因,这样的动态sql每次编译后结果一致。

  • CallableStatement Extends PreparedStatement

    对应存储过程,略

Statement执行后对应一个ResultSet,这没啥好说的,结构和DBMS中表类似,示例如下

ini 复制代码
String selectSql = "SELECT * FROM employees"; 
try (ResultSet resultSet = stmt.executeQuery(selectSql)) {
    List<Employee> employees = new ArrayList<>(); 
    while (resultSet.next()) { 
        Employee emp = new Employee(); 
        emp.setId(resultSet.getInt("emp_id")); 
        emp.setName(resultSet.getString("name")); 
        emp.setPosition(resultSet.getString("position")); 
        emp.setSalary(resultSet.getDouble("salary")); 
        employees.add(emp); 
    }
}

mybatis结构 mapperProxy->sqlSession->executor->statementHandler

参考文献:

1\]: [Processing SQL Statements with JDBC](https://link.juejin.cn?target=https%3A%2F%2Fdocs.oracle.com%2Fjavase%2Ftutorial%2Fjdbc%2Fbasics%2Fprocessingsqlstatements.html "https://docs.oracle.com/javase/tutorial/jdbc/basics/processingsqlstatements.html") \[2\]:[Introduction to JDBC](https://link.juejin.cn?target=https%3A%2F%2Fwww.baeldung.com%2Fjava-jdbc "https://www.baeldung.com/java-jdbc")

相关推荐
是小崔啊9 分钟前
tomcat源码02 - 理解Tomcat架构设计
java·tomcat
没有bug.的程序员26 分钟前
JAVA面试宝典 -《安全攻防:从 SQL 注入到 JWT 鉴权》
java·安全·面试
栈溢出了27 分钟前
MyBatis实现分页查询-苍穹外卖笔记
java·笔记·mybatis
morningcat201835 分钟前
java17 gc笔记
java·jvm·笔记
1 小时前
Unity开发中常用的洗牌算法
java·算法·unity·游戏引擎·游戏开发
Your易元1 小时前
设计模式-模板方法模式
java·设计模式·模板方法模式
risc1234562 小时前
Elasticsearch 线程池
java·大数据·elasticsearch
NE_STOP3 小时前
SpringBoot--如何整体读取多个配置属性及其相关操作
java·spring
apihz3 小时前
通用图片搜索-搜狗源免费API接口使用指南
android·java·python·php·音视频
风象南3 小时前
基于 SpringBoot 的 REST API 与 RPC 调用的统一封装
java·spring boot·后端