目 录CONTENT

文章目录

DruidDataSource

FatFish1
2025-02-27 / 0 评论 / 0 点赞 / 47 阅读 / 0 字 / 正在检测是否收录...

alibaba出品的DataSource封装

DruidDataSourceFactory - 对外API

首先看一个简单案例:

public DataSource getDataSource() throws Exception {
    Properties props = new Properties();
    props.put("url", "jdbc:mariadb://127.0.0.1:3306/testDb?create=true&"
        + "autoReconnect=true&failOverReadOnly=false&rewriteBatchedStatements=true&zeroDateTimeBehavior=round&"
        + "useCompression=true&serverTimezone=UTC&enabledTLSProtocols=TLSv1.2&allowLocalInfile=false");
    props.put("username", "root");
    props.put("dbName", "testDb");
    props.put("password", CipherUtils.decodeDeployParam(dbcfg.getPasswd()));
    props.put("driverClassName", "org.mariadb.jdbc.Driver")
    DruidDataSource druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(props);
    druidDataSource.setConnectTimeout(CONNECT_TIMEOUT);
    druidDataSource.setSocketTimeout(SOCKET_TIMEOUT);
    return druidDataSource;
}

理论上,有url、username、dbName、password,加上对应jdbc的驱动就可以构造一个被DruidDataSource封装的数据源

其中核心是DruidDataSourceFactory这个工厂类,其提供的构造方法除了使用Properties的api以外:

public static DataSource createDataSource(Properties properties) throws Exception {
    return createDataSource((Map) properties);
}

还支持Map类型入参,实际上Properties入参版本也是基于Map版本实现的

public static DataSource createDataSource(Map properties) throws Exception {
    DruidDataSource dataSource = new DruidDataSource();
    config(dataSource, properties);
    return dataSource;
}

首先构造DruidDataSource,然后通过config完成配置和初始化

config

public static void config(DruidDataSource dataSource, Map<?, ?> properties) throws SQLException {
    // 首先配置属性    
    String value = null;
    ……
    // 这里配置的就是driver
    value = (String) properties.get(PROP_DRIVERCLASSNAME);
    if (value != null) {
        dataSource.setDriverClassName(value);
    }
    // 然后配置了很多看上去像是pool2相关的属性,推测这个池化管理的逻辑跟pool2的逻辑是类似的
    value = (String) properties.get(PROP_MAXACTIVE);
    if (value != null) {
        dataSource.setMaxActive(Integer.parseInt(value));
    }
    value = (String) properties.get(PROP_MAXIDLE);
    if (value != null) {
        dataSource.setMaxIdle(Integer.parseInt(value));
    }
    ……

    value = (String) properties.get(PROP_INIT);
    if ("true".equals(value)) {
        dataSource.init();
    }
}

到这里调用DruidDataSource#init点我跳转)开始真正初始化数据源

DruidDataSource - 数据源包装

核心成员变量

先看下里面包装了些什么

private volatile DruidConnectionHolder[] connections;
……
//
private DruidConnectionHolder[] evictConnections;
private DruidConnectionHolder[] keepAliveConnections;
// for clean connection old references.
private volatile DruidConnectionHolder[] nullConnections;

有一个用来封装Connection的类:DruidConnectionHolder

// threads
private volatile ScheduledFuture<?> destroySchedulerFuture;
private DestroyTask destroyTask;
private final Map<CreateConnectionTask, Future<?>> createSchedulerFutures = new ConcurrentHashMap<>(16);
private CreateConnectionThread createConnectionThread;
private DestroyConnectionThread destroyConnectionThread;

一批与线程相关的属性,应该是用来执行sql方法的

构造函数

public DruidDataSource(boolean fairLock) {
    super(fairLock);
    configFromPropeties(System.getProperties());
}

跟进父类看下:

public DruidAbstractDataSource(boolean lockFair) {
    lock = new ReentrantLock(lockFair);
    notEmpty = lock.newCondition();
    empty = lock.newCondition();
}

父类提供了一些锁和condition相关的能力

然后看下第二行配置configFromPropeties(System.getProperties()),这里是默认从系统环境变量里面取配置,取到了就直接在初始化的时候进行配置,否则在后面init方法配置

init - 数据源初始化

if (inited) {
    return;
}
// bug fixed for dead lock, for issue #2980
DruidDriver.getInstance();
final ReentrantLock lock = this.lock;
try {
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    throw new SQLException("interrupt", e);
}
boolean init = false;
try {
    if (inited) {
        return;
    }

这里一开始做了一个inited属性的double check,同时通过ReentrantLock避免重复初始化

使用ReentrantLock可以提高一些性能

if (this.jdbcUrl != null) {
    this.jdbcUrl = this.jdbcUrl.trim();
    initFromWrapDriverUrl();
}

这里对url做了一层封装

中间省略一批配置,然后到了下面这一部分

// 通过SPI加载filter
initFromSPIServiceLoader();
// 解析jdbcDriver
resolveDriver();
// 做初始化检查
initCheck();

解析Driver参考resolveDriver方法

再往下走,这里初始化这四个Connection的封装

connections = new DruidConnectionHolder[maxActive];
evictConnections = new DruidConnectionHolder[maxActive];
keepAliveConnections = new DruidConnectionHolder[maxActive];
nullConnections = new DruidConnectionHolder[maxActive];

然后这里就是最核心的地方了

try {
    // 创建物理连接
    PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
    // 向池子中注册物理连接
    DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
    connections[poolingCount++] = holder;
} catch (SQLException ex) {....

这里外层做了连接池的管理逻辑,如果不自己搞,使用pool2,逻辑可能会更简单一些

创建物理连接参考createPhysicalConnection方法

resolveDriver

if (this.driver == null) {
    if (this.driverClass == null || this.driverClass.isEmpty()) {
        this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);
    }

如果目前driver还没加载,并且也没有配置driverClass属性,这个属性就是前面DruidDataSourceFactory里面传进来的,那么就会根据url解析Driver

但是根据URL解析的可选项有限,因为底层走的是startWith和硬编码

// --- JdbcUtils ---
public static String getDriverClassName(String rawUrl) throws SQLException {
    ……
    if (rawUrl.startsWith("jdbc:derby:")) {
        return "org.apache.derby.jdbc.EmbeddedDriver";
    } else if (rawUrl.startsWith("jdbc:mysql:")) {
        ……
    } else if (rawUrl.startsWith("jdbc:log4jdbc:")) {
        return LOG4JDBC_DRIVER;
    } else if (rawUrl.startsWith("jdbc:mariadb:")) {
        return MARIADB_DRIVER;
    } else if ...

如果有driverClass配置项,则就有很多扩展性了:

if (MockDriver.class.getName().equals(driverClass)) {
    driver = MockDriver.instance;
} else if ("com.alibaba.druid.support.clickhouse.BalancedClickhouseDriver".equals(driverClass)) {
   ……
} else if ("com.alibaba.druid.support.clickhouse.BalancedClickhouseDriverNative".equals(driverClass)) {
    ……
} else {
    if (jdbcUrl == null && (driverClass == null || driverClass.length() == 0)) {
        throw new SQLException("url not set");
    }
    driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
}

前三个分别对测试MockDriver做特殊处理,然后对alibaba自己的ClickHouse做适配

最后一个else是支持其他外部jdbcDriver

createPhysicalConnection

try {
    conn = createPhysicalConnection(url, physicalConnectProperties);
    connectedNanos = System.nanoTime();
    if (conn == null) {
        throw new SQLException("connect error, url " + url + ", driverClass " + this.driverClass);
    }
    initPhysicalConnection(conn, variables, globalVariables);

逻辑很长,核心在这一块

先跟进createPhysicalConnection(url, physicalConnectProperties) 方法看下:

public Connection createPhysicalConnection(String url, Properties info) throws SQLException {
    Connection conn;
    if (getProxyFilters().isEmpty()) {
        Connection rawConn = getDriver().connect(url, info);
        Statement stmt = rawConn.createStatement();
        conn = new DruidStatementConnection(rawConn, stmt);
    } else {
        FilterChainImpl filterChain = createChain();
        conn = filterChain.connection_connect(info);
        recycleFilterChain(filterChain);
    }
    createCountUpdater.incrementAndGet(this);
    return conn;
}

这里就是调用jdbc模板创建Connection,然后创建Statement了,然后把Connection和Statement封装进DruidStatementConnection做内存持久化,便于后续池化

然后看下initPhysicalConnection

public void initPhysicalConnection(Connection conn,
                                   Map<String, Object> variables,
                                   Map<String, Object> globalVariables) throws SQLException {
    boolean skipAutoCommit = "odps".equals(dbTypeName);
    if ((!skipAutoCommit) && conn.getAutoCommit() != defaultAutoCommit) {
        conn.setAutoCommit(defaultAutoCommit);
    }
    if (defaultReadOnly != null) {
        if (conn.isReadOnly() != defaultReadOnly) {
            conn.setReadOnly(defaultReadOnly);
        }
    }
    if (getDefaultTransactionIsolation() != null) {
        if (conn.getTransactionIsolation() != getDefaultTransactionIsolation().intValue()) {
            conn.setTransactionIsolation(getDefaultTransactionIsolation());
        }
    }
    if (getDefaultCatalog() != null && getDefaultCatalog().length() != 0) {
        conn.setCatalog(getDefaultCatalog());
    }
}

这里面就是做了一些配置的初始化

0

评论区