Mybatis作为持久化框架,datasource是必不可少的,对应的自然少不了连接池,常用的数据库连接池有c3p0、BasicDatasource、druid。我们一起去看看
数据源类图一波带走不谢
PooledDatasource和UnpooledDataSource是mybatis自定义数据源,从下图不难看出,下面是一个工厂方法。不同的工厂创建其对应的数据源
工厂模式场景文章参见: http://blog.csdn.net/zhangziwen94nb/article/details/44062793
UnpooledDatasource创建并获取连接的时序图
注册驱动的过程
//UnpooledDataSource.java
//1.注册驱动
//2.创建连接
//3.设置属性
private Connection doGetConnection(Properties properties) throws SQLException {
//初始化驱动
initializeDriver();
//创建连接
Connection connection = DriverManager.getConnection(url, properties);
//配置数据库autocommit、隔离级别
configureConnection(connection);
return connection;
}
//通过反射实例化驱动
private synchronized void initializeDriver() throws SQLException {
if (!registeredDrivers.containsKey(driver)) {
Class> driverType;
try {
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
// DriverManager requires the driver to be loaded via the system ClassLoader.
// http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Driver driverInstance = (Driver)driverType.newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
Map registeredDrivers = new ConcurrentHashMap();
注意上段代码中: UnpooledDataSource中的registeredDrivers与DriverManager中的registeredDrivers的区别 这2个集合中都存有注册驱动的实例信息,DriverProxy 是对Driver的做了一层代理
//DriverManager.java 驱动管理器注册驱动
public static synchronized void registerDriver(java.sql.Driver driver,
DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if(driver != null) {
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
CopyOnWriteArrayList registeredDrivers = new CopyOnWriteArrayList<>()
//针对copyOnwriter的理解可参考文章
http://ifeve.com/java-copy-on-write/
大致意思就是写入时将容器进行拷贝,将元素写入新的容器,完毕后将原有引用指向新的元素,这样目的可以提高并发读性能。
创建连接
//DriverManager.java getConnection()
private static Connection getConnection(
String url, java.util.Properties info, Class> caller) throws SQLException {
//获取类加载器
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
// synchronize loading of the correct classloader.
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
//创建连接
SQLException reason = null;
for(DriverInfo aDriver : registeredDrivers) {
// 如果类加载器不允许加载该驱动,则跳过
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
//通过不同的驱动类创建驱动
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
//其中驱动类poolingDriver.java 就是apache.jdbc的driver
实现了Driver的类图如下:
###配置属性
//UnpooledDataSourceFactory.java 这里主要用于设置配置文件配置的数据库属性
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
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);
}
}
Mybatis内部是如何实现数据连接池的呢,时序图如下:
连接池中类图如下:
通过PooledDataSource创建数据源,实现依赖于UnpooledDataSource,获取连接交给了PooledConnection,其实现了jdk自带的InvocationHandler接口,(很熟悉的感觉,干啥的,必然做代理的。代理的目标对象是Connection啦);获取的连接的状态交给PoolState去管理,揭开神秘的面纱吧
//PooledDataSource.java
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
while (conn == null) {//如果连接为null
synchronized (state) {//PoolState对象加锁
if (!state.idleConnections.isEmpty()) {//如果空闲连接池中有可用的对象
conn = state.idleConnections.remove(0);//则从中取出一个直接返回
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// 否则 如果活跃连接数<最大的活跃连接数poolMaximumActiveConnections默认为10
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// 则创建连接,注意构造方法有代理对象的处理
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// 活跃连接池已满,不能再创建连接,取出第一个连接
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
//当前时间-获取其从连接池中取出连接的时间戳【判断这个连接使用了多长时间了】
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {//是否大于设置的最大checkout时长默认20000
// 记录该连接信息,准备从活跃连接池中移除
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {//如果当前连接不是自动提交
try {
oldestActiveConnection.getRealConnection().rollback();//则回滚
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back");
}
}
//重新创建连接
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
oldestActiveConnection.invalidate();//设置原来连接失效
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
//如果活跃连接池中没有可丢掉的对象,则阻塞等待
try {
if (!countedWait) {//等待状态为false
state.hadToWaitCount++;//等待次数加+1
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);//设置等待时间 默认20000
state.accumulatedWaitTime += System.currentTimeMillis() - wt;//记录等待时长
} catch (InterruptedException e) {
break;
}
}
}
}
//如果连接不为空
if (conn != null) {
// 进行pingConnection校验
if (conn.isValid()) {
//如果不是自动提交的,则将之前的数据回滚
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//设置连接类型 url+name+password的hashcode
conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
conn.setCheckoutTimestamp(System.currentTimeMillis());
conn.setLastUsedTimestamp(System.currentTimeMillis());
//添加到活跃连接池中,设置请求次数
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {//如果失效连接数>最大空闲连接数默认5+可容忍的最大失效连接数默认3
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Could not get a good connection to the database.");
}
throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
}
}
}
}
}
下段代码中调用close方法的时候会触发代理对象,调用invoke方法 会调用PooledDataSource.pushConnection方法,将当前连接对象重新仍回到连接池中,知道持有引用的原因了吧
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//从活跃对象中先删除当前的对象
state.activeConnections.remove(conn);
//判断连接有效
if (conn.isValid()) {//如果空闲连接数<最大空闲连接数 &&连接hashCode[用于多数据源的情况]一致
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {//设置checkout的时间
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {//如果不是自动提交,则回滚
conn.getRealConnection().rollback();
}//创建新的连接
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);//添加到空闲连接池中
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
conn.invalidate();//设置连接失效
if (log.isDebugEnabled()) {
log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
}
state.notifyAll();//唤醒在等待
} else { //超过最大空闲连接数 则关闭
state.accumulatedCheckoutTime += conn.getCheckoutTime();
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
conn.getRealConnection().close();
if (log.isDebugEnabled()) {
log.debug("Closed connection " + conn.getRealHashCode() + ".");
}
conn.invalidate();
}
} else {//如果无效 说明该连接是异常的
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
}
state.badConnectionCount++;
}
}
}
在调用isValid时,通过pingConnection方法来检查连接是否有效 一般执行select 1
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) {//关闭异常
if (poolPingEnabled) {//连接超过它设置的值>0&& 当前时间-连接最后被使用的时间>该值
if (poolPingConnectionsNotUsedFor >= 0 && conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {//实际就是说该连接最后一次被使用的时间大于了我们设置的默认时间就执行一次
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
Connection realConn = conn.getRealConnection();
Statement statement = realConn.createStatement();
ResultSet rs = statement.executeQuery(poolPingQuery);
rs.close();
statement.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;
}
pooledConnection实现的invoke方法
PooledConnection.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//判断连接是否关闭,如果关闭则扔回到池中
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
//连接对象扔回到PooledDataSource中
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
//检查连接是否存在 通过valid判断 valid方法会检查该连接是否存在
checkConnection();
}
//调用该连接的方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
private final int hashCode;//连接对象的hashCode
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;//检查该连接是否有效
PoolState状态管理的属性
PoolState.java主要用于管理PooledConnection对象状态的集合如下
protected final List idleConnections = new ArrayList();//空闲连接集合
protected final List activeConnections = new ArrayList();//活动连接集合
protected long requestCount = 0;//数据库连接请求次数
protected long accumulatedRequestTime = 0;//所有连接访问累计时间
protected long accumulatedCheckoutTime = 0;//所有连接累计checkouttime时长【从连接池中取出连接到将连接放回连接池中的时长】
protected long claimedOverdueConnectionCount = 0;//连接放回连接池时超时的连接个数
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
protected long accumulatedWaitTime = 0;//所有连接等待时长
protected long hadToWaitCount = 0;//等待次数
protected long badConnectionCount = 0;//无效连接次数
Mybatis数据源就介绍到这里了,下面我们对比下c3p0、BasicDatasource、druid一些性能
对比
Git clone:https://github.com/alibaba/druid.git
找到测试类Case0.java
//Case0.java
private void p0(DataSource dataSource, String name) throws SQLException {
//执行时间
long startMillis = System.currentTimeMillis();
//youngGC时间
long startYGC = TestUtil.getYoungGC();
//FullGC时间
long startFullGC = TestUtil.getFullGC();
//COUNT = 1000 * 1000 * 1
for (int i = 0; i < COUNT; ++i) {
Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement();
conn.close();
}
long millis = System.currentTimeMillis() - startMillis;
long ygc = TestUtil.getYoungGC() - startYGC;
long fullGC = TestUtil.getFullGC() - startFullGC;
System.out.println(name + " millis : " + NumberFormat.getInstance().format(millis) + ", YGC " + ygc + " FGC "
+ fullGC);
}
public void test_druid() throws Exception {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setInitialSize(initialSize);
dataSource.setMaxActive(maxActive);
dataSource.setMinIdle(minIdle);
dataSource.setMaxIdle(maxIdle);
dataSource.setPoolPreparedStatements(true);
dataSource.setDriverClassName(driverClass);
dataSource.setUrl(jdbcUrl);
dataSource.setPoolPreparedStatements(true);
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
//LOOP_COUN = 5
for (int i = 0; i < LOOP_COUNT; ++i) {
p0(dataSource, "druid");
}
System.out.println();
}
-执行1- | -执行2- |
---|---|
druid millis : 861, YGC 65 FGC 0 | druid millis : 1,030, YGC 121 FGC 0 |
druid millis : 376, YGC 62 FGC 0 | druid millis : 669, YGC 117 FGC 0 |
druid millis : 369, YGC 62 FGC 0 | druid millis : 673, YGC 117 FGC 0 |
druid millis : 374, YGC 62 FGC 0 | druid millis : 664, YGC 117 FGC 0 |
druid millis : 366, YGC 62 FGC 0 | druid millis : 685, YGC 117 FGC 0 |
---dbcp---------- | |
dbcp millis : 1,121, YGC 171 FGC 0 | dbcp millis : 1,663, YGC 227 FGC 0 |
dbcp millis : 1,013, YGC 168 FGC 0 | dbcp millis : 1,415, YGC 225 FGC 0 |
dbcp millis : 1,015, YGC 167 FGC 0 | dbcp millis : 1,363, YGC 224 FGC 0 |
dbcp millis : 999, YGC 168 FGC 0 | dbcp millis : 1,371, YGC 225 FGC 0 |
dbcp millis : 1,009, YGC 167 FGC 0 | dbcp millis : 1,360, YGC 225 FGC 0 |
-----c3p0-------- | |
c3p0 millis : 4,893, YGC 242 FGC 0 | c3p0 millis : 6,112, YGC 300 FGC 0 |
c3p0 millis : 4,836, YGC 239 FGC 0 | c3p0 millis : 5,840, YGC 293 FGC 0 |
c3p0 millis : 5,022, YGC 238 FGC 0 | c3p0 millis : 5,852, YGC 294 FGC 0 |
c3p0 millis : 4,828, YGC 239 FGC 0 | c3p0 millis : 5,508, YGC 293 FGC 0 |
c3p0 millis : 4,764, YGC 238 FGC 0 | c3p0 millis : 5,509, YGC 294 FGC 0 |
Druid、c3p0、BasicDatasource 分别执行结果如下:
-执行1- | -执行2- |
---|---|
druid millis : 861, YGC 65 FGC 0 | druid millis : 1,030, YGC 121 FGC 0 |
druid millis : 376, YGC 62 FGC 0 | druid millis : 669, YGC 117 FGC 0 |
druid millis : 369, YGC 62 FGC 0 | druid millis : 673, YGC 117 FGC 0 |
druid millis : 374, YGC 62 FGC 0 | druid millis : 664, YGC 117 FGC 0 |
druid millis : 366, YGC 62 FGC 0 | druid millis : 685, YGC 117 FGC 0 |
---dbcp---------- | |
dbcp millis : 1,121, YGC 171 FGC 0 | dbcp millis : 1,663, YGC 227 FGC 0 |
dbcp millis : 1,013, YGC 168 FGC 0 | dbcp millis : 1,415, YGC 225 FGC 0 |
dbcp millis : 1,015, YGC 167 FGC 0 | dbcp millis : 1,363, YGC 224 FGC 0 |
dbcp millis : 999, YGC 168 FGC 0 | dbcp millis : 1,371, YGC 225 FGC 0 |
dbcp millis : 1,009, YGC 167 FGC 0 | dbcp millis : 1,360, YGC 225 FGC 0 |
-----c3p0-------- | |
c3p0 millis : 4,893, YGC 242 FGC 0 | c3p0 millis : 6,112, YGC 300 FGC 0 |
c3p0 millis : 4,836, YGC 239 FGC 0 | c3p0 millis : 5,840, YGC 293 FGC 0 |
c3p0 millis : 5,022, YGC 238 FGC 0 | c3p0 millis : 5,852, YGC 294 FGC 0 |
c3p0 millis : 4,828, YGC 239 FGC 0 | c3p0 millis : 5,508, YGC 293 FGC 0 |
c3p0 millis : 4,764, YGC 238 FGC 0 | c3p0 millis : 5,509, YGC 294 FGC 0 |
从上面可以看出执行效率 druid>dpcp>c3p0
网上已经有很多文章来描述了 笔者只是想告诉大家 druid包下有很多测试类可供我们借鉴。
比如 上文中它是如何监测youngGC、FullGC的,有机会会给大家带来druid的分析.需要的赶紧一睹为快吧