Hibernate5のマルチテナンシーガイド
1. 序章
マルチテナンシーを使用すると、複数のクライアントまたはテナントが単一のリソース、またはこの記事のコンテキストでは単一のデータベースインスタンスを使用できます。 目的は、各テナントが必要とする情報を共有データベースから分離することです。
このチュートリアルでは、さまざまなアプローチを紹介します
2. Mavenの依存関係
pom.xmlファイルにhibernate-core依存関係を含める必要があります。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.12.Final</version>
</dependency>
テストには、H2インメモリデータベースを使用するので、この依存関係をpom.xmlファイルにも追加しましょう。
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
3. Hibernateのマルチテナンシーを理解する
公式のHibernateユーザーガイドで述べられているように、Hibernateのマルチテナンシーには3つのアプローチがあります。
- 個別のスキーマ–同じ物理データベースインスタンス内のテナントごとに1つのスキーマ
- 個別のデータベース–テナントごとに1つの個別の物理データベースインスタンス
- パーティション化された(ディスクリミネーター)データ–各テナントのデータはディスクリミネーター値によってパーティション化されます
The
いつものように、Hibernateは各アプローチの実装に関する複雑さを抽象化します。
必要なのは、これら2つのインターフェースの実装を提供することです:
-
MultiTenantConnectionProvider –テナントごとの接続を提供します
-
CurrentTenantIdentifierResolver –使用するテナント識別子を解決します
データベースとスキーマアプローチの例を実行する前に、各概念をより詳細に見ていきましょう。
3.1. MultiTenantConnectionProvider
基本的に、このインターフェースは具体的なテナント識別子のデータベース接続を提供します。
その2つの主な方法を見てみましょう。
interface MultiTenantConnectionProvider extends Service, Wrapped {
Connection getAnyConnection() throws SQLException;
Connection getConnection(String tenantIdentifier) throws SQLException;
// ...
}
Hibernateが使用するテナント識別子を解決できない場合、HibernateはメソッドgetAnyConnectionを使用して接続を取得します。 それ以外の場合は、メソッドgetConnectionを使用します。
Hibernateは、データベース接続の定義方法に応じて、このインターフェースの2つの実装を提供します。
- JavaのDataSourceインターフェースを使用– DataSourceBasedMultiTenantConnectionProviderImpl実装を使用します
- HibernateのConnectionProviderインターフェイスを使用する– AbstractMultiTenantConnectionProvider実装を使用します
3.2. CurrentTenantIdentifierResolver
テナント識別子を解決する方法はたくさんあります。 たとえば、実装では、構成ファイルで定義された1つのテナント識別子を使用できます。
別の方法は、パスパラメータからテナント識別子を使用することです。
このインターフェースを見てみましょう:
public interface CurrentTenantIdentifierResolver {
String resolveCurrentTenantIdentifier();
boolean validateExistingCurrentSessions();
}
HibernateはメソッドresolveCurrentTenantIdentifierを呼び出して、テナントIDを取得します。 Hibernateがすべての既存のセッションが同じテナント識別子に属していることを検証する場合、メソッドvalidateExistingCurrentSessionsはtrueを返す必要があります。
4. スキーマアプローチ
この戦略では、同じ物理データベースインスタンスで異なるスキーマまたはユーザーを使用します。
また、 CurrentTenantIdentifierResolver インターフェイスをモックして、テスト中に選択できる1つのテナント識別子を提供します。
public abstract class MultitenancyIntegrationTest {
@Mock
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
private SessionFactory sessionFactory;
@Before
public void setup() throws IOException {
MockitoAnnotations.initMocks(this);
when(currentTenantIdentifierResolver.validateExistingCurrentSessions())
.thenReturn(false);
Properties properties = getHibernateProperties();
properties.put(
AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER,
currentTenantIdentifierResolver);
sessionFactory = buildSessionFactory(properties);
initTenant(TenantIdNames.MYDB1);
initTenant(TenantIdNames.MYDB2);
}
protected void initTenant(String tenantId) {
when(currentTenantIdentifierResolver
.resolveCurrentTenantIdentifier())
.thenReturn(tenantId);
createCarTable();
}
}
MultiTenantConnectionProvider インターフェースの実装は、接続が要求されるたびに使用するスキーマを設定します:
class SchemaMultiTenantConnectionProvider
extends AbstractMultiTenantConnectionProvider {
private ConnectionProvider connectionProvider;
public SchemaMultiTenantConnectionProvider() throws IOException {
this.connectionProvider = initConnectionProvider();
}
@Override
protected ConnectionProvider getAnyConnectionProvider() {
return connectionProvider;
}
@Override
protected ConnectionProvider selectConnectionProvider(
String tenantIdentifier) {
return connectionProvider;
}
@Override
public Connection getConnection(String tenantIdentifier)
throws SQLException {
Connection connection = super.getConnection(tenantIdentifier);
connection.createStatement()
.execute(String.format("SET SCHEMA %s;", tenantIdentifier));
return connection;
}
private ConnectionProvider initConnectionProvider() throws IOException {
Properties properties = new Properties();
properties.load(getClass()
.getResourceAsStream("/hibernate.properties"));
DriverManagerConnectionProviderImpl connectionProvider
= new DriverManagerConnectionProviderImpl();
connectionProvider.configure(properties);
return connectionProvider;
}
}
したがって、テナントごとに1つずつ、2つのスキーマを持つ1つのインメモリH2データベースを使用します。
スキーママルチテナンシーモードとMultiTenantConnectionProvider interface :の実装を使用するようにhibernate.propertiesを構成しましょう。
hibernate.connection.url=jdbc:h2:mem:mydb1;DB_CLOSE_DELAY=-1;\
INIT=CREATE SCHEMA IF NOT EXISTS MYDB1\\;CREATE SCHEMA IF NOT EXISTS MYDB2\\;
hibernate.multiTenancy=SCHEMA
hibernate.multi_tenant_connection_provider=\
com.baeldung.hibernate.multitenancy.schema.SchemaMultiTenantConnectionProvider
テストの目的で、hibernate.connection.urlプロパティを構成して2つのスキーマを作成しました。 スキーマはすでに配置されているはずなので、これは実際のアプリケーションには必要ありません。
テストでは、1つ追加します車テナントへの入場
@Test
void whenAddingEntries_thenOnlyAddedToConcreteDatabase() {
whenCurrentTenantIs(TenantIdNames.MYDB1);
whenAddCar("myCar");
thenCarFound("myCar");
whenCurrentTenantIs(TenantIdNames.MYDB2);
thenCarNotFound("myCar");
}
テストでわかるように、whenCurrentTenantIsメソッドを呼び出すときにテナントを変更します。
5. データベースアプローチ
データベースマルチテナンシーアプローチでは、テナントごとに異なる物理データベースインスタンスを使用します。 各テナントは完全に分離されているため、最高のパフォーマンスが必要な場合よりも、テナントごとのバックアップなどの特別なデータベース機能が必要な場合は、この戦略を選択する必要があります。
データベースアプローチでは、上記と同じMultitenancyIntegrationTestクラスとCurrentTenantIdentifierResolverインターフェイスを使用します。
MultiTenantConnectionProvider インターフェイスの場合、 Map コレクションを使用して、テナント識別子ごとにConnectionProviderを取得します。
class MapMultiTenantConnectionProvider
extends AbstractMultiTenantConnectionProvider {
private Map<String, ConnectionProvider> connectionProviderMap
= new HashMap<>();
public MapMultiTenantConnectionProvider() throws IOException {
initConnectionProviderForTenant(TenantIdNames.MYDB1);
initConnectionProviderForTenant(TenantIdNames.MYDB2);
}
@Override
protected ConnectionProvider getAnyConnectionProvider() {
return connectionProviderMap.values()
.iterator()
.next();
}
@Override
protected ConnectionProvider selectConnectionProvider(
String tenantIdentifier) {
return connectionProviderMap.get(tenantIdentifier);
}
private void initConnectionProviderForTenant(String tenantId)
throws IOException {
Properties properties = new Properties();
properties.load(getClass().getResourceAsStream(
String.format("/hibernate-database-%s.properties", tenantId)));
DriverManagerConnectionProviderImpl connectionProvider
= new DriverManagerConnectionProviderImpl();
connectionProvider.configure(properties);
this.connectionProviderMap.put(tenantId, connectionProvider);
}
}
各 ConnectionProvider 構成ファイルを介して入力されます hibernate-データベース-
hibernate.connection.driver_class=org.h2.Driver
hibernate.connection.url=jdbc:h2:mem:<Tenant Identifier>;DB_CLOSE_DELAY=-1
hibernate.connection.username=sa
hibernate.dialect=org.hibernate.dialect.H2Dialect
最後に、 hibernate.properties を再度更新して、データベースマルチテナンシーモードとMultiTenantConnectionProviderインターフェイスの実装を使用します。
hibernate.multiTenancy=DATABASE
hibernate.multi_tenant_connection_provider=\
com.baeldung.hibernate.multitenancy.database.MapMultiTenantConnectionProvider
スキーマアプローチとまったく同じテストを実行すると、テストは再び合格します。
6. 結論
この記事では、個別のデータベースと個別のスキーマアプローチを使用したマルチテナンシーのHibernate5サポートについて説明します。 これら2つの戦略の違いを調査するために、非常に単純な実装と例を提供します。
この記事で使用されている完全なコードサンプルは、GitHubプロジェクトで入手できます。