zoukankan      html  css  js  c++  java
  • 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,具体的可以参考其源码。
  • 相关阅读:
    Java 读取大容量excel
    Linux 安装mysql
    Linux 配置nginx
    java 微信H5支付
    微信公众号授权登录两种方式
    Jsoup 获取页面返回的table中的内容
    Python Model执行迁移数据库失败
    java上传txt文件,出现中文乱码
    在Window环境下,使用Django shell 命令查询数据库
    Java模拟form表单提交普通参数和文件
  • 原文地址:https://www.cnblogs.com/chenying99/p/2709031.html
Copyright © 2011-2022 走看看