当前位置: 首页 > 知识库问答 >
问题:

多租户应用程序Java Spring Hibernate Mysql OAuth2 Spring Securit

杨晟
2023-03-14

当hibernate调用getConnection(String tenantIdentifier)时,我想在MYSQL数据库中设置正确的模式。我实现使用了Mysql命令来更改scheme“use sample_tenant_identifier”。我必须使用名为“用户”和“管理员”的用户。每个用户都有自己的模式。我的问题很奇怪。示例所有选择操作使用“user”模式,但插入或更新使用“admin”模式。在结果“admin”中,查看“user”模式中的数据,但将数据插入“admin”模式。

软件包com.mycompany.myapp.tenancy.hibernate;

import org.hibernate.HibernateException;
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.Map;

/**
 * Created by AdamS on 2015-04-02.
 */
public class SchemaMultiTenantConnectionProviderImpl  implements MultiTenantConnectionProvider, ServiceRegistryAwareService {

    private final Logger log = LoggerFactory.getLogger(SchemaMultiTenantConnectionProviderImpl.class);
    DataSource dataSource;

    @Override
    public Connection getAnyConnection() throws SQLException {
        return this.dataSource.getConnection();
    }

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        try {
            connection.createStatement().execute("USE jhipster;");
        }
        catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to specified schema [public]", e);
        }
        connection.close();
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        log.debug("Tenatd is:"+tenantIdentifier);
        final Connection connection = getAnyConnection();
        try {
            connection.createStatement().execute("USE " + tenantIdentifier + ";");
//            connection.setCatalog(tenantIdentifier);
//            connection.setSchema(tenantIdentifier);
        }
        catch (SQLException e) {
            throw new HibernateException("Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]", e);
        }
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
//        try {
//            connection.createStatement().execute("USE "+tenantIdentifier+";");
//        }
//        catch (SQLException e) {
//            throw new HibernateException("Could not alter JDBC connection to specified schema [public]", e);
//        }
        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;
    }
}

我创建了第二个工作示例,其中我为每个租户创建新的DataSource并将其存储在Map中。这个例子工作可以,但并发映射,这不是我想要的。

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);
    }
}
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();
    }
}
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;
    }
}

共有1个答案

长孙翔
2023-03-14

替换

connection.createStatement().execute("USE " + tenantIdentifier + ";");

connection.setCatalog(tenantIdentifier);

代码中的主要问题是(我认为)使用sqluse...而不是使用mysql连接器文档中解释的setCatalog()函数

 类似资料:
  • 我必须在j2ee中开发一个多租户SaaS应用程序,从Iaas和PaaS开始实现三种云模型,我选择了openstack和openshift origin。SaaS应用程序的第一个标准是多租户,我知道有三种方法来实现它——单独的数据库——共享数据库,单独的模式——共享数据库,共享模式。我在这里迷失了方向,因为许多框架,比如ATHENA,ORM,比如hibernate,还有TOPLINK。我需要帮助了解

  • 我已经安装了keycloak-angular包,我使用它的方式如下:https://www.npmjs.com/package/keycloak-angular 问题是,在我的应用程序中,我希望有多租户。这意味着在应用程序加载期间不知道领域名。 在说明中,它说“KeycloakService应该在应用程序加载期间使用APP_INITIALIZER标记初始化”,问题是该领域是由用户给定的,在应用程序

  • 我正在使用Java、Spring、Struts2和Hibernate设计一个多租户SaaS Web应用程序。经过一些研究,我选择在共享数据库、共享模式、共享表的方法中实现多租户。并用tenantid标记每个db行。 我已经重写了我的应用程序,所以管理者和DAO将把tenantId作为一个参数,只为正确的数据库资源服务。 当获取信息时,这对所有视图来说都是完美的。也用于创建新的东西(使用登录的用户t

  • 我目前正试图找出为我的系统设置多租户的最佳方法。我面临的问题是,租户并不总是必须是子域,但可以作为子域的一部分进行设置,子域可以有多个租户。我似乎在网上找不到任何东西可以帮助我在Laravel 6中进行设置。 系统要求: 一台服务器可以有许多子域 系统必须设置一个数据库,该数据库将使用tenant_id来确定哪些数据属于租户。 我目前正在以以下结构将所有子域数据存储在“subdomains”表中:

  • 我正在学习多租户应用程序,以及如何使用PostgreSQL的模式来实现这一点。 在研究这个主题时,我发现了一篇文章,作者描述了在多租户应用程序中使用PostgreSQL模式时的糟糕体验。主要问题是迁移性能差和数据库资源使用率高。 似乎只有一个模式(在租户之间共享表)会比每个租户有一个单独的模式带来更好的性能。但我觉得很奇怪。我的想法正好相反,因为较小表上的索引往往比较大表上的索引轻。 为什么在许多

  • 我正在使用spring boot开发一个多租户应用程序。 系统的业务逻辑将根据每个租户进行更改。 例如,应用程序上的特定租户租赁空间可能希望改变使用一些复杂的自定义逻辑计算值的方式。我想为应用程序注册默认行为/依赖项,并允许特定租户覆盖它。 这可以使用Spring Boot完成吗?

  • 我的问题类似于问题多租户应用程序-OneDrive业务API 但我想让我的应用程序从其他不同azure订阅的租户那里访问一个驱动器,有可能吗?我明白,如果我注册我的应用程序并将其标记为多租户,它将允许我在azure订阅中访问租户,但如果我想使用相同的应用程序但在不同的azure订阅中访问租户,怎么办。