常见的数据源组件都实现了javax.sql.DataSource
接口,MyBatis自身实现的数据源实现也不例外。MyBatis提供了两个javax.sql.DataSource
接口实现,分别是PooledDataSource和UnpooledDataSource。MyBatis使用不同的DataSourceFactory接口实现创建不同类型的DataSource,如下图所示:
1、DataSourceFactory
DataSourceFactory是JndiDataSourceFactory和UnpooledDataSourceFactory两个工厂类的顶层接口,只定义了两个方法,如下所示:
public interface DataSourceFactory {
//设置DataSource的相关属性,一般在初始化完成后进行设置
void setProperties(Properties props);
//获取DataSource对象
DataSource getDataSource();
}
2、UnpooledDataSourceFactory
在UnpooledDataSourceFactory的构造函数中会直接创建UnpooledDataSource对象。setProperties()
方法会完成对UnpooledDataSource对象的配置
public class UnpooledDataSourceFactory implements DataSourceFactory {
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
//对应的数据源,即UnpooledDataSource
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
//创建DataSource相应的MetaObject
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
//遍历properties集合,该集合中配置了数据源所需要的信息
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
//以driver.开头的配置项是对DataSource的配置,记录到driverProperties中保存
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
//根据属性类型进行类型转换,主要是Integer、Long、Boolean三种类型的转换
Object convertedValue = convertValue(metaDataSource, propertyName, value);
//设置DataSource的相关属性值
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
//设置DataSource.driverProperties属性值
metaDataSource.setValue("driverProperties", driverProperties);
}
}
//返回数据源
@Override
public DataSource getDataSource() {
return dataSource;
}
//类型转换
private Object convertValue(MetaObject metaDataSource, String propertyName, String value) {
Object convertedValue = value;
Class<?> targetType = metaDataSource.getSetterType(propertyName);
if (targetType == Integer.class || targetType == int.class) {
convertedValue = Integer.valueOf(value);
} else if (targetType == Long.class || targetType == long.class) {
convertedValue = Long.valueOf(value);
} else if (targetType == Boolean.class || targetType == boolean.class) {
convertedValue = Boolean.valueOf(value);
}
return convertedValue;
}
}
3、PooledDataSourceFactory
PooledDataSourceFactory继承了UnpooledDataSourceFactory,但并没有覆盖setProperties()
和getDataSource()
方法。两者唯一的区别是PooledDataSourceFactory的构造函数会将其dataSource字段初始化为PooledDataSource对象
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
4、不使用连接池的UnpooledDataSource
UnpooledDataSource实现了javax.sql.DataSource
接口中定义的getConnection()
方法,用于获取数据库连接。每次通过UnpooledDataSource.getConnection()
方法获取数据库连接时都会创建一个新连接
public class UnpooledDataSource implements DataSource {
//加载Driver类的类加载器
private ClassLoader driverClassLoader;
//数据库连接驱动的相关配置
private Properties driverProperties;
//缓存所有已注册的数据库连接驱动
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
private String driver;
private String url;
private String username;
private String password;
//是否自动提交
private Boolean autoCommit;
//事务隔离级别
private Integer defaultTransactionIsolationLevel;
private Integer defaultNetworkTimeout;
//静态代码块,初始化的时候,从DriverManager中获取所有已注册的驱动信息,并缓存到该类的registeredDrivers中
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
public UnpooledDataSource() {
}
public UnpooledDataSource(String driver, String url, String username, String password) {
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
接下来看下获取连接的方法:
public class PooledDataSource implements DataSource {
@Override
public Connection getConnection() throws SQLException {
return doGetConnection(username, password);
}
@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);
}
//根据properties获取一个新的数据库连接
private Connection doGetConnection(Properties properties) throws SQLException {
//初始化数据库驱动
initializeDriver();
//通过DriverManager获取一个数据库连接
Connection connection = DriverManager.getConnection(url, properties);
//配置数据连接是否自动提交和事务隔离级别
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);
}
//创建驱动
Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
//向JDBC的DriverManager注册驱动
DriverManager.registerDriver(new DriverProxy(driverInstance));
//向本类的registeredDrivers注册驱动
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
//配置数据连接是否自动提交和事务隔离级别
private void configureConnection(Connection conn) throws SQLException {
if (defaultNetworkTimeout != null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
}
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
以上代码就是UnpooledDataSource类的主要实现逻辑,每次获取连接都是从数据库新创建一个连接进行返回,又因为数据库连接的创建是一个耗时的操作,且数据库连接是非常珍贵的资源,如果每次获取连接都创建一个,则可能会造成系统的瓶颈,拖垮响应速度等,这时就需要数据库连接池了,Mybatis也提供了自己数据库连接池的实现,就是PooledDataSource类
5、使用连接池的PooledDataSource
PooledDataSource实现了简易数据库连接池的功能,PooledDataSource新建数据库连接是使用UnpooledDataSource来实现的
1)、PooledConnection
UnpooledDataSource并不会管理java.sql.Connection
对象,而是管理org.apache.ibatis.datasource.pooled.PooledConnection
对象,在PooledConnection中封装了真正的数据库连接对象和其代理对象,这里的代理对象是通过JDK动态代理产生的。PooledConnection继承了InvocationHandler接口
class PooledConnection implements InvocationHandler {
//记录当前PooledConnection对象所在的PooledDataSource对象,当调用close()方法时会将PooledConnection放回该PooledDataSource中
private final PooledDataSource dataSource;
//真正的数据库连接
private final Connection realConnection;
//数据库连接的代理对象
private final Connection proxyConnection;
//从连接池中取出该连接的时间戳
private long checkoutTimestamp;
//该连接创建的时间戳
private long createdTimestamp;
//最后一次被使用的时间戳
private long lastUsedTimestamp;
//由数据库URL、用户名和密码计算出来的hash值,可用于标识该连接所在的连接池
private int connectionTypeCode;
//检测当前PooledConnection是否有效,主要是为了防止程序通过close()方法将连接归还给连接池之后,依然通过该连接操作数据库
private boolean valid;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//如果调用close()方法,则将其重新放入连接池,而不是真正关闭数据库连接
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
if (!Object.class.equals(method.getDeclaringClass())) {
//通过valid字段检测连接是否有效
checkConnection();
}
//调用真正数据库连接对象的对应方法
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
2)、PoolState
PoolState是用于管理PooledConnection对象状态的组件,它通过两个List<PooledConnection>
集合分别管理空闲状态的连接和活跃状态的连接:
public class PoolState {
protected PooledDataSource dataSource;
//空闲的PooledConnection集合
protected final List<PooledConnection> idleConnections = new ArrayList<>();
//活跃的PooledConnection集合
protected final List<PooledConnection> activeConnections = new ArrayList<>();
//请求数据库连接的次数
protected long requestCount = 0;
//获取连接的累积时间
protected long accumulatedRequestTime = 0;
//checkoutTime表示应用从连接池中取出连接,到归还连接这段时长,accumulatedCheckoutTime记录了所有连接累积的checkoutTime时长
protected long accumulatedCheckoutTime = 0;
//当连接长时间未归还给连接池时,会被认为该连接超时,claimedOverdueConnectionCount记录了超时的连接个数
protected long claimedOverdueConnectionCount = 0;
//累积超时时间
protected long accumulatedCheckoutTimeOfOverdueConnections = 0;
//累积等待时间
protected long accumulatedWaitTime = 0;
//等待次数
protected long hadToWaitCount = 0;
//无效的连接数
protected long badConnectionCount = 0;
3)、PooledDataSource
PooledDataSource新建数据库连接是使用UnpooledDataSource来实现的,PooledConnection用来管理连接池中的连接,PoolState是用于管理连接池的状态
PooledDataSource中核心属性如下:
public class PooledDataSource implements DataSource {
//连接池的状态
private final PoolState state = new PoolState(this);
//用来创建真正的数据库连接对象
private final UnpooledDataSource dataSource;
//最大活跃的连接数,默认为10
protected int poolMaximumActiveConnections = 10;
//最大空闲连接数,默认为5
protected int poolMaximumIdleConnections = 5;
//最大获取连接的时长
protected int poolMaximumCheckoutTime = 20000;
//在无法获取到连接时,最大等待的时间
protected int poolTimeToWait = 20000;
//在检测一个连接是否可用时,会向数据库发送一个测试SQL
protected String poolPingQuery = "NO PING QUERY SET";
//是否允许发送测试SQL
protected boolean poolPingEnabled;
//当连接超过poolPingConnectionsNotUsedFor毫秒未使用时,会发送一次测试SQL语句,测试连接是否正常
protected int poolPingConnectionsNotUsedFor;
//标志着当前的连接池,是url+username+password的hash值
private int expectedConnectionTypeCode;
PooledDataSource主要有以下几个方法:获取数据库连接的方法popConnection()
,把连接放回连接池的方法pushConnection()
,检测数据库连接是否有效的方法pingConnection()
,还有关闭连接池中所有连接的方法forceCloseAll()
1)popConnection()
PooledDataSource中getConnection()
方法首先会调用popConnection()
方法获PooledConnection对象,然后通过PooledConnection.getProxyConnection()
方法获取数据库连接的代理对象
public class PooledDataSource implements DataSource {
//获取连接
@Override
public Connection getConnection() throws SQLException {
return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
}
//从连接池中获取连接
private PooledConnection popConnection(String username, String password) throws SQLException {
//等待的个数
boolean countedWait = false;
//PooledConnection对象
PooledConnection conn = null;
long t = System.currentTimeMillis();
//无效的连接个数
int localBadConnectionCount = 0;
while (conn == null) {
synchronized (state) {
//检测是否还有空闲的连接
if (!state.idleConnections.isEmpty()) {
//连接池中还有空闲的连接,则直接获取连接返回
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled()) {
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
} else {
//连接池中已经没有空闲连接了
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) {
//如果该连接超时,则进行相应的统计
state.claimedOverdueConnectionCount++;
state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
state.accumulatedCheckoutTime += longestCheckoutTime;
//将超时连接移出activeConnections集合
state.activeConnections.remove(oldestActiveConnection);
if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
try {
//如果超时未提交,则自动回滚
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back");
}
}
//创建新的PooledConnection对象,但是真正的数据库连接并没有创建
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) {
//等待次数
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 + poolMaximumLocalBadConnectionTolerance)) {
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;
}
2)pushConnection()
通过分析PooledConnection的invoke()
方法我们知道,当调用连接的代理对象的close()
方法时,并未关闭真正的数据连接,而是调用 PooledDataSource的pushConnection()
方法将PooledConnection对象归还给连接池,供之后重用
public class PooledDataSource implements DataSource {
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
//从活跃的集合中移除掉该连接
state.activeConnections.remove(conn);
//检测连接是否有效
if (conn.isValid()) {
//如果空闲连接数没有达到最大值,且PooledConnection为该连接池的连接
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
//累计checkout时长
state.accumulatedCheckoutTime += conn.getCheckoutTime();
//事务回滚
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
//为返还的连接创建新的PooledConnection对象
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.");
}
//无效连接个数加1
state.badConnectionCount++;
}
}
}
3)pingConnection()
在上面两个方法中,都调用了isValid()
方法来检测连接是否可用,该方法除了检测valid字段外,还会调用pingConnection()
方法来尝试让数据库执行测试SQL语句,从而检测真正的数据库连接对象是否依然正常可用
class PooledConnection implements InvocationHandler {
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
public class PooledDataSource implements DataSource {
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;
}
//如果真正的数据库连接还没关闭,需要执行测试SQL语句,长时间(poolPingConnectionsNotUsedFor指定的时长)未使用的连接,才需要ping操作来检测连接是否正常
if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
try {
if (log.isDebugEnabled()) {
log.debug("Testing connection " + conn.getRealHashCode() + " ...");
}
//发送测试SQL语句执行
Connection realConn = conn.getRealConnection();
try (Statement statement = realConn.createStatement()) {
statement.executeQuery(poolPingQuery).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;
}
4)forceCloseAll()
当修改PooledDataSource相应的字段,如数据库的URL、用户名或密码等,需要将连接池中连接全部关闭,之后获取连接的时候从重新初始化。关闭连接池中全部连接的方法为forceCloseAll()
public class PooledDataSource implements DataSource {
public void forceCloseAll() {
synchronized (state) {
expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
//处理活跃的连接
for (int i = state.activeConnections.size(); i > 0; i--) {
try {
PooledConnection conn = state.activeConnections.remove(i - 1);
//设置连接为无效状态
conn.invalidate();
//获取数据库真正的连接
Connection realConn = conn.getRealConnection();
//事务回滚
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
//关闭数据库连接
realConn.close();
} catch (Exception e) {
// ignore
}
}
//处理空闲的连接
for (int i = state.idleConnections.size(); i > 0; i--) {
try {
PooledConnection conn = state.idleConnections.remove(i - 1);
//设置为无效状态
conn.invalidate();
Connection realConn = conn.getRealConnection();
if (!realConn.getAutoCommit()) {
realConn.rollback();
}
realConn.close();
} catch (Exception e) {
// ignore
}
}
}
if (log.isDebugEnabled()) {
log.debug("PooledDataSource forcefully closed/removed all connections.");
}
}
参考:
《MyBatis技术内幕》
https://my.oschina.net/mengyuankan/blog/2664784