之前想看看连接池的底层代码实现,然后根据大神的博客看了一下BasicDataSource https://www.cnblogs.com/biakia/p/4352197.html,感触良多....总之很复杂.忽然想起来了牛逼的德鲁伊,就各种浪了...百度各种文档想看看前人的源码阅读....然后就崩溃了,好吧索性就自己亲自去看看究竟牛在哪里!
首先搭建环境
javax.servlet
jsp-api
2.0
com.alibaba
druid
1.0.27
javax.servlet
javax.servlet-api
3.0.1
mysql
mysql-connector-java
5.1.34
引入核心类
package testDruid;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
public class DruidDemo01 {
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1,创建Druid连接池对象
DruidDataSource dataSource = new DruidDataSource();
// 2,为数据库添加配置文件
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource
.setUrl("jdbc:mysql://localhost:3306/first?useUnicode=true&characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 用Druid来连接
conn = dataSource.getConnection();
// 2,执行数据库语句
String sql = "SELECT * FROM user";
// 3,用prepareStatement获取sql语句
ps = conn.prepareStatement(sql);
// 4,执行sql语句,查询用executeQuery,增删改用executeUpdate
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + " "
+ rs.getString("loginname"));
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
// 释放资源
conn.close();
}
}
}
//因为够简单,所以我喜欢,起码目前喜欢
下面分析上面代码
1.拿到dataSource并且获得connectio
2.rs = ps.executeQuery(); 将输入的解析成sql并拿到返回值
起码目前来看就这么两步!!!!!
-------------------------------------------------------------------------------
下面就看牛逼的第一步拿到 conn = dataSource.getConnection()
跟进去看到这么一段
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
//初始化 代码1.1
init()
//这个应该就是网上收的牛逼的责任链,有点类似struts的感觉 代码1.2
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
//看看初始化都干了啥
//和lock区别自己百度,简单来说就是如果锁着呢就直接抛异常出去 类似于 if(isLock){throw Exception} } catch (InterruptedException e) { throw new SQLException("interrupt", e); }
if (inited) {
return;
}
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
throw new SQLException("interrupt", e);
}
boolean init = false;
try {
if (inited) {
return;
}
//拿到了堆栈信息,这东西是jdk给放里面的,具体以后再说主要我也没看呢
initStackTrace = Utils.toString(Thread.currentThread().getStackTrace())
//这里吐槽一下,druid各种没注释....这个是一个高并发下的int,AtomicInteger(0) 里面有个volatile的value
this.id = DruidDriver.createDataSourceId(); //拿到了一个int
//然后一顿set
........
//这里对mysql数据库的cach做了个特殊处理
if (JdbcConstants.MYSQL.equals(this.dbType) || //
JdbcConstants.MARIADB.equals(this.dbType)) {
boolean cacheServerConfigurationSet = false;
if (this.connectProperties.containsKey("cacheServerConfiguration")) {
cacheServerConfigurationSet = true;
} else if (this.jdbcUrl.indexOf("cacheServerConfiguration") != -1) {
cacheServerConfigurationSet = true;
}
if (cacheServerConfigurationSet) {
this.connectProperties.put("cacheServerConfiguration", "true");
}
}
//632行 这里面扫描了AutoLoad注解的Filter注意如果有druid.load.spifilter.skip那么注解就会失效
initFromSPIServiceLoader()
----------从这开始其实都是区分方言----------
//650行 也不明白他是怎么起的名字,里面就是对db2和oracle的版本过滤...
initCheck()
//652 这句话的意思是当不同的数据源,mysql,oracle等都会有自己对应的Exception的解析
initExceptionSorter();
//653 检查自己对应数据库的连接(根据方言)
initValidConnectionChecker()
//这句是没有方言的校验的连接的验证方式testOnBorrow还是return
validationQueryCheck();
//下面这段判断全局
if (isUseGlobalDataSourceStat()) {//默认false
dataSourceStat = JdbcDataSourceStat.getGlobal();
if (dataSourceStat == null) {
dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbType);
JdbcDataSourceStat.setGlobal(dataSourceStat);
}
if (dataSourceStat.getDbType() == null) {
dataSourceStat.setDbType(this.dbType);
}
} else {
dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbType, this.connectProperties);
}
//分析一下JdbcDateSourceStart
@SuppressWarnings("serial")
public JdbcDataSourceStat(String name, String url, String dbType, Properties connectProperties){
//获得了他的最大值
this.name = name;
this.url = url;
this.dbType = dbType;
if (connectProperties != null) {
Object arg = connectProperties.get(Constants.DRUID_STAT_SQL_MAX_SIZE);
if (arg == null) {
arg = System.getProperty(Constants.DRUID_STAT_SQL_MAX_SIZE);
}
if (arg != null) {
try {
maxSqlSize = Integer.parseInt(arg.toString());
} catch (NumberFormatException ex) {
LOG.error("maxSize parse error", ex);
}
}
}
//这里说一下看一下JdbcSqlStat的源码就会涨姿势...高并发下的牛逼操作
//这块设定了初始容量和加载因子,本着去学习下牛逼的操作.....然后发现这些就是默认值,简单点写相当于sqlStatMap = new LinkedHashMap
();
sqlStatMap = new LinkedHashMap(16, 0.75f, false) {
protected boolean removeEldestEntry(Map.Entry eldest) {
boolean remove = (size() > maxSqlSize);
if (remove) {
JdbcSqlStat sqlStat = eldest.getValue();
if (sqlStat.getRunningCount() > 0 || sqlStat.getExecuteCount() > 0) {
skipSqlCount.incrementAndGet();
}
}
return remove;
}
};
}
//668行
dataSourceStat.setResetStatEnable(this.resetStatEnable);
connections = new DruidConnectionHolder[maxActive];
SQLException connectError = null;
// 676行init connections,终于真正构建连接池了
for (int i = 0, size = getInitialSize(); i < size; ++i) {
//创建一个连接
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
//构建连接池了
connections[poolingCount] = holder;
incrementPoolingCount();
}
//createPhysicalConnection();跟进去
public PhysicalConnectionInfo createPhysicalConnection() throws SQLException {
//...............一堆的set
//1841行,这里连接了数据库同时增加了过滤器
conn = createPhysicalConnection(url, physicalConnectProperties);
//1488 如果存在测试sql就执行验证
initPhysicalConnection(conn);
//1510 这里面遵循了阿里的开发规范,构造器禁止添加业务逻辑
return new PhysicalConnectionInfo(conn, connectStartNanos, connectedNanos, initedNanos, validatedNanos);
}
//这里说一下连接池的原理,连接建立一个connection以后是挂载在tomcat下面的一个长连接,只要connection不close或者tomcat服务不停止就会一直存在,可以通过sql show full processlist 查看mysql活连接验证
//回到主方法 692 创建了三个线程
createAndLogThread();
createAndStartCreatorThread();
createAndStartDestroyThread();
//这里用了一个CountDownLatch的阻塞器,保证了上面的方法强制性的同步
initedLatch.await();
//-------------到这里为止也就爱结束了init的分析,说白就是初始化了connection池,里面很多多线程的知识--------------------分析一下是 DruidDataSource
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
只看这里面可以发现也就是从现有的里面返回一个connection return getConnectionDirect(maxWaitMillis);
里面比较核心的就是1024行
poolableConnection = getConnectionInternal(maxWaitMillis);
看到这里貌似连接的部分也就OK了
我们回过头来再看一遍最外层的调用
// 1,创建Druid连接池对象
DruidDataSource dataSource = new DruidDataSource();
// 2,为数据库添加配置文件
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/first?useUnicode=true&characterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 用Druid来连接
conn = dataSource.getConnection();
dataSource.getConnection(); 这里其实作了两个动作,初始化dataSource自己,然后检查filter,然后从初始化的连接里面返回一个connection,但是不得不承认的是里面充斥着大量的多线程高并发写法,
个人感觉
1.里面有着大量的多线程,高并发下的设计
2.里面虽然代码复杂但是很干净,看起来也有条理,虽然没有注释但是也能猜个差不多
3.严谨,里面很多貌似冗余的代码逻辑能出来这个框架趟过多少的坑