转载http://blog.163.com/among_1985/blog/static/2750052320126168394939/
在Hibernate 4.1.4中,其中使用的数据库连接均由ConnectionProvider.getConnection()方法获取。
public interface ConnectionProvider extends Service, Wrapped {
/**
* 获取连接,通常是从连接池中借出连接
*/
public Connection getConnection() throws SQLException;
/**
* 释放连接* 这里不直接嗲用 conn.close() 方法,因为在归还连接时,其实现类可能需要进行一些处理
*/
public void closeConnection(Connection conn) throws SQLException;
/**
* 这个方法目前没看明白,后面看懂了再补充
*/
public boolean supportsAggressiveRelease();
}
同时, DatasourceConnectionProviderImpl 实现了Configurable接口,用于在系统初始化时,完成ConnectionProvider的初始化,如下所示:/**
* DataSource对象
*/
private DataSource dataSource;
/*
* 连接数据源的用户名和密码
*/
private String user;
private String pass;
/*
* 从数据源中取得新连接是否需要用户名和密码
*/
private boolean useCredentials;
/*
* 获取JNDI全局参数的接口,用于从JNDI中获取数据源
*/
private JndiService jndiService;
/** 表示当前数据源是否可用
*/
private boolean available;
DatasourceConnectionProviderImpl 直接代理了从DataSource中获取Connection对象的方法,如下所示;public void configure(Map configValues) {
/* 同一个数据源,不会多次初始化 */
if ( this.dataSource == null ) {
final Object dataSource = configValues.get( Environment.DATASOURCE );
if ( DataSource.class.isInstance( dataSource ) ) {/*
* 如果当前Environment.DATASOURCE属性获取到的值,已经是数据源了,那么直接赋值
* 从目前代码来看,这个地方可能需要通过EL或代码的方式注入
*/
this.dataSource = (DataSource) dataSource;
}
else {/* 这里是一般代码走的流程,通过jndi名称,获取对应的数据源 */
final String dataSourceJndiName = (String) dataSource;
if ( dataSourceJndiName == null ) {
throw new HibernateException(
"DataSource to use was not injected nor specified by [" + Environment.DATASOURCE
+ "] configuration property"
);
}
if ( jndiService == null ) {
throw new HibernateException( "Unable to locate JndiService to lookup Datasource" );
}/* 从jndi中获取数据源 */
this.dataSource = (DataSource) jndiService.locate( dataSourceJndiName );
}
}
if ( this.dataSource == null ) {
throw new HibernateException( "Unable to determine appropriate DataSource to use" );
}
user = (String) configValues.get( Environment.USER );
pass = (String) configValues.get( Environment.PASS );/* 当用户名和密码都非空时,认为获取连接需要校验用户信息 */
useCredentials = user != null || pass != null;
available = true;
}
DriverManagerConnectionProviderImplpublic Connection getConnection() throws SQLException {
if ( !available ) {
throw new HibernateException( "Provider is closed!" );
}return useCredentials ? dataSource.getConnection( user, pass ) : dataSource.getConnection();
}
public void closeConnection(Connection connection) throws SQLException {
connection.close();
}
private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, DriverManagerConnectionProviderImpl.class.getName() );
/* 数据库连接URL */
private String url;
/* 数据库连接参数 */
private Properties connectionProps;
/*
* 数据库事务隔离级别,是int类型的变量
* 其语义就是java.sql.Connection接口中定义的几种事务隔离级别。
*/
private Integer isolation;
/* 内部连接池大小,如果不配置,默认就是20,在hibernate的第一个例子中,将其大小配置为1 */
private int poolSize;
/* 是否自动进行事务提交,默认为false */
private boolean autocommit;
/* 使用ArrayList实现的内部最简单的连接池 */private final ArrayList<Connection> pool = new ArrayList<Connection>();
/* 记录当前已借出连接的大小 */
private int checkedOut = 0;
/* ConnectionProvider是否已经停止 */
private boolean stopped;
private transient ServiceRegistryImplementor serviceRegistry;
DriverManagerConnectionProviderImpl 借出连接时,会根据内部连接池的状态,判断是否创建新的连接,代码如下:public void configure(Map configurationValues) {
LOG.usingHibernateBuiltInConnectionPool();
String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER );
if ( driverClassName == null ) {/* 如果没有配置driverClass,走到这里,个人感觉这里应该直接抛异常的 */
LOG.jdbcDriverNotSpecified( AvailableSettings.DRIVER );
}
else if ( serviceRegistry != null ) {
try {/* 我跟踪代码时,初始化DriverClass是走到这里,目前还不知道ClassLoaderService这个东西,是从什么地方注入进来的 */
serviceRegistry.getService( ClassLoaderService.class ).classForName( driverClassName );
}
catch ( ClassLoadingException e ) {
throw new ClassLoadingException(
"Specified JDBC Driver " + driverClassName + " class not found",
e
);
}
}
/* 目前看来,下面的代码走不到的 */
else {
try {
// trying via forName() first to be as close to DriverManager's semantics
Class.forName( driverClassName );
}
catch ( ClassNotFoundException cnfe ) {
try{
ReflectHelper.classForName( driverClassName );
}
catch ( ClassNotFoundException e ) {
throw new HibernateException( "Specified JDBC Driver " + driverClassName + " class not found", e );
}
}
}
poolSize = ConfigurationHelper.getInt( AvailableSettings.POOL_SIZE, configurationValues, 20 ); // default pool size 20
LOG.hibernateConnectionPoolSize(poolSize);
autocommit = ConfigurationHelper.getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues );
LOG.autoCommitMode( autocommit );
isolation = ConfigurationHelper.getInteger( AvailableSettings.ISOLATION, configurationValues );
if (isolation != null) LOG.jdbcIsolationLevel(Environment.isolationLevelToString(isolation.intValue()));
url = (String) configurationValues.get( AvailableSettings.URL );
if ( url == null ) {
String msg = LOG.jdbcUrlNotSpecified(AvailableSettings.URL);
LOG.error(msg);
throw new HibernateException( msg );
}
/* ConnectionProviderInitiator.getConnectionProperties看下这个方法的实现,就知道与连接相关的参数,是怎么被读取和注入的了 */connectionProps = ConnectionProviderInitiator.getConnectionProperties( configurationValues );
LOG.usingDriver( driverClassName, url );
// if debug level is enabled, then log the password, otherwise mask it
if ( LOG.isDebugEnabled() )
LOG.connectionProperties( connectionProps );
else
LOG.connectionProperties( ConfigurationHelper.maskOut( connectionProps, "password" ) );
}
public Connection getConnection() throws SQLException {
LOG.tracev( "Total checked-out connections: {0}", checkedOut );
/* 如果当前连接池非空,就从连接池中取出一个连接,注意,这里没有对连接做任何有效性校验 */
synchronized (pool) {
if ( !pool.isEmpty() ) {
int last = pool.size() - 1;
LOG.tracev( "Using pooled JDBC connection, pool size: {0}", last );
Connection pooled = pool.remove( last );
if ( isolation != null ) {
pooled.setTransactionIsolation( isolation.intValue() );
}
if ( pooled.getAutoCommit() != autocommit ) {
pooled.setAutoCommit( autocommit );
}
checkedOut++;
return pooled;
}
}
/** 如果连接池中没有连接可用,就新建一个连接,这在高压力的情况下,会导致系统创建无数个连接,最终耗尽资源。
* 所以这个ConnectionProvider不能作为生产环境使用,只能在学习过程中使用
*/
LOG.debug( "Opening new JDBC connection" );
Connection conn = DriverManager.getConnection( url, connectionProps );
if ( isolation != null ) {
conn.setTransactionIsolation( isolation.intValue() );
}
if ( conn.getAutoCommit() != autocommit ) {
conn.setAutoCommit(autocommit);
}
if ( LOG.isDebugEnabled() ) {
LOG.debugf( "Created connection to: %s, Isolation Level: %s", url, conn.getTransactionIsolation() );
}
checkedOut++;
return conn;
}
public void closeConnection(Connection conn) throws SQLException {
checkedOut--;
/** 如果内部缓冲区有空位,将归还的连接加入其中
* 这里没有对连接进行任何有效性校验,也没有对连接进行任何包装,导致即使调用这个方法,外部仍然可以正常使用conn对象,如果对象再次被借出,同时两个连接使用,那么会导致并发问题。这点也导致此ConnectionProvider不能作为生产环境使用。
*/
synchronized (pool) {
int currentSize = pool.size();
if ( currentSize < poolSize ) {
LOG.tracev( "Returning connection to pool, pool size: {0}", ( currentSize + 1 ) );
pool.add(conn);
return;
}
}
LOG.debug( "Closing JDBC connection" );
conn.close();
}