Mybatis内置数据源
今天的文章稍微没有那么肝,但是还是拖到了晚上!!哈哈哈,其实昨天已经写好了,只不过一直想不到个人唠叨应该写什么,所以就拖了一下。
内置数据源初始化过程
在详细分析 UnpooledDataSource 和 PooledDataSource 两种数据源实现之前,我们先来了解一下数据源的配置与初始化过程。现在看数据源是如何配置的,如下:
<!--使用连接池-->
<dataSource type="POOLED">
<!--这里会替换为local-mysql.properties中的对应字段的值-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
以UnpooledDataSource为例。UnpooledDataSource,从名称上即可知道,该种数据源不具有池化特性。该种数据源每次会返回一个新的数据库连接,而非复用旧的连接。由于 UnpooledDataSource 无需提供连接池功能,因此它的实现非常简单。核心的方法有三个,分别如下:
- initializeDriver - 初始化数据库驱动
- doGetConnection - 获取数据连接
- configureConnection - 配置数据库连接
初始化数据库驱动
回顾我们一开始学习使用 JDBC 访问数据库时的情景,在执行 SQL 之前,通常都是先获取数据库连接。一般步骤都是加载数据库驱动,然后通过 DriverManager 获取数据库连接。UnpooledDataSource 也是使用 JDBC 访问数据库的,因此它获取数据库连接的过程也大致如此,只不过会稍有不同。先来看看这个调用栈吧,
// -☆- UnpooledDataSource
private synchronized void initializeDriver() throws SQLException {
// 检测缓存中是否包含了与 driver 对应的驱动实例
if (!registeredDrivers.containsKey(driver)) {
Class<?> driverType;
try {
// 加载驱动类型
if (driverClassLoader != null) {
// 使用 driverClassLoader 加载驱动
driverType = Class.forName(driver, true, driverClassLoader);
} else {
// 通过其他 ClassLoader 加载驱动
driverType = Resources.classForName(driver);
}
// 通过反射创建驱动实例
Driver driverInstance = (Driver) driverType.newInstance();
/*
* 注册驱动,注意这里是将 Driver 代理类 DriverProxy 对象注册到 DriverManager 中的,
* 而非 Driver 对象本身。DriverProxy 中并没什么特别的逻辑,就不分析。
*/
DriverManager.registerDriver(new DriverProxy(driverInstance));
// 缓存驱动类名和实例
registeredDrivers.put(driver, driverInstance);
} catch (Exception e) {
throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
}
}
}
// 之所以上面有缓存相关的逻辑,是用于便面重复注册驱动
获取数据库连接
这跟JDBC有点类似
// -☆- UnpooledDataSource
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) {
// 存储 user 配置
props.setProperty("user", username);
}
if (password != null) {
// 存储 password 配置
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;
}
private void configureConnection(Connection conn) throws SQLException {
if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
// 设置自动提交
conn.setAutoCommit(autoCommit);
}
if (defaultTransactionIsolationLevel != null) {
// 设置事务隔离级别
conn.setTransactionIsolation(defaultTransactionIsolationLevel);
}
}
上面方法将一些配置信息放入到 Properties 对象中,然后将数据库连接和 Properties 对象传给 DriverManager 的 getConnection 方法即可获取到数据库连接
PooledDataSource
内部实现了连接池技术
获取连接
先看看调用栈,入口方法和之前的UnPooled一样
public Connection getConnection() throws SQLException {
// 返回 Connection 的代理对象
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) {
// 检测空闲连接集合(idleConnections)是否为空
if (!state.idleConnections.isEmpty()) {
// idleConnections 不为空,表示有空闲连接可以使用
conn = state.idleConnections.remove(0);
} else {
/*
* 暂无空闲连接可用,但如果活跃连接数还未超出限制
*(poolMaximumActiveConnections),则可创建新的连接
*/
if (state.activeConnections.size() < poolMaximumActiveConnections) {
// 创建新连接
conn = new PooledConnection(dataSource.getConnection(), this);
} 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) {...}
}
/*
* 创建一个新的 PooledConnection,注意,
* 此处复用 oldestActiveConnection 的 realConnection 变量
*/
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
/*
* 复用 oldestActiveConnection 的一些信息,注意 PooledConnection 中的
* createdTimestamp 用于记录 Connection 的创建时间,而非 PooledConnection
* 的创建时间。所以这里要复用原连接的时间信息。
*/
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// 设置连接为无效状态
oldestActiveConnection.invalidate();
} else { // 运行时间最长的连接并未超时
try {
if (!countedWait) {
state.hadToWaitCount++;
countedWait = true;
}
long wt = System.currentTimeMillis();
// 当前线程进入等待状态
state.wait(poolTimeToWait);
state.accumulatedWaitTime += System.currentTimeMillis() - wt;
} catch (InterruptedException e) {
break;
}
}
}
}
if (conn != null) {
/*
* 检测连接是否有效,isValid 方法除了会检测 valid 是否为 true,
* 还会通过 PooledConnection 的 pingConnection 方法执行 SQL 语句,
* 检测连接是否可用。pingConnection 方法的逻辑不复杂,大家可以自行分析。
* 另外,官方文档在介绍 POOLED 类型数据源时,也介绍了连接有效性检测方面的
* 属性,有三个:poolPingQuery,poolPingEnabled 和
* poolPingConnectionsNotUsedFor。关于这三个属性,大家可以查阅官方文档
*/
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 {
// 连接无效,此时累加无效连接相关的统计字段
state.badConnectionCount++;
localBadConnectionCount++;
conn = null;
if (localBadConnectionCount > (poolMaximumIdleConnections
+ poolMaximumLocalBadConnectionTolerance)) {
throw new SQLException(...);
}
}
}
}
}
if (conn == null) {
throw new SQLException(...);
}
return conn;
}
这里的代码有点复杂
稍微总结了一下:
回收连接
相比于获取连接,回收连接的逻辑要简单的多。回收连接成功与否只取决于空闲连接集合的状态,所需处理情况很少,因此比较简单。
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
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
// 复用时间信息
newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
// 将原连接置为无效状态
conn.invalidate();
// 通知等待的线程
state.notifyAll();
} else { // 空闲连接集合已满
state.accumulatedCheckoutTime += conn.getCheckoutTime();
// 回滚未提交的事务
if (!conn.getRealConnection().getAutoCommit()) {
conn.getRealConnection().rollback();
}
// 关闭数据库连接
conn.getRealConnection().close();
conn.invalidate();
}
} else {
state.badConnectionCount++;
}
}
}
回收连接的入口:
// -☆- PooledConnection
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
// 检测 close 方法是否被调用,若被调用则拦截之
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);
}
}
}
本篇文章大概就讲这么多,重点还是在对Pool和UnPool的理解,因为际场景下,我们基本不用 MyBatis 自带的数据库连接池的实现。所以,上面的流程了解即可!!!
个人唠叨
日常总结
今天下午已经把Mybatis系列全部写完!!!哈哈哈,后面会陆续发的。感觉现在的自己阅读源码越来越牛逼了(日常吹牛逼),本来计划是再阅读一遍Spring源码的,因为其实每一次阅读源码,你都会有不同的收获!!但是基于计划的原因,如果再阅读一遍的话,恐怕又要花一周的时间了。所以,今晚就特意提前进入算法的刷题了,不为啥,因为之前一直害怕算法,所以刷了一次之后,就停了一段时间了,然后又忘了,然后6月份又刷了一下链表,因为项目和期末考的原因就先放下了,现在又把算法捡起来了,这次是抱着刷刷刷的决心来的,这次一定要坚持刷,一定要坚持,坚持,坚持!!!
说说今晚刷算法的感受
今晚应该不算刷吧,算复习一下6月份刷的链表题目,8点开始复习,重新刷了一遍,才刷了8道题了,用了两个小时,而且都是在6月份做过的,仅仅用了一个月,就把做过的题目忘光光了,那平时的那些知识点更不用说了唉!所以,今晚是一个好的开始,之后一直到找到实习offer之前都要一直刷下去,先计划一下这个暑假刷算法的时间吧!每天晚上8点开始(最迟)开始刷,刷到10点左右吧,每天差不多这个时间段,题量具体而定,然后每周划出周日的晚上不刷新题目,只做复习,如果当天晚上复习不完,就留给明天晚上!!!在此立下Flag,之后的个人唠叨中,我都会稍微提一下这个Flag的情况,哈哈哈,希望大家和我一样能尽早克服对算法的恐惧!!(大佬勿理)
谈谈我大学遇到的那个她(这里先说结论,我们最后是没有在一起的)上篇(哈哈哈,留个悬念下篇再继续)
哈哈哈哈,怎么突然今晚会说这个呢?嗯,就是爸妈今晚吃饭的时候,我在跟我爸妈提到了,我的同学都准备要结婚了,哈哈哈哈哈,才20岁耶,就结婚了,卧槽,我。。。(语塞)!!我其实也一点都不羡慕啦,我跟我爸妈说,我喜欢在这段时间好好奋斗,因为这是我精力最旺盛的时候啊,真的不能留下遗憾哎,拍拖会不会有点浪费时间呢?这时候,我爸妈就说,你大学能找到合适的,当然最好,他怕我出来工作之后找不到女朋友,好像我三叔一样,虽然工资很高,也挺有钱的,但是现在30多岁还没找到老婆,所以我爸妈一直催我,在大学就赶紧找个女朋友啦!!我:我都想找啊,但我无人问津,失礼啊!!哈哈哈哈,开玩笑的
接下来就进入主题了,大学的上半阶段结束了,我在大二上确实遇到过这么一个女孩,她超可爱的,她是我部门的一个师姐!那我是什么时候开始喜欢她的呢?是在三下乡的时候,短短8天时间,回忆真的好多,先挑几张经典的图出来压压惊
哈哈哈 这个是跟大头的工作照,跟着大头哥学到了很多很多,虽然现在也不怎么拿起相机继续拍照了,但是偶尔也会拿起手机拍几张,然后修一下,留在相册里面吃灰
还有上面那位小姐姐,看看时间,哈哈哈哈,凌晨一点多的我还在工作,那时的我是兼任排版+拍照+剪视频,卧槽,现在想想都恐怖,天天排版排到凌晨2点多3点,然后早上7点多又起床了,真的太恐怖了,现在都没试过这种工作模式了!
正经合照就不发了,因为好像找不到,就随便找了一张那时候去吃东西拍的合照,嗯,本文的主角就是我(戴眼镜,男,在右边)前面那位带着白色帽子的女生!
今天就先说到这,哈哈哈,因为也快11点了,有点困了,要早点休息了!!!
欲知详情,请继续关注下篇文章(狗头。。)
晚安!!!