Mybatis数据源模块
Mybatis- 数据源模块
本篇学习Mybatis的数据源模块
org.apache.ibatis.datasource 数据源模块要做什么
数据源模块要实现的功能: 常见的数据源组件都实现了javax.sql.DataSource接口; MyBatis不但要能集成第三方的数据源组件,自身也提供了数据源的实现; 一般情况下,数据源的初始化过程参数较多,比较复杂
首先我们创建 dataSource的过程是非常复杂的。而且我们有需要方便扩展.因此我们可以使用工厂模式(创建型的模式)
工厂模式可以查看我之前写的一篇 https://mp.toutiao.com/profile_v4/graphic/preview?pgc_id=7008076815566717447 整个包结构
从这个包结构也可以看出来 数据源模块整体的设计是一个 工厂模式 . unpooled: 非连接池 pooled: 连接处 jndi: 容器连接 如 tomcat等.(这个我们本次不进行解析) 源码解析
先来看下抽象工厂类: public interface DataSourceFactory { // 设置dataSource属性 void setProperties(Properties props); // 获取dataSource DataSource getDataSource(); }非连接池包
非连接池包里面一共就2个类 UnpooledDataSourceFactory UnpooledDataSource
UnpooledDataSourceFactory 是实现了 抽象工厂DataSourceFactory的类.
UnpooledDataSource则是上面工厂类实际的调用的类
我们先看下UnpooledDataSource public class UnpooledDataSource implements DataSource { // 数据库驱动 private ClassLoader driverClassLoader; // 数据库连接相关配置信息 private Properties driverProperties; // 驱动的注册中心 private static Map registeredDrivers = new ConcurrentHashMap<>(); // 数据库连接的一些属性 private String driver; private String url; private String username; private String password; // 是否自动提交事务 private Boolean autoCommit; // 默认事务级别 private Integer defaultTransactionIsolationLevel; // 默认超时时间 private Integer defaultNetworkTimeout; // 使用静态代码快将驱动注册到DriverManage // 这个地方和Mysql Driver里面的原理大致一致 static { Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); registeredDrivers.put(driver.getClass().getName(), driver); } } // 一大波构造函数和setter ... // 获取conn通过用户名和密码 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); } // 获取conn通过配置文件 private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); 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); } // DriverManager requires the driver to be loaded via the system ClassLoader. // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance(); DriverManager.registerDriver(new DriverProxy(driverInstance)); 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); } } ... }
我们在继续看下工厂类UnpooledDataSourceFactory /** * @author Clinton Begin * 子类工厂对应的产品为 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(); 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,将属性设置到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); } } // 设置DataSource.driverProperties属性 if (driverProperties.size() > 0) { metaDataSource.setValue("driverProperties", driverProperties); } } @Override public DataSource getDataSource() { return dataSource; } ... } 连接池包
连接池包中类多了几个 PooledDataSourceFactory : 工厂类 PoolState : 用于管理PooledConnection对象状态的组件 PooledConnection: 数据库连接对象 PooledDataSource : 数据库连接池
直接贴源码分析:
PooledDataSourceFactory // 继承了 UnpooledDataSourceFactory UnpooledDataSourceFactory实现了最基本的抽象工厂接口 public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } }
PoolState /** * @author Clinton Begin * PoolState:用于管理PooledConnection对象状态的组件, * 通过两个list分别,管理空闲状态的连接资源和活跃状态的连接资源 * 并且提供了一些属性来记录时间次数等 */ public class PoolState { protected PooledDataSource dataSource; // 空闲的连接池资源集合 protected final List idleConnections = new ArrayList<>(); // 活跃的连接池资源集合 protected final List 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; ... }
PooledConnection /** * @author Clinton Begin * 数据库连接对象 - 采用了动态代理的方式 */ class PooledConnection implements InvocationHandler { private static final String CLOSE = "close"; private static final Class<?>[] IFACES = new Class<?>[] { Connection.class }; private final int hashCode; /* 记录当前连接所在的数据源对象, 本次连接是由这个数据源创建的,关闭后也是回到这个数据源; 可以理解为这个对象是提供给客户端使用的 */ 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; // 连接是否还有效 private boolean valid; /** * Constructor for SimplePooledConnection that uses the Connection and PooledDataSource passed in. * * @param connection * - the connection that is to be presented as a pooled connection * @param dataSource * - the dataSource that the connection is from */ 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; // 这里使用了动态代理来设置代理对象,这个代理对象最后在获取连接的时候会返回给调用Mybatis的一方 this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); } ... }
PooledDataSource 这个类是核心类了,获取和归还连接都是由这个类来完成的,我这边也仅对几个核心的方法进行分析 public class PooledDataSource implements DataSource { private static final Log log = LogFactory.getLog(PooledDataSource.class); 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 int poolMaximumLocalBadConnectionTolerance = 3; // 测试连接是否有效的sql语句 protected String poolPingQuery = "NO PING QUERY SET"; // 是否允许测试连接 protected boolean poolPingEnabled; // 连接在这个配置时间内没有被使用,才允许测试连接是否有效 protected int poolPingConnectionsNotUsedFor; // 连接池的唯一标志: 根据数据库url、用户名、密码生成一个hash值 private int expectedConnectionTypeCode; /** * 归还连接 * @param conn * @throws SQLException */ 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++; } } } /** * 获取数据库连接 * @param username * @param password * @return * @throws SQLException * 整个流程分为两部分 * 1. 获取连接 * 2. 对获取的连接做池化数据的信息处理 * * 第一部分: 循环判断获取连接是否为空 * 优先从空闲连接中获取连接 * 有:空闲连接就进入第二部分 * 没有: 判断是否可以新增连接(活跃连接数是否已达到最大连接数) * 若没到最大线程数则直接创建俩姐进入第二部分 * 若无法新增连接,则检查是否存在超时的连接,如果有就使用这个连接,没有则进入阻塞状态 * 第二部分: * 修改线程的活跃线程数,请求次数等. (PoolState) */ private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis(); int localBadConnectionCount = 0; // 这里是一个循环,直到拿到 conn,不然就会一致循环下去 // 循环体中有超时和超次机制 while (conn == null) { // 使用 synchronized 来处理多线程间的同步 synchronized (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) { /* Just log a message for debug and continue to execute the following statement like nothing happened. Wrap the bad connection with a new PooledConnection, this will help to not interrupt current executing thread and give current thread a chance to join the next competition for another valid/good database connection. At the end of this loop, bad {@link @conn} will be set as null. */ 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; } } } } // 上面的循环已经拿到了 conn 这里做一个二次校验 if (conn != null) { // ping to server and check the connection is valid or not // 检测连接是否有效 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."); } // 累计的获取无效连接次数+1 state.badConnectionCount++; // 当前获取无效连接次数+1 localBadConnectionCount++; conn = null; // 拿到无效连接,但如果没有超过重试的次数,则重新走上面的 while 循环 // 允许再次尝试获取连接,否则抛出异常 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; } }
整体流程图:
壁纸图,侵权删
裁员潮半年后,我看清了真实的租房市场近半年,各大互联网公司展开了击鼓传花式裁员。小助手从朋友那里了解到,包括大家熟知的淘宝钉钉达摩院等在内的各大业务线,都在毕业和向社会输送人才。人员变动较大的,还有饿了么和口碑等在内
电动汽车一定是汽车行业的未来吗?你还要被骗多久?我经过近十年的研究发明了重力新能源电动汽车,这种车它更安全,永不爆胎,零排放,零耗能,无限续航。发明国家已授权专利,也已申报世界发明PCT,这种汽车的发明将彻底终结现有燃油汽车,和
海南两所公立医院推动区块链卫生健康规模化场景应用近日,海南两所大型公立医院海南省人民医院海南医学院第一附属医院先后与云海链控股在海口举行数字化战略全面合作签约仪式,共建基于区块链的区域医疗健康数字化协同共享平台国家创新试点。签约
焦点分析为了你的iPhone能磁吸充电,苹果又花了5亿买材料作者袁斯来编辑苏建勋iPhone12刚发布时,回归的MagSafe吸引了用户不少兴趣。苹果第一次将磁吸式充电用到手机上,看上去新鲜又有未来感。这项技术中,关键零件是隐藏在iPhon
中国有锂新能源不会重蹈铁矿石覆辙随着新能源电动汽车智能手机便携式电动设备的普及,现在确实是有锂走遍天下。锂这种最轻的金属,作为锂电池的主要材料正在支撑起世界经济的发展,也深刻地影响着人们的生活。只要打开你的新能源
如何给手机里的照片加文字?怎样能让手机在拍摄时,照片就自带文字说明(时间地址拍摄数据自动显示一次成型)?如何能让手机在拍摄后照片就自带时间,地址等文字说明,这个操作是靠手机相机里的水印实现的。说下具体的设置
程序开发语言优势ESL综合需要更明确的定义Synplicity营销高级副总裁AndrewHaines过去几年来,对于电子系统层级(ESL)综合技术既有正面报道,也有负面报道。近期,一篇题为ESL的
Rust编程语言实现数据管理如果要实现一个数据管理的功能该如何是好呢假如可以用vector当做一个池来存储数据池里每一个位置都存储一个数字用来表示代数表示这个位置的数据是否更改代数也是更改次数当一个位置重用了
rust语言基础学习rust中的slice类型今天来学习Rust中的slice类型。为什么需要slice类型为什么Rust会提供slice类型呢?Rust中的借用(Borrow语义)可以将一个值在其所有权不发生转移的情况下,借
莫开伟数字人民币将在五方面发挥重要作用中新经纬1月13日电题数字人民币将在五方面发挥重要作用作者莫开伟中国地方金融研究院研究员数字人民币(试点版)APP自1月4日上架后,掀起一波体验热潮。同时,作为12个试点城市之一的
36氪首发瑞捷生物完成1亿元融资,微流控系统落地超250家医院36氪获悉,近日鸿瑞泰捷生物获得数千万A轮融资(以下简称瑞捷生物),由楹联健康基金领投,动平衡资本老股东阳光融汇跟投。连同2021年4月的PreA轮融资,公司在过一年间完成2轮共1
百万召回一年白干,上汽通用五菱的口碑危机?一则召回信息,将聚光灯照在了国民神车上。7月16日,上汽通用五菱汽车股份有限公司根据缺陷汽车产品召回管理条例缺陷汽车产品召回管理条例实施办法和机动车排放召回管理规定的要求,向国家市
游戏性能全面觉醒,iQOONeo3助你轻松上分上星如今,智能手机成为人们生活中不可缺少的需求。对于年轻人而言,手机游戏也成为了他们闲暇之余的娱乐项目,常常一起开黑带妹。王者荣耀和平精英崩坏3等等大型手游,对手机的硬件配置也有一定要
2020年中国移动互联网内容生态洞察报告核心摘要随着智能终端的发展和人们精神消费品质的不断升级,用户需求驱动着内容消费形式的不断革新,深刻地改变着移动互联网下的内容生产分发传播消费全过程,不断拓宽原有应用场景和边界,助力
SVC综艺市场洞察创造营2020内容共建IP矩阵核心摘要腾讯视频S级女团成长综艺创造营2020于今年5月重磅上线,收视口碑赢得双丰收,赛制升级和导师阵容为节目一度带来超过行业平均水平14倍的讨论热度。另一方面,节目与品牌携手通过
2020年中国视频会议行业研究报告核心摘要概念界定视频会议强调不同地点的人或群体,通过网络与多媒体设备,将声音影像与文件等资料经过编码分发解码等流程实现实时互传。不同于直播与录播的单向互动,视频会议更加注重多方音视
重磅!20192020年中国产业互联网发展指数报告核心摘要2019年以来,中国GDP增长的科技进步贡献率达到58。5,与发达国家相比仍有近20个百分点的增长空间。与此同时,经历二十余年的发展中国已成长为互联网超级应用大国,围绕消费
2020年中国公共充电桩行业研究报告核心摘要充电桩市场2020年充电桩纳入新基建,窗口期缩短,充电桩行业有望迎来关键发展阶段。驱动因素充电桩政策及补贴逐渐向运营端转移运营及盈利能力的提高将带动企业获得新一轮融资而充电
2020年中国AI零售行业发展研究报告核心摘要概念界定通过人工智能技术作为主要驱动力,为零售行业各参与主体各业务环节赋能,突出AI技术对零售业的整体升级改造。发展特点AI零售技术服务可帮助零售企业及品牌商促进降本增效提
2020年中国政务云行业研究报告核心摘要驱动因素政府管理理念的转变驱动政务信息化向政务云演进的内在动因。以云计算产业的发展进步为基石,各项国家政策对政务云起到直接推动作用。基于数字中国建设的大背景,转变职能中心的
2020年中国AI医疗行业研究报告核心摘要前言本次AI医疗研究范畴仅限于围绕临床诊疗开展的核心医疗活动,包括CDSS智慧病案AI检查AI新药研发及手术机器人。目前中国对AI医疗的需求逐渐扩大,而供给尚显不足,整体供
2021年中国电竞行业研究报告核心摘要市场2020年电竞整体市场规模超过1450亿元,增长主要来自于移动电竞游戏市场和电竞生态市场的快速发展。预计在2021年电竞市场将突破1800亿元。驱动电竞成为杭州亚运会正