文章目录
- Mybatis数据源模块和工厂模式、代理模式
- 一、简介
- 二、工厂模式引入
- 2.1 来源
- 2.2 优点
- 2.3 缺点
- 2.4 借鉴
- 2.5 分类
- 2.5.1 简单工厂模式
- 2.5.1 工厂方法模式
- 2.5.1 抽象工厂模式
- 三、目录结构
- 四、源码解析
- 4.1 pooled
- 4.1.1 PooledDataSource
- 4.1.1.1 获取连接
- 4.1.1.2 归还连接
- 4.1.1.3 获取数据源状态
- 4.1.1.4 pingConnection
- 4.1.1.5 forceCloseAll
- 4.1.2 PooledConnection
- 4.1.2.1 invoke
- 4.1.2.2 构造方法
- 4.1.2.3 其他方法
- 4.1.3 PoolState
- 4.1.4 PooledDataSourceFactory
- 4.1.5 PooledDataSource的连接获取/归还流程图
- 4.1.5.1 获取流程图
- 4.1.5.2 归还流程图
- 4.2 unpooled
- 4.2.1 UnpooledDataSource
- 4.2.1.1 获取连接
- 4.2.1.2 归还连接
- 4.2.1.3 initializeDriver
- 4.2.2 UnpooledDataSourceFactory
- 4.3 JNDI
- 五、小结
- 六、参考
Mybatis数据源模块和工厂模式、代理模式
一、简介
- Mybatis数据源组件和常见的数据源组件一样都实现了javax.sql.DataSource接口,除了自身包含的数据源组件,Mybatis也可以集成专业的第三方数据源组件,通常使用第三方的数据源组件,因为自身的实现比较简单。
- Mybatis数据源组件包括3种:pooled代表使用连接池的数据源,unpooled代表不使用连接池的数据源,另外还有JNDI类型。
- 数据源通常包含比较复杂的初始化参数,因此使用了工厂模式。
- pooled使用了连接池,对原始连接进行了增强,因此使用了代理模式。
二、工厂模式引入
2.1 来源
- 创建对象的三种方式:new关键字/反射/工厂模式
- 前两种方式将创建对象和使用对象耦合在一起,扩展业务可能需要修改业务代码,不符合单一职责和开闭原则
2.2 优点
- 创建对象和对象的使用分离,负责单一职责原则
- 将创建对象的过程抽离出来,便于维护,提高代码复用性,
- 业务扩展时,只需要增加工厂子类,不需要修改原有代码,符合开闭原则
2.3 缺点
2.4 借鉴
- Spring的IOC容器可以理解为一个工厂模式,将对象的创建和维护交由容器管理,使用的时候只需要注入即可,不关心对象的创建。
2.5 分类
2.5.1 简单工厂模式
- 在类中使用类似于if判断或者switch的语句来根据不同的条件创建不同的对象,适用于比较简单的场景,但是不便于扩展,不同类型产品的创建耦合在一起。
2.5.1 工厂方法模式
- 工厂和商品都是抽象类,若干个子类工厂,每个子类负责一个具体类型产品的创建,当有新的商品需求时,只需要增加工厂和商品的实现类即可
2.5.1 抽象工厂模式
三、目录结构
- 包:org.apache.ibatis.datasource包
- 主要分为pooled和unpooled和jndi三种类型的数据源
四、源码解析
4.1 pooled
- pooled类型是最常用的,也是使用了连接池的数据源,我们先看这个,我们先通过一个表格梳理几个关键类
类名 |
描述 |
功能 |
PooledDataSource |
使用连接池的数据源,且线程安全 |
提供连接的获取,归还,和连接池内部状态维护的功能 |
PooledConnection |
从PooledDataSource中获取的数据库连接对象(不是真正的连接,只是一个代理) |
提供数据库连接功能 |
PooledDataSourceFactory |
数据源工厂 |
根据配置生产数据源 |
PoolState |
用于描述PooledConnection连接对象状态的组件 |
通过两个list分别管理连接池中空闲连接和活跃连接,另外还包含很多状态计数变量 |
4.1.1 PooledDataSource
- PooledDataSource是具备连接池的数据源,是简单,同步且线程安全的数据库连接池,内部维护一个连接池避免频繁创建数据库连接,
- PooledDataSource的实现基于两个重要的类,一个是PooledConnection,它代表一个数据库连接,还有一个是PoolState,代表了一个连
接池的状态,内部封装了很多连接池的信息。
- PooledDataSource源码比较多,我们主要看数据库连接的获取,归还,和几个比较核心的方法
4.1.1.1 获取连接
- PooledDataSource#popConnection是获取连接的核心方法
@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 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;
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);
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 + 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;
}
4.1.1.2 归还连接
- PooledDataSource#pushConnection是归还连接的核心方法。
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn);
if (conn.isValid()) {
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++;
}
}
}
- 查看源码之后发现,其实源码不难,这两个方法都使用synchronized同步代码块来保证线程安全,另外还用到了线程之间的同步唤醒机制(wait和notifyAll)
4.1.1.3 获取数据源状态
- 数据源的状态实际上就是整个连接池的状态,实际上就是通过内部的PoolState对象来表示的,这里细节我们在PoolState部分再分析
private final PoolState state = new PoolState(this);
public PoolState getPoolState() {
return state;
}
4.1.1.4 pingConnection
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) {
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) {
}
result = false;
if (log.isDebugEnabled()) {
log.debug("Connection " + conn.getRealHashCode() + " is BAD: " + e.getMessage());
}
}
}
}
}
return result;
}
4.1.1.5 forceCloseAll
- forceCloseAll用于强制关闭所有的活跃队列连接和空闲队列连接
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) {
}
}
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) {
}
}
}
if (log.isDebugEnabled()) {
log.debug("PooledDataSource forcefully closed/removed all connections.");
}
}
4.1.2 PooledConnection
- PooledConnection具备连接池能力的连接对象,PooledDataSource里面获取和归还的连接就是PooledConnection对象。但是PooledConnection不是
真正的数据库连接对象,它封装了真正的连接对象Connection,采用代理模式拦截了Connection的方法,当客户端调用获取方法的时候,如果是close
方法,那么实际上只是在内部将连接回收,并不会销毁,如果是object的方法,那么就直接由内部的Connect对象调用,如果是其他的方法,那么就检
查内部的连接是否合法,再调用。因为使用了动态代理,因此PooledConnection实现了InvocationHandler接口
class PooledConnection implements InvocationHandler {
private final PooledDataSource dataSource;
private final Connection realConnection;
private final Connection proxyConnection;
private long checkoutTimestamp;
private long createdTimestamp;
private long lastUsedTimestamp;
private int connectionTypeCode;
private boolean valid;
}
4.1.2.1 invoke
- 动态代理模式中最重要的方法就是handler类的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())) {
checkConnection();
}
return method.invoke(realConnection, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
4.1.2.2 构造方法
public PooledConnection(Connection connection, PooledDataSource dataSource) {
this.hashCode = connection.hashCode();
this.realConnection = connection;
this.dataSource = dataSource;
this.createdTimestamp = System.currentTimeMillis();
this.lastUsedTimestamp = System.currentTimeMillis();
this.valid = true;
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this);
}
4.1.2.3 其他方法
- 其他大部分都是一些状态相关的方法,比如获取创建时间,获取最近一次的检查时间,判断状态是否合法,获取realConnection等
public boolean isValid() {
return valid && realConnection != null && dataSource.pingConnection(this);
}
4.1.3 PoolState
- PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别管理空闲状态的连接资源和活跃状态的连接资源,
另外还有相关的计数状态变量,在获取和回收连接的过程中都会更新这些计数状态
public class PoolState {
protected PooledDataSource dataSource;
protected final List<PooledConnection> idleConnections = new ArrayList<>();
protected final List<PooledConnection> activeConnections = new ArrayList<>();
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;
public PoolState(PooledDataSource dataSource) {
this.dataSource = dataSource;
}
public synchronized long getRequestCount() {
return requestCount;
}
}
4.1.4 PooledDataSourceFactory
- PooledDataSourceFactory是获取PooledDataSource的工厂,使用了工厂模式,而且看到PooledDataSourceFactory是继承了UnpooledDataSourceFactory,这里主要继承了
成员变量和设置属性,获取getDataSource的方法,因为这一套逻辑完全是复用的,具体可以参考后面的UnpooledDataSourceFactory。
- 关于这里为什么直接继承有几点疑问,UnpooledDataSourceFactory是用于创建UnpooledDataSource的,现在继承了他,在构造的时候传入了PooledDataSource,那么等下创建
出来的就是PooledDataSource,而数据库连接池的获取,归还和状态维护等是PooledDataSource维护的,PooledDataSource和UnpooledDataSource都是DataSource,因此这里使用
返回DataSource的方式,将2种DataSource的创建共用一套逻辑。
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
- 示意图(从下图可以看出,对于UnpooledDataSource和DataSource来说,对于DataSourceFctory来说逻辑是一样的,因此2中Factory共用了1套逻辑,只是构造方法传入的DataSource类型不同):
4.1.5 PooledDataSource的连接获取/归还流程图
4.1.5.1 获取流程图
4.1.5.2 归还流程图
4.2 unpooled
4.2.1 UnpooledDataSource
- UnpooledDataSource是不使用连接池的数据源,每次获取连接都是创建一个新的连接。
4.2.1.1 获取连接
- UnpooledDataSource#doGetConnection是获取连接的核心方法,因为不使用连接池,每次获取的都是一个新的连接,因此代码其实比较简单,没有太多需要维护的状态信息,
3个关键方法如下:
@Override
public Connection getConnection() 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 Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
4.2.1.2 归还连接
- UnpooledDataSource每次创建的都是一个新的连接,不提供连接的归还方法,相当于一次性使用,使用后就销毁了。
4.2.1.3 initializeDriver
- 我们这里看看前面获取连接的初始化驱动方法,和JDBC类似
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.newInstance();
DriverManager.registerDriver(new DriverProxy(driverInstance));
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
4.2.2 UnpooledDataSourceFactory
- UnpooledDataSourceFactory的这套逻辑和PooledDataSourceFactory是一致的,前面我们没有分析,在这里分析,它实现了DataSourceFactory接口,
代码还是比较清晰简单的
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
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();
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
this.dataSource = new UnpooledDataSource();
}
@Override
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);
}
}
@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;
}
}
4.3 JNDI
- JNDI数据源的实现是为了能在如EJB或tomcat应用服务器这类容器中使用,容器可以集中或在外部配置数据源,,因为使用不多,
代码也不复杂,只有一个实现了DataSourceFactory的JndiDataSourceFactory工厂,就不解析了,可以阅读参考文献[1];
五、小结
- 本文的主体是"数据源和工厂模式,代理模式",主要是解析了Mybatis中Pooled和Unpooled这两种主要的数据源类型,在创建数据源的时候,
使用了UnpooledDataSourceFactory和PooledDataSourceFactory分别去创建UnpooledDataSource和PooledDataSource,符合工厂方法模式。
- 在PooledDataSource中并没有直接使用数据库连接,而是使用PooledConnection这个包装了realConnection的加强版数据库连接,它加强的功能
我们在invoke方法中可以比较清楚的看到,主要是对回收做了一定的增强,会收拾放到连接池队列,并且会维护连接池的状态。
- 对于PooledDataSource,其最核心的职责是维护一个数据库连接池,内部通过2个队列(一个活跃队列,一个空闲队列)来维护的,在获取连接和回收
连接的时候,都和这2个队列息息相关,具体可以参照4.1.4中的流程图和代码解析
六、参考
- [1] MyBatis Tomcat JNDI原理及源码分析