Hibernate源码解读--ConnectionProvider源码解读
2012-09-30 00:54
190 查看
转载http://blog.163.com/among_1985/blog/static/2750052320126168394939/
在Hibernate 4.1.4中,其中使用的数据库连接均由ConnectionProvider.getConnection()方法获取。
ConnectionProvider是个接口,其各个子类实现实际数据库连接的获取和释放。在hibernate的框架中ConnectionProvider以及其子类,使用适配器模式,将各种不同类型数据源的适配工作,交给子类进行。类结构如下:
ConnectionProvider
ConnectionProvider是org.hibernate.service.jdbc.connections.spi包中的一个重要接口,它提供了三个接口,如下所示:
DriverManagerConnectionProviderImpl:直接使用DriverManager获取连接,并且只提供很少一点的连接池
UserSuppliedConnectionProviderImpl:由用户自己提供JDBC连接
DatasourceConnectionProviderImplDatasourceConnectionProviderImpl对于DataSource进行了简单的封装,用以提供连接的借用和关闭功能。DatasourceConnectionProviderImpl中,有以下属性:
DriverManagerConnectionProviderImpl是hibernate入门时,使用的第一个ConnectionProvider,我们根据hibernate手册,搭建的第一个基于控制台的hibernate程序,最终使用的ConnectionProvider实现,就是这个类。DriverManagerConnectionProviderImpl实现了最简单的使用数据库DriverClass类来获取连接、同时进行连接释放的方法,与此同时,DriverManagerConnectionProviderImpl还使用一个ArrayList对象,实现了一个最简单的连接池。DriverManagerConnectionProviderImpl 类中,保存了以下属性:
DriverManagerConnectionProviderImpl 与DatasourceConnectionProviderImpl 一样,实现了Configurable接口,意味着它可以在系统初始化时,通过框架回调configure()方法,完成对应内部属性和连接池的初始化。初始化DriverManagerConnectionProviderImpl 的代码如下所示:
在Hibernate 4.1.4中,其中使用的数据库连接均由ConnectionProvider.getConnection()方法获取。
ConnectionProvider是个接口,其各个子类实现实际数据库连接的获取和释放。在hibernate的框架中ConnectionProvider以及其子类,使用适配器模式,将各种不同类型数据源的适配工作,交给子类进行。类结构如下:
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连接
DatasourceConnectionProviderImplDatasourceConnectionProviderImpl对于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,具体的可以参考其源码。
相关文章推荐
- Hibernate源码解读--DialectResolver源码解读
- Hibernate源码解读——查询
- 源码解读心得 - net.sf.hibernate.util.StringHelper
- Hibernate 源码解读
- Log4j2源码解读——删除过期文件
- JVM源码分析之javaagent原理完全解读
- faster rcnn源码解读(四)之数据类型imdb.py和pascal_voc.py(主要是imdb和roidb数据类型的解说)
- Hypertable源码解读之数据写入客户端逻辑
- underscore 源码解读之 bind 方法的实现
- spring beans源码解读之--Bean的注解(annotation)
- 15、Spark Streaming源码解读之No Receivers彻底思考
- JQuery3.1.1源码解读(三)【Sizzle 选择器】
- PhotoSwipe源码解读系列(二)
- JSPatch 源码解读 及使用
- Robotium源码解读-native控件/webview元素的获取和操作
- 解读和分析Linux核心源码的两种方法
- JQuery3.1.1源码解读(九)【prevObject】
- DEDE源码分析与学习之三: member/archives_*.php文件解读
- spring beans源码解读之--XmlBeanFactory
- YYModel 源码解读(二)之YYClassInfo.h (3)