Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-Tenancy with Spring boot(2.7.18) + Hibernate: "SessionFactory configured for multi-tenancy, but no tenant identifier specified" #3406

Open
NaveenRamu opened this issue Mar 21, 2024 · 3 comments
Assignees
Labels
status: feedback-provided Feedback has been provided

Comments

@NaveenRamu
Copy link

Hi,

I created a multi-tenancy application with Spring Boot and JPA.
When TenantIdentifier is not returning any default tenant value then my application is failing with the below error.

image

` TenantContext.java

public class TenantContext {

private static final InheritableThreadLocal<String> CURRENT_TENANT = new InheritableThreadLocal();
private static final Logger LOGGER = LoggerFactory.getLogger(TenantContext.class);

public TenantContext() {
}

public static String getCurrentTenant() {
    return (String)CURRENT_TENANT.get();
}

public static void setCurrentTenant(String tenant) {
    CURRENT_TENANT.set(tenant);
    LOGGER.debug("Setting current tenant in tenant context to:{}", tenant);
}
}

`

`
TenantIdentifier.java

@Component
public class TenantIdentifier implements CurrentTenantIdentifierResolver {

private static final Logger logger = LoggerFactory.getLogger(TenantIdentifier.class);

@Override
public String resolveCurrentTenantIdentifier() {
    logger.info("resolveCurrentTenantIdentifier called");
    String tenant = TenantContext.getCurrentTenant();
    logger.info("resolveCurrentTenantIdentifier called tenant {}", tenant);
    return tenant;
}

@Override
public boolean validateExistingCurrentSessions() {
    return true;
}
}

`

`PersistenceJpaConfigWithMultitenancy.java

@Configuration
@EnableJpaRepositories(basePackages = {"*****.**.persistence.repository"}, repositoryFactoryBeanClass = 
ConfigurationRepositoryFactory.class)
@EnableTransactionManagement
public class PersistenceJpaConfigWithMultitenancy {

private static final Logger logger = LoggerFactory.getLogger(PersistenceJpaConfigWithMultitenancy.class);

@Autowired
HikariDataSourceBuilder hikariDataSourceBuilder;

@Autowired
private EnvironmentService environmentService;


@Bean
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
    logger.info("Called DataSourceBasedMultiTenantConnectionProviderImpl");
    return new DataSourceBasedMultiTenantConnectionProviderImpl();
}

@Bean
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
    logger.info("called TenantIdentifier");
    return new TenantIdentifier();
}

private LocalContainerEntityManagerFactoryBean getLocalContainerEntityManagerFactoryBean() {
    return new LocalContainerEntityManagerFactoryBean();
}

@Bean(name = "entityManagerFactory")
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
    logger.info("Setting the entityManagerFactory bean"); //TODO: to be removed later
    JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    Map<String, Object> hibernateProps = new LinkedHashMap<>();
    hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
    hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, 
    DataSourceBasedMultiTenantConnectionProviderImpl.class);
    hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, TenantIdentifier.class);

    LocalContainerEntityManagerFactoryBean em = getLocalContainerEntityManagerFactoryBean();

    em.setPackagesToScan("com.swgrp.itomdi.administration.persistence.model");
    em.setJpaVendorAdapter(vendorAdapter);
    em.setJpaProperties(additionalProperties());
    em.setJpaPropertyMap(hibernateProps);

    return em;
}

@Bean
@ConditionalOnProperty(
        name = "config.store.type",
        havingValue = "db")
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager
            = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
}

@Bean
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
    return entityManagerFactoryBean.getObject();
}

@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
    return new PersistenceExceptionTranslationPostProcessor();
}

Properties additionalProperties() {
    Properties properties = new Properties();
    properties.setProperty("spring.jpa.database", "Vertica");
    properties.setProperty("hibernate.show_sql", "true");
    properties.setProperty(
            "hibernate.dialect", "org.hibernate.dialect.VerticaDialect");
    properties.setProperty("spring.data.jpa.repositories.enabled", "true");
    properties.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
    return properties;
}
}

`

` DataSourceBasedMultiTenantConnectionProviderImpl.java

@Component
public class DataSourceBasedMultiTenantConnectionProviderImpl extends 
AbstractDataSourceBasedMultiTenantConnectionProviderImpl {

private static Logger logger = LoggerFactory.getLogger(DataSourceBasedMultiTenantConnectionProviderImpl.class);

@Autowired
private HikariDataSourceBuilder hikariDataSourceBuilder;

@Autowired
private EnvironmentService environmentService;


@Override
protected DataSource selectAnyDataSource() {
    logger.info("Calling selectAnyDataSource");
    return AdministrationCacheProvider.getAdminCache().values().iterator().next().getTenantDataSource(); //TODO: check if this can be removed and send null instead
}

@Override
protected DataSource selectDataSource(String tenant) {
    logger.info("Returning datasource for tenant:{}", tenant);
    return AdministrationCacheProvider.getAdminCache().get(tenant).getTenantDataSource();
}
}

`

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Mar 21, 2024
@christophstrobl
Copy link
Member

If you'd like us to spend some time investigating, please take the time to provide a complete minimal sample (something that we can unzip or git clone, build, and deploy) that reproduces the problem. It would also help if the above works with plain hibernate without any spring data being involved.

@christophstrobl christophstrobl added status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged labels Mar 25, 2024
@NaveenRamu
Copy link
Author

NaveenRamu commented Mar 26, 2024

Hi @christophstrobl,

Thank you for your quick response.

Please find the attached reproducible code for the problem
schema-based-multi-tenancy.zip

API endpoint: http://localhost:8080/notes
header: [ X-TenantID - tenant1]

Note: If I uncomment lines 14 to 16 in TenantIdentifierResolver.java class, the application will run without any errors, and the session will open with the default tenant.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Mar 26, 2024
@chavanp97
Copy link

chavanp97 commented Jun 3, 2024

Hi @christophstrobl , @spring-projects-issues
Just wanted to check if you were able to reproduce the issue at your end using the standalone project provided by us?
Kindly let us know if you need any other info from our end. We still have this issue open with us and waiting for any kind of technical assistance.

@christophstrobl christophstrobl self-assigned this Jun 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: feedback-provided Feedback has been provided
Projects
None yet
Development

No branches or pull requests

4 participants