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 ?
- return jdbcManager.selectBySqlFile(
- XXXXDto.class,
- "XXX.sql",
- param).getResultList();
这里的jdbcManager来源于S2AbstractService<T>中的被注入的变量
这里的注入的信息来源于<s2jdbc.dicon>中如下的配置
[html] view plain copy print ?
- <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中,有如下方法
[java] view plain copy print ?
- 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,里面有一个如下的变量
[java] view plain copy print ?
- protected DataSource dataSource;
这里的dataSource来源于jdbc.dicon中的如下的注入
[html] view plain copy print ?
- <component name="dataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
在DataSourceImpl中,有如下的一个变量
[java] view plain copy print ?
- private ConnectionPool connectionPool;
它来源于如下的注入
[html] view plain copy print ?
- <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,在里面看到如下变量
[java] view plain copy print ?
- private XADataSource xaDataSource;
这里是真正的datasource connection data被注入进去了
[html] view plain copy print ?
- <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中
[java] view plain copy print ?
- 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;
- }
上面吸引我的是如下的代码
[java] view plain copy print ?
- JdbcContext jdbcContext = jdbcManager.getJdbcContext();
这里拿到了jdbc上下文,具体的代码如下
[java] view plain copy print ?
- 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上下文
[java] view plain copy print ?
- 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中
[java] view plain copy print ?
-
- public synchronized ConnectionWrapper checkOut() throws SQLException {
-
- Transaction tx = getTransaction();
- if (tx == null && !isAllowLocalTx()) {
- throw new SIllegalStateException("ESSR0311", null);
- }
-
-
- ConnectionWrapper con = getConnectionTxActivePool(tx);
- if (con != null) {
- if (logger.isDebugEnabled()) {
- logger.log("DSSR0007", new Object[] { tx });
- }
- return con;
- }
-
- 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);
- }
- }
-
- con = checkOutFreePool(tx);
- if (con == null) {
-
- con = createConnection(tx);
- }
- if (tx == null) {
-
- setConnectionActivePool(con);
- } else {
-
- 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中实现了以下三个数据结构
[java] view plain copy print ?
-
- private Set activePool = new HashSet();
-
- private Map txActivePool = new HashMap();
-
- private SLinkedList freePool = new SLinkedList();
和有些框架不同, Seasar并没有随着容器的启动, 就把所有的connection初期化保存起来,而是通过如下的算法分配
A. 第一次发生conn请求,发现activePool txActivePool freePool 三个数据结构都是null, 用如下代码新建conn
[java] view plain copy print ?
- 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中执行以下方法
[java] view plain copy print ?
- 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. connectionPool与Web线程安全
所有用DI容器的web项目都是这样,无论spring还是seasar:
1. 当web容器启动的时候,你本工程里所有的instance(包括jdbc操作)就已经在单例的模式下完成了初期化,并且被保存在JVM(tomcat用)的堆里
2. http request发起之后, web容器中生成了专门的thread,用来处理这个request。这个thead会到容器中找到url目的地的Java instance chain ,然后读入到当前thread的栈内存中执行
3. 当前thread的java instance chain,如果一直执行,最后在connection pool 中拿connection 。 关键点就在于当前connection pool是线程安全的吗?
searsar框架中,connection pool是在ConnectionPoolImpl中实现的,这个类默认是以单例模式实例化。
也就是说,多个http 处理thread其实,是读取共用一个ConnectionPoolImpl的instance。
在ConnectionPoolImpl的实现中,可以看到所有对conn pool的操作都加了线程锁。
因此,conn pool 在DI容器中是绝对的线程安全