当前位置:   article > 正文

解析Mybatis之Sqlsession、Connection和Transaction原理与三者间的关系_sqlsession和事务的关系

sqlsession和事务的关系

Mybatis之Sqlsession、Connection和Transaction解析关系与原理

对于我们开发来讲,不管跟任何关系型数据库打交道都无法规避这三巨头,数据库的会话-Sqlsession、链接-Connection和事务-Transaction,今天让我们一起来梳理下这三者之间的工作原理和关系
1、首先来解析下会话

Sqlsession

会话是Mybatis持久化层跟关系型数据库交互的基础,所有的查询、数据更新(包含保存、更新、删除)操作都在与数据库建立会话的基础上进行的;MyBatis中的会话是SqlSession,默认实现是DefaultSqlSession。可以通过SqlSessionFactory的openSession来获取的。
通过SqlSessionFactory获取SqlSession的代码如下:

    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
  • 1
  • 2
  • 3
  • 4

打开一个会话的时序图大致流程如下:
在这里插入图片描述

第一步:通过new SqlSessionFactoryBuilder().build(inputStream)来构造SqlSessionFactory,参数是配置文件的输入流。*
主要实现的代码块如下:*

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException(“Error building SqlSession.”, e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

第二步:XMLConfigBuilder的parse方法会解析配置文件,解析的结果就是得出一个Configuration对象。其中一步就是根据配置文件中的datasource节点解析出数据源
主要实现的代码块如下:

<dataSource type="POOLED">
    <!--这里会替换为local-mysql.properties中的对应字段的值-->
    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true"/>
    <property name="username" value="root"/>
    <property name="password" value="12345678"/>
    <property name="poolMaximumActiveConnections" value="2"/>
    <property name="poolMaximumIdleConnections" value="2"/>
</dataSource>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

第三步:SqlSessionFactory的openSession会获取SqlSession。具体实现代码如下:

    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  

```yaml
在这里插入代码片
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2、接下来再分析下Mybatis的链接

Connection

MyBatis在第一次执行SQL操作时,在获取Statement时,会去获取数据库链接。
在这里插入图片描述
我们配置的数据源为POOLED,这里会使用PooledDataSource来获取connection。

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()) {
      // 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 happend.
                 Wrap the bad connection with a new PooledConnection, this will help
                 to not intterupt current executing thread and give current thread a
                 chance to join the next competion 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;
          }
        }
      }
    }
    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.");
        }
        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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

}
这里进行了数据库的链接进行了池化管理:如果idle的connection,就直接取出一个返回。数据库链接的获取底层代码如下:

获取链接后的第一件事,就是设置connection的autoCommit属性。这里可以看出MyBatis通过自身的数据源PooledDataSource来进行数据库链接的管理。

3、然后再来说下事务

Transaction

在执行sqlSession.commit时,会去提交事务。

    UserMapperExt userMapperExt = sqlSession.getMapper(UserMapperExt.class);

    userMapperExt.insert(new UserDTO("houliu",23));
    userMapperExt.findUserListByName("zhangsan");
    userMapperExt.update("name" ,"wangwu",22);
    sqlSession.commit();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

执行commit后,会调用如下代码:
在这里插入图片描述

一个sqlSession中可以进行多个事务提交:

SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapperExt userMapperExt1 = sqlSession1.getMapper(UserMapperExt.class);

    userMapperExt1.insert(new UserDTO("houliu",23));
    userMapperExt1.findUserListByName("zhangsan");
    userMapperExt1.update("name" ,"wangwu",22);
    sqlSession1.commit();

    //SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserMapperExt userMapperExt2 = sqlSession1.getMapper(UserMapperExt.class);

    userMapperExt2.insert(new UserDTO("houliu",23));
    userMapperExt2.findUserListByName("zhangsan");
    userMapperExt2.update("name" ,"wangwu",22);
    sqlSession1.commit();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

原生jdbc中一个connection可以执行多次commit:

Class.forName(“com.mysql.cj.jdbc.Driver”); //classLoader,加载对应驱动
Connection connection = DriverManager.getConnection(“jdbc:mysql://127.0.0.1:3306/test?useUnicode=true”, “root”, “12345678”);
connection.setAutoCommit(false);
PreparedStatement preparedStatement = connection.prepareStatement(“update cnt_user set age = 201 where name = ‘zhangsan’”);
preparedStatement.execute();

    connection.commit();
    preparedStatement = connection.prepareStatement("update cnt_user set age = 233 where name = 'zhangsan'");
    preparedStatement.execute();

    preparedStatement = connection.prepareStatement("insert into cnt_user (age , name) values(100 ,'liusi')");
    preparedStatement.execute();

    connection.commit();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看出,事务是依附在SqlSession上的。

好了,讲完了上面三者的原理后,最后我们来总结下三者的关系
**

Sqlsession、Connection和Transaction之间关系

链接可以通过数据库链接池被复用。在MyBatis中,不同时刻的SqlSession可以复用同一个Connection,同一个SqlSession中可以提交多个事务。因此,链接—会话—事务的关系如下:

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/158262
推荐阅读
相关标签
  

闽ICP备14008679号