Seasar底层对JDBC操作的支持

Seasar底层对JDBC操作的支持

1. 基础知识准备

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

A. web项目connection数据库的root是 http 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

[java] view plain copy print ?
  1. return jdbcManager.selectBySqlFile(  
  2.                 XXXXDto.class,  
  3.                 "XXX.sql",  
  4.                     param).getResultList();  

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

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

[html] view plain copy print ?
  1. <component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl">  
  2.         <property name="maxRows">0</property>  
  3.         <property name="fetchSize">0</property>  
  4.         <property name="queryTimeout">0</property>  
  5.         <property name="dialect">mysqlDialect</property>  
  6.         </component>  


 

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

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

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

jdbcManagerImpl中,有如下方法

[java] view plain copy print ?
  1. public <T> SqlFileSelect<T> selectBySqlFile(Class<T> baseClass,  
  2.             String path, Object parameter) {  
  3.         return new SqlFileSelectImpl<T>(this, baseClass, path, parameter)  
  4.                 .maxRows(maxRows).fetchSize(fetchSize).queryTimeout(  
  5.                         queryTimeout);  
  6.         }  


 

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

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

[java] view plain copy print ?
  1. protected DataSource dataSource;  

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

[html] view plain copy print ?
  1. <component name="dataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>  

 

DataSourceImpl中,有如下的一个变量

[java] view plain copy print ?
  1. private ConnectionPool connectionPool;  

它来源于如下的注入

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


 

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

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

[java] view plain copy print ?
  1. private XADataSource xaDataSource;  

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

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


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

 

3. SQL的执行和connectionPool的怒放

真正的sql的执行发生在AbstactSelect

[java] view plain copy print ?
  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.                 throw new SNoResultException(executedSql);  
  20.             }  
  21.         } finally {  
  22.             if (!jdbcContext.isTransactional()) {  
  23.                 jdbcContext.destroy();  
  24.             }  
  25.         }  
  26.         return ret;  
  27.     }  


 

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

[java] view plain copy print ?
  1. JdbcContext jdbcContext = jdbcManager.getJdbcContext();  


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

[java] view plain copy print ?
  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.         }  


 

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

[java] view plain copy print ?
  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.         return null;  
  13.     }  


 

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

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

[java] view plain copy print ?
  1. //线程安全  
  2.   public synchronized ConnectionWrapper checkOut() throws SQLException {  
  3. //拿到当前thread对应的事务,通过threadLocal对象存储  
  4.        Transaction tx = getTransaction();  
  5.        if (tx == null && !isAllowLocalTx()) {  
  6.            throw new 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.                throw new SSQLException("ESSR0104"null);  
  23.            }  
  24.            final long startTime = System.currentTimeMillis();  
  25.            try {  
  26.                wait((maxWait == -1L) ? 0L : wait);  
  27.            } catch (InterruptedException e) {  
  28.                throw new SSQLException("ESSR0104"null, e);  
  29.            }  
  30.            final long 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.     }  


 

4. connectionPool的算法

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

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


 

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

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

[java] view plain copy print ?
  1. XAConnection xaConnection = xaDataSource.getXAConnection();  
  2.         Connection connection = xaConnection.getConnection();  
  3.         ConnectionWrapper con = new ConnectionWrapperImpl(xaConnection,  
  4.                     connection, this, tx);  


 

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

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

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

[java] view plain copy print ?
  1. public void 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.     }  


 

这里的所谓的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容器中是绝对的线程安全

你可能感兴趣的:(seasar)