複数DB

複数DBとは?

複数DBという言葉はとても曖昧なので、DBFluteとしての解釈をここで厳密に定義します。

  • A. 全く "違う" テーブル構造の別DBを利用
  • B. 全く "同じ" テーブル構造の別DBを利用

基本とする複数DB

DBFluteでは、基本的に "複数DB" と言った場合は "A" を示します。このページでも自動生成環境が複数になる "A" の場合の環境構築に関して説明をします。

冗長化複数DB

一方で、"B" を 冗長化複数DB と呼び、こちらの実現方法は、DIコンテナやトランザクションフレームワークなどに依存します。 逆にDBFluteでは、特に自動生成環境が複数になることはなく(同じテーブル構造のため)、 DIコンテナ経由で利用している javax.sql.DataSource の実装クラスを冗長化複数DB対応のものに差し替えることで実現できます。 (例えば、Seasar(S2Container)であれば、SelectableDataSourceProxyを利用)

複数スキーマは別の機能

また、DBFluteの機能としては、アプリで利用対象となる複数DBに対して、一つのデータソース(DBへのコネクション)でアクセスできるような場合は、"複数DB" はなく、"複数スキーマ" と捉え、(このページで紹介する方法とは)全く別の機能で実現することができます。例えば、メインDBが Oracle で、別に PostgreSQL をサブDBとして利用するような場合は問答無用で "複数DB" です。一方、同じ Oracle インスタンス内の別のスキーマ、もしくは、同じ PostgreSQL インスタンス内の別の(もしくは同じ)データベースのスキーマにおいては "複数スキーマ" となります。"複数スキーマ" は、AdditionalSchema という別の機能が利用でき、そちらで対応するのがお奨めです。(この場合、一つの自動生成環境で複数のスキーマを扱うことができます)

複数DBと複数スキーマの境目

"複数スキーマ" の構成も "複数DB" のやり方で実現することは可能ですが("複数DB" は、"複数スキーマ" を(意味的に)包含します)、もし、複数DB間でFK制約などの関連を持っているような場合は、AdditionalSchema を利用することで、自動生成された Entity 間で関連として(ConditionBeanなどで)扱うことができます。 DB間の業務的な(かつ、実装的な)密接度が高い場合は "複数スキーマ" として取り扱う と良いでしょう。一方で、"複数スキーマ" 構成だとしても、テーブル設計などのやり方・文化などがあまりに違う場合は(例えば、共通カラムの構造が全く違うなど)、 "複数スキーマ" 構成で実現できるにしても、互いに独立した設定をしやすい "複数DB" として扱うのが良いでしょう。

複数DB対応の環境構築

ポイントになる点は以下の通りです。

DBFluteクライアントが複数

DBの個数分、DBFluteクライアントも作成します(DBFluteモジュールは一つでOK)。

e.g. 複数DB対応のDBFluteクライアント構造 {seadb,landdb} @EclipseProject
xxx-project
 |-dbflute_seadb // FooDB用のDBFluteクライアント
 |  |-dfprop
 |  |-log
 |  |-...
 |-dbflute_landdb // BarDB用のDBFluteクライアント
 |  |-dfprop
 |  |-log
 |  |-...
 |-mydbflute
 |     |-dbflute-1.0.0 // 共通のDBFluteモジュール
 |-src/main/java
 |-...

それぞれ自動生成されるクラスをDBごとに違うプロジェクトに配置する場合は、一つのプロジェクトに複数DBFluteクライアントではなく、 それぞれのプロジェクトごとに一つのDBFluteクライアントを作成する構成で問題ありません。 (その構成でDBFluteモジュールを共有させる場合は、_project.sh|bat の参照設定を修正します)

自動生成クラスのパッケージをユニークにする

それぞれの basicInfoMap.dfprop の packageBase をユニークにします。

例えば、単一DBでは "org.docksidestage.xxx.dbflute" としていたものを、以下のようにします。

SeaDB
org.docksidestage.xxx.dbflute.seadb
LandDB
org.docksidestage.xxx.dbflute.landdb
e.g. 複数DB対応のパッケージ構造 {seadb,landdb} @Directory
xxx-project
 |-src/main/java
 |  |-org.docksidestage.xxx.dbflute.seadb // SeaDB用のパッケージ
 |  |  |-allcommon
 |  |  |-bsbhv
 |  |  |-...
 |  |-org.docksidestage.xxx.dbflute.landdb // LandDB用のパッケージ
 |  |  |-allcommon
 |  |  |-bsbhv
 |  |  |-...
 |-dbflute_seadb
 |-dbflute_landdb
 |-mydbflute
 |     |-dbflute-1.1.x
 |-...

ただし、開発・運用途中で複数DBになった場合は、追加されたDBの方だけを "...dbflute.landdb" とするような構造でも良いです。 メインのDBだけは普通の構成のまま、追加されたサブのDBは複数DBを意識した構成にして区別できればOKです。

DI設定ファイルの名前をユニークにする

dependencyInjectionMap.dfprop にて、DI設定ファイル名 をユニークにします。複数のDBでDI設定ファイルの名前がバッティングするのを回避するためです。

DIコンテナごとにプロパティが変わります。

SpringでJavaConfig方式
自動生成されるクラスがパッケージ分けされているので設定不要
Springでxml方式
dfprop の dbfluteBeansFileName を利用
Lasta Di
dfprop の dbfluteDiXmlFileName を利用
Seasar
dfprop の dbfluteDiconFileName を利用
Google Guice
自動生成されるクラスがパッケージ分けされているので設定不要

dfpropを設定してGenerateすると、自動生成されるファイル名が変わります。

e.g. Lasta Diのときの複数DB対応の DI xml {seadb,landdb} @Directory
xxx-project
 |-src/main/java
 |-src/main/resources
 |  |-dbflute-seadb.xml // SeaDB用のdbflute.xml
 |  |-dbflute-landdb.xml // LandDB用のdbflute.xml
 |-dbflute_seadb
 |-dbflute_landdb
 |-mydbflute
 |     |-dbflute-1.1.x
 |-...

DI設定ファイル内の DataSource 参照をユニークにする

dependencyInjectionMap.dfprop にて、DataSource参照 をユニークにします。それぞれのDBごとのDataSourceを利用するためです。

DIコンテナごとにプロパティが変わります。

Spring
dfprop の dbfluteBeansDataSourceName を利用
Lasta Di
dfprop の rdbDiXmlResourceName を利用
Seasar
dfprop の j2eeDiconResourceName を利用
Google Guice
自動生成されるクラスの引数でDataSourceを受け取るので設定不要

dfpropを設定してGenerateすると、それぞれのDI設定ファイルから参照するDataSourceのコンポーネント名が変わります。

e.g. Springで、複数DB対応のDI設定ファイルのDataSource参照 {seadb} @dbfluteBeansSeaDb.xml
<bean id="seaInvokerAssistant" class="...ImplementedInvokerAssistant" ...">
    <property name="dataSource"><ref bean="seaDbDataSource"/></property>
    ...
</bean>
e.g. Lasta DIで、複数DB対応の DI xml のDataSource参照 {seadb} @dbflute-seadb.xml
<components namespace="dbflute">
    <include condition="#exists('#path')" path="my_included_dbflute.xml"/>
    <include path="rdb-seadb.xml"/>
...

Lasta Diの場合、dbflute-seadb.xml から参照される rdb-seadb.xml, 並びにそこから参照される jdbc-seadb.xml を自作します。 Lasta Diに組み込まれている rdb.xml, そして、LastaFluteに組み込まれている jdbc.xml をそれぞれコピーしてファイル名を変更し、中身を対象DBに合わせて修正します。

e.g. Lasta DIの複数DB対応の DI xml の include 解像 {seadb} @Model
dbflute-seadb.xml   // この名前で自動生成されるようにdfpropで調整
 |-rdb-seadb.xml    // 自作、中身はjdbc-seadb.xmlを参照するように修正するだけ
   |-jdbc-seadb.xml // 自作、中身はseadbに接続するように修正
e.g. Lasta DIで、rdb-seadb.xml を作成、jdbc-seadb.xmlを参照するように修正 {seadb} @rdb-seadb.xml
<components namespace="rdb">
    ...
    <include path="jdbc-seadb.xml"/> // もともと jdbc.xml だったのを修正
    ...
</components>
e.g. Lasta DIで、jdbc-seadb.xml を作成、seadbのプロパティを参照するように修正 {seadb} @jdbc-seadb.xml
    ...
<component name="xaDataSource" class="org.lastaflute.db.dbcp.HookedXADataSource">
    <property name="driverClassName">
        provider.config().getSeadbJdbcDriver()
    </property>
    <property name="URL">
        provider.config().getSeadbJdbcUrl()
    </property>
    ...

<component name="connectionPool" class="org.lastaflute.db.dbcp.HookedConnectionPool">
    <property name="maxPoolSize">provider.config().getSeadbJdbcConnectionPoolingSize()</property>

    ...
    <property name="minPoolSize">        provider.config().getOrDefault("seadb.jdbc.connection.pooling.min.size", null)            </property>
    <property name="maxWait">            provider.config().getOrDefault("seadb.jdbc.connection.pooling.max.wait", null)            </property>
    <property name="timeout">            provider.config().getOrDefault("seadb.jdbc.connection.pooling.timeout", null)            </property>

クラス名の prefix を (必要であれば) 設定する

それぞれの basicInfoMap.dfprop で、どのDBかを識別できる projectPrefix を設定します。

メインDBとサブDBというように分けられるなら、サブDBの方にだけ設定でも良いです。 (後から2個目のDBが追加されるケースは、自然とそのようになるでしょう)

全体的にクラス名やコンポーネント名にprefixが付き、どのDBに対応するクラスなのか?がわかりやすくなります。 同じテーブル名がある場合などはprefixがないとコード上でややこしくなるでしょう。 また、ユニークなコンポーネント名が求められるDIコンテナの場合は必須となります。

e.g. 複数DB対応のクラス名の projectPrefix {Se,La} @Directory
xxx-project
 |-dbflute_seadb
 |-dbflute_landdb
 |-src/main/java
 |  |-org.docksidestage.xxx.dbflute.seadb.exentity
 |     |-SeMystic.java // 元々は Mystic.java
 |-src/main/java
 |  |-org.docksidestage.xxx.dbflute.landdb.exentity
 |     |-LaOneman.java // 元々は Oneman.java
 |-...

一方で、全体ではなく allcommon のクラスだけに限定するのであれば、allcommonPrefix を使うと良いでしょう。 (CDefクラスなどが同じ名前になると扱いづらいので、そこだけは識別できる方が良いです)

e.g. 複数DB対応のクラス名の allcommonPrefix {Se,La} @Directory
xxx-project
 |-dbflute_seadb
 |-dbflute_landdb
 |-src/main/java
 |  |-org.docksidestage.xxx.dbflute.seadb.allcommon
 |     |-SeCDef.java // 元々は CDef.java
 |  |-org.docksidestage.xxx.dbflute.seadb.exentity
 |     |-Mystic.java
 |-src/main/java
 |  |-org.docksidestage.xxx.dbflute.landdb.allcommon
 |     |-LaCDef.java // 元々は CDef.java
 |  |-org.docksidestage.xxx.dbflute.landdb.exentity
 |     |-Oneman.java
 |-...

projectPrefix もしくは allcommonPrefix が環境的に必要かどうかはDIコンテナよって変わります。

Spring
必要 (メインDBは設定しなくてもいい)
Lasta Di
不要
Seasar
不要
Google Guice
必要 (メインDBは設定しなくてもいい)

全体でユニークなコンポーネント名が必要かどうか?次第です。

DBFluteランタイムコンポーネントを byName に

Google Guice の場合に必要です。

Google Guice の DBFluteModule では、DBFluteランタイムコンポーネントたち (BehaviorSelectorなど) は、デフォルトで byType でDI登録されます。複数DBの場合、型による重複コンポーネントにならないように名前で識別する必要があります。

dependencyInjectionMap.dfprop の isDBFluteModuleGuiceRuntimeComponentByName を true にすることで、それらコンポーネントは byName でDI登録されます。@since 1.2.6

projectPrefix や allcommonPrefix を付けていることが前提になるので、prefixを付けていないメインDBでは不要です。 (prefixがないのにtrueにすると自動生成時に例外になります)

また、byNameで登録するとbyTypeでのDIができなくなるので、すでに開発が進んでいる最初のDB(メインDB)があるのであれば、念のための互換のために新しく追加するサブDBのみで設定するのが良いでしょう。

外だしSQLの対象パッケージを(必要であれば)設定する

それぞれの outsideSqlMap.dfprop の sqlPackage をユニークにします。通常、Sql2Entity や OutsideSqlTest は、src/main/resources 配下の全てのSQLを対象とするため、別のDBのSQLを処理対象としないようにするために、明示的に設定します。

設定値は、基本的に $$PACKAGE_BASE$$ とだけ設定すれば良いでしょう。

e.g. 複数DB対応のSQLパッケージ @outsideSqlMap.dfprop
; sqlPackage = $$PACKAGE_BASE$$

この設定をしなくても、両方のDBで外だしSQLをそれぞれ一つ以上作るまでは特に問題なく動作します。 外だしSQLを作り始める時に、Sql2Entityで別のDB用のSQLを実行してしまってSQLエラーになって気付くでしょう。

一方で、自動生成クラスをDBごとに違うプロジェクトに配置する場合は、この設定は不要です。

自動生成するテーブルを絞り込み

例えば、メインのDBとサブのDBとカテゴリ分けできる場合、サブのDBのテーブルをすべて利用するとは限りません。 利用しないテーブルのクラスを自動生成すると無駄にコンパイルスピードを落とすだけなので、自動生成されないようにすると良いでしょう。

databaseInfoMap.dfprop の tableExceptList や tableTargetList で指定します。

一部テーブルを利用しないなら
tableExceptList
一部テーブルだけ利用するなら
tableTargetList

ただ、"SchemaHTML や HistoryHTML ではすべてのテーブルを表示したい" という場合は、@gen の suffix をうまく使って除外すると良いでしょう。

Exampleのススメ

テストプロジェクトではありますが、実際に複数DBの構成を実現しているプロジェクトがあります。 主に環境面において、ぜひ参考にすると良いでしょう。

Spring Framework
dbflute-test-dbms-oracle (Oracle利用)
Google Guice
dbflute-test-active-hangar (H2利用)
Seasar(S2Container)
dbflute-test-dbms-mysql (MySQL利用)
Lasta Di
lastaflute-test-fortress (MySQL利用)