Slim3のコネクションプーリングの実装を読んでみる

DIってAOPと組み合わせないとうまみがあんまり無いよね
AOPっていったらログよりトランザクションだよね
トランザクションっていったらコネクションプーリング関係するよね
→じゃあSlim3のコネクションプーリングの実装を読んでみるか。まだソース少ないからよみやすそうだし。

というわけで

Javaのコネクションプーリングの仕組み - yvsu pron. yas

をたよりに読んでみました。ちなみにリビジョン71ね。72以降は登場するものが増えているので。

DataSource#getConnectionして、コミットして、Connection#closeまでの流れを読んでみる。あ、ちなみにステップ実行とかはしてない
し、間違ってるとこあるかも。

DataSourceの実装クラスはXADataSourceImplです。このクラスがコネクションプーリングを実現している。freePoolフィールドにプーリングされるコネクションが入る。

getConnectionがよばれるとgetXAConnectionからとってきたXAConnectionImplをかえして、XAConnectionImplがConnectionWrapperをかえします。ConnectionWrapperが論理コネクションでConnectionの実装クラスです。
XAConnectionImplはPooledConnectionを継承したXAConnectionの実装クラスです。つまりXAConnectionImplがプーリングされるコネクションを意味します。ちなみにXAインターフェースはRM(リソースマネージャ)がTM(トランザクションマネージャ)に公開するインターフェースです。分散トランザクションに関連するものですね。JTAが分散トランザクションを意識しているのでややこしくなってます。まあ僕もようわからんですw そのあたりは、2004-04-15 - 日記が参考になるかも。

で、getXAConnectionを見てみると、ThreadLocalで管理しているXAConnectionImplが無かったらプールからとってくる。
プールがいっぱいだったらwaitする。空だったらDriverManager#getConnectionでとってきた物理コネクションをもとにXAConnectionImplをつくって、addConnectionEventListenerでリスナーを登録する。で、XAConnectionImplからXAResourceImplをとってきてTransaction#enlistResourceする。
つまりここでリソースとトランザクションの関連付けが行われる。このあたりは、2005-01-17 - 日記が参考になるかも。

    public synchronized XAConnection getXAConnection(String user,
            String password) throws SQLException {
        XAConnectionImpl pooledConnection = activeTxConnection.get();
        if (pooledConnection == null) {
            while (maxActiveSize > 0
                    && activeConnectionCounter >= maxActiveSize) {
                try {
                    wait();
                } catch (InterruptedException ignore) {
                }
            }
            if (freePool.isEmpty()) {
                pooledConnection = new XAConnectionImpl(getPhysicalConnection(
                        user, password));
                pooledConnection.addConnectionEventListener(this);
            } else {
                PoolTask poolTask = freePool.removeLast();
                poolTask.cancel();
                pooledConnection = poolTask.pooledConnection;
                if (!validateConnection(pooledConnection.physicalConnection)) {
                    pooledConnection.close();
                    pooledConnection = new XAConnectionImpl(
                            getPhysicalConnection(user, password));
                    pooledConnection.addConnectionEventListener(this);
                }
            }
            try {
                Transaction tx = transactionManager.getTransaction();
                if (tx != null) {
                    pooledConnection.setTransactional(true);
                    activeTxConnection.set(pooledConnection);
                    tx.enlistResource(pooledConnection.getXAResource());
                    tx.registerSynchronization(new SynchronizationImpl());
                } else {
                    pooledConnection.setTransactional(false);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }

        }
        activeConnectionCounter++;
        if (log.isDebugEnabled()) {
            log.debug("The logical connection was obtained.");
        }
        return pooledConnection;
    }

トランザクションがはじまるとXAResourceImpl#beginでconnection.setAutoCommit(false)してXAResourceImpl#commitでconnection.commit()とconnection.setAutoCommit(true)をする。

コネクションのクローズはConnectionWrapper#closeね。

    public synchronized void close() throws SQLException {
        if (physicalConnection == null) {
            return;
        }
        pooledConnection.notifyConnectionClosed();
        physicalConnection = null;
        pooledConnection = null;
        if (log.isDebugEnabled()) {
            log.debug("The logical connection was closed.");
        }

    }

XAConnectionImpl#notifyConnectionClosedで登録されたリスナー、ConnectionEventListenerの実装クラスつまりXADataSourceImplのconnectionClosedを呼び出す。

    public synchronized void notifyConnectionClosed() {
        if (physicalConnection == null) {
            return;
        }
        ConnectionEvent event = new ConnectionEvent(this);
        for (ConnectionEventListener l : listeners) {
            l.connectionClosed(event);
        }
    }

connectionClosedではコネクションをプールに返しているね。

    public synchronized void connectionClosed(ConnectionEvent event) {
        activeConnectionCounter--;
        XAConnectionImpl pooledConnection = (XAConnectionImpl) event
                .getSource();
        if (!pooledConnection.isTransactional()) {
            addFreePool(pooledConnection);
        }
        notify();
    }

まとめると、

* コネクションプール (おそらく DataSource を実装している) は,物理コネクション (PooledConnection を実装している) のインスタンスをプーリングしています.このコネクションプールは ConnectionEventListener を実装しており,コネクションプール自身を物理コネクションに addConnectionEventLister() しています.
* アプリケーションがコネクションプールから getConnection() などすると,コネクションプールは物理コネクションをプールから取り出し,その getConnection() を呼び出します.そして戻り値である論理コネクション (Connection を実装している) をアプリケーションに返します.
* アプリケーションが論理コネクションの close() を呼び出すと,PooledConnection は登録されているリスナー (コネクションプール) の ConnectionEventLister#connectionClosed(ConnectionEvent) を呼び出します.
* コネクションプールは物理コネクションをプールに戻します.

2004-12-17 - 日記

なんだ全部書いてあるじゃんw

ちなみにS2DBCPはこれとは少し違うね。addConnectionEventListerとconnectionClosedの仕組みは使ってないね。あとで読んどく。