複数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クライアントが複数
- 自動生成クラスのパッケージをユニークにする
- DI設定ファイルの名前をユニークにする
- DI設定ファイル内の DataSource 参照をユニークにする
- クラス名の prefix を(必要であれば)設定する
- 外だしSQLの対象パッケージを(必要であれば)設定する
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利用)