• Mybatis数据源


    了解数据源

    使用JDBC时,必须使用`DriverManager.getConnection()`来创建连接,连接建立后,我们才可以进行数据库操作

    使用mybatis时,使用Mybatis为我们提供的`SqlSessionFactory`工具类来获取对应的`SqlSession`通过会话对象去操作数据库

    在通过`SqlSessionFactory`调用`openSession`方法之后,它调用了内部的一个私有的方法`openSessionFromDataSource`

    1. public SqlSession openSession(boolean autoCommit) {
    2. return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
    3. }

    打开openSessionFromDataSource

    1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    2. Transaction tx = null;
    3. DefaultSqlSession var8;
    4. try {
    5. //获取当前环境(由配置文件映射的对象实体)
    6. Environment environment = this.configuration.getEnvironment();
    7. //事务工厂(暂时不提,下一板块讲解)
    8. TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
    9. //配置文件中:
    10. //生成事务(根据我们的配置,会默认生成JdbcTransaction),这里是关键,我们看到这里用到了environment.getDataSource()方法
    11. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
    12. //执行器,包括全部的数据库操作方法定义,本质上是在使用执行器操作数据库,需要传入事务对象
    13. Executor executor = this.configuration.newExecutor(tx, execType);
    14. //封装为SqlSession对象
    15. var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
    16. } catch (Exception var12) {
    17. this.closeTransaction(tx);
    18. throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
    19. } finally {
    20. ErrorContext.instance().reset();
    21. }
    22. return var8;
    23. }

    我们的数据源配置信息,存放在了`Transaction`对象中,那么现在我们只需要知道执行器到底是如何执行SQL语句的,我们就知道到底如何创建`Connection`对象了,就需要获取数据库的链接信息了,打开datasource

    1. public interface DataSource extends CommonDataSource, Wrapper {
    2. Connection getConnection() throws SQLException;
    3. Connection getConnection(String username, String password)
    4. throws SQLException;
    5. }

    它是在`javax.sql`定义的一个接口,包括了两个方法,都是用于获取连接的。mybatis并不是通过之前`DriverManager`的方法去获取连接了,而是使用`DataSource`的实现类来获取的

     数据库链接的建立和关闭是极其耗费系统资源的操作,通过DriverManager获取的数据库连接,一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完后立即关闭连接,频繁的打开、关闭连接会持续消耗网络资源,造成整个系统性能的低下。

    因此,JDBC为我们定义了一个数据源的标准,也就是`DataSource`接口,告诉数据源数据库的连接信息,并将所有的连接全部交给数据源进行集中管理,当需要一个`Connection`对象时,可以向数据源申请,数据源会根据内部机制,合理地分配连接对象给我们。

    一般比较常用的`DataSource`实现,都是采用池化技术,就是在一开始就创建好N个连接,这样之后使用就无需再次进行连接,而是直接使用现成的`Connection`对象进行数据库操作

    当然,也可以使用传统的即用即连的方式获取`Connection`对象,Mybatis为我们提供了几个默认的数据源实现

    1. <dataSource type="POOLED">
    2. <property name="driver" value="${driver}"/>
    3. <property name="url" value="${url}"/>
    4. <property name="username" value="${username}"/>
    5. <property name="password" value="${password}"/>
    6. dataSource>

    * UNPOOLED    不使用连接池的数据源

    * POOLED      使用连接池的数据源

    * JNDI        使用JNDI实现的数据源

    Mybatis数据源实现

    `UnpooledDataSource`的实现

    这个类中定义了很多的成员,包括数据库的连接信息、数据库驱动信息、事务相关信息等

    实现`DataSource`中提供的接口的

    1. public Connection getConnection() throws SQLException {
    2. return this.doGetConnection(this.username, this.password);
    3. }
    4. public Connection getConnection(String username, String password) throws SQLException {
    5. return this.doGetConnection(username, password);
    6. }

    这两个方法都指向了内部的一个`doGetConnection`方法

    1. private Connection doGetConnection(String username, String password) throws SQLException {
    2. Properties props = new Properties();
    3. if (this.driverProperties != null) {
    4. props.putAll(this.driverProperties);
    5. }
    6. if (username != null) {
    7. props.setProperty("user", username);
    8. }
    9. if (password != null) {
    10. props.setProperty("password", password);
    11. }
    12. return this.doGetConnection(props);
    13. }

    它将数据库的连接信息也给添加到`Properties`对象中进行存放,并交给下一个`doGetConnection`来处理

    1. private Connection doGetConnection(Properties properties) throws SQLException {
    2. //若未初始化驱动,需要先初始化,内部维护了一个Map来记录初始化信息,这里不多介绍了
    3. this.initializeDriver();
    4. //传统的获取连接的方式
    5. Connection connection = DriverManager.getConnection(this.url, properties);
    6. //对连接进行额外的一些配置
    7. this.configureConnection(connection);
    8. return connection;
    9. }

    返回`Connection`对象了,而此对象正是通过`DriverManager`来创建的,因此,非池化的数据源实现依然使用的是传统的连接创建方式

    PooledDataSource`类的实现

    这里的定义就比非池化的实现复杂得多了,因为它还要考虑并发的问题,并且还要考虑如何合理地存放大量的链接对象,该如何进行合理分配

    它存放了一个UnpooledDataSource,此对象是在构造时就被创建,其实创建Connection还是依靠数据库驱动创建

    接口的实现

    1. public Connection getConnection() throws SQLException {
    2. return this.popConnection(this.dataSource.getUsername(), this.dataSource.getPassword()).getProxyConnection();
    3. }
    4. public Connection getConnection(String username, String password) throws SQLException {
    5. return this.popConnection(username, password).getProxyConnection();
    6. }

    调用了`popConnection()`方法来获取连接对象,然后进行了一个代理

    1. private PooledConnection popConnection(String username, String password) throws SQLException {
    2. boolean countedWait = false;
    3. //返回的是PooledConnection对象,
    4. PooledConnection conn = null;
    5. long t = System.currentTimeMillis();
    6. int localBadConnectionCount = 0;
    7. while(conn == null) {
    8. synchronized(this.state) { //加锁,因为有可能很多个线程都需要获取连接对象
    9. PoolState var10000;
    10. //PoolState存了两个List,一个是空闲列表,一个是活跃列表
    11. if (!this.state.idleConnections.isEmpty()) { //有空闲连接时,可以直接分配Connection
    12. conn = (PooledConnection)this.state.idleConnections.remove(0); //ArrayList中取第一个元素
    13. if (log.isDebugEnabled()) {
    14. log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
    15. }
    16. //如果已经没有多余的连接可以分配,那么就检查一下活跃连接数是否达到最大的分配上限,如果没有,就new一个
    17. } else if (this.state.activeConnections.size() < this.poolMaximumActiveConnections) {
    18. //注意new了之后并没有立即往List里面塞,只是存了一些基本信息
    19. //我们发现,这里依靠UnpooledDataSource创建了一个Connection对象,并将其封装到PooledConnection中
    20. conn = new PooledConnection(this.dataSource.getConnection(), this);
    21. if (log.isDebugEnabled()) {
    22. log.debug("Created connection " + conn.getRealHashCode() + ".");
    23. }
    24. //以上条件都不满足,那么只能从之前的连接中寻找了,看看有没有那种卡住的链接(由于网络问题有可能之前的连接一直被卡住,然而正常情况下早就结束并且可以使用了,所以这里相当于是优化也算是一种捡漏的方式)
    25. } else {
    26. //获取最早创建的连接
    27. PooledConnection oldestActiveConnection = (PooledConnection)this.state.activeConnections.get(0);
    28. long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
    29. //判断是否超过最大的使用时间
    30. if (longestCheckoutTime > (long)this.poolMaximumCheckoutTime) {
    31. //超时统计信息(不重要)
    32. ++this.state.claimedOverdueConnectionCount;
    33. var10000 = this.state;
    34. var10000.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
    35. var10000 = this.state;
    36. var10000.accumulatedCheckoutTime += longestCheckoutTime;
    37. //从活跃列表中移除此链接信息
    38. this.state.activeConnections.remove(oldestActiveConnection);
    39. //如果开启事务,还需要回滚一下
    40. if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
    41. try {
    42. oldestActiveConnection.getRealConnection().rollback();
    43. } catch (SQLException var15) {
    44. log.debug("Bad connection. Could not roll back");
    45. }
    46. }
    47. //这里就根据之前的连接对象直接new一个新的连接(注意使用的还是之前的Connection对象,只是被重新封装了)
    48. conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
    49. conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
    50. conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
    51. //过期
    52. oldestActiveConnection.invalidate();
    53. if (log.isDebugEnabled()) {
    54. log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
    55. }
    56. } else {
    57. //确实是没得用了,只能卡住了(阻塞)
    58. //然后记录一下有几个线程在等待当前的任务搞完
    59. try {
    60. if (!countedWait) {
    61. ++this.state.hadToWaitCount;
    62. countedWait = true;
    63. }
    64. if (log.isDebugEnabled()) {
    65. log.debug("Waiting as long as " + this.poolTimeToWait + " milliseconds for connection.");
    66. }
    67. long wt = System.currentTimeMillis();
    68. this.state.wait((long)this.poolTimeToWait); //要是超过等待时间还是没等到,只能放弃
    69. //注意这样的话con就为null了
    70. var10000 = this.state;
    71. var10000.accumulatedWaitTime += System.currentTimeMillis() - wt;
    72. } catch (InterruptedException var16) {
    73. break;
    74. }
    75. }
    76. }
    77. //经过之前的操作,已经成功分配到连接对象的情况下
    78. if (conn != null) {
    79. if (conn.isValid()) { //是否有效
    80. if (!conn.getRealConnection().getAutoCommit()) { //清理之前遗留的事务操作
    81. conn.getRealConnection().rollback();
    82. }
    83. conn.setConnectionTypeCode(this.assembleConnectionTypeCode(this.dataSource.getUrl(), username, password));
    84. conn.setCheckoutTimestamp(System.currentTimeMillis());
    85. conn.setLastUsedTimestamp(System.currentTimeMillis());
    86. //添加到活跃表中
    87. this.state.activeConnections.add(conn);
    88. //统计信息(不重要)
    89. ++this.state.requestCount;
    90. var10000 = this.state;
    91. var10000.accumulatedRequestTime += System.currentTimeMillis() - t;
    92. } else {
    93. //无效的连接,直接抛异常
    94. if (log.isDebugEnabled()) {
    95. log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
    96. }
    97. ++this.state.badConnectionCount;
    98. ++localBadConnectionCount;
    99. conn = null;
    100. if (localBadConnectionCount > this.poolMaximumIdleConnections + this.poolMaximumLocalBadConnectionTolerance) {
    101. if (log.isDebugEnabled()) {
    102. log.debug("PooledDataSource: Could not get a good connection to the database.");
    103. }
    104. throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
    105. }
    106. }
    107. }
    108. }
    109. }
    110. //最后该干嘛干嘛,拿不到连接直接抛异常
    111. if (conn == null) {
    112. if (log.isDebugEnabled()) {
    113. log.debug("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
    114. }
    115. throw new SQLException("PooledDataSource: Unknown severe error condition. The connection pool returned a null connection.");
    116. } else {
    117. return conn;
    118. }
    119. }

    如果最后得到了连接对象(有可能是从空闲列表中得到,有可能是直接创建的新的,还有可能是经过回收策略回收得到的),那么连接(Connection)对象一定会被放在活跃列表中(state.activeConnections)

    此方法返回之后会调用`getProxyConnection`来获取一个代理对象,实际上就是`PooledConnection`类

    1. class PooledConnection implements InvocationHandler {
    2. private static final String CLOSE = "close";
    3. private static final Class[] IFACES = new Class[]{Connection.class};
    4. private final int hashCode;
    5. //会记录是来自哪一个数据源创建的的
    6. private final PooledDataSource dataSource;
    7. //连接对象本体
    8. private final Connection realConnection;
    9. //代理的链接对象
    10. private final Connection proxyConnection;

    直接代理了构造方法中传入的Connection对象,也是使用JDK的动态代理实现的

    1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    2. String methodName = method.getName();
    3. //如果调用的是Connection对象的close方法,
    4. if ("close".equals(methodName)) {
    5. //这里并不会真的关闭连接(这也是为什么用代理),而是调用之前数据源的pushConnection方法,将此连接改为为空闲状态
    6. this.dataSource.pushConnection(this);
    7. return null;
    8. } else {
    9. try {
    10. if (!Object.class.equals(method.getDeclaringClass())) {
    11. this.checkConnection();
    12. //任何操作执行之前都会检查连接是否可用
    13. }
    14. //该干嘛干嘛
    15. return method.invoke(this.realConnection, args);
    16. } catch (Throwable var6) {
    17. throw ExceptionUtil.unwrapThrowable(var6);
    18. }
    19. }
    20. }

    `pushConnection`方法

    1. protected void pushConnection(PooledConnection conn) throws SQLException {
    2. synchronized(this.state) { //老规矩,先来把锁
    3. //先从活跃列表移除此连接
    4. this.state.activeConnections.remove(conn);
    5. //判断此链接是否可用
    6. if (conn.isValid()) {
    7. PoolState var10000;
    8. //看看闲置列表容量是否已满(容量满了就回不去了)
    9. if (this.state.idleConnections.size() < this.poolMaximumIdleConnections && conn.getConnectionTypeCode() == this.expectedConnectionTypeCode) {
    10. var10000 = this.state;
    11. var10000.accumulatedCheckoutTime += conn.getCheckoutTime();
    12. if (!conn.getRealConnection().getAutoCommit()) {
    13. conn.getRealConnection().rollback();
    14. }
    15. //把唯一有用的Connection对象拿出来,然后重新创建一个PooledConnection
    16. PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
    17. //放入闲置列表,成功回收
    18. this.state.idleConnections.add(newConn);
    19. newConn.setCreatedTimestamp(conn.getCreatedTimestamp());
    20. newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp());
    21. conn.invalidate();
    22. if (log.isDebugEnabled()) {
    23. log.debug("Returned connection " + newConn.getRealHashCode() + " to pool.");
    24. }
    25. this.state.notifyAll();
    26. } else {
    27. var10000 = this.state;
    28. var10000.accumulatedCheckoutTime += conn.getCheckoutTime();
    29. if (!conn.getRealConnection().getAutoCommit()) {
    30. conn.getRealConnection().rollback();
    31. }
    32. conn.getRealConnection().close();
    33. if (log.isDebugEnabled()) {
    34. log.debug("Closed connection " + conn.getRealHashCode() + ".");
    35. }
    36. conn.invalidate();
    37. }
    38. } else {
    39. if (log.isDebugEnabled()) {
    40. log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection.");
    41. }
    42. ++this.state.badConnectionCount;
    43. }
    44. }
    45. }

    因此,无论Connection管理方式如何变换,无论数据源再高级,它都最终都会使`DriverManager`来创建连接对象,而最终使用的也是`DriverManager`提供的`Connection`对象。

  • 相关阅读:
    EasyExcel导出转换@ExcelProperty注解中converter不生效,以及EasyExcel导入日期转换失败问题
    汇编语言程序设计·RB(AT&T汇编)_笔记_第12章:使用Linux系统调用
    安全狗受邀出席CIS 2022网络安全创新大会
    神经网络如何避免过拟合,神经网络过度拟合
    uniapp 滑动页面至某个元素或顶部
    Web前端:具有正确技能的Angular开发人员可以帮助你的业务!
    FlinkSQL自定义UDTF使用的四种方式
    009-BSP学习笔记-在开发板上移植UBUNTU
    Java单链表
    11、C++进阶编程STL
  • 原文地址:https://blog.csdn.net/weixin_51992178/article/details/126921549