likes
comments
collection
share

JDBC初步

作者站长头像
站长
· 阅读数 3

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

JDBC初步

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

JDBC初步

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

// 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

// 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

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不负责实现具体连接逻辑,将实体实现委托给驱动类。至于驱动类如何实现,这里不做过多介绍。有空填坑

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中表类似,示例如下

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

[2]:Introduction to JDBC