複数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利用)