提要:
1.JDBC连接数据库前常见到Class.forName("com.mysql.jdbc.Driver"),为什么要这么一句话?可不可以不要。
2.ibatis使用SqlMapClient时如果要显示使用数据库连接,sqlMap.getCurrentConnection()和sqlMap.getDatasource().getConnection()的区别是什么?
3.通过sqlMap.getDatasource().getConnection()拿到的连接需要close()么,那么sqlMap.getCurrentConnection() 的需要自己close么。
================================================================================
理解:
1.之前学习JDBC时总会遇到在获取连接前使用Class.form(#Driver#)的语句,知道是将Driver注册到DriverManager里,但一直不明白其原理。因为Class.forName的返回值并没有被使用。后来看了Driver里的代码才明白原理。
关键的一句是:
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
可以见到在static块中,Driver将自己注册到了DriverManager中,因此只要Driver被加载就能完成此操作。这就是为啥要用Class.formName来注册了。
那么可以不可以不要这句话呢,我试了下,jdk1.7是可以的,这又是为什么呢?
这个归功SPI,一种服务发现机制,这个代码在DriverManager的loadInitialDrivers方法中
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
println(" Loading done by the java.util.ServiceLoader : "+driversIterator.next());
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
ServiceLoader就是SPI的关键,他通过搜索classpath下的jar包里的META-INF/services/下面的文件文本文件,文件名是接口名,文件内容为实现类的className,然后通过文件内容找到对应接口的实现类进行加载,这样就完成了自动加载。这种服务发现的机制真的很精妙,各个厂商按一定标准完成自己的Driver,使用者只需要将jar包加到classpath下,DriverManager使用SPI加载驱动,没有一句硬编码。当然这个也得益于DriverManager对桥接模式的使用。也就是将行为进行抽象,面向接口编程。
2.之前在集成了ibatis的项目里需要拿到Connection执行单独的sql语句,然后发现sqlMapClient拿到连接有两种方式,一个是API sqlMapClient.getCurrentConnection(),一个是使用Datasource,sqlMapClient.getDatasource().getConnection(),我到底使用哪个呢?
其实直接使用sqlMapClient.getCurrentConnection()返回的是NULL,那么这个Current是什么意思呢?
实际上current是获取当前线程启动的事务里创建的连接,
要先调用sqlMapClient.startTransaction(),那么getCurrentConnection()才会有值。
SqlMapSessionImpl
public Connection getCurrentConnection() throws SQLException {
try {
Connection conn = null;
Transaction trans = delegate.getTransaction(sessionScope);
if (trans != null) {
conn = trans.getConnection();
}
return conn;
} catch (TransactionException e) {
throw new NestedSQLException("Error getting Connection from Transaction. Cause: " + e, e);
}
}
可以看到其实是获取到Transaction中的Connection,如果没有事务,那么con为NULL,因此getCurrentConnection是在事务期间获取当前事务连接的方法。
ps,其实sqlMap会为每次查询创建事务,如果你没有显示启动事务,即使是select语句,
如SqlMapExecutorDelegate
public Object queryForObject(SessionScope sessionScope, String id, Object paramObject, Object resultObject) throws SQLException {
Object object = null;
MappedStatement ms = getMappedStatement(id);
Transaction trans = getTransaction(sessionScope);
boolean autoStart = trans == null;
try {
trans = autoStartTransaction(sessionScope, autoStart, trans);
StatementScope statementScope = beginStatementScope(sessionScope, ms);
try {
object = ms.executeQueryForObject(statementScope, trans, paramObject, resultObject);
} finally {
endStatementScope(statementScope);
}
autoCommitTransaction(sessionScope, autoStart);
} finally {
autoEndTransaction(sessionScope, autoStart);
}
return object;
}
可以看到,如果transaction是空的,sqlMap会显示开启一个transaction。因此为了避免开启事务,我选择了sqlMapClient.getDatasource().getConnection().通过数据源BaiscDatasource显示拿到Connection,那又有一个问题了,拿到这个Connection我是否需要把他关闭掉呢?
3.这个自然就联想到了,ibatis,我们在使用sqlMap时并没有释放连接的动作。其实他是在模板模式里释放了,然后看了下Datasource的接口
public interface DataSource extends CommonDataSource,Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
也没有releaseConnection的操作,难道通过Datasource拿连接不需要release么。
实际上不是的,我做了下试验,手动创建一个BasicDatasource,然后调用getConnection(),在第9次时发生阻塞了,说明已经没有可用的连接了,所以我们需要使用connection.close()方法来释放连接的。那么close()到底是关闭连接还是释放连接呢。实际上是释放连接,连接并不会关闭。
其实BaiscDatasource里对Connection进行了包装,使用PoolableConnectionFactory来创建PoolableConnection:
public Object makeObject() throws Exception {
Connection conn = _connFactory.createConnection();
if (conn == null) {
throw new IllegalStateException("Connection factory returned null from createConnection");
}
initializeConnection(conn);
if(null != _stmtPoolFactory) {
KeyedObjectPool stmtpool = _stmtPoolFactory.createPool();
conn = new PoolingConnection(conn,stmtpool);
stmtpool.setFactory((PoolingConnection)conn);
}
return new PoolableConnection(conn,_pool,_config);
}
其中_connectionFactory是在前面创建的DriverConnectionFactory,实现直接调用Driver获取原始的Connection,然后在return时包装为PoolableConnection。
所以让我们看看PoolableConnection的close方法:
if (!isUnderlyingConectionClosed) {
// Normal close: underlying connection is still open, so we
// simply need to return this proxy to the pool
try {
_pool.returnObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException e) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw (SQLException) new SQLException("Cannot close connection (return to pool failed)").initCause(e);
}
} else {
// Abnormal close: underlying connection closed unexpectedly, so we
// must destroy this proxy
try {
_pool.invalidateObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException e) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING, "Already closed" exception thrown below
}
throw new SQLException("Already closed.");
}
可以看到如果连接没有被强制关闭,那么PoolConnecion是将连接释放会connectionPool,如果连接已经是关闭的,则把连接从Pool里失效掉,在下次获取时,新的连接将会被创建。保证Pool里有足够多的ActiveConnection(默认是8个)。
那么SqlMap是在哪里关闭连接的呢?实际上是在endTransaction()里,
摘自ExternalTransaction
public void close() throws SQLException, TransactionException {
if (connection != null) {
try {
isolationLevel.restoreIsolationLevel(connection);
} finally {
connection.close();
connection = null;
}
}
}
Finish