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
参考文献: