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());
}
}
这里面就是做了一些配置的初始化
评论区