放浪エンジニアのLog

少しハッピーになれる開発

Playframework (java) で環境によってDBの接続先を変える方法

今まではRailsの記事を書いていましたが、最近は Java の Playframework にはまっており、今回はその Play の環境にまつわるお話です。

よく環境毎に接続するDBが違う場合があり、そのために読み込むconfファイルを動的に変えたいシチュエーションなどがあると思います。
(開発、テスト、本番環境などでDBが変わる場合)


今回は「サーバのホスト名」などによって環境を判別し、その環境によって読み込むDBの設定ファイルを動的に変える方法を記載します。
(相変わらずにっちな需要だと思いますが….)

実現方法

方法としてはアプリケーション起動時に読み込むConfファイルを変えます。
こう書いてしまうと、とても簡単ですね….

Play2.3までであれば GlobalSettings を継承したクラスを作成し、その中で onStart などが使えたので、そこに記載すればよかったのです。

※ Play 2.3までであれば
public class ApplicationGlobal extends GlobalSettings {

    @Override
    public void onStart(Appcliation app) {
        // ここに起動時の設定などを記載する。
    }


しかし、残念ながらこのような書き方は Play2.4 からはできなくなりました(GlobalSettingsが非推奨となったため)。

GlobalSettingsが使えなくなった理由

それは Play2.4からGuiceを利用したDIが利用可能になったため、そちらを使うことが推奨されたためです。

Play2.4からの実装方法

まずは前提条件から記載しておきます
■ 今回の想定

  • HostName に応じて「local環境」、「テスト環境」、「本番環境」のDB接続先を変える
    • HostName が ○○○ だったら本番環境用の conf/prod.conf
    • HostName が test-○○○ だったらテスト環境用の conf/test.conf
    • 上記以外のHostNameだったら conf/local.conf

まずはDB接続用の設定用confの作成を行います。
(記載忘れていましたが、DB は今回 MySQL を使用します)

- conf/prod.conf
- conf/test.conf
- conf/local.conf

# 環境に応じた値を記入
driver=com.mysql.jdbc.Driver
url="jdbc:mysql://○○○:3306/hoge-db"
username=UserName
password="Password"


- ApplicationModule.java

import javax.inject.Inject;
import com.google.inject.AbstractModule;
import play.api.inject.ApplicationLifecycle;
 
public class ApplicationModule extends AbstractModule {

 
    @Override
    protected void configure() {
        bind(ApplicationJobs.class).asEagerSingleton();
    }


    public static class ApplicationJobs {

        @Inject
        public ApplicationJobs(ApplicationLifecycle lifecycle) {

            // DBのConnection設定
            ConnectionFactory.getInstanse().init();

            // その他初回起動でしたいことがあれば記載する  
        }
    }
}

9行目
Guiceがモジュールに渡す Binderによって、オブジェクトの作成方法を Guiceに指示しています。
ここでは asEagerSingleton() を使って、起動時にシングルトンのインスタンスを生成させています。

- ConnectionFactory.java

import java.io.File;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.net.InetAddress;

import org.apache.commons.dbcp2.BasicDataSource;
import org.avaje.datasource.DataSourceConfig;

import com.avaje.ebean.EbeanServerFactory;
import com.avaje.ebean.config.ServerConfig;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

public class ConnectionFactory {

    /** シングルトンインスタンス */
    private static ConnectionFactory instance = null;

    /** データソース設定 */
    BasicDataSource bds;

    public static ConnectionFactory getInstanse() {
        if (instance == null) {
            instance = new ConnectionFactory();
        }
        return instance;
    }

    public void init() {
        getConnection();
    }


    private void initBasicDataSource() {
        String hostName = InetAddress.getLocalHost().getHostName();
        Config dbConfig;

        if (hostName.equals("○○○")) {
            // 本番環境の設定
            dbConfig = ConfigFactory.load(ConfigFactory.parseFileAnySyntax(new File("conf/prod.conf")));
            
        } else if (gnaviEnv.equals("test-○○○")) {
            // テスト環境の設定
            dbConfig = ConfigFactory.load(ConfigFactory.parseFileAnySyntax(new File("conf/test.conf")));
        } else {
            // それ以外(local環境の設定)
            dbConfig = ConfigFactory.load(ConfigFactory.parseFileAnySyntax(new File("conf/local.conf"))));
        }

        DataSourceConfig dsConfig = new DataSourceConfig() {
            {
                setDriver(dbConfig.getString("driver"));
                setUrl(dbConfig.getString("url"));
                setUsername(dbConfig.getString("username"));
                setPassword(dbConfig.getString("password"));
                setCaptureStackTrace(true);
                // ここらへんは必要に応じて設定
                // setMaxAgeMinutes(○○);
                // setMaxConnections(○○);
            }
        };

        ServerConfig serverConfig = new ServerConfig() {
            {
                List<Class<?>> classList = new ArrayList<>();

                // Modelの追加
                classList.add(○○Model.class);
                classList.add(○○Model.class);

                loadFromProperties();
                setClasses(classList);
                // DDLの出力先
                setName("db/ddl");
                setDataSourceConfig(dsConfig);

                setDefaultServer(true);
                setRegister(true);
                // trueにするとDDDLが自動生成される
                setDdlGenerate(true);
                // trueにするとApplication起動時にデータがすべて削除されて、DDLから新規でテーブル作成しなおす
                setDdlRun(false);

            }
        };
        EbeanServerFactory.create(serverConfig);
    }
}

ConnectionFactory では純粋にDBのConnectionまわりの設定を行っております。
※ 注意ポイント: Evolutionsを使ってのModules読み込みをしないので、Modelファイルを追加する度に手動で classList.add を行い、Modelファイルの追加をしてあげる必要があります。
(本当はこれも自動で読み込めるようにしてあげた方がよいが、さくっとかけなかった…..)