MyBatis源碼淺析

什麼是MyBatis      html

      MyBatis是支持定製化SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎全部的 JDBC 代碼和手工設置參數以及抽取結果集。MyBatis 使用簡單的 XML 或註解來配置和映射基本體,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。java

MyBatis簡單示例mysql

      雖然在使用MyBatis時通常都會使用XML文件,可是本文爲了分析程序的簡單性,簡單的測試程序將不包含XML配置,該測試程序包含一個接口、一個啓動類:sql

public interface UserMapper {
  @Select("SELECT * FROM user WHERE id = #{id}")
  User selectUser(int id);
}

public class Test2 {
	public static void main(String[] args) {
		SqlSessionFactory sqlSessionFactory = initSqlSessionFactory();
		SqlSession session = sqlSessionFactory.openSession();
		try {
			User user = (User) session.selectOne(
					"org.mybatis.example.UserMapper.selectUser", 1);
			System.out.println(user.getUserAddress());
			System.out.println(user.getUserName());
		} finally {
			session.close();
		}
	}

	private static SqlSessionFactory initSqlSessionFactory() {
		DataSource dataSource = new PooledDataSource("com.mysql.jdbc.Driver",
				"jdbc:mysql://127.0.0.1:3306/jdbc", "root", "");
		TransactionFactory transactionFactory = new JdbcTransactionFactory();
		Environment environment = new Environment("development",
				transactionFactory, dataSource);
		Configuration configuration = new Configuration(environment);
		configuration.addMapper(UserMapper.class);
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
				.build(configuration);

		return sqlSessionFactory;
	}
}

  UserMapper是一個接口,咱們在構建sqlSessionFactory時經過configuration.addMapper(UserMapper.class)把該接口註冊進了sqlSessionFactory中。從上面的代碼中咱們能夠看出,要使用MyBatis,咱們應該通過如下步驟:一、建立sqlSessionFactory(一次性操做);二、用sqlSessionFactory對象構造sqlSession對象;三、調用sqlSession的相應方法;四、關閉sqlSession對象。數據庫

      在main方法中,咱們沒有配置sql,也沒有根據查詢結果拼接對象,只需在調用sqlSession方法時傳入一個命名空間以及方法參數參數便可,全部的操做都是面向對象的。在UserMapper接口中,咱們定製了本身的sql,MyBatis把書寫sql的權利給予了咱們,方便咱們進行sql優化及sql排錯。apache

JDBC基礎回顧緩存

      直接使用JDBC是很痛苦的,JDBC鏈接數據庫包含如下幾個基本步驟:一、註冊驅動 ;二、創建鏈接(Connection);三、建立SQL語句(Statement);四、執行語句;五、處理執行結果(ResultSet);六、釋放資源,示例代碼以下:session

public static void test() throws SQLException{
    // 1.註冊驅動
    Class.forName("com.mysql.jdbc.Driver");
 
    // 2.創建鏈接  url格式 - JDBC:子協議:子名稱//主機名:端口/數據庫名?屬性名=屬性值&…
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", "");
 
    // 3.建立語句
    Statement st = conn.createStatement();
 
    // 4.執行語句
    ResultSet rs = st.executeQuery("select * from user");
 
    // 5.處理結果
    while (rs.next()) {
      User user = new User(rs.getObject(1), rs.getObject(2)); } // 6.釋放資源 rs.close(); st.close(); conn.close(); }

  能夠看到與直接使用JDBC相比,MyBatis爲咱們簡化了不少工做:mybatis

      一、把建立鏈接相關工做抽象成一個sqlSessionFactory對象,一次建立屢次使用;app

      二、把sql語句從業務層剝離,代碼邏輯更加清晰,增長可維護性;

      三、自動完成結果集處理,不須要咱們編寫重複代碼。

      可是,咱們應該知道的是,框架雖然可以幫助咱們簡化工做,可是框架底層的代碼確定仍是最基礎的JDBC代碼,由於這是Java平臺鏈接數據庫的通用方法,今天我將分析一下MyBatis源碼,看看MyBatis是如何把這些基礎代碼封裝成一個框架的。

MyBatis調用流程

      咱們最終調用的是sqlSession對象上的方法,因此咱們先跟蹤sqlSession的建立方法:sqlSessionFactory.openSession(),最終這個方法會調用到DefaultSqlSessionFactory的如下方法:

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對象,在調試模式下,咱們看到autoCommit爲false,executor爲CachingExecutor類型,在CachingExecutor裏面有屬性delegate,其類型爲simpleExecutor:

      如今,咱們跟進DefaultSqlSession的selectOne()方法,查看該方法的調用流程,selectOne()方法又會調用selectList()方法:

  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  能夠看到要獲得查詢結果,最終仍是要調用executor上的query方法,這裏的executor是CachingExecutor實例,跟進程序獲得以下代碼:

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, parameterObject, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  MyBatis框架首先生成了一個boundSql和CacheKey,在boundSql中包含有咱們傳入的sql語句:

      生成boundSql和CacheKey後會調用一個重載函數,在重載函數中,咱們會檢測是否有緩存,這個緩存是MyBatis的二級緩存,咱們沒有配置,那麼直接調用最後一句delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql),前面說過這個delagate其實就是simpleExecutor,跟進去查看一下:

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

  關鍵代碼是如下三行:

      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }

  首先嚐試從localCache中根據key獲得List,這裏的localCache是MyBatis的一級緩存,若是得不到則調用queryFromDatabase()從數據庫中查詢:

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

      其中關鍵代碼是調用doQuery()代碼,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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  調用了prepareStatement方法,該方法以下:

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }

  終於,咱們看到熟悉的代碼了,首先獲得Connection,而後從Connection中獲得Statement,同時在調試模式下咱們看到,咱們的sql語句已經被設置到stmt中了:

  如今Statement對象有了,sql也設置進去了,就只差執行以及對象映射了,繼續跟進代碼,咱們會跟蹤到org.apache.ibatis.executor.statement.

PreparedStatementHandler類的executor方法:

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

  在這裏,調用了ps.execute()方法執行sql,接下來調用的resultSetHandler.<E> handleResultSets(ps)方法明顯是對結果集進行封裝,我就不繼續跟進了。      

MyBatis的數據庫鏈接池

     上面一部分介紹了MyBatis執行的總體流程,這一部分打算討論一個具體話題:MyBatis的數據庫鏈接池。

     咱們知道,每次鏈接數據庫時都建立Connection是十分耗費性能的,因此咱們在寫JDBC代碼時,通常都會使用數據庫鏈接池,把用過的Connection不是直接關閉,而是放入數據庫鏈接池中,方便下次複用,開源的數據庫鏈接池有DBCP、C3P0等,MyBatis也實現了本身的數據庫鏈接池,在這一節我將探索一下MyBatis實現的數據庫鏈接池源碼。

      跟進上一節的getConnection()方法,咱們最終會進入JdbcTransaction的getConnection()方法,getConnection()方法又會調用openConnection()方法,而openConnection()又將調用dataSource的getConnection()方法:

public Connection getConnection() throws SQLException {
	if (connection == null) {
		openConnection();
	}
	return connection;
}

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);
}

  這裏的dataSource是PooledDataSource類型,跟進查看源碼以下:

  public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

  private PooledConnection popConnection(String username, String password) throws SQLException {
    //暫不分析
  }

      能夠看到,在這裏咱們返回的對象其實已經不是原生的Connection對象了,而是一個動態代理對象,是PooledConnection的一個屬性,全部對對Connection對象的操做都將被PooledConnection攔截,咱們能夠查看PooledConnection的定義以下:

class PooledConnection implements InvocationHandler {
	private static final String CLOSE = "close";
	private static final Class<?>[] IFACES = new Class<?>[] { Connection.class };
	private int hashCode = 0;
	private PooledDataSource dataSource;
	private Connection realConnection;
	private Connection proxyConnection;
	private long checkoutTimestamp;
	private long createdTimestamp;
	private long lastUsedTimestamp;
	private int connectionTypeCode;
	private boolean valid;

	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;
		this.proxyConnection = (Connection) Proxy.newProxyInstance(
				Connection.class.getClassLoader(), IFACES, this);
	}

	public void invalidate() {
		valid = false;
	}

	public boolean isValid() {
		return valid && realConnection != null
				&& dataSource.pingConnection(this);
	}

	public Connection getRealConnection() {
		return realConnection;
	}

	public Connection getProxyConnection() {
		return proxyConnection;
	}

	public int getRealHashCode() {
		if (realConnection == null) {
			return 0;
		} else {
			return realConnection.hashCode();
		}
	}

	public int getConnectionTypeCode() {
		return connectionTypeCode;
	}

	public void setConnectionTypeCode(int connectionTypeCode) {
		this.connectionTypeCode = connectionTypeCode;
	}

	public long getCreatedTimestamp() {
		return createdTimestamp;
	}

	public void setCreatedTimestamp(long createdTimestamp) {
		this.createdTimestamp = createdTimestamp;
	}

	public long getLastUsedTimestamp() {
		return lastUsedTimestamp;
	}

	public void setLastUsedTimestamp(long lastUsedTimestamp) {
		this.lastUsedTimestamp = lastUsedTimestamp;
	}

	public long getTimeElapsedSinceLastUse() {
		return System.currentTimeMillis() - lastUsedTimestamp;
	}

	public long getAge() {
		return System.currentTimeMillis() - createdTimestamp;
	}

	public long getCheckoutTimestamp() {
		return checkoutTimestamp;
	}

	public void setCheckoutTimestamp(long timestamp) {
		this.checkoutTimestamp = timestamp;
	}

	public long getCheckoutTime() {
		return System.currentTimeMillis() - checkoutTimestamp;
	}

	public int hashCode() {
		return hashCode;
	}

	public boolean equals(Object obj) {
		if (obj instanceof PooledConnection) {
			return realConnection.hashCode() == (((PooledConnection) obj).realConnection
					.hashCode());
		} else if (obj instanceof Connection) {
			return hashCode == obj.hashCode();
		} else {
			return false;
		}
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		String methodName = method.getName();
		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);
			}
		}
	}

	private void checkConnection() throws SQLException {
		if (!valid) {
			throw new SQLException(
					"Error accessing PooledConnection. Connection is invalid.");
		}
	}
}

  能夠看到這個類暴露了不少接口檢測Connection狀態,例如鏈接是否有效,鏈接建立時間最近使用鏈接等:

      這個類實現了InvocationHandler接口,最主要的一個方法以下:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    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);
      }
    }
  }

  能夠看到,PooledConnection會攔截close方法,當客戶端調用close()方法時,程序不會關閉Connection,而是會調用dataSource.pushConnection(this)方法,該方法的實現以下:

  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++;
      }
    }
  }

  能夠看到,首先會把Connection從活躍列表中刪除,而後檢測空閒列表的長度有沒有達到最大長度(默認爲5),若沒有達到,把Connection放入空閒鏈表,不然關閉鏈接。這裏的state是一個PoolState對象,該對象定義以下:

public class PoolState {
  protected PooledDataSource dataSource;
  protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
  protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();
  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;

  public PoolState(PooledDataSource dataSource) {
    this.dataSource = dataSource;
  }

  public synchronized long getRequestCount() {
    return requestCount;
  }

  public synchronized long getAverageRequestTime() {
    return requestCount == 0 ? 0 : accumulatedRequestTime / requestCount;
  }

  public synchronized long getAverageWaitTime() {
    return hadToWaitCount == 0 ? 0 : accumulatedWaitTime / hadToWaitCount;
  }

  public synchronized long getHadToWaitCount() {
    return hadToWaitCount;
  }

  public synchronized long getBadConnectionCount() {
    return badConnectionCount;
  }

  public synchronized long getClaimedOverdueConnectionCount() {
    return claimedOverdueConnectionCount;
  }

  public synchronized long getAverageOverdueCheckoutTime() {
    return claimedOverdueConnectionCount == 0 ? 0 : accumulatedCheckoutTimeOfOverdueConnections / claimedOverdueConnectionCount;
  }

  public synchronized long getAverageCheckoutTime() {
    return requestCount == 0 ? 0 : accumulatedCheckoutTime / requestCount;
  }

  public synchronized int getIdleConnectionCount() {
    return idleConnections.size();
  }

  public synchronized int getActiveConnectionCount() {
    return activeConnections.size();
  }
}

  能夠看到最終咱們的Connection對象是放在ArrayList中的,該類還提供一些接口返回鏈接池基本信息。

      好了,如今咱們能夠回去看看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;
  }

  能夠看到獲取Connection一共分如下幾種狀況:一、若是有空閒Connection,那麼直接使用空閒Connection,不然2;二、若是活躍Connection沒有達到活躍Connection的上限,那麼建立一個新Connection並返回,不然3;三、若是達到活躍上限,且被檢出的Connection檢出時間過長,那麼把該Connection置爲失效,新建立一個Connection,不然4;四、等待空閒Connection。

      至此,咱們就把MyBatis的數據庫鏈接池代碼整理了一遍,其中有兩個關鍵點:一、檢出的Connection其實不是原生Connection,而是一個代理對象;二、存放Connection的容器是ArrayList,Connection的檢出聽從先進先出原則。

MyBatis的緩存

      這篇博客講的很好,mark一下:http://www.cnblogs.com/fangjian0423/p/mybatis-cache.html

MyBatis的事務

      首先回顧一下JDBC的事務知識。

      JDBC能夠操做Connection的setAutoCommit()方法,給它false參數,提示數據庫啓動事務,在下達一連串的SQL命令後,自行調用Connection的commit()方法,提示數據庫確認(Commit)操做。若是中間發生錯誤,則調用rollback(),提示數據庫撤銷(ROLLBACK)全部執行。同時,若是僅想要撤回某個SQL執行點,則能夠設置存儲點(SAVEPOINT)。一個示範的事務流程以下:

Connection conn = ...;
Savepoint point = null;
try {
    conn.setAutoCommit(false);
    Statement stmt = conn.createStatement();
    stmt.executeUpdate("INSERT INTO ...");
    ...
    point = conn.setSavepoint();
    stmt.executeUpdate("INSERT INTO ...");
    ...
    conn.commit();
} catch (SQLException e) {
    e.printStackTrace();
    if (conn != null) {
        try {
            if (point == null) {
                conn.rollback();
            } else {
                conn.rollback(point);
                conn.releaseSavepoint(point);
            }
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
} finally {
    ...
    if (conn != null) {
        try {
            conn.setAutoCommit(true);
            conn.close();
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
}

  在MyBatis調用流程一節就寫過,在調試模式下,咱們看到autoCommit爲false,因此每一個sqlSession其實都是一個事務,這也是爲何每次作刪、改、查時都必須調用commit的緣由。

相關文章
相關標籤/搜索