目录
系列文章:
文章 | 状态 | 时间 | 描述 |
---|---|---|---|
(一)Mybatis 基本使用 | 已复习 | 2022-12-14 | 对Mybtais的基本使用,能够开发 |
(二)Mybatis-config.xml的初始化 | 已复习 | 2023-02-10 | 对我们编写的mybatis配置文件的解析 |
(三)SqlSessionFactory的初始化 | 已复习 | 2023-02-11 | SqlSession会话工厂的初始化 |
(四)Mapper文件的解析 | 已复习 | 2023-02-12 | 主要对我们编写的Mapper.xml进行解析 |
(五)SqlSession的创建 | 已复习 | 2023-02-13 | 主要介绍构建DefaultSqlSessionFactory |
(六)Mapper的接口代理 | 已复习 | 2023-02-14 | 如何通过动态代理来执行我们编写的方法 |
(七)MapperMethod的INSERT分析 | 已复习 | 2023-02-15 | 通过代理对象来执行Insert语句,返回结果 |
(八)MapperMethod的Select分析 | 已复习 | 2023-02-16 | 通过代理对象来执行Select语句,返回结果 |
(九)Mybatis的PreparedStatement | 已复习 | 2023-02-17 | 预处理语句的常见,以及与数据库打交道 |
(十)Mybatis的结果隐射 | 已复习 | 2023-02-18 | 数据库结果与实体类对象的转换 |
(十一)Mybatis中的缓存 | 计划中 | – | Mybatis中一级缓存与二级缓存 |
(十二)Mybatis中的插件开发 | 计划中 | – | Mybatis中的插件运行机制与开发 |
(十三)Mybatis中的四大组件梳理 | 计划中 | – | Mybatis中的四大组件的梳理 |
(十四)Mybatis中的设计模式梳理 | 计划中 | – | Mybatis中设计模式的整理 |
(十五)Spring-Mybatis整理 | 计划中 | – | Spring与Mybatis整合 |
(十六)Mybatis疑惑总结 | 计划中 | – | 我遇到的疑惑与问题 |
上一篇文章我们分析了,Select的语句的执行过程,但是在与数据库打交道的那一块,我们没有详细介绍,下面我们来看看Mybatis中是如何与数据库打交道的?
学习到的知识
:::info
- 数据库连接池获取数据库连接,池化技术
- Sql的预编译,以及设置参数
- 动态代理
:::
过程梳理
:::warning
- 从前面我们知道创建了一个预处理语句处理器PreparedStatementHandler
- 接着从DataSource中获取数据库连接,返回Connection对象,当然这里的DataSource是可配置的,一般默认为池化,最后通过PooledConnection生成一个代理对象,由代理连接对象,来完成数据库的交互
- 通过数据库连接代理对象返回ClientPreparedStatement
- 通过不同的类型处理器,设置参数
- 调用query方法执行真正的查询方法
:::
温馨提示:后面的代码比较打脑壳,与数据库交互这一块
SimpleExecutor
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建处理器
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 预编译参数
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
一 JDBC的PreparedStatement
文档说明:
表示预编译SQL语句的对象。 SQL语句被预编译并存储在PreparedStatement对象中。然后,可以使用该对象多次有效地执行此语句。 注意:用于设置IN形参值的setter方法(setShort、setString等)必须指定与输入形参定义的SQL类型兼容的类型。例如,如果IN参数的SQL类型为INTEGER,则应该使用方法setInt。 如果需要任意参数类型转换,则应该将方法setObject与目标SQL类型一起使用。 在下面的参数设置示例中,con表示活动连接:
PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEES . 设定工资= ?Where id = ?");
pstmt.setBigDecimal (153833.00)
pstmt.setInt (110592)
JDBC中java.sql.PreparedStatement是java.sql.Statement的子接口,它主要提供了无参数执行方法如executeQuery和executeUpdate等,以及大量形如set{Type}(int, {Type})形式的方法用于设置参数。
PreparedStatement
public interface PreparedStatement extends Statement {
// 执行PreparedStatement对象中的SQL查询,并返回由该查询生成的ResultSet对象。
ResultSet executeQuery() throws SQLException;
// 执行PreparedStatement对象中的SQL语句,该语句必须是SQL数据操作语言(DML)语句,如INSERT、UPDATE或DELETE;或不返回任何结果的SQL语句,如DDL语句。
int executeUpdate() throws SQLException;
// 其他省略
}
这里我们可以看到executeQuery与executeUpdate方法前者主要是处理Select语句后者处理INSERT、UPDATE或DELETE语句,这里他的子类太多了我们介绍与Mybatis相关的CallableStatement与JdbcPreparedStatement
- CallableStatement:用于执行SQL存储过程的接口。JDBC API提供了一种存储过程SQL转义语法,允许以标准方式对所有rdbms调用存储过程。此转义语法有一种形式包含结果形参,另一种形式不包含结果形参。如果使用,结果参数必须注册为OUT参数。其他参数可用于输入、输出或同时用于输入和输出。参数按数字顺序引用,第一个参数为1。 {?= call [( , ,…)]} {call [( , ,…)]}
- JdbcPreparedStatement:表示预编译SQL语句的对象。 SQL语句被预编译并存储在PreparedStatement对象中。
下面:我们来看看在JDBC中使用PreparedStatement
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class PreparedStatementTest {
public static void main(String[] args) throws Throwable {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost/test";
// 获取连接
try (Connection con = DriverManager.getConnection(url, "root", null)) {
String sql = "insert into t select ?,?";
// 预编译Sql
PreparedStatement statement = con.prepareStatement(sql);
// 设置参数
statement.setInt(1, 123456);
statement.setString(2, "abc");
// 执行
statement.executeUpdate();
// 关闭
statement.close();
}
}
}
通常我们的一条sql在db接收到最终执行完毕返回可以分为下面三个过程:
- 词法和语义解析
- 优化sql语句,制定执行计划
- 执行并返回结果
我们把这种普通语句称作Immediate Statements。
二 prepareStatement的准备阶段
让我们回到之前的准备工作?
ReuseExecutor
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
// 获取好的解析文件
Configuration configuration = ms.getConfiguration();
// 参数与处理器
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
- SimpleStatementHandler,这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理;
- PreparedStatementHandler,这个对应JDBC中的PreparedStatement,预编译SQL的接口;
- CallableStatementHandler,这个对应JDBC中CallableStatement,用于执行存储过程相关的接口;
- RoutingStatementHandler,这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。
- 首先获取Connection
- SQL预编译
- 设置参数
- 执行Sql,返回结果
- 关闭
2.1 获取Connection
首先我们来看看获取Connection,是如何获取的?
ReuseExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
BaseExecutor
/**
* 获取一个Connection对象
* @param statementLog 日志对象
* @return Connection对象
* @throws SQLException
*/
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) { // 启用调试日志
// 生成Connection对象的具有日志记录功能的代理对象ConnectionLogger对象
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
// 返回原始的Connection对象
return connection;
}
}
transaction事务工厂,在DefaultSqlSessionFactory#openSession()创建,在我们的配置中JdbcTransactionFactory
DefaultSqlSessionFactory
/**
* 从数据源中获取SqlSession对象
* @param execType 执行器类型
* @param level 事务隔离级别
* @param autoCommit 是否自动提交事务
* @return SqlSession对象
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 找出要使用的指定环境
final Environment environment = configuration.getEnvironment();
// 从环境中获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从事务工厂中生产事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
让我们来看看Transaction类的接口信息,他的实现类:JdbcTransaction与ManagedTransaction
Transaction
public interface Transaction {
/**
* 获取该事务对应的数据库连接
* @return 数据库连接
* @throws SQLException
*/
Connection getConnection() throws SQLException;
/**
* 提交事务
* @throws SQLException
*/
void commit() throws SQLException;
/**
* 回滚事务
* @throws SQLException
*/
void rollback() throws SQLException;
/**
* 关闭对应的数据连接
* @throws SQLException
*/
void close() throws SQLException;
/**
* 读取设置的事务超时时间
* @return 事务超时时间
* @throws SQLException
*/
Integer getTimeout() throws SQLException;
}
Transaction接口只是定义了基本的方法,关键在于他的实现,我们来看看JdbcTransaction
JdbcTransaction
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
// 数据库连接
protected Connection connection;
// 数据源
protected DataSource dataSource;
// 事务隔离级别
protected TransactionIsolationLevel level;
// 是否自动提交事务
protected boolean autoCommit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
/**
* 提交事务
* @throws SQLException
*/
@Override
public void commit() throws SQLException {
// 连接存在且不会自动提交事务
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
// 调用connection对象的方法提交事务
connection.commit();
}
}
/**
* 回滚事务
* @throws SQLException
*/
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
// 关闭
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
//通过dataSource来获取connection,并设置到transaction的connection属性中
connection = dataSource.getConnection();
if (level != null) {
//通过connection设置事务的隔离级别
connection.setTransactionIsolation(level.getLevel());
}
//设置事务是否自动提交
setDesiredAutoCommit(autoCommmit);
}
// 其他方法省略
}
我们看到JdbcTransaction中有一个**Connection属性和dataSource属性,使用connection来进行提交、回滚、关闭等操作,也就是说JdbcTransaction其实只是在jdbc的connection上面封装了一下,实际使用的其实还是jdbc的事务。**我们看看getConnection()方法。
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
// 从数据源中获取connection这与上面的案例是一致的
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
- 我们再来看看dataSource.getConnection()这个方法获取connection
这里先看看官方文档对dataSource的配置信息吧:虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源,有三种内建的数据源类型(也就是 type=“[UNPOOLED|POOLED|JNDI]”):
对数据源元素的解析,请参考前面的文章对environments元素进行解析,下面贴关键代码
XMLConfigBuilder
/**
* 解析配置信息,获取数据源工厂
* 被解析的配置信息示例如下:
* <dataSource type="POOLED">
* <property name="driver" value="{dataSource.driver}"/>
* <property name="url" value="{dataSource.url}"/>
* <property name="username" value="${dataSource.username}"/>
* <property name="password" value="${dataSource.password}"/>
* </dataSource>
*
* @param context 被解析的节点
* @return 数据源工厂
* @throws Exception
*/
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 通过这里的类型判断数据源类型,例如POOLED、UNPOOLED、JNDI
String type = context.getStringAttribute("type");
// 获取dataSource节点下配置的property
Properties props = context.getChildrenAsProperties();
// 根据dataSource的type值获取相应的DataSourceFactory对象
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
// 设置DataSourceFactory对象的属性
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
扩展问题:DefaultSqlSession线程安全吗?:Mybatis(二)SqlSession之线程安全
下面我们仔细来介绍下数据源的创建?首先我们看看DataSource接口信息
DataSource
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
我们再来看看他的实现类PooledDataSource与UnpooledDataSource,池化与非池化,首先看看UnpooledDataSource的成员变量,他的步骤如下
- initializeDriver - 初始化数据库驱动
- doGetConnection - 获取数据连接
- configureConnection - 配置数据库连接
2.1.1 UnpooledDataSource
UnpooledDataSource
public class UnpooledDataSource implements DataSource {
// 驱动加载器
private ClassLoader driverClassLoader;
// 驱动配置信息
private Properties driverProperties;
// 已经注册的所有驱动
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
// 数据库驱动
private String driver;
// 数据源地址
private String url;
// 数据源用户名
private String username;
// 数据源密码
private String password;
// 是否自动提交
private Boolean autoCommit;
// 默认事务隔离级别
private Integer defaultTransactionIsolationLevel;
// 最长等待时间。发出请求后,最长等待该时间后如果数据库还没有回应,则认为失败
private Integer defaultNetworkTimeout;
static {
// 首先将java.sql.DriverManager中的驱动都加载进来
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
}
UnpooledDataSource
// 传入用户密码
@Override
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
/**
* 建立数据库连接
* @param properties 里面包含建立连接的"user"、"password"、驱动配置信息
* @return 数据库连接对象
* @throws SQLException
*/
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化驱动
initializeDriver();
// 通过DriverManager获取连接
Connection connection = DriverManager.getConnection(url, properties);
// 配置连接,要设置的属性有defaultNetworkTimeout、autoCommit、defaultTransactionIsolationLevel
configureConnection(connection);
return connection;
}
- 我们来看看他初始化数据库驱动方法initializeDriver()来初始化数据库驱动
UnpooledDataSource
/**
* 初始化数据库驱动
* @throws SQLException
*/
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) { // 如果所需的驱动尚未被注册到registeredDrivers
Class<?> driverType;
try {
if (driverClassLoader != null) { // 如果存在驱动类加载器
// 优先使用驱动类加载器加载驱动类
driverType = Class.forName(driver, true, driverClassLoader);
} else {
// 使用Resources中的所有加载器加载驱动类
driverType = Resources.classForName(driver);
}
// 实例化驱动
Driver driverInstance = (Driver)driverType.newInstance();
// 向DriverManager注册该驱动的代理
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 注册到registeredDrivers,表明该驱动已经加载
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
通过反射机制加载驱动Driver,并将其注册到DriverManager中的一个常量集合中,供后面获取连接时使用,为什么这里是一个List呢?我们实际开发中有可能使用到了多种数据库类型,如Mysql、Oracle等,其驱动都是不同的,不同的数据源获取连接时使用的是不同的驱动。
- 我们仔细从驱动管理器中来看看他说如何获取理数据库连接的
Connection connection = DriverManager.getConnection(url, properties)
DriverManager
// Worker method called by the public getConnection() methods.
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// 当callerCl为空时,我们应该检查应用程序的(间接调用该类的)类加载器,以便可以从这里加载rt.jar外部的JDBC驱动程序类。
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 + "\")");
// 遍历已加载的registeredDrivers,试图建立连接。
// 记住第一个被引发的异常,这样我们就可以重新引发它。
SQLException reason = null;
// 遍历已经注册是使用驱动
for(DriverInfo aDriver : registeredDrivers) {
// 如果调用者没有加载驱动程序的权限,那么跳过它。
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");
}
- 从配置文件中来配置连接比如:超时,自动提交,事务隔离级别
configureConnection(connection)
UnpooledDataSource
private void configureConnection(Connection conn) throws SQLException {
if (defaultNetworkTimeout != null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
}
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
2.1.2 PooledDataSource
PooledDataSource 内部实现了连接池功能,用于复用数据库连接。因此,从效率上来说,PooledDataSource 要高于 UnpooledDataSource,但是最终获取Connection还是通过UnpooledDataSource,只不过PooledDataSource 提供一个存储Connection的功能,也是默认实现,我们仔细来看看
PooledDataSource
public class PooledDataSource implements DataSource {
private static final Log log = LogFactory.getLog(PooledDataSource.class);
// 池化状态
private final PoolState state = new PoolState(this);
// 持有一个UnpooledDataSource对象
private final UnpooledDataSource dataSource;
// 和连接池设置有关的配置项
protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected int poolMaximumLocalBadConnectionTolerance = 3;
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;
// 存储池子中的连接的编码,编码用("" + url + username + password).hashCode()算出来
// 因此,整个池子中的所有连接的编码必须是一致的,里面的连接是等价的
private int expectedConnectionTypeCode;
}
PoolState 用于记录连接池运行时的状态,比如连接获取次数,无效连接数量等。同时 PoolState 内部定义了两个 PooledConnection 集合,用于存储空闲连接和活跃连接。
PoolState
public class PoolState {
// 池化数据源
protected PooledDataSource dataSource;
// 空闲的连接
protected final List<PooledConnection> idleConnections = new ArrayList<>();
// 活动的连接
protected final List<PooledConnection> activeConnections = new ArrayList<>();
// 连接被取出的次数
protected long requestCount = 0;
// 取出请求花费时间的累计值。从准备取出请求到取出结束的时间为取出请求花费的时间
protected long accumulatedRequestTime = 0;
// 累积被检出的时间
protected long accumulatedCheckoutTime = 0;
// 声明的过期连接数
protected long claimedOverdueConnectionCount = 0;
// 过期的连接数的总检出时长
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
// 总等待时间
protected long accumulatedWaitTime = 0;
// 等待的轮次
protected long hadToWaitCount = 0;
// 坏连接的数目
protected long badConnectionCount = 0;
public PoolState(PooledDataSource dataSource) {
this.dataSource = dataSource;
}
}
PooledConnection 内部定义了一个 Connection 类型的变量,用于指向真实的数据库连接。以及一个 Connection 的代理类,用于对部分方法调用进行拦截,至于为什么要拦截,随后将进行分析。除此之外,PooledConnection 内部也定义了一些字段,用于记录数据库连接的一些运行时状态。
PooledConnection
class PooledConnection implements InvocationHandler {
private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
// 该连接的哈希值
private final int hashCode;
// 该连接所属的连接池
private final PooledDataSource dataSource;
// 真正的Connection
private final Connection realConnection;
// 代理Connection
private final Connection proxyConnection;
// 从连接池中取出的时间
private long checkoutTimestamp;
// 创建时间
private long createdTimestamp;
// 上次使用时间
private long lastUsedTimestamp;
// 标志所在连接池的连接类型编码
private int connectionTypeCode;
// 连接是否可用
private boolean valid;
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
// 参数依次是:被代理对象的类加载器 被代理对象的接口 包含代理对象的类(实现InvocationHandler接口的类)
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
}
获取连接调用了PooledDataSource#popConnection方法
/**
* 从池化数据源中给出一个连接
* @param username 用户名
* @param password 密码
* @return 池化的数据库连接
* @throws SQLException
*/
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
// 用于统计取出连接花费的时长的时间起点
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {
// 给state加同步锁
synchronized (state) {
if (!state.idleConnections.isEmpty()) { // 池中存在空闲连接
// 左移操作,取出第一个连接
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else { // 池中没有空余连接
if (state.activeConnections.size() < poolMaximumActiveConnections) { // 池中还有空余位置
// 可以创建新连接,也是通过DriverManager.getConnection拿到的连接
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else { // 连接池已满,不能创建新连接
// 找到借出去最久的连接
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
// 查看借出去最久的连接已经被借了多久
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) { // 借出时间超过设定的借出时长
// 声明该连接超期不还
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// 因超期不还而从池中除名
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) { // 如果超期不还的连接没有设置自动提交事务
// 尝试替它提交回滚事务
try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
// 即使替它回滚事务的操作失败,也不抛出异常,仅仅做一下记录
log.debug("Bad connection. Could not roll back");
}
}
// 新建一个连接替代超期不还连接的位置
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else { // 借出去最久的连接,并未超期
// 继续等待,等待有连接归还到连接池
try {
if (!countedWait) {
// 记录发生等待的次数。某次请求等待多轮也只能算作发生了一次等待
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
// 沉睡一段时间再试,防止一直占有计算资源
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) { // 取到了连接
// 判断连接是否可用
if (conn.isValid()) { // 如果连接可用
if (!conn.getRealConnection().getAutoCommit()) { // 该连接没有设置自动提交
// 回滚未提交的操作
conn.getRealConnection().rollback();
}
// 每个借出去的连接都到打上数据源的连接类型编码,以便在归还时确保正确
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
// 数据记录操作
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else { // 连接不可用
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
// 直接删除连接
conn = null;
// 如果没有一个连接能用,说明连不上数据库
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
// 如果到这里还没拿到连接,则会循环此过程,继续尝试取连接
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
从连接池中获取连接首先会遇到两种情况:
- 连接池中有空闲连接
- 连接池中无空闲连接
对于第一种情况,把连接取出返回即可。对于第二种情况,则要进行细分,会有如下的情况。
- 活跃连接数没有超出最大活跃连接数
- 活跃连接数超出最大活跃连接数
对于上面两种情况,第一种情况比较好处理,直接创建新的连接即可。至于第二种情况,需要再次进行细分。
- 活跃连接的运行时间超出限制,即超时了
- 活跃连接未超时
对于第一种情况,我们直接将超时连接强行中断,并进行回滚,然后复用部分字段重新创建 PooledConnection 即可。对于第二种情况,目前没有更好的处理方式了,只能等待了。
- 回收连接相比于获取连接,回收连接的逻辑要简单的多。回收连接成功与否只取决于空闲连接集合的状态,所需处理情况很少,因此比较简单。
PooledConnection
/**
* 代理方法
* @param proxy 代理对象,未用
* @param method 当前执行的方法
* @param args 当前执行的方法的参数
* @return 方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) { // 如果调用了关闭方法
// 那么把Connection返回给连接池,而不是真正的关闭
dataSource.pushConnection(this);
return null;
}
try {
// 校验连接是否可用
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
// 用真正的连接去执行操作
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
我们来看看pushConnection方法,回收一个连接
/**
* 收回一个连接
* @param conn 连接
* @throws SQLException
*/
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
// 将该连接从活跃连接中删除
state.activeConnections.remove(conn);
if (conn.isValid()) { // 当前连接是可用的
// 判断连接池未满 + 该连接确实属于该连接池
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) { // 如果连接没有设置自动提交
// 将未完成的操作回滚
conn.getRealConnection().rollback();
}
// 重新整理连接
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
// 将连接放入空闲连接池
state.idleConnections.add(newConn);
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
// 设置连接为未校验,以便取出时重新校验
conn.invalidate();
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
state.notifyAll();
} else { // 连接池已满或者该连接不属于该连接池
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 直接关闭连接,而不是将其放入连接池中
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else { // 当前连接不可用
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
先将连接从活跃连接集合中移除,如果空闲集合未满,此时复用原连接的字段信息创建新的连接,并将其放入空闲集合中即可;若空闲集合已满,此时无需回收连接,直接关闭即可
最后利用动态代理,生成代理连接对象ConnectionImpl返回
下面我们继续看看PooledConnection动态代理对象的执行过程,跟前面mapper接口动态大力一样我们来看看invoke方法
PooledConnection
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取方法名称
String methodName = method.getName();
// 如果是关闭方法,就回收连接
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
// 调用 ConnectionImpl的方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
这里我们通过dataSource获取到connection连接,并通过动态代理模式设置了自动提交返回
connection连接
好了,我们已经获取到了数据库连接,接下来要创建PrepareStatement了,我们上面JDBC的例子是怎么获取的? psmt = conn.prepareStatement(sql);,直接通过Connection来获取,并且把sql传进去了,我们看看Mybaits中是怎么创建PrepareStatement的
2.2 Sql的预编译PreparedStatementHandler
让我们回到之前的步骤,我们已经获取数据库连接,接下来来看看Sql的预编译,从Connection中创建一个Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
BaseStatementHandler
// 从连接中获取一个Statement,并设置事务超时时间
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 获取一个Statement对象
statement = instantiateStatement(connection);
// 设置设置查询超时时间
setStatementTimeout(statement, transactionTimeout);
// 获取数据大小限制
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
我们接着来看看instantiateStatemen接口,根据上面的的信息,我们这的默认实现是PreparedStatementHandler
PreparedStatementHandler
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// //获取sql字符串,比如"select * from user where id= ?"
String sql = boundSql.getSql();
// / 根据条件调用不同的 prepareStatement 方法创建 PreparedStatement
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
//通过connection获取Statement,将sql语句传进去
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
// 如果是默认的话
else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// 通过动态代理模式
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
PooledConnection
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLLTLEgm-1677045567079)(https://cdn.nlark.com/yuque/0/2023/png/12426173/1677031906508-9b0b7779-15ec-4ad2-8750-b4b6fbf31904.png#averageHue=%235e7c63&clientId=uf9c1fa2e-aa14-4&from=paste&height=660&id=u5af36b57&name=image.png&originHeight=825&originWidth=1862&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=187933&status=done&style=none&taskId=ua8300b8a-52cd-44f1-b3f4-5f8026f6c41&title=&width=1489.6)]
ConnectionImpl
最终他还是调用了prepareStatement方法
我们接着上面prepareStatement来看看他到底是如何创建的
ConnectionImpl
@Override
public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
synchronized (getConnectionMutex()) {
checkClosed();
//
// FIXME: Create warnings if can't create results of the given type or concurrency
//
// 客服端预处理语句
ClientPreparedStatement pStmt = null;
boolean canServerPrepare = true;
String nativeSql = this.processEscapeCodesForPrepStmts.getValue() ? nativeSQL(sql) : sql;
if (this.useServerPrepStmts.getValue() && this.emulateUnsupportedPstmts.getValue()) {
canServerPrepare = canHandleAsServerPreparedStatement(nativeSql);
}
if (this.useServerPrepStmts.getValue() && canServerPrepare) {
if (this.cachePrepStmts.getValue()) {
synchronized (this.serverSideStatementCache) {
pStmt = this.serverSideStatementCache.remove(new CompoundCacheKey(this.database, sql));
if (pStmt != null) {
((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).setClosed(false);
pStmt.clearParameters();
}
if (pStmt == null) {
try {
pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType,
resultSetConcurrency);
if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) {
((com.mysql.cj.jdbc.ServerPreparedStatement) pStmt).isCacheable = true;
}
pStmt.setResultSetType(resultSetType);
pStmt.setResultSetConcurrency(resultSetConcurrency);
} catch (SQLException sqlEx) {
// Punt, if necessary
if (this.emulateUnsupportedPstmts.getValue()) {
pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
if (sql.length() < this.prepStmtCacheSqlLimit.getValue()) {
this.serverSideStatementCheckCache.put(sql, Boolean.FALSE);
}
} else {
throw sqlEx;
}
}
}
}
} else {
try {
pStmt = ServerPreparedStatement.getInstance(getMultiHostSafeProxy(), nativeSql, this.database, resultSetType, resultSetConcurrency);
pStmt.setResultSetType(resultSetType);
pStmt.setResultSetConcurrency(resultSetConcurrency);
} catch (SQLException sqlEx) {
// Punt, if necessary
if (this.emulateUnsupportedPstmts.getValue()) {
pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
} else {
throw sqlEx;
}
}
}
} else {
pStmt = (ClientPreparedStatement) clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false);
}
return pStmt;
}
}
上面的代码设计到JDBC代码,看不懂没关系,我们知道他返回的对象就行了,ClientPreparedStatement
下面讲解如何为Statement设置参数
2.3 为Statement设置参数
SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
我们这里StatementHandler在前面我们已经看到了是PreparedStatementHandler,我们来看看他是如何处理的
PreparedStatementHandler
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
DefaultParameterHandler
/**
* 为语句设置参数
* @param ps 语句
*/
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 取出参数列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// ParameterMode.OUT是CallableStatement的输出参数,已经单独注册。故忽略
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
// 取出属性名称
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
// 从附加参数中读取属性值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 参数对象是基本类型,则参数对象即为参数值
value = parameterObject;
} else {
// 参数对象是复杂类型,取出参数对象的该属性值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 确定该参数的处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 此方法最终根据参数类型,调用java.sql.PreparedStatement类中的参数赋值方法,对SQL语句中的参数赋值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
可以看到调用不同的参数处理器来设置参数类型,我们先看看TypeHandler这个接口信息
TypeHandler
public interface TypeHandler<T> {
/**
* 设置参数
* @param ps
* @param i
* @param parameter
* @param jdbcType
* @throws SQLException
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 获取结果
* @param rs
* @param columnName
* @return
* @throws SQLException
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
调用不同的参数处理器,来设置不同的参数这里的类型调用来设置参数
BaseTypeHandler来处理的设置参数类型,而许多类型的处理器继承BaseTypeHandler
最后根据参数类型,调用java.sql.PreparedStatement类中的参数赋值方法,对SQL语句中的参数赋值
DefaultParameterHandler
typeHandler.setParameter(ps, i + 1, value, jdbcType);
参数也设置好了,下面我们来看看执行具体的查询语句吧,最后
2.4 执行具体的语句过程
回到刚开始的地方
SimpleExecutor
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 获取不同的StatementHandler处理器
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 获取到不同的 Statement并完成参数绑定
stmt = prepareStatement(handler, ms.getStatementLog());
// 交给具体的StatementHandler实现执行
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
我们以默认为例:PreparedStatementHandler
PreparedStatementHandler
@Override
public int update(Statement statement) throws SQLException {
//ClientPreparedStatement对象
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 返回影响条数
int rows = ps.getUpdateCount();
// 参数对象
Object parameterObject = boundSql.getParameterObject();
// 主键自增器
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 后置执行主键自增
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
从前面的数据库连接开始,我们就可以知道PreparedStatement返回的对象是ClientPreparedStatement,因此我们来看看他的execute方法
ClientPreparedStatement
ClientPreparedStatement
@Override
public boolean execute() throws SQLException {
// 其他代码省略
rs = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(),
(((PreparedQuery<?>) this.query).getParseInfo().getFirstStmtChar() == 'S'), cachedMetadata, false);
// 其他代码省略
}
protected <M extends Message> ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, M sendPacket, boolean createStreamingResultSet,
boolean queryIsSelectOnly, ColumnDefinition metadata, boolean isBatch) throws SQLException {
// 其他代码省略
rs = ((NativeSession) locallyScopedConnection.getSession()).execSQL(this, null, maxRowsToRetrieve, (NativePacketPayload) sendPacket,
createStreamingResultSet, getResultSetFactory(), metadata, isBatch);
// 其他代码省略
}
关键在于调用executeInternal方法,感兴趣的朋友可以自己通过Debug去查看
到最后我们可以拿到ResultSetImpl中拿到执行的结果
PreparedStatementHandler
int rows = ps.getUpdateCount();
ClientPreparedStatement
@Override
public int getUpdateCount() throws SQLException {
// 调用父类的方法
int count = super.getUpdateCount();
if (containsOnDuplicateKeyUpdateInSQL() && this.compensateForOnDuplicateKeyUpdate) {
if (count == 2 || count == 0) {
count = 1;
}
}
return count;
}
详细的过程,请参考mysql驱动包的源码部分,下面介绍数据库结果对对象关系的隐射