上一篇文章中我们讲了environments,在environments中主要的配置就是dataSource和transactionManager.本篇文章我们就来讲讲dataSource.
不知道大家还记不记得解析environments标签中的解析dataSource的代码:
// 通过xml文件配置返回一个数据源工厂
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 通过工厂获取一个数据源DataSource
DataSource dataSource = dsFactory.getDataSource();
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
从上述代码我们可以看出,我们在xml中配置的type实际上需要是一个DataSourceFactory(实际上DataSourceFactory的作用只是用来生成一个DataSource(数据源)),而我们可以自定义DataSource,不过自定义DataSource不是本篇文章的重点.
本篇文章,我们首先来看一下mybatis内置的三种DataSource(数据源):
1.UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接.
2.POOLED:这种数据源的实现利用池的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间.
3.JNDI:这个数据源的实现是为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用.
Configuration类的构造方法中,已经帮我们定义了这三个类的别名,所以我们如果使用这三个DataSourceFactory中的一个,只需要使用它们的别名即可:
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
在看mybatis内置的三种数据源之前,我们先来看看DataSourceFactory接口:
public interface DataSourceFactory {
// 为数据源设置参数
void setProperties(Properties props);
// 返回一个数据源
DataSource getDataSource();
}
从这里可以看出DataSourceFactory的作用就是获取DataSource.
而DataSource主要的作用就是获取Connection:
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
1.UNPOOLED
我们先来看一下UnpooledDataSourceFactory的构造方法:
public UnpooledDataSourceFactory() {
// 从这里可以看出,如果dataSource标签的type使用的是UNPOOLED,那么最终将使用UnpooledDataSource数据源
this.dataSource = new UnpooledDataSource();
}
再来我们看一下UnpooledDataSourceFactory中的其他方法:
// 这个方法就是返回UnpooledDataSourceFactory中的数据源
public DataSource getDataSource() {
return dataSource;
}
// 不知道大家还记不记得,这个方法是在解析dataSource标签的dataSourceElement方法中调用的,当时通过resolveClass实例化了一个DataSourceFactory,之后就调用了factory.setProperties(props);
// 这个方法虽然看起来复杂,但是主要作用就是将配置的一些参数值,设置到dataSource中,这里不对该方法展开来分析
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);
}
}
从上述代码我们可以看出,其实最重要的还是DataSource,所以我们来看一下UnpooledDataSource的getConnection()方法:
public Connection getConnection() throws SQLException {
// 使用在DataSourceFactory的setProperties方法中设置的username和password去打开Connection
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);
}
// 将各个参数放入Properties,调用doGetConnection
return doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
// 初始化数据库驱动
initializeDriver();
// 这里的代码相信写过JDBC的同学会特别熟悉,这完全就是JDBC中通过DriverManager获取Connection的代码
Connection connection = DriverManager.getConnection(url, properties);
// 配置Connection的一些参数
configureConnection(connection);
return connection;
}
private void configureConnection(Connection conn) throws SQLException {
// 设置Connection是否自动提交
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
// 设置Connection的隔离级别
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
private synchronized void initializeDriver() throws SQLException {
// registeredDrivers是存储已经注册的数据库驱动,若还未注册驱动,则注册数据库驱动
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
if (driverClassLoader != null) {
driverType = Class.forName(driver, true, driverClassLoader);
} else {
driverType = Resources.classForName(driver);
}
Driver driverInstance = (Driver)driverType.newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 注册完驱动后,加入registeredDrivers中
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
从上述代码我们可以看出,若是使用UNPOOLED数据源,其实本质上就是每次获取Connection就会去获取一个新的连接.
2.POOLED
我们接着来看POOLED,首先我们看PooledDataSourceFactory:
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
从这里我们可以看出PooledDataSourceFactory的setProperties和getDataSource和UnpooledDataSourceFactory一样,不一样的是PooledDataSourceFactory的getDataSource方法返回的是PooledDataSource.
所以我们直接来看PooledDataSource的代码,首先看PooledDataSource的构造方法:
private final UnpooledDataSource dataSource;
// 我们可以看到,在PooledDataSource中持有了一个UnpooledDataSource
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}
我们继续来看PooledDataSource的getConnection()方法:
public Connection getConnection() throws SQLException {
// popConnection方法返回一个PooledConnection,通过PooledConnection的getProxyConnection方法获取一个Connection
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
继续看popConnection方法:
private PooledConnection popConnection(String username, String password) throws SQLException {
boolean countedWait = false;
PooledConnection conn = null;
long t = System.currentTimeMillis();
int localBadConnectionCount = 0;
// 当conn为null时,继续循环去获取conn
while (conn == null) {
// state的定义:private final PoolState state = new PoolState(this),PoolState中维护着PooledDataSource中空闲的Connection的List和忙碌的Connection的List.
// 获取Connection的过程中要加锁
synchronized (state) {
// idleConnections实际上是一个ArrayList,定义为:protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
// idleConnections这个属性就是表示当前这个DataSource连接池中的空闲的Connection的List,如果有,则直接返回第一个空闲Connection
if (state.idleConnections.size() > 0) {
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else { // 若连接池中没有空闲的Connection,则运行到这里
// activeConnections也是一个ArrayList,定义为:protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
// activeConnections这个属性就是表示当前这个DataSource连接池中的忙碌的Connection的List
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// 若忙碌的Connection的数量小于poolMaximumActiveConnections(最大连接数,默认是10),则创建一个新的Connection
// 我们可以看到,虽然这里返回的是PooledConnection,但是实际使用的是dataSource.getConnection(),而dataSource就是在构造方法中实例化的UnpooledDataSource.
// 这里使用PooledConnection的原因其实是为了给真正的Connection做一个代理,让其close的时候,并不是真正关闭Connection,而是将Connection返还给连接池,PooledConnection的代码我们接下来会分析
conn = new PooledConnection(dataSource.getConnection(), this);
@SuppressWarnings("unused")
Connection realConn = conn.getRealConnection();
if (log.isDebugEnabled()) {
log.debug("Created connection " + conn.getRealHashCode() + ".");
}
} else {
// 若Connection数量已经达到最大,则获取第一个工作中的Connection(也就是工作最久的一个Connection)
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
// 这里取出的时间是该Connection已经在工作状态工作的时间
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
if (longestCheckoutTime > poolMaximumCheckoutTime) {
// 若已经工作的时间大于最大的工作时间(默认20000毫秒,也就是20秒),则记录一些值
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
// 然后将该Connection从工作List中移除
state.activeConnections.remove(oldestActiveConnection);
// 如果该Connection事务不是自动提交,则回滚之前所有操作(从这里我们可以看出,如果一个Connection工作时间超过了最大的工作时间,该Connection可能会被别的操作给抢夺走)
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
oldestActiveConnection.getRealConnection().rollback();
}
// 然后将其封装成一个新的PooledConnection
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
// 将valid设置为false,该值可以理解为Connection是否可用
oldestActiveConnection.invalidate();
if (log.isDebugEnabled()) {
log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
}
} else {
// 如果没有超过最大工作时间的Connection,则需要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();
// 等待最大时间为poolTimeToWait,(poolTimeToWait默认为20000毫秒,也就是20秒)
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
// 如果线程被中断了,则直接退出while循环
break;
}
}
}
}
if (conn != null) {
// 如果获得的Connection是可用的,并且事务不是自动提交,则回滚之前的所有操作
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());
// 将该Connection加入到工作List中
state.activeConnections.add(conn);
state.requestCount++;
state.accumulatedRequestTime += System.currentTimeMillis() - t;
} else {
// 若获取的Connection不可用,则记录一些状态,然后继续获取,因为while(conn != null)
if (log.isDebugEnabled()) {
log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
}
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
// 当获取的不可用Connection数量大于最大可以空闲的Connection数poolMaximumIdleConnections+3时,(poolMaximumIdleConnections默认为5),则抛出异常
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;
}
上述代码的流程图:
之后就是调用PooledConnection的getProxyConnection()方法返回一个Connection,我们先来看一下PooledConnection的构造方法:
// 该Connection最初是通过dataSource.getConnection()获取的,也就是UnpooledDataSource
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
// 真正的Connection
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
// 该属性就是被代理的Connection
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
我们继续来看PooledConnection的invoke方法,看看生成的代理Connection会做什么:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 调用close方法时,并不会去执行Connection的close方法去关闭连接,而是执行pushConnection将该连接返还给数据库连接池,这里就是数据库连接池中的连接得以复用的原因.
// 这里的dataSource是构造方法中传入的PooledDataSource
if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
} else {
try {
// 如果调用其他方法,都会去检测一下,当前Connection是否还可用
if (!Object.class.equals(method.getDeclaringClass())) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
我们接着来看PooledDataSource的pushConnection方法:
protected void pushConnection(PooledConnection conn) throws SQLException {
// 将连接返回给连接池时,也需要加锁
synchronized (state) {
// 在该连接池的工作Connection的List中移除该连接
state.activeConnections.remove(conn);
if (conn.isValid()) {
// 若该连接池中的空闲Connection的List的数量小于最大空闲连接poolMaximumIdleConnections(默认为5)且expectedConnectionTypeCode相同(expectedConnectionTypeCode是数据库连接的url+username+password生成的一个hashCode值),则将该连接中的真实连接realConnection取出,重新封装成一个PooledConnection,并将其加入空闲Connection的List中
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.");
}
// 这里执行的notifyAll,会唤醒之前在获取Connection时,调用wait()等待的线程
state.notifyAll();
} else {
// 若空闲的Connection数量已经达到最大,则取出真实连接realConnection,将其真正的close掉
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++;
}
}
}
3.JNDI
这个数据源,笔者也没有使用过,这里就不展开讲了,之后如果有机会,可以单独开一篇文章讲解.
到这里,dataSource相关内容就基本讲完了.