マルチテナントWebアプリケーションをセットアップしようとしていますが、(理想的には)データベース分離アプローチとスキーマ分離アプローチの両方を同時に使用できます。スキーマの分離から始めますが。現在使用しているもの:
ほとんど私は this thread に従いました(@Transactional
の解決策のため)。しかし、私はMultiTenantContextConnectionProvider
の実装で少し迷っています。 この同様の質問 もここでSOに尋ねられますが、私には理解できないいくつかの側面があります:
1)接続プーリングはどうなりますか?つまり、それはSpringまたはHibernateによって管理されていますか?私はConnectionProviderBuilder
と推測します-または提案されているように-その実装のいずれか、Hibernateはそれを管理する人です。
2)Springが接続プーリングを管理しないのは良いアプローチですか?またはSpringがそれを管理することは可能ですか?
3)これは、データベースとスキーマの両方の分離を将来実装するための正しい道ですか?
コメントや説明は完全にありがたいです。
application-context.xml
<beans>
...
<bean id="dataSource" class="org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy">
<property name="targetDataSource" ref="c3p0DataSource" />
</bean>
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="org.postgresql.Driver" />
... other C3P0 related config
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="packagesToScan" value="com.webapp.domain.model" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.default_schema">public</prop>
<prop key="hibernate.multiTenancy">SCHEMA</prop>
<prop key="hibernate.tenant_identifier_resolver">com.webapp.persistence.utility.CurrentTenantContextIdentifierResolver</prop>
<prop key="hibernate.multi_tenant_connection_provider">com.webapp.persistence.utility.MultiTenantContextConnectionProvider</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="autodetectDataSource" value="false" />
<property name="sessionFactory" ref="sessionFactory" />
</bean>
...
</beans>
CurrentTenantContextIdentifierResolver.Java
public class CurrentTenantContextIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
return CurrentTenantIdentifier; // e.g.: public, tid130, tid456, ...
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
MultiTenantContextConnectionProvider.Java
public class MultiTenantContextConnectionProvider extends AbstractMultiTenantConnectionProvider {
// Do I need this and its configuratrion?
//private C3P0ConnectionProvider connectionProvider = null;
@Override
public ConnectionProvider getAnyConnectionProvider() {
// the main question is here.
}
@Override
public ConnectionProvider selectConnectionProvider(String tenantIdentifier) {
// and of course here.
}
}
編集
@ ben75の 答え について:
これはMultiTenantContextConnectionProvider
の新しい実装です。 AbstractMultiTenantConnectionProvider
を拡張しなくなりました。むしろMultiTenantConnectionProvider
を実装し、[Connection][4]
ではなく[ConnectionProvider][5]
を返すことができるようにします
public class MultiTenantContextConnectionProvider implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
private DataSource lazyDatasource;;
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();
lazyDatasource = (DataSource) lSettings.get( Environment.DATASOURCE );
}
@Override
public Connection getAnyConnection() throws SQLException {
return lazyDatasource.getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute("SET SCHEMA '" + tenantIdentifier + "'");
}
catch (SQLException e) {
throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
}
return connection;
}
}
接続ポーリングに影響を与える3つの異なる戦略から選択できます。いずれの場合も、 MultiTenantConnectionProvider
の実装を提供する必要があります。もちろん、選択する戦略は実装に影響を与えます。
MultiTenantConnectionProvider.getAnyConnection()
に関する一般的な注意
getAnyConnection()
は、Hibernateがメタデータを収集してSessionFactoryをセットアップするために必要です。通常、マルチテナントアーキテクチャでは、どのテナントでも使用されていない特別な/マスターデータベース(またはスキーマ)があります。これは一種のテンプレートデータベース(またはスキーマ)です。このメソッドがこのデータベース(またはスキーマ)への接続を返す場合は問題ありません。
戦略1:各テナントには独自のデータベースがあります(したがって、独自の接続プールです)
この場合、各テナントにはC3POによって管理される独自の接続プールがあり、 MultiTenantConnectionProvider
に基づいて AbstractMultiTenantConnectionProvider
の実装を提供できます
すべてのテナントには独自の _C3P0ConnectionProvider
_ があるため、 selectConnectionProvider(tenantIdentifier)
で行う必要があるのは、正しいものを返すことだけです。マップを保持してそれらをキャッシュし、次のようにC3POConnectionProviderを遅延初期化できます。
_private ConnectionProvider lazyInit(String tenantIdentifier){
C3P0ConnectionProvider connectionProvider = new C3P0ConnectionProvider();
connectionProvider.configure(getC3POProperties(tenantIdentifier));
return connectionProvider;
}
private Map getC3POProperties(String tenantIdentifier){
// here you have to get the default hibernate and c3po config properties
// from a file or from Spring application context (there are good chances
// that those default properties point to the special/master database)
// and alter them so that the datasource point to the tenant database
// i.e. : change the property hibernate.connection.url
// (and any other tenant specific property in your architecture like :
// hibernate.connection.username=tenantIdentifier
// hibernate.connection.password=...
// ...)
}
_
戦略2:各テナントには独自のスキーマと独自の接続プールが1つのデータベースにあります
ConnectionProvider
を基本クラスとして使用して AbstractMultiTenantConnectionProvider
を実装することもできるため、このケースはMultiTenantConnectionProvider
の実装に関する最初の戦略と非常に似ています。
この実装は、c3po構成のデータベースではなくスキーマを変更する必要があることを除いて、戦略1の推奨実装と非常に似ています。
戦略3:各テナントは単一のデータベースに独自のスキーマを持っていますが、共有接続プールを使用します
すべてのテナントが同じ接続プロバイダーを使用するため(この場合、接続プールは共有されます)、このケースは少し異なります。この場合:接続プロバイダーは、接続を使用する前に、使用するスキーマを設定する必要があります。つまり、MultiTenantConnectionProvider.getConnection(String tenantIdentifier)
を実装する必要があります(つまり、AbstractMultiTenantConnectionProvider
が提供するデフォルトの実装は機能しません)。
postgresql を使用すると、次のように実行できます。
_ SET search_path to <schema_name_for_tenant>;
_
またはエイリアスを使用して
_ SET schema <schema_name_for_tenant>;
_
ですから、getConnection(tenant_identifier);
は次のようになります。
_@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "SET search_path TO " + tenanantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" +
tenantIdentifier + "]",
e
);
}
return connection;
}
_
役立つリファレンスは ここ (公式ドキュメント)
その他の便利なリンク C3POConnectionProvider.Java
実装で戦略1と戦略2を組み合わせることができます。現在のテナントの正しい接続プロパティ/接続URLを見つける方法が必要なだけです。
[〜#〜]編集[〜#〜]
戦略2と3のどちらを選択するかは、トラフィックとアプリのテナント数に依存すると思います。個別の接続プールを使用する場合:1つのテナントで使用できる接続の量ははるかに少なくなるため、正当な理由で1つのテナントが突然多くの接続を必要とする場合、この特定のテナントから見たパフォーマンスは大幅に低下します(他のテナントはそうではありません)影響を受ける)。
一方、戦略3では、何らかの正当な理由で1つのテナントが突然多くの接続を必要とする場合、すべてのテナントから見たパフォーマンスが低下します。
一般的に、戦略2の方がより柔軟で安全だと思います。すべてのテナントが特定の接続量を超えることはできません(必要に応じて、この量はテナントごとに構成できます)。
IMHO、接続プールの管理はデフォルトでSQL Server自体によって処理されますが、C#などの一部のプログラミング言語はプールを制御するいくつかの方法を提供しています。参照 ここ
(1)スキーマまたは(2)テナント用の個別のデータベースの選択は、テナントで予想できるデータの量によって異なります。ただし、以下の検討事項は調査する価値があります。
トライアル顧客と少量顧客の共有スキーマモデルを作成します。これは、顧客のオンボーディングプロセス中にテナントに提供する機能の数で識別できます。
大規模なトランザクションデータが存在する可能性があるエンタープライズレベルの顧客を作成またはオンボードする場合は、別のデータベースを使用するのが理想的です。
スキーマモデルには、SQL Serverの実装とMySQL Serverの実装が異なる場合があるため、考慮する必要があります。
また、オプションを選択するときは、かなりの時間とシステムの使用後に顧客[テナント]が喜んでスケールアウトする可能性があるという事実を考慮してください。アプリでサポートされている適切なスケールアウトオプションがない場合は、煩わされる必要があります。
この議論をさらに進めるために、上記のポイントに関するコメントを共有してください