我正在使用Spring2.x、Spring Data REST、Hibernate5.x、MySQL创建一个服务器REST应用程序。
我按照以下准则配置了多租户:https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation,唯一的区别是我使用了每个租户一个数据库。
我有一个MultiTenantConnectionProvider
来创建到DB的连接,还有一个TenantiDentifierResolver
来获取当前承租人。
一些相关的代码:
@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getCurrentTenant();
if (tenantId != null) {
return tenantId;
}
return DEFAULT_TENANT_ID;
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
...
@Component
@Profile("prod")
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 3193007611085791247L;
private Logger log = LogManager.getLogger();
private Map<String, HikariDataSource> dataSourceMap = new ConcurrentHashMap<String, HikariDataSource>();
@Autowired
private TenantRestClient tenantRestClient;
@Autowired
private PasswordEncrypt passwordEncrypt;
@Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
@Override
public Connection getAnyConnection() throws SQLException {
Connection connection = getDataSource(TenantIdResolver.TENANT_DEFAULT).getConnection();
return connection;
}
@Override
public Connection getConnection(String tenantId) throws SQLException {
Connection connection = getDataSource(tenantId).getConnection();
return connection;
}
@Override
public void releaseConnection(String tenantId, Connection connection) throws SQLException {
log.info("releaseConnection " + tenantId);
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;
}
public HikariDataSource getDataSource(@NotNull String tentantId) throws SQLException {
if (dataSourceMap.containsKey(tentantId)) {
return dataSourceMap.get(tentantId);
} else {
HikariDataSource dataSource = createDataSource(tentantId);
dataSourceMap.put(tentantId, dataSource);
return dataSource;
}
}
public HikariDataSource createDataSource(String tenantId) throws SQLException {
log.info("Create Datasource for tenant {}", tenantId);
try {
Database database = tenantRestClient.getDatabase(tenantId);
DatabaseInstance databaseInstance = tenantRestClient.getDatabaseInstance(tenantId);
if (database != null && databaseInstance != null) {
HikariConfig hikari = new HikariConfig();
String driver = "";
String options = "";
switch (databaseInstance.getType()) {
case MYSQL:
driver = "jdbc:mysql://";
options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";
break;
default:
driver = "jdbc:mysql://";
options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";
}
hikari.setJdbcUrl(driver + databaseInstance.getHost() + ":" + databaseInstance.getPort() + "/" + database.getName() + options);
hikari.setUsername(database.getUsername());
hikari.setPassword(passwordEncrypt.decryptPassword(database.getPassword()));
// MySQL optimizations, see
// https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
hikari.addDataSourceProperty("cachePrepStmts", true);
hikari.addDataSourceProperty("prepStmtCacheSize", "250");
hikari.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
hikari.addDataSourceProperty("useServerPrepStmts", "true");
hikari.addDataSourceProperty("useLocalSessionState", "true");
hikari.addDataSourceProperty("useLocalTransactionState", "true");
hikari.addDataSourceProperty("rewriteBatchedStatements", "true");
hikari.addDataSourceProperty("cacheResultSetMetadata", "true");
hikari.addDataSourceProperty("cacheServerConfiguration", "true");
hikari.addDataSourceProperty("elideSetAutoCommits", "true");
hikari.addDataSourceProperty("maintainTimeStats", "false");
hikari.setMinimumIdle(3);
hikari.setMaximumPoolSize(5);
hikari.setIdleTimeout(30000);
hikari.setPoolName("JPAHikari_" + tenantId);
// mysql wait_timeout 600seconds
hikari.setMaxLifetime(580000);
hikari.setLeakDetectionThreshold(60 * 1000);
HikariDataSource dataSource = new HikariDataSource(hikari);
return dataSource;
} else {
throw new SQLException(String.format("DB not found for tenant %s!", tenantId));
}
} catch (Exception e) {
throw new SQLException(e.getMessage());
}
}
}
@Configuration
@Profile("prod")
public class HibernateConfig {
@Autowired
private JpaProperties jpaProperties;
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
Map<String, Object> properties = new HashMap<>();
properties.putAll(jpaProperties.getHibernateProperties(new HibernateSettings()));
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.server");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
spring.datasource.url=jdbc:mysql://url:3306/empty?useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false
spring.datasource.username=empty
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.password=empty
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.hibernate.ddl-auto: validate
spring.jpa.hibernate.naming.physical- strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.show-sql: false
在应用程序启动期间,我看到Spring创建了一个连接池,连接到我在属性文件中配置的db。
我希望避免这种情况,因为我的所有连接都是由MultiTenantConnectionProviderImpl
创建的。我希望在bean中继续使用注入EntityManager
和DataSource
的功能。
我已经在这里看到了如何禁用Spring Boot数据源配置,但是这样做我就不能再在应用程序中注入数据源了。
对于如何从属性文件中完全删除数据源的定义,并以编程方式从MultiTenantConnectionProviderImpl
在应用程序中注入数据源,您有什么建议吗?
下面是关于如何以编程方式创建数据源的完整示例。
只是参数取自属性文件,而不是在java类内部进行硬编码。
定义多个数据源时,必须定义一个@primary
和仅定义一个,然后将有一个@qualifier
来标识每个数据源。如何管理它们应该是很简单的。
@RequiredArgsConstructor
@PropertySource({"classpath:persistence/persistence-primarydb.properties"})
@EnableJpaRepositories(basePackages = "io.vforge.cauldron.repository.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager")
@EnableJpaAuditing
@Configuration
public class CauldronPrimaryDatasource {
private final Environment env;
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primaryDataSource());
em.setPackagesToScan("io.vforge.cauldron.model.primary");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("primary.hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("primary.hibernate.dialect"));
properties.put("hibernate.show_sql", env.getProperty("primary.hibernate.show_sql"));
em.setJpaPropertyMap(properties);
return em;
}
@Primary
@Bean
public HikariDataSource primaryDataSource() {
final HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(env.getProperty("primary.datasource.url"));
dataSource.setUsername(env.getProperty("primary.datasource.username"));
dataSource.setPassword(env.getProperty("primary.datasource.password"));
return dataSource;
}
@Primary
@Bean
public PlatformTransactionManager primaryTransactionManager() {
JpaTransactionManager transactionManager= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
primaryEntityManagerFactory().getObject());
return transactionManager;
}
}
问题内容: 我正在尝试使用此网络研讨会中概述的单独的架构方法向Java应用程序添加多租户 我想知道如何通过spring来配置多个数据源,也许是通过使用属性文件并基于租户id从spring上下文中获取数据源。 更重要的是,尽管我希望能够配置支持此多租户功能的自定义连接提供程序实现,以供Hibernate使用,而不是默认使用的注入功能。 我怎样才能做到这一点。 问题答案: 使用。
我试图设置一个配置来管理一个多租户环境,使用sping-start、sping-data-jpa、hibernate和mysql(相同的模式,每个租户表都有一个tenant_code列)。对于依赖项,父maven项目是sping-boo-starter-父(2.1.2.发布)。 在保存实体hibernate抛出此异常:org.hibernate.StaleObjectStateExcture:行被
我有一个KeyClope实例,为每个域创建了两个域和一个用户。 我有我的spring boot应用程序。yml(resource server-API)用于一个特定领域,并在我的代码中修复。 它正在为Realm1工作并进行验证。 但现在我可以接收来自user2(tenant2)的请求,而令牌将无效,因为公钥(realm1)对于已签名的请求jwt令牌(realm2)无效。 允许多领域多租户和动态配置
问题内容: 在Spring 3应用程序中,我试图通过Hibernate 4的本机MultiTenantConnectionProvider和CurrentTenantIdentifierResolver实现多租户。我发现在Hibernate 4.1.3 中存在此问题,但是我正在运行4.1.9并仍收到类似的异常: 以下是相关代码。在I中,我现在只写了一些简单的代码,每次都只返回一个新的连接,并且在这
问题内容: 在Spring 3应用程序中,我试图通过Hibernate 4的本机MultiTenantConnectionProvider和CurrentTenantIdentifierResolver实现多租户。我看到在Hibernate 4.1.3 中存在此问题,但是我正在运行4.1.9并仍然收到类似的异常: 以下是相关代码。在I中,我现在只写了一些哑代码,每次都只返回一个新的连接,并且此时总
问题内容: 目前,我正在使用带有@Transactional批注的DriverManagerDataSource来管理事务。但是所有事务都非常非常慢,这可能是因为数据源每次都打开和关闭与db的连接。 我应该使用什么数据源来加快交易速度? 问题答案: 实际上不是连接池,只能用于测试。您应该尝试使用Apache Commons DBCP 。就像是: