在jdbc中,我们可以通过DriverManager的getConnection方法来获取数据库连接,但是这种方式会去查找相应的驱动,然后与数据库地址建立真实的连接,因此比较耗时。如果在应用中存在大量的连接数据库操作,那么这种方式的效率将会非常低下。为了解决这个问题,数据源的技术应运而生,所谓的数据源,其实就是把获取的连接(Connection)放在一个“池”中,以达到复用的目的。
Mybatis自带的数据源类型有三种:unpooled、pooled以及jndi。其中unpooled会直接从DriverManager获取新连接,而pooled会对连接进行缓存,也就是具有“池”的功能。
首先来看java本身的DataSource接口:
它只有获取Connection的功能。
下面我们首先来看unpooled类型的数据源,它的实现类是:
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<String, Driver>();
private String driver;
private String url;
private String username;
private String password;
我们来看看它的获取Connection的方法:
@Override
public Connection getConnection(String username, String password) throws SQLException {
return doGetConnection(username, password);
}
……
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if (driverProperties != null) {
props.putAll(driverProperties);
}
if (username != null) {
props.setProperty("user", username);
}
if (password != null) {
props.setProperty("password", password);
}
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
最终调用的是doGetConnection()方法。在该方法中,直接通过DriverManager.getConnection来获取连接。因此unpooled的特点是简单、效率低,不适合用于大量操作数据库的应用。接下来我们来看看具有连接池功能的pooled数据源:
public class PooledDataSource implements DataSource {
private static final Log log = LogFactory.getLog(PooledDataSource.class);
//存放Connection的池
private final PoolState state = new PoolState(this);
private final UnpooledDataSource dataSource;
// OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10;//最大活动连接数
protected int poolMaximumIdleConnections = 5;//最大空闲连接数
protected int poolMaximumCheckoutTime = 20000;//活动连接最大过期时间
protected int poolTimeToWait = 20000;//等待连接最大时间
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;
private int expectedConnectionTypeCode; //由url、username、password构成的一个hashcode
…………
这个PooledDataSource里面有一个PoolState,这个就是存放Connection的地方。我们还发现有一个UnpooledDataSource,真正获取连接的操作还是通过UnpooledDataSource来完成。下面我们来看看这个PoolState:
public class PoolState {
protected PooledDataSource dataSource;
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();//空闲连接池
protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();//活动连接池
protected long requestCount = 0;//请求次数
protected long accumulatedRequestTime = 0;//累计请求时间
protected long accumulatedCheckoutTime = 0;//活动连接池中所有连接累计存活时间
protected long claimedOverdueConnectionCount = 0;//超过最大存活时间的累计连接数
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;//超过最大存活时间的累计连接数的累计时间
protected long accumulatedWaitTime = 0;//累计等待连接时间(线程被挂起以等待有新的连接可用)
protected long hadToWaitCount = 0;//累计等待线程数量
protected long badConnectionCount = 0;//无效连接数量
…………
这里的idleConnections中存放的就是空间的Connection,也就是可以立即拿来用的Connection。而activeConnections里面存放的是活动Connection,也就是正在被使用的连接。
这里我们发现这两个池里面存放的是PooledConnection,而不是Connection,下面我们就来看看这个PooledConnection。
class PooledConnection implements InvocationHandler {
private static final String CLOSE = "close";
private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
private int hashCode = 0;
private PooledDataSource dataSource; //所属的DataSource
private Connection realConnection;//真实Connection
private Connection proxyConnection;//代理Connection
……
public PooledConnection(Connection connection, PooledDataSource dataSource) {
……
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
原来这个PooledConnection是一个代理类,我们来看看它的invoke函数:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
if (!Object.class.equals(method.getDeclaringClass())) {
// issue #579 toString() should never fail
// throw an SQLException instead of a Runtime
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
从这里我们可以发现,当我们调用PooledConnection的close方法时,并不会真正关闭这个连接,而是调用了PooledDataSource的pushConnection()方法。我们先不来看这个pushConnection()方法,我们先来看看如何从PooledDataSource获取一个连接:
//获取连接
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) {
synchronized (state) { //由于state属于共享对象,所以对它的操作需要加锁。
if (!state.idleConnections.isEmpty()) {//首先查看空闲连接池中是否有连接,如果有则直接使用
// Pool has available connection
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
// Pool does not have available connection
if (state.activeConnections.size() < poolMaximumActiveConnections) {//如果活动连接池中的数量没有达到最大值,则重新获取一个连接
// Can create new connection
conn = new PooledConnection(dataSource.getConnection(), this);
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// Cannot create new connection
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// Can claim overdue connection
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 {//如果活动连接池中所有连接都没有超时,那么该线程就需要等待。
// Must wait
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
if (log.isDebugEnabled()) {
log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
}
long wt = System.currentTimeMillis();
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
if (conn.isValid()) {
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
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 + 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.");
}
}
}
}
}
if (conn == null) {
if (log.isDebugEnabled()) {
log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
}
return conn;
}
当我们想从PooledDataSource获取一个连接时,最终会调用popConnection()这个方法。由于这里涉及对共享变量的操作,这里会对连接池加锁。首先它会查看空闲连接池中是否有连接,如果有则直接使用;如果空闲连接池没有可用连接,那么它会判断当前活动连接是否已达活动连接数的最大值,如果没有达到,那么会调用UnpooledDataSource重新获取一个连接;如果活动连接已达到最大值了,它会尝试从活动连接池中获取一个最早放入的连接,并查看它的过期时间,如果它已经过期,则把该连接移除出活动连接池,然后对未提交了内容执行rollback操作,然后将该连接赋给当前线程使用。如果活动连接池中的所有连接均未过期,那么当前线程将会阻塞,以等待有新的连接可用。
现在我们回过头去看pushConnection()方法:
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//首先从活动连接池中移除该Connection
state.activeConnections.remove(conn);
if (conn.isValid()) {
//如果空闲连接池中数量未达到最大值,则把该连接放入空闲连接池,然后调用nitifyAll通知其他线程
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
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++;
}
}
}
同样,该操作会对连接池进行加锁,然后将该连接从活动池中移除,接下来查看空闲池中的连接数量是否已达到最大值,如果没有,则把该连接放入空闲池,并调用notifyAll通知其他等待该资源的线程;如果空闲池已满,则关闭该连接。
总结:mybatis内置的datasource还是比较简单的,在实际开发中,我们通常会使用第三方的DataSource,这些第三方的数据源往往能提供更加强大的功能。比如Druid。