hakobera's blog

技術メモ。たまに雑談

Doma で DataSource を JNDI 経由で取得する方法

Seasar2 とかを使わずに JavaEE コンテナから JNDI 経由で DataSource を取得する方法のメモ。

ソースをまったく書き換えることなく、UT、ローカル開発サーバ、本番環境に対応できるので便利。Glassfish v3.0.1 上の JAX-RS + CDI + Doma なアプリ環境で稼動確認してます。

Doma 設定クラス

package sample;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.seasar.doma.jdbc.DomaAbstractConfig;
import org.seasar.doma.jdbc.JdbcLogger;
import org.seasar.doma.jdbc.dialect.Dialect;
import org.seasar.doma.jdbc.dialect.OracleDialect;

public class DaoConfig extends DomaAbstractConfig {
    
    public final static String JNDI_DATASOURCE = "jdbc/sample";
    
    protected static DataSource dataSource = createDataSource();
    
    private static JdbcLogger jdbcLogger = new CommonsJdbcLogger();

    // 使用するデータベースに合わせて変える
    private static Dialect dialect = new OracleDialect();

    @Override
    public DataSource getDataSource() {
        return dataSource;
    }

    @Override
    public Dialect getDialect() {
        return dialect;
    }

    @Override
    public JdbcLogger getJdbcLogger() {
        return jdbcLogger;
    }
    
    protected static DataSource createDataSource() {
        try {
            return (DataSource) InitialContext.doLookup(JNDI_DATASOURCE);
        } catch (NamingException e) {
            throw new RuntimeException(e);
        }
    }

}

JUnit4.7 用の TestRunnner

ついでに、JUnit4.7 用の TestRunnner の雛形。テスト開始前にトランザクションを開始して、終了時にロールバックします。

package sample;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import org.junit.internal.runners.statements.InvokeMethod;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
import org.seasar.doma.jdbc.JdbcLogger;
import org.seasar.doma.jdbc.SimpleDataSource;
import org.seasar.doma.jdbc.tx.LocalTransaction;
import org.seasar.doma.jdbc.tx.LocalTransactionalDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sample.CommonsJdbcLogger;
import sample.DaoConfig;

public class DaoTestRunner extends BlockJUnit4ClassRunner {

    protected static LocalTransactionalDataSource dataSource;

    protected static JdbcLogger defaultJdbcLogger = new CommonsJdbcLogger();

    private static final Logger logger = LoggerFactory.getLogger(DaoTestRunner.class);

    private static boolean initialized = false;

    public DaoTestRunner(Class<?> klass) throws InitializationError {
        super(klass);
        if (!initialized) {
            init();
        }
    }

    protected void init() {
        try {
            dataSource = createDataSource();

            // Create initial context
            System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
            System.setProperty(Context.URL_PKG_PREFIXES, "org.apache.naming");
            InitialContext ic = new InitialContext();
            
            ic.createSubcontext("jdbc");
            /* JNDI 名に合わせて Subcontext 作成。Tomcat だと以下のような感じ?
            ic.createSubcontext("java:");
            ic.createSubcontext("java:/comp");
            ic.createSubcontext("java:/comp/env");
            ic.createSubcontext("java:/comp/env/jdbc");
            */
            
            ic.bind(DaoConfig.JNDI_DATASOURCE, dataSource);
        } catch (NamingException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    // データベース接続設定
    protected LocalTransactionalDataSource createDataSource() {
        SimpleDataSource dataSource = new SimpleDataSource();
        dataSource.setUrl("xxx");
        dataSource.setUser("xxx");
        dataSource.setPassword("xxx");
        return new LocalTransactionalDataSource(dataSource);
    }

    public LocalTransaction getLocalTransaction() {
        return dataSource.getLocalTransaction(defaultJdbcLogger);
    }

    @Override
    protected Statement methodInvoker(FrameworkMethod method, Object test) {
        return new DatabaseTestInvokeMethod(method, test);
    }

    private class DatabaseTestInvokeMethod extends InvokeMethod {
        private FrameworkMethod testMethod;
        private Object target;

        public DatabaseTestInvokeMethod(FrameworkMethod testMethod, Object target) {
            super(testMethod, target);
            this.testMethod = testMethod;
            this.target = target;
        }

        @Override
        public void evaluate() throws Throwable {
            LocalTransaction tx = getLocalTransaction();
            try {
                logger.info(String.format("%s#%s の初期化処理を実行します", target.getClass(), testMethod.getName()));
                tx.begin();
                logger.info("トランザクションを開始しました");
                super.evaluate();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                logger.info(String.format("%s#%s の終了処理を実行します", target.getClass(), testMethod.getName()));
                tx.rollback();
                logger.info("ロールバックしました");
            }
        }
    }

}

ずっと Seasar2 使ってたので、InitialContext の使い方を度忘れして、2時間くらい浪費してしまいました。