数据源模块解析
1、配置文件-数据源配置解析
配置:
解析:
1、从environments中 拿到type为POOLED的数据源工厂对象DataSourceFactory
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
// 配置通过数据源工厂封装是datasource中
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
2、数据源对象实力化,将driver、url、username、password等配置封装到数据源,以便后续连接数据库
// 数据源工厂对象实力化时,同时实力化数据源对象
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
// 配置解析,封装到datasource
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
// 反射模块,拿到数据源元数据对象
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
// 通过反射,配置值封装到与key对应的属性字段
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
//注:反射模块在此不做解释
2、数据源使用
前期,配置文件解析完成,数据源相关配置对象都封装到Mybatis容器Configuration中。在开启会话后,通过执行语句时使用。
1、开启会话,通过数据源等实力化事务对象TX
openSession() ->
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 数据源,隔离级别、是否自动提交 实力化tx
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 绑定执行器
final Executor executor = configuration.newExecutor(tx, execType);
// Sqlsession对象
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();
}
}
2、以查询语句为例,执行器执行时,通过数据源获取数据库连接Connection,Connection创建Statment执行Sql。
org.apache.ibatis.executor.BatchExecutor#doQuery
// 查询数据
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds,
resultHandler, boundSql);
// 获取数据库连接
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// 通过JDBC事务获取连接
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
}
return connection;
}
// 数据源连接池获取连接
org.apache.ibatis.transaction.jdbc.JdbcTransaction#openConnection
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
// PooledDataSource
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
// popConnection 最终获取连接的地方
public Connection getConnection() throws SQLException {
// 返回的是代理连接
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
3、数据源连接池实现原理
1、连接池管理对象PoolState
通过该对象管理连接池,主要:控制连接池的访问,记录活跃连接和空闲连接对象以及记录一些相关配置。
org.apache.ibatis.datasource.pooled.PoolState
// 被管理的数据源
protected PooledDataSource dataSource;
// 空闲的连接
protected final List<PooledConnection> idleConnections = new ArrayList<>();
// 活跃的连接
protected final List<PooledConnection> activeConnections = new ArrayList<>();
2、数据库连接池 连接对象PooledConnection
针对真正的数据库连接进行封装,便于管理和使用
org.apache.ibatis.datasource.pooled.PooledConnection
// 连接池的数据源(每个数据源对应一个连接池,池子里有多个PooledConnection对象)
private final PooledDataSource dataSource;
// 真正的数据库连接
private final Connection realConnection;
// 数据库连接的代理对象 真正被使用的连接
// 此处使用代理连接给外部使用,我理解的也不是很透彻,只理解部分是为了保证源对象的安全。
private final Connection proxyConnection;
private long checkoutTimestamp; // 从连接池中取出该连接的时间戳
private long createdTimestamp; // 该连接创建的时间戳
private long lastUsedTimestamp; // 最后一次被使用的时间戳
private int connectionTypeCode; // 又数据库URL、用户名和密码计算出来的hash值,可用于标识该连接所在的连接池
// 连接是否有效的标志
private boolean valid;
3、连接池数据源 PooledDataSource
org.apache.ibatis.datasource.pooled.PooledDataSource
// 部分配置
// 管理状态
private final PoolState state = new PoolState(this);
// 记录UnpooledDataSource,用于生成真实的数据库连接对象
private final UnpooledDataSource dataSource;
// OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10; // 最大活跃连接数(类比线程池最大线程数量)
protected int poolMaximumIdleConnections = 5; // 最大空闲连接数(类比线程池核心线程数)
protected int poolMaximumCheckoutTime = 20000; // 最大checkout时间,连接超时时间
protected int poolTimeToWait = 20000; // 无法获取连接的线程需要等待的时长 连接池打满后,后续线程等待连接时长
protected String poolPingQuery = "NO PING QUERY SET"; // 测试的SQL语句(用来测试连接是否正常)
3.1 获取连接流程
方法:popConnection(dataSource.getUsername(), dataSource.getPassword())
可类比线程池实现原理进行理解
3.2 判断连接是否有效
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
// ping数据库进行查询测试,判断连接有效性
org.apache.ibatis.datasource.pooled.PooledDataSource#pingConnection
具体流程:
通过判断连接是否关闭,发送执行测试语句,确定连接是否有效
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;
try {
// 判断连接是否关闭
result = !conn.getRealConnection().isClosed();
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
result = false;
}
// 链接未关闭
if (result) {
// 配置控制是否允许发送测试SQL语句 用来测试链接对象
if (poolPingEnabled) {
/**
poolPingConnectionsNotUsedFor 控制连接超过该时间未被使用才允许执行测试语句
**/
if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
// 真正的连接对象
Connection realConn = conn.getRealConnection();
try (Statement statement = realConn.createStatement()) {
// 执行测试语句
statement.executeQuery(poolPingQuery).close();
}
// 未提交自动回滚
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
// 测试链接正常有效
result = true;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is GOOD!");
}
} catch (Exception e) {
log.warn("Execution of ping query '" + poolPingQuery + "' failed: " + e.getMessage());
try
// 测试异常,关闭链接
conn.getRealConnection().close();
} catch (Exception e2) {
//ignore
}
// 无效链接
result = false;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
}
}
}
}
return result;
}
3.3 关闭链接
空闲连接数未饱和,将连接处理后重新放入池子。饱和则丢弃