Você pode escolher entre 3 estratégias diferentes que afetarão a pesquisa de conexão. Em qualquer caso, você deve fornecer uma implementação de
MultiTenantConnectionProvider
. A estratégia que você escolher afetará, obviamente, sua implementação. Observação geral sobre
MultiTenantConnectionProvider.getAnyConnection()
getAnyConnection()
é exigido pelo hibernate para coletar metadados e configurar o SessionFactory. Normalmente, em uma arquitetura multilocatário, você tem um banco de dados especial/mestre (ou esquema) não usado por nenhum locatário. É uma espécie de banco de dados de modelo (ou esquema). Tudo bem se este método retornar uma conexão com este banco de dados (ou esquema). Estratégia 1:cada locatário tem seu próprio banco de dados. (e, portanto, seu próprio pool de conexões)
Nesse caso, cada locatário tem seu próprio pool de conexões gerenciado pelo C3PO e você pode fornecer uma implementação de
MultiTenantConnectionProvider
com base em AbstractMultiTenantConnectionProvider
Cada locatário tem seu próprio
C3P0ConnectionProvider
, então tudo o que você precisa fazer em selectConnectionProvider(tenantIdentifier)
é devolver o correto. Você pode manter um mapa para armazená-los em cache e inicializar com preguiça um C3POConnectionProvider com algo como: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=...
// ...)
}
Estratégia 2:cada locatário tem seu próprio esquema e seu próprio pool de conexões em um único banco de dados
Este caso é muito semelhante à primeira estratégia em relação ao
ConnectionProvider
implementação, pois você também pode usar AbstractMultiTenantConnectionProvider
como classe base para implementar seu MultiTenantConnectionProvider
A implementação é muito semelhante à implementação sugerida para a Estratégia 1 exceto que você deve alterar o esquema em vez do banco de dados na configuração do c3po
Estratégia 3:cada locatário tem seu próprio esquema em um único banco de dados, mas usa um pool de conexões compartilhado
Esse caso é um pouco diferente, pois cada locatário usará o mesmo provedor de conexão (e, portanto, o pool de conexões será compartilhado). No caso :o provedor de conexão deve definir o esquema a ser usado antes de qualquer uso da conexão. ou seja, você deve implementar
MultiTenantConnectionProvider.getConnection(String tenantIdentifier)
(ou seja, a implementação padrão fornecida por AbstractMultiTenantConnectionProvider
não funcionará). Com postgresql você pode fazer isso com:
SET search_path to <schema_name_for_tenant>;
ou usando o apelido
SET schema <schema_name_for_tenant>;
Então aqui está o que seu
getConnection(tenant_identifier);
vai parecer:@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;
}
Referência útil está aqui (documento oficial)
Outro link útil C3POConnectionProvider.java
Você pode combinar a estratégia 1 e a estratégia 2 em sua implementação. Você só precisa de uma maneira de encontrar as propriedades de conexão/url de conexão corretas para o locatário atual.
EDITAR
Acho que a escolha entre a estratégia 2 ou 3 depende do tráfego e do número de locatários em seu aplicativo. Com pools de conexão separados:a quantidade de conexões disponíveis para um locatário será muito menor e assim:se por algum motivo legítimo um locatário precisar de repente de muitas conexões, o desempenho visto por esse locatário específico diminuirá drasticamente (enquanto o outro locatário não será impactado).
Por outro lado, com a estratégia 3, se por algum motivo legítimo um locatário precisar repentinamente de muitas conexões:o desempenho visto por cada locatário diminuirá.
Em geral, acho que a estratégia 2 é mais flexível e segura:cada locatário não pode consumir mais do que uma determinada quantidade de conexão (e essa quantidade pode ser configurada por locatário se precisar)