Ich arbeite an POC Java-Anwendung, die Multi Tenancy unterstützt. Ich starte meine POC mit JHipster Generator und OAUTH2 Authentifizierung starten auf Spring Boot. Jeder Mieter hat eigene SCHEMA, aber Mieter und OAUTH2-Tabellen sind öffentlich. JHipster verwendet Hibernate und Spring Data, um eine Verbindung mit der DB herzustellen. In meinem Beispiel verwende ich Mysql als Datenbank.Multi-Tenancy-App Java Frühling Hibernate Mysql OAuth2 Frühling Securit
Ich möchte eine Lösung mit einer einzigen DataSource und einem einzigen Verbindungspool erreichen. Als Verbindungspool JHipster verwenden Sie HikariCP. In MultiTenantConnectionProvider möchte ich SCHEMA in ähnlicher Weise wie Hibernate doc ändern beschreiben (siehe Beispiel 16.3.)
http://docs.jboss.org/hibernate/orm/4.2/devguide/en-US/html/ch16.html#d5e4866
Wenn Hibernate getConnection (String tenantIdentifier) nenne ich richtig SCHEMA in MYSQL-Datenbank festlegen möchten. Meine Implementierung verwenden Mysql Befehl, um Schema "USE sample_tenant_identifier" zu ändern. Ich muss Benutzer mit Namen "Benutzer" und "Admin". Jeder dieser Benutzer hat eigene SCHEMA. Das Problem, das ich habe, ist sehr seltsam. Beispiel alle SELECT-Operationen verwenden "Benutzer" -Schema, aber INSERT oder UPDATE verwenden "admin" SCHEMA. Im Ergebnis "admin" sehen Sie Daten in "user" SCHEMA aber INSERT Daten in "admin" SCHEMA.
Paket com.mycompany.myapp.tenancy.hibernate;
Ich erstelle ein zweites Arbeitsbeispiel, wo ich neue DataSource für jeden Mandanten erstellen und in Map speichern. Dieses Beispiel funktioniert OK, aber gleichzeitige Karte das ist nicht was ich genau will.
package com.mycompany.myapp.tenancy.hibernate;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider;
import org.hibernate.service.spi.ServiceRegistryAwareService;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* Created by AdamS on 2015-03-12.
*/
public class MyMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider, ServiceRegistryAwareService {
private final Logger log = LoggerFactory.getLogger(MyMultiTenantConnectionProviderImpl.class);
DataSource dataSource;
private Map<String, DataSource> dataSourceMap = new HashMap<String, DataSource>();
public MyMultiTenantConnectionProviderImpl() {
getSource("main");
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getAnyConnection() throws SQLException {
//return this.dataSource.getConnection();
log.info("get eny connection return main");
return getSource("jhipster").getConnection();
}
@Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
log.info("Tenatd is:" + tenantIdentifier);
return getSource(tenantIdentifier).getConnection();
}
@Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
log.info("releaseConnection " + tenantIdentifier);
connection.close();
}
@Override
public boolean supportsAggressiveRelease() {
return false;
}
@Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
@Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
@Override
public void injectServices(ServiceRegistryImplementor serviceRegistry) {
Map lSettings = serviceRegistry.getService(ConfigurationService.class).getSettings();
DataSource localDs = (DataSource) lSettings.get("hibernate.connection.datasource");
dataSource = localDs;
}
public DataSource getSource(String tentant) {
if(dataSourceMap.containsKey(tentant)){
return dataSourceMap.get(tentant);
} else {
DataSource ds = dataSource(tentant);
dataSourceMap.put(tentant,ds);
return ds;
}
}
public DataSource dataSource(String tentant) {
log.info("Create Datasource "+tentant);
HikariConfig config = new HikariConfig();
config.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
config.addDataSourceProperty("url", "jdbc:mysql://localhost:3306/"+tentant);
config.addDataSourceProperty("user", "root");
config.addDataSourceProperty("password", "");
//MySQL optimizations, see https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
config.addDataSourceProperty("cachePrepStmts", true);
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useServerPrepStmts", "true");
return new HikariDataSource(config);
}
}
Meine Konfigurationsklasse:
package com.mycompany.myapp.config;
...
@Configuration
@EnableJpaRepositories(basePackages = {"com.mycompany.myapp.repository"},entityManagerFactoryRef = "entityManagerFactory",transactionManagerRef = "transactionManager")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
//@EnableTransactionManagement()
//@EnableAutoConfiguration(exclude = HibernateJpaAutoConfiguration.class)
public class DatabaseConfiguration implements EnvironmentAware {
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
private RelaxedPropertyResolver propertyResolver;
private Environment env;
private DataSource dataSource;
@Autowired(required = false)
private MetricRegistry metricRegistry;
@Override
public void setEnvironment(Environment env) {
this.env = env;
this.propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
}
@Bean(destroyMethod = "shutdown")
@ConditionalOnMissingClass(name = "com.mycompany.myapp.config.HerokuDatabaseConfiguration")
@Profile("!" + Constants.SPRING_PROFILE_CLOUD)
public DataSource dataSource() {
log.debug("Configuring Datasource");
if (propertyResolver.getProperty("url") == null && propertyResolver.getProperty("databaseName") == null) {
log.error("Your database connection pool configuration is incorrect! The application" +
"cannot start. Please check your Spring profile, current profiles are: {}",
Arrays.toString(env.getActiveProfiles()));
throw new ApplicationContextException("Database connection pool is not configured correctly");
}
HikariConfig config = new HikariConfig();
config.setDataSourceClassName(propertyResolver.getProperty("dataSourceClassName"));
if (propertyResolver.getProperty("url") == null || "".equals(propertyResolver.getProperty("url"))) {
config.addDataSourceProperty("databaseName", propertyResolver.getProperty("databaseName"));
config.addDataSourceProperty("serverName", propertyResolver.getProperty("serverName"));
} else {
config.addDataSourceProperty("url", propertyResolver.getProperty("url"));
}
config.addDataSourceProperty("user", propertyResolver.getProperty("username"));
config.addDataSourceProperty("password", propertyResolver.getProperty("password"));
//MySQL optimizations, see https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
if ("com.mysql.jdbc.jdbc2.optional.MysqlDataSource".equals(propertyResolver.getProperty("dataSourceClassName"))) {
config.addDataSourceProperty("cachePrepStmts", propertyResolver.getProperty("cachePrepStmts", "true"));
config.addDataSourceProperty("prepStmtCacheSize", propertyResolver.getProperty("prepStmtCacheSize", "250"));
config.addDataSourceProperty("prepStmtCacheSqlLimit", propertyResolver.getProperty("prepStmtCacheSqlLimit", "2048"));
config.addDataSourceProperty("useServerPrepStmts", propertyResolver.getProperty("useServerPrepStmts", "true"));
}
if (metricRegistry != null) {
config.setMetricRegistry(metricRegistry);
}
dataSource = new HikariDataSource(config);
return dataSource;
}
@Bean
public SpringLiquibase liquibase(DataSource dataSource) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:config/liquibase/master.xml");
liquibase.setContexts("development, production");
if (env.acceptsProfiles(Constants.SPRING_PROFILE_FAST)) {
if ("org.h2.jdbcx.JdbcDataSource".equals(propertyResolver.getProperty("dataSourceClassName"))) {
liquibase.setShouldRun(true);
log.warn("Using '{}' profile with H2 database in memory is not optimal, you should consider switching to" +
" MySQL or Postgresql to avoid rebuilding your database upon each start.", Constants.SPRING_PROFILE_FAST);
} else {
liquibase.setShouldRun(false);
}
} else {
log.debug("Configuring Liquibase");
}
return liquibase;
}
@Bean
public MultiTenantSpringLiquibase liquibaseMt(DataSource dataSource) throws SQLException {
MultiTenantSpringLiquibase multiTenantSpringLiquibase = new MultiTenantSpringLiquibase();
multiTenantSpringLiquibase.setDataSource(dataSource);
Statement stmt = null;
stmt = dataSource.getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT tu.tentantId FROM t_user tu WHERE tu.tentantId IS NOT NULL");
ArrayList<String> schemas = new ArrayList<>();
while(rs.next()) {
String schemaName = rs.getString("tentantId");
dataSource.getConnection().createStatement().executeUpdate("CREATE DATABASE IF NOT EXISTS "+schemaName);
schemas.add(schemaName);
}
multiTenantSpringLiquibase.setSchemas(schemas);
multiTenantSpringLiquibase.setChangeLog("classpath:config/liquibase/mt_master.xml");
multiTenantSpringLiquibase.setContexts("development, production");
if (env.acceptsProfiles(Constants.SPRING_PROFILE_FAST)) {
if ("org.h2.jdbcx.JdbcDataSource".equals(propertyResolver.getProperty("dataSourceClassName"))) {
multiTenantSpringLiquibase.setShouldRun(true);
log.warn("Using '{}' profile with H2 database in memory is not optimal, you should consider switching to" +
" MySQL or Postgresql to avoid rebuilding your database upon each start.", Constants.SPRING_PROFILE_FAST);
} else {
multiTenantSpringLiquibase.setShouldRun(false);
}
} else {
log.debug("Configuring MultiTenantSpringLiquibase");
}
return multiTenantSpringLiquibase;
}
@Bean
public Hibernate4Module hibernate4Module() {
return new Hibernate4Module();
}
}
Und EntityManagerConfiguration:
package com.mycompany.myapp.config;
....
/**
* Created by AdamS on 2015-03-31.
*/
@Configuration
@EnableTransactionManagement
public class EntityManagerConfiguration {
@Autowired
private DataSource dataSource;
@Autowired
private JpaVendorAdapter jpaVendorAdapter;
@Bean(name = "entityManagerFactory")
//@DependsOn("transactionManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws Throwable {
HashMap<String, Object> properties = new HashMap<String, Object>();
//properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
//properties.put("javax.persistence.transactionType", "JTA");
properties.put("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
properties.put("hibernate.cache.use_second_level_cache", "false");
properties.put("hibernate.cache.use_query_cache", "false");
properties.put("hibernate.generate_statistics", "true");
properties.put("hibernate.tenant_identifier_resolver", "com.mycompany.myapp.tenancy.hibernate.MyCurrentTenantIdentifierResolver");
/* MANY DATASOURCES. WORKING SOLUTION */
//properties.put("hibernate.multi_tenant_connection_provider", "com.mycompany.myapp.tenancy.hibernate.MyMultiTenantConnectionProviderImpl");
/*SCHEMA CONFIG THAT IS NOT WORKING*/
properties.put("hibernate.multi_tenant_connection_provider", "com.mycompany.myapp.tenancy.hibernate.SchemaMultiTenantConnectionProviderImpl");
properties.put("hibernate.multiTenancy", "SCHEMA");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(dataSource);
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
//entityManager.setPackagesToScan("com.mycompany.myapp.domain");
entityManager.setPackagesToScan(new String[] { "com.mycompany.myapp.domain","com.mycompany.myapp.tenancy.domain" });
entityManager.setPersistenceUnitName("persistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
Wer eine Idee haben, warum meine Hibernate Implementierung auf diese Weise arbeiten können? ganze Projekt, das Sie auf Github finden:
https://github.com/upway/jhipster-multi-tenancy-poc
Das scheint wie ein Fehler in Hibernate welche Version verwenden Sie? –
Ich benutze Hibernate 4.3.6.Final – sadsax