Setting hibernate dialect dynamically
Asked Answered
P

4

11

I have implemented Hibernate's multitenant database architecture, where a specific database connection is chosen depending on the tenant. I'm using Spring 4.3 and Hibernate 5.2.

All good when tenants are using the same RDBMS, but when they are different, I have to change the dialect setting in hibernate properties dynamically which I don't know how.

My hibernate properties are in dispatcher-servlet.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
         http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.example"/>
    <mvc:annotation-driven/>
    <context:property-placeholder location="classpath:application.properties"/>
    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean" >
        <property name="packagesToScan">
            <list>
                <value>com.example.model</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <!--<prop key="hibernate.dialect">${hibernate.dialect}</prop>-->
                <prop key="hibernate.show_sql">${hibernate.show_sql:false}</prop>
                <prop key="hibernate.format_sql">${hibernate.format_sql:false}</prop>
                <prop key="hibernate.multiTenancy">DATABASE</prop>
                <prop key="hibernate.tenant_identifier_resolver">com.example.multitenancy.CurrentTenantIdentifierResolverImpl</prop>
                <prop key="hibernate.multi_tenant_connection_provider">com.example.multitenancy.MultiTenantConnectionProviderImpl</prop>
            </props>
        </property>
    </bean>
    <bean id="transactionManager"  class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>

Below is the implementation of Hibernate's CurrentTenantIdentifierResolver:

public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver {
    @Override
    public String resolveCurrentTenantIdentifier() {

        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        return Helper.getTenantFromAuthentication(authentication);
    }
    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

and the implementation of AbstractDataSourceBasedMultiTenantConnectionProviderImpl:

public class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {

    @Override
    protected DataSource selectAnyDataSource() {
        return getDataSource("tenantId1");
    }
    @Override
    protected DataSource selectDataSource(String tenantIdentifier) {
        return getDataSource(tenantIdentifier);
    }

    private DataSource getDataSource(String prefix) {

        Properties properties = new Properties();
        try {
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("application.properties"));

        } catch (IOException e) {
            throw new RuntimeException();
        }

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(properties.getProperty(prefix + ".driverClassName"));
        dataSource.setUrl(properties.getProperty(prefix + ".url"));
        dataSource.setUsername(properties.getProperty(prefix + ".username"));
        dataSource.setPassword(properties.getProperty(prefix + ".password"));
        return dataSource;
    }
}

The application.properties file looks like this:

tenantId1.driverClassName = org.postgresql.Driver
tenantId1.url = <...>
tenantId1.username = <...>
tenantId1.password = <...>

tenantId2.driverClassName = com.mysql.jdbc.Driver
tenantId2.url = <...>
tenantId2.username = <...>
tenantId2.password = <...>

Is there a way to change the hibernate dialect dynamically?

Py answered 11/5, 2018 at 12:51 Comment(3)
#6075178Cline
@nikesh, that certainly looks relevant, but could you elaborate on how to get the existing configuration and change it? I would gladly accept your answer.Py
Get SessionFactory bean from Spring and callGetConfiguration method. It shud work. using id : sessionFactory as in your code.Cline
D
1

You cannot achieve using single hibernate configuration file. You need to have different configurations files for each database.

For example, you have two database MySql and Oracle:

To configure mysql database

hibernate-mysql.cfg.xml

To configure oracle database

hibernate-oracle.cfg.xml

Create two different sessions for and code should be like this.

private static SessionFactory sessionAnnotationFactory; 

    sessionAnnotationFactory = new Configuration().configure("hibernate-mysql.cfg.xml").buildSessionFactory();

Session MySqlSession = sessionAnnotationFactory.openSession();

For Oracle database configuration

sessionAnnotationFactory = new Configuration().configure("hibernate-oracle.cfg.xml").buildSessionFactory();

Session OracleSession = sessionAnnotationFactory.openSession()
Dasha answered 25/5, 2018 at 8:0 Comment(1)
This is the old version style of the writing of the code. As per current version by default hibernate.cfg.xml will load.Longfaced
U
0

I think you should create two sessionfactory objects for the two different database.

Refer here: Hibernate configuring multiple datasources and multiple session factories

Alternatively, please see my below sol and tell me if any concern:

You can use setHibernateProperties(java.util.Properties hibernateProperties) method of LocalSessionFactoryBean to change the hibernate.dialect.

You have injected the hibernate sessionfactory bean through Spring. So all you have to do is get the bean from spring container and change the hibernate properties. Below I have added only the dialect to the session factory but I recommend you the set all the required properties.

The below method is just for reference and understanding, change the logic and parameters as needed.

Call the below method when you want to change the hibernate dialect.

@Autowired
private LocalSessionFactoryBean sessionFactory; 

//Change the logic and parameters as needed.
public void changedialect(String database) {    

    Properties prop=new Properties();
    String dialect="org.hibernate.dialect.MySQLDialect";

    if("postgresql".equals(database)) dialect="org.hibernate.dialect.PostgreSQLDialect";


    prop.setProperty("hibernate.dialect", dialect);
    sessionFactory.setHibernateProperties(prop);

}
Untinged answered 25/5, 2018 at 10:15 Comment(0)
I
0

I don't see why you could not achieve this using a purely annotation-based configuration. I set my dialect like this :

properties.put("hibernate.dialect", "org.hibernate.dialect.SQLServerDialect");

I actually have some code in place that fully rebuilds the database along with some test data if a value in a config file is set to ON. You should be able to do the same by switching to a fully java-based config.

Indulgent answered 28/5, 2018 at 12:18 Comment(0)
B
0

If you doesn't set explicit dialect, hibenrate auto resolve it with DialectResolver implemented class.

You can write own autoresolver. OwnDialectResolver example

Blubberhead answered 22/9, 2021 at 7:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.