数据源模块解析

数据源模块解析

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 关闭链接

空闲连接数未饱和,将连接处理后重新放入池子。饱和则丢弃

在这里插入图片描述

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值