在Java工程項目中,我們常會用到Mybatis
框架對數(shù)據(jù)庫中的數(shù)據(jù)進行增刪查改,其原理就是對 JDBC
做了一層封裝,并優(yōu)化數(shù)據(jù)源的連接。
? 我們先來回顧下 JDBC
操作數(shù)據(jù)庫的過程。
JDBC
操作數(shù)據(jù)庫
JDBC
操作數(shù)據(jù)庫的時候需要指定 連接類型、加載驅(qū)動、建立連接、最終執(zhí)行 SQL
語句,代碼如下:
public static final String url = "jdbc:mysql://127.0.0.1/somedb";
public static final String name = "com.mysql.jdbc.Driver";
public static final String user = "root";
public static final String password = "root";
public Connection conn = null;
public PreparedStatement pst = null;
public DBHelper(String sql) {
try {
//指定連接類型
Class.forName(name);
//建立連接
conn = DriverManager.getConnection(url, user, password);
//準備執(zhí)行語句
pst = conn.prepareStatement(sql);
} catch (Exception e) {
e.printStackTrace();
}
}
public void close() {
try {
this.conn.close();
this.pst.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
? 一個SQL
的執(zhí)行,如果使用JDBC
進行處理,需要經(jīng)過 加載驅(qū)動、建立連接、再執(zhí)行SQL
的一個過程,當下一個SQL
到來的時候,還需要進行一次這個流程,這就造成不必要的性能損失,而且隨著用戶操作的逐漸增多,每次都和數(shù)據(jù)庫建立連接對數(shù)據(jù)庫本身來說也是一種壓力。
? 為了減少這種不必要的消耗,可以對數(shù)據(jù)的操作進行拆分。在Mybatis
中,數(shù)據(jù)庫連接的建立和管理的部分叫做數(shù)據(jù)庫連接池。
Mybatis
數(shù)據(jù)源DateSource
的分類
- UNPOOLED 不使用連接池的數(shù)據(jù)源
- POOLED 使用連接池的數(shù)據(jù)源
- JNDI 使用JNDI實現(xiàn)的數(shù)據(jù)
-
UNPOOLED
?
UNPOOLED
不使用連接池的數(shù)據(jù)源,當dateSource
的type屬性被配置成了UNPOOLED
,MyBatis
首先會實例化一個UnpooledDataSourceFactory
工廠實例,然后通過.getDataSource()
方法返回一個UnpooledDataSource
實例對象引用,我們假定為dataSource
。? 使用
UnpooledDataSource
的getConnection()
,每調(diào)用一次就會產(chǎn)生一個新的Connection
實例對象。UnPooledDataSource
的getConnection()
方法實現(xiàn)如下:
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap();
private String driver;
private String url;
private String username;
private String password;
private Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
public UnpooledDataSource() {
}
public UnpooledDataSource(String driver, String url, String username, String password){
this.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
public Connection getConnection() throws SQLException {
return this.doGetConnection(this.username, this.password);
}
private Connection doGetConnection(String username, String password) throws SQLException {
Properties props = new Properties();
if(this.driverProperties != null) {
props.putAll(this.driverProperties);
}
if(username != null) {
props.setProperty("user", username);
}
if(password != null) {
props.setProperty("password", password);
}
return this.doGetConnection(props);
}
private Connection doGetConnection(Properties properties) throws SQLException {
this.initializeDriver();
Connection connection = DriverManager.getConnection(this.url, properties);
this.configureConnection(connection);
return connection;
}
}
如上代碼所示,UnpooledDataSource
會做以下事情:
初始化驅(qū)動: 判斷driver驅(qū)動是否已經(jīng)加載到內(nèi)存中,如果還沒有加載,則會動態(tài)地加載
driver
類,并實例化一個Driver
對象,使用DriverManager.registerDriver()
方法將其注冊到內(nèi)存中,以供后續(xù)使用。創(chuàng)建Connection對象: 使用
DriverManager.getConnection()
方法創(chuàng)建連接。配置Connection對象: 設(shè)置是否自動提交
autoCommit
和隔離級別isolationLevel
。返回Connection對象
從上述的代碼中可以看到,我們每調(diào)用一次
getConnection()
方法,都會通過DriverManager.getConnection()
返回新的java.sql.Connection
實例,所以沒有連接池。
-
POOLED
數(shù)據(jù)源 連接池
PooledDataSource: 將java.sql.Connection
對象包裹成PooledConnection
對象放到了PoolState
類型的容器中維護。 MyBatis
將連接池中的PooledConnection
分為兩種狀態(tài): 空閑狀態(tài)(idle)和活動狀態(tài)(active),這兩種狀態(tài)的PooledConnection
對象分別被存儲到PoolState
容器內(nèi)的idleConnections
和activeConnections
兩個List集合中:
idleConnections: 空閑(idle)狀態(tài)PooledConnection
對象被放置到此集合中,表示當前閑置的沒有被使用的PooledConnection
集合,調(diào)用PooledDataSource
的getConnection()
方法時,會優(yōu)先從此集合中取PooledConnection
對象。當用完一個java.sql.Connection對象時,MyBatis
會將其包裹成PooledConnection
對象放到此集合中。
activeConnections: 活動(active)狀態(tài)的PooledConnection
對象被放置到名為activeConnections
的ArrayList
中,表示當前正在被使用的PooledConnection
集合,調(diào)用PooledDataSource
的getConnection()
方法時,會優(yōu)先從idleConnections
集合中取PooledConnection
對象,如果沒有,則看此集合是否已滿,如果未滿,PooledDataSource
會創(chuàng)建出一個PooledConnection
,添加到此集合中,并返回
現(xiàn)在讓我們看一下popConnection()
方法到底做了什么:
- 先看是否有空閑(idle)狀態(tài)下的
PooledConnection
對象,如果有,就直接返回一個可用的PooledConnection
對象;否則進行第2步。
- 查看活動狀態(tài)的
PooledConnection
池activeConnections
是否已滿;如果沒有滿,則創(chuàng)建一個新的PooledConnection
對象,然后放到activeConnections
池中,然后返回此PooledConnection
對象;否則進行第三步;
看最先進入
activeConnections
池中的PooledConnection
對象是否已經(jīng)過期:如果已經(jīng)過期,從activeConnections
池中移除此對象,然后創(chuàng)建一個新的PooledConnection
對象,添加到activeConnections
中,然后將此對象返回;否則進行第4步。線程等待,循環(huán)2步
/*
* 傳遞一個用戶名和密碼,從連接池中返回可用的PooledConnection
*/
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)
{
// 連接池中有空閑連接,取出第一個
conn = state.idleConnections.remove(0);
if (log.isDebugEnabled())
{
log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
}
}
else
{
// 連接池中沒有空閑連接,則取當前正在使用的連接數(shù)小于最大限定值,
if (state.activeConnections.size() < poolMaximumActiveConnections)
{
// 創(chuàng)建一個新的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 當活動連接池已滿,不能創(chuàng)建時,取出活動連接池的第一個,即最先進入連接池的PooledConnection對象
// 計算它的校驗時間,如果校驗時間大于連接池規(guī)定的最大校驗時間,則認為它已經(jīng)過期了,利用這個PoolConnection內(nèi)部的realConnection重新生成一個PooledConnection
//
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;
}
}
}
}
//如果獲取PooledConnection成功,則更新其信息
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;
}
java.sql.Connection
對象的回收
? 當我們的程序中使用完Connection
對象時,如果不使用數(shù)據(jù)庫連接池,我們一般會調(diào)用 connection.close()
方法,關(guān)閉connection
連接,釋放資源
調(diào)用過close()
方法的Connection
對象所持有的資源會被全部釋放掉,Connection
對象也就不能再使用。那么,如果我們使用了連接池,我們在用完了Connection
對象時,需要將它放在連接池中,該怎樣做呢?
? 可能大家第一個在腦海里閃現(xiàn)出來的想法就是:我在應(yīng)該調(diào)用con.close()
方法的時候,不調(diào)用close()
方法,將其換成將Connection
對象放到連接池容器中的代碼!
怎樣實現(xiàn)Connection
對象調(diào)用了close()
方法,而實際是將其添加到連接池中
? 這是要使用代理模式,為真正的Connection
對象創(chuàng)建一個代理對象,代理對象所有的方法都是調(diào)用相應(yīng)的真正Connection
對象的方法實現(xiàn)。當代理對象執(zhí)行close()
方法時,要特殊處理,不調(diào)用真正Connection
對象的close()
方法,而是將Connection
對象添加到連接池中。
MyBatis
的PooledDataSource
的PoolState
內(nèi)部維護的對象是PooledConnection
類型的對象,而PooledConnection
則是對真正的數(shù)據(jù)庫連接java.sql.Connection
實例對象的包裹器。
PooledConnection
對象內(nèi)持有一個真正的數(shù)據(jù)庫連接java.sql.Connection
實例對象和一個java.sql.Connection
的代理:
其源碼如下:
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 Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
//當close時候,會回收 connection , 不會真正的close
if("close".hashCode() == methodName.hashCode() && "close".equals(methodName)) {
this.dataSource.pushConnection(this);
return null;
} else {
try {
if(!Object.class.equals(method.getDeclaringClass())) {
this.checkConnection();
}
return method.invoke(this.realConnection, args);
} catch (Throwable var6) {
throw ExceptionUtil.unwrapThrowable(var6);
}
}
}
}