The data source module for Mybatis is located inOrg.apache.ibatis.datasource.
Common data source components are implementedJavax.sql.DataSourceInterface.Within Mybatis, third-party data source components are integrated, and data source implementations are provided.In general, data source initialization is more complex and has more parameters, so the factory mode is used here.
Factory Mode
Factory Pattern is a creator mode that provides the best way to create objects.Defines an interface for creating objects so that its subclasses feel like instantiating which factory class, and factory mode delays it until the subclass.
Factory interface: The factory interface is the core interface of the factory mode, and callers get specific implementation classes by interacting directly with the factory interface.
ConcreteFactory: An implementation class of a factory interface used to instantiate product objects. Different specific factory classes instantiate different product implementation classes based on different requirements.
Product interface: A product interface is used to define the functionality of a product class, which must be implemented for all products produced by a specific factory class.
ConcreteProduct: The implementation class of a product interface in which specific business logic is defined.
Data Source Module
DataSourceFactory: The factory interface for the Mybatis data source.Mybatis provides two specific factory classes, PooledDataSourceFactory (a data source factory that uses connection pools), UnpooledDataSourceFactory (a data source factory that does not use connection pools), which instances PooledDataSource and UnpooledDataSource, respectively.
public interface DataSourceFactory { //Set DataSource related properties void setProperties(Properties props); //Get Data Source DataSource getDataSource(); }
public class UnpooledDataSourceFactory implements DataSourceFactory { protected DataSource dataSource; public UnpooledDataSourceFactory() { this.dataSource = new UnpooledDataSource(); } ... }
public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { this.dataSource = new PooledDataSource(); } }
UnpooledDataSource: A data source without a connection pool, where connections are acquired the same way that jdbc manually acquires them.
private Connection doGetConnection(Properties properties) throws SQLException { initializeDriver(); Connection connection = DriverManager.getConnection(url, properties); //Set transaction autocommit, transaction isolation level configureConnection(connection); return connection; }
PooledDataSource: A data source with a connection pool.PooledDataSource manages the components of the PooledConnection object state through PoolState, and the connection resources of the idle state and the active state through two list s, respectively.
public class PoolState { protected PooledDataSource dataSource; //Idle Connection Pool Resource Collection protected final List<PooledConnection> idleConnections = new ArrayList<>(); //Active Connection Pool Resource Collection protected final List<PooledConnection> activeConnections = new ArrayList<>(); //Number of requests protected long requestCount = 0; //Cumulative time to acquire connection protected long accumulatedRequestTime = 0; //Cumulative time spent using connections.Calculate the time of one use from the connection to the return; protected long accumulatedCheckoutTime = 0; //Number of Timeouts Using Connections protected long claimedOverdueConnectionCount = 0; //Cumulative timeout protected long accumulatedCheckoutTimeOfOverdueConnections = 0; //Cumulative wait time protected long accumulatedWaitTime = 0; //Number of Waits protected long hadToWaitCount = 0; //Number of invalid connections protected long badConnectionCount = 0; ... }
PooledDataSource: A data source with a connection pool.Connection pools are managed through PoolStatus within PooledDataSource, thread pools are taken and put through popConnection and pushConnection, and getConnection calls popConnection.
protected void pushConnection(PooledConnection conn) throws SQLException { synchronized (state) {//Recycle connections must be synchronous state.activeConnections.remove(conn);//Delete this connection from the active connection pool if (conn.isValid()) { //Determine if idle connection pool resources have reached the maximum if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) { //Upper limit not reached, recycling state.accumulatedCheckoutTime += conn.getCheckoutTime(); if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback();//Rollback if there are still transactions uncommitted } //Based on the connection, create a new connection resource and refresh the connection status PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this); state.idleConnections.add(newConn); newConn.setCreatedTimestamp(conn.getCreatedTimestamp()); newConn.setLastUsedTimestamp(conn.getLastUsedTimestamp()); //Old Connection Failure conn.invalidate(); if (log.isDebugEnabled()) { log.debug("Returned connection " + newConn.getRealHashCode() + " to pool."); } //Wake up other blocked threads state.notifyAll(); } else {//If the idle connection pool has reached its maximum limit, the connection is actually closed state.accumulatedCheckoutTime += conn.getCheckoutTime(); if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); } //Close Real Database Connections conn.getRealConnection().close(); if (log.isDebugEnabled()) { log.debug("Closed connection " + conn.getRealHashCode() + "."); } //Setting connection object to invalid conn.invalidate(); } } else { if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") attempted to return to the pool, discarding connection."); } state.badConnectionCount++; } } } private PooledConnection popConnection(String username, String password) throws SQLException { boolean countedWait = false; PooledConnection conn = null; long t = System.currentTimeMillis();//Record attempts to get connection start timestamp int localBadConnectionCount = 0;//Number of Initialization Gets Invalid Connections while (conn == null) { synchronized (state) {//Getting connections must be synchronous if (!state.idleConnections.isEmpty()) {//Check for idle connections // Pool has available connection //Direct use with free connections conn = state.idleConnections.remove(0); if (log.isDebugEnabled()) { log.debug("Checked out connection " + conn.getRealHashCode() + " from pool."); } } else {// No idle connection if (state.activeConnections.size() < poolMaximumActiveConnections) {//Determine if the number of active connections in the pool is greater than the maximum number of connections // No, you can create a new connection conn = new PooledConnection(dataSource.getConnection(), this); if (log.isDebugEnabled()) { log.debug("Created connection " + conn.getRealHashCode() + "."); } } else {// New connections cannot be created if they are already equal to the maximum number of connections //Get the earliest connection created PooledConnection oldestActiveConnection = state.activeConnections.get(0); long longestCheckoutTime = oldestActiveConnection.getCheckoutTime(); if (longestCheckoutTime > poolMaximumCheckoutTime) {//Detect if maximum usage time has been reached and exceeded // Statistics of timeout connection information if timeout occurs state.claimedOverdueConnectionCount++;//Timeout Connections+1 state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;//Cumulative Timeout Increase state.accumulatedCheckoutTime += longestCheckoutTime;//Cumulative increase in connection usage time state.activeConnections.remove(oldestActiveConnection);//Remove from active queue if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {//Manual rollback if timeout connection is not committed try { oldestActiveConnection.getRealConnection().rollback(); } catch (SQLException e) {//Exceptions only log /* 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"); } } //Create a new connection in the connection pool, note that no new connection has been created for the database; conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this); conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp()); conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp()); //Invalidate old connections oldestActiveConnection.invalidate(); if (log.isDebugEnabled()) { log.debug("Claimed overdue connection " + conn.getRealHashCode() + "."); } } else { // There are no idle connections, the earliest connections are not broken, no new connections can be created, only blocked try { if (!countedWait) { state.hadToWaitCount++;//Connection pool cumulative wait times plus 1 countedWait = true; } if (log.isDebugEnabled()) { log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection."); } long wt = System.currentTimeMillis(); state.wait(poolTimeToWait);//Blocking wait specified time state.accumulatedWaitTime += System.currentTimeMillis() - wt;//Cumulative wait time increase } catch (InterruptedException e) { break; } } } } if (conn != null) {//To get a successful connection, test if the connection is valid and update statistics // ping to server and check the connection is valid or not if (conn.isValid()) {//Check if the connection is valid if (!conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback();//If a legacy transaction, roll back } //Connection pool related statistics update 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 the connection is invalid if (log.isDebugEnabled()) { log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection."); } state.badConnectionCount++;//Cumulative Get Invalid Connections + 1 localBadConnectionCount++;//Current Number of Get Invalid Connections+1 conn = null; //Get an invalid connection, but if the number of retries is not exceeded, allow another attempt to get the connection or throw an exception 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; }