Hibernate源码解读ConnectionProvider源码解读

转载http://blog.163.com/among_1985/blog/static/2750052320126168394939/ 
在Hibernate 4.1.4中,其中使用的数据库连接均由ConnectionProvider.getConnection()方法获取。

ConnectionProvider是个接口,其各个子类实现实际数据库连接的获取和释放。在hibernate的框架中ConnectionProvider以及其子类,使用适配器模式,将各种不同类型数据源的适配工作,交给子类进行。
类结构如下:
Hibernate源码解读--ConnectionProvider源码解读 - among_1985 - 我的梦想家园
 
ConnectionProvider 
ConnectionProvider是org.hibernate.service.jdbc.connections.spi包中的一个重要接口,它提供了三个接口,如下所示:

public interface ConnectionProvider extends Service, Wrapped {
/**
* 获取连接,通常是从连接池中借出连接
*/
public Connection getConnection() throws SQLException;

/**
* 释放连接

* 这里不直接嗲用 conn.close() 方法,因为在归还连接时,其实现类可能需要进行一些处理
*/
public void closeConnection(Connection conn) throws SQLException;

/**
* 这个方法目前没看明白,后面看懂了再补充
*/
public boolean supportsAggressiveRelease();
}

在hibernate的核心代码中,上述ConnectionProvider接口有以下三个实现:
  • DatasourceConnectionProviderImpl:封装DataSource的ConnectionProvider
  • DriverManagerConnectionProviderImpl:直接使用DriverManager获取连接,并且只提供很少一点的连接池
  • UserSuppliedConnectionProviderImpl:由用户自己提供JDBC连接
DatasourceConnectionProviderImpl
DatasourceConnectionProviderImpl对于DataSource进行了简单的封装,用以提供连接的借用和关闭功能。
DatasourceConnectionProviderImpl中,有以下属性:

/**

* DataSource对象

*/

private DataSource dataSource;


/*

* 连接数据源的用户名和密码

*/
private String user;
private String pass;


/*

* 从数据源中取得新连接是否需要用户名和密码

*/
private boolean useCredentials;

 

/*

* 获取JNDI全局参数的接口,用于从JNDI中获取数据源

*/
private JndiService jndiService;

/*

* 表示当前数据源是否可用

*/
private boolean available;

同时, DatasourceConnectionProviderImpl 实现了Configurable接口,用于在系统初始化时,完成ConnectionProvider的初始化,如下所示:

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;
}

DatasourceConnectionProviderImpl 直接代理了从DataSource中获取Connection对象的方法,如下所示;

public 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();
}

DriverManagerConnectionProviderImpl
DriverManagerConnectionProviderImpl是hibernate入门时,使用的第一个ConnectionProvider,我们根据hibernate手册,搭建的第一个基于控制台的hibernate程序,最终使用的ConnectionProvider实现,就是这个类。
DriverManagerConnectionProviderImpl实现了最简单的使用数据库DriverClass类来获取连接、同时进行连接释放的方法,与此同时,DriverManagerConnectionProviderImpl还使用一个ArrayList对象,实现了一个最简单的连接池。
DriverManagerConnectionProviderImpl 类中,保存了以下属性:

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 与DatasourceConnectionProviderImpl 一样,实现了Configurable接口,意味着它可以在系统初始化时,通过框架回调configure()方法,完成对应内部属性和连接池的初始化。
初始化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" ) );
}

DriverManagerConnectionProviderImpl 借出连接时,会根据内部连接池的状态,判断是否创建新的连接,代码如下:

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;
}

DriverManagerConnectionProviderImpl 借出连接时,会根据内部连接池的状态,判断是否将连接加入连接池,代码如下:

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();
}

UserSuppliedConnectionProviderImpl
根据UserSuppliedConnectionProviderImpl类的描述,这个类是在没有提供任何ConnectionProvider的情况下使用的,目前没看明白它干什么用户,暂时先不进行分析。
其他问题
1. hibernate根据什么配置,决定初始化哪个ConnectionProvider?
答:hibernate中,有一个ConnectionProviderInitiator类,专门用来初始化ConnectionProvider。例如:当配置文件中有“connection.url”时,创建DriverManagerConnectionProviderImpl,具体的可以参考其源码。
原文地址:https://www.cnblogs.com/chenying99/p/2709031.html