码迷,mamicode.com
首页 > 数据库 > 详细

Mybatis深入之获取数据库连接

时间:2015-06-23 00:57:20      阅读:223      评论:0      收藏:0      [点我收藏+]

标签:mybatis   获取数据   数据库连接池   数据库连接   

Mybatis深入之获取数据库连接

简介

主要记录Mybatis何时获取数据库连接以及获取数据库连接的过程。难点在于明白在使用Mybatis数据库连接池情况下的数据库连接的获取过程。

何时获取数据库连接

Mybatis只有在真正执行sql操作的时候才会去获取数据库连接。至于如何验证:

不深入源码

简单来讲就是有意将数据库配置信息写成、在一个sql执行过程中看哪一步抛数据库连接异常。

    public static void main(String[] args) throws Exception {
        String mybatisConfigPath = "config/mybatis/mybatis.xml";
        InputStream inputStream = Resources.getResourceAsStream(mybatisConfigPath);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");
        System.out.println(count);
    }
  • 上面是一段Mybatis执行代码
  • 我们可以将Mybatis连接数据库的信息有意写错
  • 再DEBUG模式下一步一步调试看哪一步会抛异常
  • 抛异常的那一步就是真正获取数据库连接的一步

异常信息:
技术分享

深入源码

这里简单提一下、具体后面会有。最有迷惑性的是觉得在openSession()的时候会获取数据库连接、其实不然:
openSession()最终只是返回一个操作数据库的会话、并不包含数据库连接,DefaultSqlSession(这个是Mybatis初始化的时候返回的一个SqlSession)中的方法:

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    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();
    }
  }
  • 主要装配DefaultSqlSession中执行Sql的Excutor、后面关于一个Sql完整的执行过程会有对其的详细分析

显然真正获取数据库连接的操作是在sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");进行的。

获取数据库连接

在真正的获取数据库连接代码之前、还有许多为sql执行而生的代码、这里暂时忽略或者一些必要的说明、主要重心放在如何获取数据库连接。
书接上回、从前面执行sql代码开始:

int count = (Integer)sqlSession.selectOne("org.alien.mybatis.samples.mapper.AuthorMapper.getAllAuthorsCount");

下图是上面代码一系列方法调用过程:

技术分享

经过一系列调用到SimpleExecutor——》doQuery():

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //RoutingStatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //MappedStatement、这一句是关键
      stmt = prepareStatement(handler, ms.getStatementLog());
      /*
       *参数:
       * stmt: PreparedStatementLogger
       * resultHandler: null 
       */
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
  • 上面经过一系列跳转之后各个引用的实例我加了注释、有兴趣的可以自己跟一下、有时候可能要多跟几遍
  • 关键代码stmt=prepareStatement(handler,ms.getStatementLog());、这一句根据方法名就能猜测、是根据Connection来获取执行Sql的PrepareStatement
  • 但是到现在为止我们都没有看到方法的参数中有关于数据库连接的、在此方法中

SimpleExecutor——》prepareStatement();

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //获取数据库连接。statementLog:org.apache.ibatis.loggin.slf4j.Slf4Impl
    Connection connection = getConnection(statementLog);
    //获取执行Sql的Statement——PrepareStatementLogger
    //PrepareStatementLogger是PrepareStatement的代理、多了对使用Mybatis执行sql语句时记录sql语句的功能
    stmt = handler.prepare(connection);
    //将执行Sql需要的参数设置到PrepareStatement中。
    handler.parameterize(stmt);
    return stmt;
  }

其他的不关注、这里只看数据库连接代码:Connection connection = getConnection(statementLog);BaseExecutor——》getConnection():

  protected Connection getConnection(Log statementLog) throws SQLException {
    //如果关于数据库连接的日志记录级别是DEBUG级别、则为获取的Connection进行代理、新增日志记录功能、这里不是重点。
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }
  • Mybatis中关于事务配置项的值是”JDBC”、所以从Mybatis深入之事务管理 知道这里的transaction其实是:JdbcTransaction
  • 最终到JdbcTransaction获取连接的方法中

JdbcTransaction——》openConnection()

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }

PooledDataSource——》getConnection():

  public Connection getConnection() throws SQLException {

/*
 * 为理解方便、将原来代码拆分如下:
 */
    PooledConnection pooledConnection = popConnection(dataSource.getUsername(), dataSource.getPassword());
    Connection connection = pooledConnection.getProxyConnection();
    return connection ;

//return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }
  • 从拆分代码看分两步
    • 获取数据库真正连接
    • 获取真正数据库连接的代理类作为最终返回结果、至于代理是做什么、后面继续

PooledDataSource——》popConnection():

  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.size() > 0) {
          // 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);
            @SuppressWarnings("unused")
            //used in logging, if enabled
            Connection realConn = conn.getRealConnection();
            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()) {
                oldestActiveConnection.getRealConnection().rollback();
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              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) {
          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;
  }
  • 先解释一下逻辑、再关注具体的数据库连接方法
1.  先看是否有空闲(idle)状态下的PooledConnection对象,如果有,就直接返回一个可用的PooledConnection对象;否则进行第2步。

2.  查看活动状态的PooledConnection池activeConnections是否已满;如果没有满,则创建一个新的PooledConnection对象,然后放到activeConnections池中,然后返回此PooledConnection对象;否则进行第三步;

3.  看最先进入activeConnections池中的PooledConnection对象是否已经过期:如果已经过期,从activeConnections池中移除此对象,然后创建一个新的PooledConnection对象,添加到activeConnections中,然后将此对象返回;否则进行第4步。

4.  线程等待,循环2步

具体的创建数据库连接代码conn = new PooledConnection(dataSource.getConnection(), this);

所以先要看UnpooledDataSource——getConnection() 经过一系列跳转到同类如下方法:

  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }
  • 上面我们可以看到很熟悉的加载驱动、获取数据库连接

补充

其实关于数据库部分还有很多要写的、比如数据库连接池工作原理、数据库连接何时关闭。这里暂不准备一次将所有的东都放在一起。觉得分开点更容易说清楚理解起来不是那么费劲。
后面会有一篇Mybatis数据库连接池原理来分析它。

更多内容Mybatis 目录

Mybatis深入之获取数据库连接

标签:mybatis   获取数据   数据库连接池   数据库连接   

原文地址:http://blog.csdn.net/crave_shy/article/details/46597239

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!