szgzwf

http://www.etsec.com.cn

导航

Seasar底层对JDBC操作的支持

Seasar底层对JDBC操作的支持

1. 基础知识准备

这篇文章其实是想说明以下几个技术问题:

A. web项目connection数据库的roothttp request -> serlvet -> class instance -> connection pool -> tcp connection -> DB

B. Connection pool 的生存周期是如何的, conncetion pool instance 的内存空间在哪里?

 

选用Seasar框架,是因为最近一直在接触它, 但是道理是普适的, spring也类似。

和大多数框架一样,可以选择把connection pool的配置和管理放在WEB容器中,但是这里只谈在框架中做的情况。

和大多数框架也一样,可以支持jdbc事务,也可以支持JTA事务,这里只谈JDBC

2. 从哪里开始呢

MVC框架暂时先省去不谈,我们直接到访问数据库层的service

spring类似,从抽象的service的基类S2AbstractService<T>中继承而来

然后就能直接执行如下代码,访问DB

  1. return jdbcManager.selectBySqlFile( 
  2.                 XXXXDto.class
  3.                 "XXX.sql"
  4.                     param).getResultList(); 
return jdbcManager.selectBySqlFile(
                XXXXDto.class,
                "XXX.sql",
	                param).getResultList();

这里的jdbcManager来源于S2AbstractService<T>中的被注入的变量

这里的注入的信息来源于<s2jdbc.dicon>中如下的配置

  1. <componentname="jdbcManager"class="org.seasar.extension.jdbc.manager.JdbcManagerImpl"> 
  2.         <propertyname="maxRows">0</property> 
  3.         <propertyname="fetchSize">0</property> 
  4.         <propertyname="queryTimeout">0</property> 
  5.         <propertyname="dialect">mysqlDialect</property> 
  6.         </component> 
<component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl">
		<property name="maxRows">0</property>
		<property name="fetchSize">0</property>
		<property name="queryTimeout">0</property>
		<property name="dialect">mysqlDialect</property>
		</component>

 

关于如何注入,请看如下文章

http://blog.csdn.net/nanjingjiangbiao/article/details/8207601

 

上面这段执行sql的代码,首先是get到了一个封装好的sql执行instance

jdbcManagerImpl中,有如下方法

  1. public <T> SqlFileSelect<T> selectBySqlFile(Class<T> baseClass, 
  2.             String path, Object parameter) { 
  3.         returnnew SqlFileSelectImpl<T>(this, baseClass, path, parameter) 
  4.                 .maxRows(maxRows).fetchSize(fetchSize).queryTimeout( 
  5.                         queryTimeout); 
  6.         } 
public <T> SqlFileSelect<T> selectBySqlFile(Class<T> baseClass,
            String path, Object parameter) {
        return new SqlFileSelectImpl<T>(this, baseClass, path, parameter)
                .maxRows(maxRows).fetchSize(fetchSize).queryTimeout(
                        queryTimeout);
	    }

 

这里大量运用了模板技术,构造并且返回了具体的实现类SqlFileSelectImpl最关键的,构造的时候,把jdbcManagerImpl对象本身传进去

 

回过头来看JdbcManagerImpl里面有一个如下的变量

  1. protected DataSource dataSource; 
	protected DataSource dataSource;

这里的dataSource来源于jdbc.dicon中的如下的注入

  1. <componentname="dataSource"class="org.seasar.extension.dbcp.impl.DataSourceImpl"/> 
	<component name="dataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>

 

DataSourceImpl中,有如下的一个变量

  1. private ConnectionPool connectionPool; 
	private ConnectionPool connectionPool;

它来源于如下的注入

  1. <componentname="connectionPool"class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl"> 
  2.       <propertyname="timeout">600</property> 
  3.       <propertyname="maxPoolSize">0</property> 
  4.       <propertyname="allowLocalTx">true</property> 
  5.       <propertyname="transactionIsolationLevel">@java.sql.Connection@TRANSACTION_READ_COMMITTED</property> 
  6.       <!-- 
  7.       <propertyname="validationQuery">"select 1"</property> 
  8.       <propertyname="validationInterval">10000</property> 
  9.       --> 
  10.       <destroyMethodname="close"/> 
  11.    </component> 
  <component name="connectionPool" class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
        <property name="timeout">600</property>
        <property name="maxPoolSize">0</property>
        <property name="allowLocalTx">true</property>
        <property name="transactionIsolationLevel">@java.sql.Connection@TRANSACTION_READ_COMMITTED</property>
        <!--
        <property name="validationQuery">"select 1"</property>
        <property name="validationInterval">10000</property>
        -->
        <destroyMethod name="close"/>
	    </component>

 

看到了没有,层层的注入,在这里注入了ConnectionPool

同样,我们打开ConnectionPool,在里面看到如下变量

  1. private XADataSource xaDataSource; 
	private XADataSource xaDataSource;

这里是真正的datasource connection data被注入进去了

  1. <componentname="xaDataSource"class="org.seasar.extension.dbcp.impl.XADataSourceImpl"> 
  2.        <propertyname="driverClassName">"com.mysql.jdbc.Driver"</property> 
  3.        <propertyname="URL"> 
  4.            "jdbc:mysql://xxxxx/xxxx?characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull
  5.        </property> 
  6.        <propertyname="user">"root"</property> 
  7.        <propertyname="password">"123456"</property> 
  8.     </component> 
 <component name="xaDataSource" class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
        <property name="driverClassName">"com.mysql.jdbc.Driver"</property>
        <property name="URL">
            "jdbc:mysql://xxxxx/xxxx?characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull"
        </property>
        <property name="user">"root"</property>
        <property name="password">"123456"</property>
	    </component>

本文作者蒋彪 原发于http://blog.csdn.net/nanjingjiangbiao 转载请注明

 

3. SQL的执行和connectionPool的怒放

真正的sql的执行发生在AbstactSelect

  1. public List<T> getResultList() { 
  2.         prepare("getResultList"); 
  3.         logSql(); 
  4.         try
  5.             return getResultListInternal(); 
  6.         } finally
  7.             completed(); 
  8.         } 
  9.     } 
  10.      
  11.     => 
  12. protected T getSingleResultInternal() { 
  13.         ResultSetHandler handler = createSingleResultResultSetHandler(); 
  14.         T ret = null
  15.         JdbcContext jdbcContext = jdbcManager.getJdbcContext(); 
  16.         try
  17.             ret = (T) processResultSet(jdbcContext, handler); 
  18.             if (disallowNoResult && ret == null) { 
  19.                 thrownew SNoResultException(executedSql); 
  20.             } 
  21.         } finally
  22.             if (!jdbcContext.isTransactional()) { 
  23.                 jdbcContext.destroy(); 
  24.             } 
  25.         } 
  26.         return ret; 
  27.     } 
public List<T> getResultList() {
        prepare("getResultList");
        logSql();
        try {
            return getResultListInternal();
        } finally {
            completed();
        }
	}
	
	=>
protected T getSingleResultInternal() {
        ResultSetHandler handler = createSingleResultResultSetHandler();
        T ret = null;
        JdbcContext jdbcContext = jdbcManager.getJdbcContext();
        try {
            ret = (T) processResultSet(jdbcContext, handler);
            if (disallowNoResult && ret == null) {
                throw new SNoResultException(executedSql);
            }
        } finally {
            if (!jdbcContext.isTransactional()) {
                jdbcContext.destroy();
            }
        }
        return ret;
    }

 

上面吸引我的是如下的代码

  1. JdbcContext jdbcContext = jdbcManager.getJdbcContext(); 
	JdbcContext jdbcContext = jdbcManager.getJdbcContext();

这里拿到了jdbc上下文,具体的代码如下

  1. public JdbcContext getJdbcContext() { 
  2.         JdbcContext ctx = getTxBoundJdbcContext(); 
  3.         if (ctx != null) { 
  4.             return ctx; 
  5.         } 
  6.         Connection con = DataSourceUtil.getConnection(dataSource); 
  7.         if (hasTransaction()) { 
  8.             ctx = createJdbcContext(con, true); 
  9.             setTxBoundJdbcContext(ctx); 
  10.         } else
  11.             ctx = createJdbcContext(con, false); 
  12.         } 
  13.         return ctx; 
  14.         } 
public JdbcContext getJdbcContext() {
        JdbcContext ctx = getTxBoundJdbcContext();
        if (ctx != null) {
            return ctx;
        }
        Connection con = DataSourceUtil.getConnection(dataSource);
        if (hasTransaction()) {
            ctx = createJdbcContext(con, true);
            setTxBoundJdbcContext(ctx);
        } else {
            ctx = createJdbcContext(con, false);
        }
        return ctx;
	    }

 

首先,如下第一行代码,拿到了与当前事务绑定的jdbc上下文, 在web环境下,也就是当前http请求中开启的事务中绑定的jdbc上下文

  1. JdbcContext ctx = getTxBoundJdbcContext(); 
  2. => 
  3. protected JdbcContext getTxBoundJdbcContext() { 
  4.         if (hasTransaction()) { 
  5.             final JdbcContextRegistryKey key = createJdbcContextRegistryKey(); 
  6.             final JdbcContext ctx = JdbcContext.class.cast(syncRegistry 
  7.                     .getResource(key)); 
  8.             if (ctx != null) { 
  9.                 return ctx; 
  10.             } 
  11.         } 
  12.         returnnull
  13.     } 
JdbcContext ctx = getTxBoundJdbcContext();
=>
protected JdbcContext getTxBoundJdbcContext() {
        if (hasTransaction()) {
            final JdbcContextRegistryKey key = createJdbcContextRegistryKey();
            final JdbcContext ctx = JdbcContext.class.cast(syncRegistry
                    .getResource(key));
            if (ctx != null) {
                return ctx;
            }
        }
        return null;
	}

 

具体的做法是,做一个线程安全的HashMap, 然后根据key(datasourcename),存入当前事务的jdbc上下文, 下次再进来的时候,根据key拿出, 事务结束之后,根据key clear掉

接下来是拿Connection,具体的逻辑发生在DataSourceImpl中

  1. //线程安全  
  2.   publicsynchronized ConnectionWrapper checkOut() throws SQLException { 
  3. //拿到当前thread对应的事务,通过threadLocal对象存储  
  4.        Transaction tx = getTransaction(); 
  5.        if (tx == null && !isAllowLocalTx()) { 
  6.            thrownew SIllegalStateException("ESSR0311", null); 
  7.        } 
  8.  
  9. //取当前事务绑定的connection,如果有的话  
  10.        ConnectionWrapper con = getConnectionTxActivePool(tx); 
  11.        if (con != null) { 
  12.            if (logger.isDebugEnabled()) { 
  13.                logger.log("DSSR0007", new Object[] { tx }); 
  14.            } 
  15.            return con; 
  16.        } 
  17. //如果所有的connection pool都被用光了,就等待,超过等待时间就抛出异常  
  18.        long wait = maxWait; 
  19.        while (getMaxPoolSize() > 0 
  20.                && getActivePoolSize() + getTxActivePoolSize() >= getMaxPoolSize()) { 
  21.            if (wait == 0L) { 
  22.                thrownew SSQLException("ESSR0104", null); 
  23.            } 
  24.            finallong startTime = System.currentTimeMillis(); 
  25.            try
  26.                wait((maxWait == -1L) ? 0L : wait); 
  27.            } catch (InterruptedException e) { 
  28.                thrownew SSQLException("ESSR0104", null, e); 
  29.            } 
  30.            finallong elapseTime = System.currentTimeMillis() - startTime; 
  31.            if (wait > 0L) { 
  32.                wait -= Math.min(wait, elapseTime); 
  33.            } 
  34.        } 
  35. //根据当前的事务,从free connection pool中拿出一个connection  
  36.        con = checkOutFreePool(tx); 
  37.        if (con == null) { 
  38.            // 如果取不到,直接从XaDataSource中拿connection   
  39.            con = createConnection(tx); 
  40.        } 
  41.        if (tx == null) { 
  42.            //在没有事务的情况下,将这个con放入activePool  
  43.            setConnectionActivePool(con); 
  44.        } else
  45.            //如果有事务,则将该事务和conn绑定到txActivePool  
  46.            TransactionUtil.enlistResource(tx, con.getXAResource()); 
  47.            TransactionUtil.registerSynchronization(tx, 
  48.                    new SynchronizationImpl(tx)); 
  49.            setConnectionTxActivePool(tx, con); 
  50.        } 
  51.        con.setReadOnly(readOnly); 
  52.        if (transactionIsolationLevel != DEFAULT_TRANSACTION_ISOLATION_LEVEL) { 
  53.            con.setTransactionIsolation(transactionIsolationLevel); 
  54.        } 
  55.        if (logger.isDebugEnabled()) { 
  56.            logger.log("DSSR0007", new Object[] { tx }); 
  57.        } 
  58.        return con; 
  59.     } 
	//线程安全
   public synchronized ConnectionWrapper checkOut() throws SQLException {
	//拿到当前thread对应的事务,通过threadLocal对象存储
        Transaction tx = getTransaction();
        if (tx == null && !isAllowLocalTx()) {
            throw new SIllegalStateException("ESSR0311", null);
        }

	//取当前事务绑定的connection,如果有的话
        ConnectionWrapper con = getConnectionTxActivePool(tx);
        if (con != null) {
            if (logger.isDebugEnabled()) {
                logger.log("DSSR0007", new Object[] { tx });
            }
            return con;
        }
	//如果所有的connection pool都被用光了,就等待,超过等待时间就抛出异常
        long wait = maxWait;
        while (getMaxPoolSize() > 0
                && getActivePoolSize() + getTxActivePoolSize() >= getMaxPoolSize()) {
            if (wait == 0L) {
                throw new SSQLException("ESSR0104", null);
            }
            final long startTime = System.currentTimeMillis();
            try {
                wait((maxWait == -1L) ? 0L : wait);
            } catch (InterruptedException e) {
                throw new SSQLException("ESSR0104", null, e);
            }
            final long elapseTime = System.currentTimeMillis() - startTime;
            if (wait > 0L) {
                wait -= Math.min(wait, elapseTime);
            }
        }
	//根据当前的事务,从free connection pool中拿出一个connection
        con = checkOutFreePool(tx);
        if (con == null) {
            // 如果取不到,直接从XaDataSource中拿connection 
            con = createConnection(tx);
        }
        if (tx == null) {
            //在没有事务的情况下,将这个con放入activePool
            setConnectionActivePool(con);
        } else {
            //如果有事务,则将该事务和conn绑定到txActivePool
            TransactionUtil.enlistResource(tx, con.getXAResource());
            TransactionUtil.registerSynchronization(tx,
                    new SynchronizationImpl(tx));
            setConnectionTxActivePool(tx, con);
        }
        con.setReadOnly(readOnly);
        if (transactionIsolationLevel != DEFAULT_TRANSACTION_ISOLATION_LEVEL) {
            con.setTransactionIsolation(transactionIsolationLevel);
        }
        if (logger.isDebugEnabled()) {
            logger.log("DSSR0007", new Object[] { tx });
        }
        return con;
	    }

 

4. connectionPool的算法

ConnectionPoolImpl中实现了以下三个数据结构

  1. //用来存放无事务的被使用的conn  
  2.         private Set activePool = new HashSet(); 
  3. //用来存放有事务的被使用的conn  
  4.     private Map txActivePool = new HashMap(); 
  5. //用来存放空闲conn的单链表  
  6.     private SLinkedList freePool = new SLinkedList(); 
	//用来存放无事务的被使用的conn
 		private Set activePool = new HashSet();
	//用来存放有事务的被使用的conn
    	private Map txActivePool = new HashMap();
	//用来存放空闲conn的单链表
	    private SLinkedList freePool = new SLinkedList();

 

和有些框架不同, Seasar并没有随着容器的启动, 就把所有的connection初期化保存起来,而是通过如下的算法分配

A. 第一次发生conn请求,发现activePooltxActivePoolfreePool三个数据结构都是null, 用如下代码新建conn

  1. XAConnection xaConnection = xaDataSource.getXAConnection(); 
  2.         Connection connection = xaConnection.getConnection(); 
  3.         ConnectionWrapper con = new ConnectionWrapperImpl(xaConnection, 
  4.                     connection, this, tx); 
XAConnection xaConnection = xaDataSource.getXAConnection();
        Connection connection = xaConnection.getConnection();
        ConnectionWrapper con = new ConnectionWrapperImpl(xaConnection,
	                connection, this, tx);

 

B. 接下来,根据是否有事务,把该conn存入activePool 或者txActivePool 中,如果同一个事务再次请求conn,就从txActivePool 中取得

C. connection 结束的时候, conn的结束一般发生在事务的提交,由框架决定

会到conn的包装类ConnectionWrapperImpl中执行以下方法

  1. publicvoid close() throws SQLException { 
  2.         if (closed_) { 
  3.             return
  4.         } 
  5.         if (logger_.isDebugEnabled()) { 
  6.             logger_.log("DSSR0002", new Object[] { tx_ }); 
  7.         } 
  8.         if (tx_ == null) { 
  9.             connectionPool_.checkIn(this); 
  10.         } else
  11.             connectionPool_.checkInTx(tx_); 
  12.         } 
  13.     } 
public void close() throws SQLException {
        if (closed_) {
            return;
        }
        if (logger_.isDebugEnabled()) {
            logger_.log("DSSR0002", new Object[] { tx_ });
        }
        if (tx_ == null) {
            connectionPool_.checkIn(this);
        } else {
            connectionPool_.checkInTx(tx_);
        }
	}

 

这里的所谓的checkin,就是指从activePool 或者txActivePool 中删除conn,并且把该conn包装之后塞入freePool

也就是说freePool 是随着程序的运行,线形填充的

5. connectionPoolWeb线程安全

所有用DI容器的web项目都是这样,无论spring还是seasar:

1. 当web容器启动的时候,你本工程里所有的instance(包括jdbc操作)就已经在单例的模式下完成了初期化,并且被保存在JVM(tomcat)的堆里

2. http request发起之后, web容器中生成了专门的thread,用来处理这个request。这个thead会到容器中找到url目的地的Java instance chain ,然后读入到当前thread的栈内存中执行

3. 当前threadjava instance chain,如果一直执行,最后在connection pool 中拿connection 。 关键点就在于当前connection pool是线程安全的吗?

searsar框架中,connection pool是在ConnectionPoolImpl中实现的,这个类默认是以单例模式实例化。

也就是说,多个http 处理thread其实,是读取共用一个ConnectionPoolImplinstance

ConnectionPoolImpl的实现中,可以看到所有对conn pool的操作都加了线程锁。

因此,conn pool DI容器中是绝对的线程安全。

#以上#

posted on 2012-12-01 21:00  szgzwf  阅读(951)  评论(0)    收藏  举报