The general way to execute an SQL against db via Java JDBC is as follows:
// Load the driver, jdbc2 can be omitted
Class.forName(driver);
// Get the DB connection
Connection connection = DriverManager.getConnection(url, user, password);
/ / create the Statement
Statement statement = connection.createStatement();
// Execute the statement
statement.execute(sql1);
Copy the code
Nowadays, with many persistence frameworks, we don’t use JDBC to operate DB directly anymore, but each framework is also the encapsulation of JDBC, to perform a SQL above the steps are still necessary, perhaps the framework does a little operation so that we don’t have to be aware of these processes.
DataSource is a simple interface that contains only two definitions of interfaces for retrieving connections:
package javax.sql;
public interface DataSource extends CommonDataSource.Wrapper {
// Get a connection to db
Connection getConnection(a) throws SQLException;
// Get a connection to db. You can specify the user and password to get the connection
Connection getConnection(String username, String password) throws SQLException;
Copy the code
Mybatis provides UNPOOLED and POOLED DataSource. DataSource related code in the SRC/main/Java/org/apache/ibatis/DataSource.
A, UnpooledDataSource
In the configuration file, if the type of the dataSource is not UNPOOLED, the dataSource is UnpooledDataSource. Each time a connection is obtained from the dataSource, a new connection is created and the db connection is closed.
The UnpooledDataSource encapsulates the properties of the DB connection that are retrieved from the configuration file when the configuration is resolved and the connection is retrieved from the values of these properties when the connection is retrieved. Includes the following attributes:
package org.apache.ibatis.datasource.unpooled;
public class UnpooledDataSource implements DataSource {
/ / driver
private String driver;
// db connection URL
private String url;
/ / user name
private String username;
/ / password
private String password;
// Other connection driver attributes, configured through driver.xxx
private Properties driverProperties;
// Whether the obtained connection is automatically committed
private Boolean autoCommit;
// Connect to the default transaction isolation level
private Integer defaultTransactionIsolationLevel;
// Connection execution timeout by default
private Integer defaultNetworkTimeout;
}
Copy the code
These properties can be found in the Mybatis documentation: mybatis.org/mybatis-3/z…
Still get connection by DriverManager. GetConnection method to obtain:
package org.apache.ibatis.datasource.unpooled;
public class UnpooledDataSource implements DataSource {
// Get the connection
private Connection doGetConnection(Properties properties) throws SQLException {
initializeDriver();
Connection connection = DriverManager.getConnection(url, properties);
configureConnection(connection);
return connection;
}
// Configure the connection properties
private void configureConnection(Connection conn) throws SQLException {
if(defaultNetworkTimeout ! =null) {
conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
}
if(autoCommit ! =null&& autoCommit ! = conn.getAutoCommit()) { conn.setAutoCommit(autoCommit); }if(defaultTransactionIsolationLevel ! =null) { conn.setTransactionIsolation(defaultTransactionIsolationLevel); }}}Copy the code
Check whether the driver is loaded by using the initializeDriver method before obtaining the connection. If not, use the specified type loader or class.forname to load the driver. Properties contains the specified user, password, and properties specified through driver.xxx. Get connected through configureConnection method after setting the autoCommit mode, defaultTransactionIsolationLevel and defaultNetworkTimeout connection.
Second, the PooledDataSource
In the configuration file, if the type of the dataSource is not POOLED, the dataSource is PooledDataSource. All connections obtained from the dataSource are POOLED. If the connection is closed, the connection is returned to the free pool without actually closing the connection to the DB.
PooledDataSource implementation in SRC/main/Java/org/apache/ibatis/datasource/pooled PooledDataSource. Java file.
2.1 Creating a DB Connection
PooledDataSource’s connections are pooled, and any of these pools are created by the UnpooledDataSource. PooledDataSource creates an instance of UnpooledDataSource with which new connections are created when needed.
package org.apache.ibatis.datasource.pooled;
public class PooledDataSource implements DataSource {
// Pool configuration
// Maximum number of active connections in the pool
protected int poolMaximumActiveConnections = 10;
private final UnpooledDataSource dataSource;
public PooledDataSource(a) {
dataSource = new UnpooledDataSource();
}
// The connection-related attributes are set to the dataSource. The other attributes are set the same as the URL
public void setUrl(String url) { dataSource.setUrl(url); . }}Copy the code
The configuration file driver, url, username, password, defaultTransactionIsolationLevel, defaultNetworkTimeout These attributes to the unconnected pool are also given to the UnpooledDataSource instance.
2.2 a javax.sql.pooledconnection
The connection in PooledDataSource is pooled, which requires that the connection be recycled back to the pool when closed, rather than actually being closed to the DB and releasing the associated resources. So once the connection is retrieved from the UnpooledDataSource, a dynamic proxy PooledConnection is created for the connection.
class PooledConnection implements InvocationHandler {
// Dynamic proxy only implements the DB Connection class
private static finalClass<? >[] IFACES =newClass<? >[] { Connection.class };// Dynamic proxy connection
private final Connection proxyConnection;
private static finalClass<? >[] IFACES =newClass<? >[] { Connection.class };public PooledConnection(Connection connection, PooledDataSource dataSource) {...// Create a dynamic proxy for the connection
this.proxyConnection = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(), IFACES, this); }}Copy the code
In PooledDataSource’s proxy method, the invoke method, calls to close put the connection back into the free connection pool, while calls to other non-Object methods check whether the connection is available first.
package org.apache.ibatis.datasource.pooled;
class PooledConnection implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
The close method puts the connection back into the pool
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null;
}
try {
// Non-object methods verify that the connection is available
if(! Object.class.equals(method.getDeclaringClass())) { checkConnection(); }// Execute method
return method.invoke(realConnection, args);
} catch (Throwable t) {
throwExceptionUtil.unwrapThrowable(t); }}}Copy the code
In addition to dynamic proxies, PooledConnection includes the following connection information for pooled management:
class PooledConnection implements InvocationHandler {
// The original connection
private final Connection realConnection;
// Dynamic proxy connection
private final Connection proxyConnection;
// Check out the time from the pool for use by other threads
private long checkoutTimestamp;
// Create time
private long createdTimestamp;
// Last usage time
private long lastUsedTimestamp;
//
private int connectionTypeCode;
// Whether the connection is valid
private boolean valid;
}
Copy the code
2.3 PoolState
PoolState records the state of the pool that already holds both idle and active connections (connections by a thread consumer), and each PooledDataSource has an instance of that class. At the same time, the PooledConnection operation is synchronous, using the PoolState instance as the synchronization lock.
package org.apache.ibatis.datasource.pooled;
public class PoolState {
// Free connection pool
protected final List<PooledConnection> idleConnections = new ArrayList<>();
// Active connection pool
protected final List<PooledConnection> activeConnections = new ArrayList<>();
}
Copy the code
Use state as a synchronization lock
public class PooledDataSource implements DataSource {
private final PoolState state = new PoolState(this);
// Get the connection
private PooledConnection popConnection(String username, String password) throws SQLException {
while (conn == null) {
// Synchronize the operation
synchronized(state) { ... }}}}Copy the code
2.4 Obtaining connections from the pool
PooledDataSource has free and active connection pools, both of which are initially empty. The basic steps to obtain a connection are as follows:
- Returns the first connection from the free pool if there are free connections in the free pool.
PooledConnection conn = null;
while (conn == null) {
if(! state.idleConnections.isEmpty()) {// From the free pool
conn = state.idleConnections.remove(0); }}Copy the code
- Otherwise, if the active connection pool is not full, create a new connection.
if (state.activeConnections.size() < poolMaximumActiveConnections) {
conn = new PooledConnection(dataSource.getConnection(), this);
}
Copy the code
- Otherwise, try to check out the first connection from the active connection pool.
If the first connection in the active connection pool has been removed from the pool for longer than the poolMaximumCheckoutTime, the connection can be forcibly returned and checked out for use by other threads:
PooledConnection oldestActiveConnection = state.activeConnections.get(0);
// Check out the interval
long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
// If the detection time is greater than the maximum, the detection time is detected
if (longestCheckoutTime > poolMaximumCheckoutTime) {
state.activeConnections.remove(oldestActiveConnection);
}
Copy the code
However, the connections in the active connection pool are those that are still being used by other threads, so the checked out connection may be used by other threads or will be used later, so the checked out connection is rolled back and set to invalid, and then a new PooledConnection is created with its real connection.
// Rollback if auto commit is not set
if(! oldestActiveConnection.getRealConnection().getAutoCommit()) {try {
oldestActiveConnection.getRealConnection().rollback();
} catch (SQLException e) {
log.debug("Bad connection. Could not roll back"); }}// Create a new connection
conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
conn.setCreatedTimestamp(oldestActiveConnection.getCreatedTimestamp());
conn.setLastUsedTimestamp(oldestActiveConnection.getLastUsedTimestamp());
// The old connection is set to invalid
oldestActiveConnection.invalidate();
Copy the code
Of course, the connection may not be currently expired, so wait poolTimeToWait and repeat to step 1.
state.wait(poolTimeToWait);
Copy the code
If an invalid connection is obtained after the previous step, then the number of invalid connections currently obtained is increased by 1. But if the number of invalid connection more than poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance will throw an exception directly:
if (conn.isValid()) {
} else {
localBadConnectionCount++;
if (localBadConnectionCount > (poolMaximumIdleConnections + poolMaximumLocalBadConnectionTolerance)) {
throw new SQLException("PooledDataSource: Could not get a good connection to the database."); }}Copy the code
After obtaining a valid connection through the above steps, put the connection into the active connection pool and put it back as follows:
2.4 Closing the Connection
PooledDataSource creates a dynamic proxy PooledConnection for the connection. If the PooledConnection proxy calls the close method, the PooledConnection proxy will be put back into the free pool:
// PooledConnectiond dynamic proxy
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (CLOSE.equals(methodName)) {
dataSource.pushConnection(this);
return null; }}Copy the code
The connection may already be invalid when it is put back into the free pool, which can happen in the following cases:
- The connection has been uploaded for longer than poolTimeToWait and has been checked out from the active pool by another thread, so the connection is invalid when the original thread connected to the closeer.
- Changing the PooledDataSource connection properties such as autoCommit, URL, and password invalidates and closes all connections in the free and active pools.
Invalid connections are ignored instead of being put back into the free pool.
// Put the connection back into the pool
protected void pushConnection(PooledConnection conn) throws SQLException {
synchronized (state) {
state.activeConnections.remove(conn);
}
if (conn.isValid()) {
....
} else {
// Invalid connection closing ignoredstate.badConnectionCount++; }}Copy the code
If the connection is valid and the free connection pool is not slow, create a PooledConnection using the real connection of the connection and put it into the free connection pool, then invalidate the original connection.
if (state.idleConnections.size() < poolMaximumIdleConnections && conn.getConnectionTypeCode() == expectedConnectionTypeCode) {
// Perform a rollback if the connection is not committed automatically
if(! conn.getRealConnection().getAutoCommit()) { conn.getRealConnection().rollback(); }// Add a new connection to the pool
PooledConnection newConn = new PooledConnection(conn.getRealConnection(), this);
state.idleConnections.add(newConn);
// The original connection is set to invalid
conn.invalidate();
}
Copy the code
2.5 Verify the connection
A PooledConnection must be checked to see if it is a valid connection. A PooledConnection must be pinged to see if it is a valid connection. To remove the following connection itself, such as the connection has been set invalid by the DB side, such errors can still be found through the ping check.
class PooledConnection implements InvocationHandler {
public boolean isValid(a) {
returnvalid && realConnection ! =null && dataSource.pingConnection(this); }}Copy the code
Verify that the real Connection is closed before ping:
protected boolean pingConnection(PooledConnection conn) {
boolean result = true;
try{ result = ! conn.getRealConnection().isClosed(); }catch (SQLException e) {
result = false; }}Copy the code
If the connection is not closed and opened poolPingEnabled configuration, at the same time, the connection of the last time to the present time interval greater than poolPingConnectionsNotUsedFor configuration of time then to launch poolPingQuery db configuration statement execution to test, If the statement does not throw an exception, the connection is available:
if (result && poolPingEnabled && poolPingConnectionsNotUsedFor >= 0
&& conn.getTimeElapsedSinceLastUse() > poolPingConnectionsNotUsedFor) {
Connection realConn = conn.getRealConnection();
try (Statement statement = realConn.createStatement()) {
statement.executeQuery(poolPingQuery).close();
}
result = true;
}
Copy the code
Create DataSorce
To configure DataSorce in the environment, you need to specify the type and various configuration parameters. Details: mybatis.org/mybatis-3/z…
<environment id="development">
<dataSource type="UNPOOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
Copy the code
When creating the SqlSessionFactory using the build method of the SqlSessionFactoryBuilder, Through the SRC/main/Java/org/apache/ibatis/builder/XML/XMLConfigBuilder Java to parse the configuration file, After parsing the Configuration and the SQL statement in the Configuration (org/apache/ibatis/session/Configuration. The Java). The dataSource configuration item is parsed during parsing and the DataSorce is generated based on the configuration.
Get the factory class for the type at parse time via the Type attribute and create an instance. Then set the property to the DataSorce via reflection:
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if(context ! =null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// Create the factory class instance
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
// Set the properties
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
Copy the code
PooledDataSourceFactory and UnpooledDataSourceFactory new directly corresponding to the DataSource. The DataSource obtained by the factory method is placed in the Configuration environment for later execution.
The use of DataSorce
4.1 Transaction Provides connections
Each SqlSession has an Executor that executes SQL, and each Executor has a transaction manager that is generated through the transactionManager configuration, The Transaction also holds the dataSource.
// src/main/java/org/apache/ibatis/session/defaults/DefaultSqlSessionFactory.java
public class DefaultSqlSessionFactory implements SqlSessionFactory {
/ / generated SqlSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// Generate the transaction manager and give the dataSource to the transaction manager
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// Generate an executor for sqlSession
final Executor executor = configuration.newExecutor(tx, execType);
return newDefaultSqlSession(configuration, executor, autoCommit); }}Copy the code
SqlSession executes statements that are executed by executor. Executor’s transaction manager is responsible for providing db connections and closing DB connections in addition to providing commit rollback operations. Here is the definition of a Transaction interface:
// src/main/java/org/apache/ibatis/transaction/Transaction.java
public interface Transaction {
// Get the connection used only by the current executor
Connection getConnection(a) throws SQLException;
// Close the connection executor is currently using
void close(a) throws SQLException;
// Other transaction-related interfaces. }Copy the code
Mybatis provides two classes of JDBC and MANAGED transaction managers. The two types implement the interface in the same way, both of which are in getConnection to determine whether the connection property is null. If it is not null, it gets one from its dataSource and gives it to the Connection property.
// src/main/java/org/apache/ibatis/transaction/jdbc/JdbcTransaction.java
public class JdbcTransaction implements Transaction {
protected Connection connection;
protected DataSource dataSource;
// The connection has a direct return connection
@Override
public Connection getConnection(a) throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
// Get the connection from the dataSource
protected void openConnection(a) throws SQLException {
connection = dataSource.getConnection();
// ...}}Copy the code
4.2 When will the connection be obtained
When a Statement is actually executed against db, the connection is retrieved from the transaction manager to generate the Statement that executes the SQL. In Executor, all statements that are not available from the cache or whose Statement is cached will first be fetched from transaction management to produce the Statement that executes the Statement.
Fetch connections are defined in the BaseExecutor superclass of all executors:
// src/main/java/org/apache/ibatis/executor/BatchExecutor.java
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
returnconnection; }}Copy the code
4.3 When do I Release the Connection
Connection release depends on closing sqlSesson, closing Executor when closing sqlSession, and closing the connection in Eecuto’s close method.
public class DefaultSqlSession implements SqlSession {
public void close(a) {
try {
executor.close(isCommitOrRollbackRequired(false)); . }finally{... }}}public class JdbcTransaction implements Transaction {
public void close(a) throws SQLException {
if(connection ! =null) { resetAutoCommit(); connection.close(); }}}Copy the code
Connections are a scarce and important resource and sqlSession is not thread safe. Therefore, sqlSession must be closed to release occupied connections. Mybatis recommends that the sqlSesion lifecycle should be request-in-request.