Seasar底层对JDBC操作的支持

Seasar底层对JDBC操作的支持

蒋彪@南京 2012-11-27

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

return jdbcManager.selectBySqlFile(
                XXXXDto.class,
                "XXX.sql",
	                param).getResultList();

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

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

<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中,有如下方法

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里面有一个如下的变量

	protected DataSource dataSource;

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

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

 

DataSourceImpl中,有如下的一个变量

	private ConnectionPool connectionPool;

它来源于如下的注入

  <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,在里面看到如下变量

	private XADataSource xaDataSource;

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

 <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

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;
    }


 

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

	JdbcContext jdbcContext = jdbcManager.getJdbcContext();


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

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上下文

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中

	//线程安全
   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中实现了以下三个数据结构

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


 

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

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

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中执行以下方法

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容器中是绝对的线程安全。

#以上#


 

你可能感兴趣的:(Seasar底层对JDBC操作的支持)