How to do database routing in read-only and read-write with Spring
Asked Answered
H

3

7

I'm studying Transaction Routing in Spring, but my application has a runtime problem.

I have two MySQL databases, one for reading and one for reading/write, but my routing configuration is not working, when I apply the read-only configuration, I don't get success.

This is my configurations:

pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.1</version>
    </parent>
    
    <groupId>br.com.multidatasources</groupId>
    <artifactId>multidatasources</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>multidatasources</name>
    
    <properties>
        <java.version>11</java.version>
    </properties>
    
    <dependencies>      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

# Database master
master.datasource.url=jdbc:mysql://localhost:3306/billionaires?createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC
master.datasource.username=root
master.datasource.password=root

# Database slave
slave.datasource.url=jdbc:mysql://localhost:3307/billionaires?createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC
slave.datasource.username=root
slave.datasource.password=root

# Database driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA property settings
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

DataSourceType.java

public enum DataSourceType {
    READ_ONLY,
    READ_WRITE
}

TransactionRoutingDataSource.java

public class TransactionRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? DataSourceType.READ_ONLY : DataSourceType.READ_WRITE;
    }

}

RoutingConfiguration.java

@Configuration
@EnableTransactionManagement
public class RoutingConfiguration {
    
    private final Environment environment;
    
    public RoutingConfiguration(Environment environment) {
        this.environment = environment;
    }
    
    @Bean
    public JpaTransactionManager transactionManager(@Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory.getObject());
    }
    
    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("routingDataSource") DataSource routingDataSource) {
        LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
        bean.setDataSource(routingDataSource);
        bean.setPackagesToScan(Billionaires.class.getPackageName());
        bean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        bean.setJpaProperties(additionalProperties());
        return bean;
    }
    
    @Bean
    public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
        return new LazyConnectionDataSourceProxy(routingDataSource);
    }
    
    @Bean
    public TransactionRoutingDataSource routingDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource
    ) {
        TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
 
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
        dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);
 
        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource());

        return routingDataSource;
    }
    
    @Bean
    public DataSource masterDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(environment.getProperty("master.datasource.url"));
        dataSource.setUsername(environment.getProperty("master.datasource.username"));
        dataSource.setPassword(environment.getProperty("master.datasource.password"));
        return dataSource;
    }

    @Bean
    public DataSource slaveDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(environment.getProperty("slave.datasource.url"));
        dataSource.setUsername(environment.getProperty("slave.datasource.username"));
        dataSource.setPassword(environment.getProperty("slave.datasource.password"));
        return dataSource;
    }
    
    private Properties additionalProperties() {
        Properties properties = new Properties();
        properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
           
        return properties;
    }

}

Billionaires.java

@Entity
@Table(name = "billionaires")
public class Billionaires {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "first_name")
    private String firstName;
    
    @Column(name = "last_name")
    private String lastName;
    
    private String career;
    
    public Billionaires() { }

    public Billionaires(Long id, String firstName, String lastName, String career) {        
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.career = career;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getCareer() {
        return career;
    }

    public void setCareer(String career) {
        this.career = career;
    }
    
}

BillionairesRepository.java

@Repository
public interface BillionairesRepository extends JpaRepository<Billionaires, Long> {

}

BillionairesService.java

@Service
public class BillionairesService {
    
    private final BillionairesRepository billionairesRepository;

    public BillionairesService(BillionairesRepository billionairesRepository) {
        this.billionairesRepository = billionairesRepository;
    }
    
    @Transactional(readOnly = true)  // Should be used the READ_ONLY  (This point not working)
    public List<Billionaires> findAll() {
        return billionairesRepository.findAll();
    }
    
    @Transactional // Should be used the READ_WRITE
    public Billionaires save(Billionaires billionaires) {
        return billionairesRepository.save(billionaires);
    }

}

In the BillionairesService class, I apply the @Transactional(readOnly = true) on findAll method for use the READ_ONLY data source, but this is not occurring.

The findAll method should be used the READ_ONLY data source and save method should be used the READ_WRITE data source.

Can someone help me fix this problem?

Hopefully answered 17/6, 2021 at 3:24 Comment(10)
When obtaining a connection from the datasource there doesn't have to be a transaction. As I suspect you are using open session in view (by default) this happens even before the datasource can determine the tx state.Montserrat
Additional tip, you are using Spring Boot so you only need the datasource configuration, the JPA stuff can still be auto configured by Spring Boot as well as @EnableTransactionManagement. Also check if you are using the correct TransactionSynchronizationManager (there are 2 one reactive and one classic, you should use the latter one!).Montserrat
@M.Deinum I use the @EnableTransactionalManagement to creating a bean entityManagerFactory, because without this setting causes the initializing exception: ` *************************** APPLICATION FAILED TO START *************************** Description: Parameter 0 of constructor in br.com.multidatasources.multidatasources.service.BillionairesService required a bean named 'entityManagerFactory' that could not be found. Action: Consider defining a bean named 'entityManagerFactory' in your configuration. `Hopefully
The TransactionSynchronizationManager I'm using classic, nothing reactive.Hopefully
@EnableTransactionManagement doesn't create that bean, it only registers the aspect to drive transactions. You should mark your LazyDataSourceConnection bean as @Primary so that it will be used by the auto configured entitymanager.Montserrat
I removed @EnableTransactionManagement and mark my LazyDataSourceConnection as @Primary bean, but does not working, the application every using only READ_WRITE mode.Hopefully
And as I stated in my first comment -> "As I suspect you are using open session in view (by default) this happens even before the datasource can determine the tx state". So unless you turn that off, your connection will already be bound before the transaction has started.Montserrat
certainly, how I do turn it off? I have the property spring.jpa.open-in-view=false configured in my application.properties, but it is not working.Hopefully
That should disable it. Please update your config with your current state (as you only should have the datasource config, everything else should be automatic). Also how is that findAll called? If that is from an other @Transactional method it will not switch to a read only connection it will just participate in the current transaction.Montserrat
Thank you for your help @M.Deinum, I solved this problem, I added the solution of this case to the answers of this post.Hopefully
M
15

I would strongly suggest to use autoconfiguration as-much as you can, it will make things a little simpler. The main key is to set to delay the getting of the connection and preparing it for the current transaction.

This can be achieved in 2 different ways.

  1. Set the prepareConnection property of the JpaDialect to false. If you don't then the JpaTransactionManager will eagerly get Connection and prepare it for the transaction. This is even before it had time to set the current state of the transaction onto the TransactionSynchronizationManager. Which will make the call to TransactionSynchronizationManager.isCurrentTransactionReadOnly always return false (as it is set at the end of the doBegin method in the JpaTransactionManager.

  2. Set the hibernate.connection.handling_mode to DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION. This will delay the getting of a connection and close the connection after the transaction. Without Spring this is also the default for Hibernate 5.2+ (see the Hibernate User Guide) but for legacy reasons Spring switches this to DELAYED_ACQUISITION_AND_HOLD.

Either of these solutions will work as the preparing of the connection is delayed and the JpaTransactionManager has thus time to sync the state in the TransactionSynchronizationManager.

@Bean
public BeanPostProcessor dialectProcessor() {

    return new BeanPostProcessor() {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof HibernateJpaVendorAdapter) {
                ((HibernateJpaVendorAdapter) bean).getJpaDialect().setPrepareConnection(false);
            }
            return bean;
        }
    };
}

However adding this property to your application.properties will also work:

spring.jpa.properties.hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_TRANSACTION

With either one of these solutions you can now ditch your transaction configuration, jpa etc. There is also an easier way to configure multiple datasources. It is described in the Spring Boot Reference Guide which will reuse as much of the Spring auto-configuration as possible.

First make sure the following is in your application.properties

# DATABASE MASTER PROPERTIES
master.datasource.url=jdbc:h2:mem:masterdb;DB_CLOSE_DELAY=-1
master.datasource.username=sa
master.datasource.password=sa
master.datasource.configuration.pool-name=Master-DB

# DATABASE SLAVE PROPERTIES
slave.datasource.url=jdbc:h2:mem:slavedb;DB_CLOSE_DELAY=-1
slave.datasource.username=sa
slave.datasource.password=sa
slave.datasource.configuration.pool-name=Slave-DB

# JPA PROPERTIES SETTINGS
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true
spring.jpa.open-in-view=false

# ENABLE ERRORS IN DESERIALIZATION OF MISSING OR IGNORED PROPERTIES
spring.jackson.deserialization.fail-on-unknown-properties=true
spring.jackson.deserialization.fail-on-ignored-properties=true

# ENABLE ERRORS ON REQUESTS FOR NON-EXISTENT RESOURCES
spring.mvc.throw-exception-if-no-handler-found=true

# DISABLE MAPPINGS OF STATIC RESOURCES (IS NOT USABLE IN DEVELOPMENT OF APIs)
spring.web.resources.add-mappings=false

NOTE: Removed the driver for JDBC (not needed) only set spring.jpa.database-platform you set either database or database-platform not both.

Now with this and the following @Configuration class you will have 2 datasources, the routing one and the BeanPostProcessor as mentioned above (if you choose to use the property you can remove said BeanPostProcessor.

@Configuration
public class DatasourceConfiguration {

    @Bean
    @ConfigurationProperties("master.datasource")
    public DataSourceProperties masterDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("master.datasource.configuration")
    public HikariDataSource masterDataSource(DataSourceProperties masterDataSourceProperties) {
        return masterDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    @ConfigurationProperties("slave.datasource")
    public DataSourceProperties slaveDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("slave.datasource.configuration")
    public HikariDataSource slaveDataSource(DataSourceProperties slaveDataSourceProperties) {
        return slaveDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
    }

    @Bean
    @Primary
    public TransactionRoutingDataSource routingDataSource(DataSource masterDataSource,  DataSource slaveDataSource) {
        TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
        dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);

        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource);

        return routingDataSource;
    }

    @Bean
    public BeanPostProcessor dialectProcessor() {

        return new BeanPostProcessor() {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                if (bean instanceof HibernateJpaVendorAdapter) {
                    ((HibernateJpaVendorAdapter) bean).getJpaDialect().setPrepareConnection(false);
                }
                return bean;
            }
        };
    }
}

This will set up everything you need for this to work and still be able to use as much of the auto-configuration and detection as you can. With this, the only configuration you need to do is this DataSource setup. No JPA, Transaction management etc. as that will be done automatically.

Finally here is a test to test this with (you can test both scenarios). The read-only one will fail because there is no schema there, the save will succeed as there is a schema on the READ_WRITE side of things.

@Test
void testDatabaseSwitch() {
    Assertions.assertThatThrownBy(() -> billionaireService.findAll())
            .isInstanceOf(DataAccessException.class);

    Billionaire newBIllionaire = new Billionaire(null, "Marten", "Deinum", "Spring Nerd.");
    billionaireService.save(newBIllionaire);

}
Montserrat answered 22/6, 2021 at 5:53 Comment(3)
This is a very good suggestion! My fault was not having defined an @Bean as @Primary, for this reason, the entityManager initialization error occurred. I am extremely grateful for having helped me.Hopefully
Thanks for the answer, was wondering what setPrepareConnection was about because the docs were not clear to me.Arizona
Option 1 is working well for us but had to turn off auto-commit at the data source level (we missed the auto-commit configuration initially). So, auto-commit is also an important parameter for this to work.Antarctica
H
2

I solved this problem by changing my implementation of the RoutingConfiguration.java class.

I configured the data source for using the setAutoCommit(false) configuration and added the property hibernate.connection.provider_disables_autocommit with value true.

@Configuration
@EnableTransactionManagement
public class RoutingConfiguration {

    private final Environment environment;

    public RoutingConfiguration(Environment environment) {
        this.environment = environment;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("routingDataSource") DataSource routingDataSource) {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();

        entityManagerFactoryBean.setPersistenceUnitName(getClass().getSimpleName());
        entityManagerFactoryBean.setPersistenceProvider(new HibernatePersistenceProvider());
        entityManagerFactoryBean.setDataSource(routingDataSource);
        entityManagerFactoryBean.setPackagesToScan(Billionaires.class.getPackageName());

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        HibernateJpaDialect jpaDialect = vendorAdapter.getJpaDialect();

        jpaDialect.setPrepareConnection(false);

        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
        entityManagerFactoryBean.setJpaProperties(additionalProperties());

        return entityManagerFactoryBean;
    }

    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory);
        return transactionManager;
    }

    @Bean
    public TransactionTemplate transactionTemplate(EntityManagerFactory entityManagerFactory) {
        return new TransactionTemplate(transactionManager(entityManagerFactory));
    }

    @Bean
    public TransactionRoutingDataSource routingDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource
    ) {
        TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
        dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);

        routingDataSource.setTargetDataSources(dataSourceMap);
        routingDataSource.setDefaultTargetDataSource(masterDataSource());

        return routingDataSource;
    }

    @Bean
    public DataSource masterDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(environment.getProperty("master.datasource.url"));
        dataSource.setUsername(environment.getProperty("master.datasource.username"));
        dataSource.setPassword(environment.getProperty("master.datasource.password"));
        return connectionPoolDataSource(dataSource, determinePoolName(DataSourceType.READ_WRITE));
    }

    @Bean
    public DataSource slaveDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(environment.getProperty("slave.datasource.url"));
        dataSource.setUsername(environment.getProperty("slave.datasource.username"));
        dataSource.setPassword(environment.getProperty("slave.datasource.password"));
        return connectionPoolDataSource(dataSource, determinePoolName(DataSourceType.READ_ONLY));
    }

    private HikariDataSource connectionPoolDataSource(DataSource dataSource, String poolName) {
        return new HikariDataSource(hikariConfig(dataSource, poolName));
    }

    private HikariConfig hikariConfig(DataSource dataSource, String poolName) {
        HikariConfig hikariConfig = new HikariConfig();

        hikariConfig.setPoolName(poolName);
        hikariConfig.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 4);
        hikariConfig.setDataSource(dataSource);
        hikariConfig.setAutoCommit(false);

        return hikariConfig;
    }

    private Properties additionalProperties() {
        Properties properties = new Properties();

        properties.setProperty("hibernate.dialect", environment.getProperty("spring.jpa.database-platform"));
        properties.setProperty("hibernate.connection.provider_disables_autocommit", "true");

        return properties;
    }

    private String determinePoolName(DataSourceType dataSourceType) {
        return dataSourceType.getPoolName().concat("-").concat(dataSourceType.name());
    }

}

The hibernate.connection.provider_disables_autocommit allows the connection is acquired prior to calling the determineCurrentLookupKey method.

Hopefully answered 17/6, 2021 at 15:44 Comment(5)
You can just set that property through application.properties you don't need to replace the auto configuration of JPA for that.Montserrat
I tried to use this approach, but it doesn't have the desired effect, being necessary that I create the beans pertinent to the JPA settings myself.Hopefully
This is the repository of the study case with the final solution: github.com/jonathanmdr/RoutingDataSourceHopefully
You aren't setting the properties through JPA (as I mentioned you should) if you don't it ofcourse still won't work.Montserrat
I did as per your directions, but it just didn't work. For this reason, I kept the implementation as per the answer.Hopefully
G
0

I tried with the above solution and found out that the HikariDataSource is always getting null. And also the routing is happening on READ_WRITE database even though added @Transactional(readOnly=true).

I am using Spring boot 3.2.4 with java 17.

DataSourceConfiguration.java

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfiguration {

@Bean
public BeanPostProcessor dialectProcessor() {

    return new BeanPostProcessor() {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            if (bean instanceof HibernateJpaVendorAdapter) {
                ((HibernateJpaVendorAdapter) bean).getJpaDialect()
                        .setPrepareConnection(false);
            }
            return bean;
        }
    };
}

@Bean
@ConfigurationProperties("app.datasource.readwrite.configuration")
public HikariDataSource masterDataSource(DataSourceProperties masterDataSourceProperties) {
    return masterDataSourceProperties.initializeDataSourceBuilder()
            .type(HikariDataSource.class)
            .build();
}

@Bean
@ConfigurationProperties("app.datasource.readwrite")
public DataSourceProperties masterDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.readonly.configuration")
public HikariDataSource slaveDataSource(DataSourceProperties slaveDataSourceProperties) {
    return slaveDataSourceProperties.initializeDataSourceBuilder()
            .type(HikariDataSource.class)
            .build();
}

@Bean
@ConfigurationProperties("app.datasource.readonly")
public DataSourceProperties slaveDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@Primary
public TransactionRoutingDataSource routingDataSource(DataSource masterDataSource,
                                                      DataSource slaveDataSource) {
    TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();

    Map<Object, Object> dataSourceMap = new HashMap<>();
    dataSourceMap.put(DataSourceType.READ_WRITE, masterDataSource);
    dataSourceMap.put(DataSourceType.READ_ONLY, slaveDataSource);

    routingDataSource.setTargetDataSources(dataSourceMap);
    routingDataSource.setDefaultTargetDataSource(masterDataSource);

    return routingDataSource;
}
}

TransactionRoutingDataSource.java

@Slf4j
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
    boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
    DataSourceType dataSourceType = isReadOnly ? DataSourceType.READ_ONLY : DataSourceType.READ_WRITE;
    log.info("Current DataSource type: {}", dataSourceType);
    return dataSourceType;
}
}

DataSourceType.java

public enum DataSourceType {
READ_ONLY,
READ_WRITE
}

application properites

app.datasource.readwrite.url=jdbc:mysql://localhost:3306/demotest
app.datasource.readwrite.username=root
app.datasource.readwrite.password=toor

app.datasource.readonly.url=jdbc:mysql://localhost:3307/demotest
app.datasource.readonly.username=root
app.datasource.readonly.password=toor
spring.jpa.database=default
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
spring.jpa.show-sql=true

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.4</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo-dbrouting</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-dbrouting</name>
<description>Demo project for Spring Boot</description>
<properties>
    <java.version>17</java.version>
</properties>
<profiles>
    <profile>
        <id>dev</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
            <log4j.level>DEBUG</log4j.level>
            <spring.profiles.active>dev</spring.profiles.active>
        </properties>
    </profile>
</profiles>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <excludes>
                    <exclude>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                    </exclude>
                </excludes>
            </configuration>
        </plugin>
    </plugins>
</build>

</project>

Please any one help me out.

Grocer answered 1/5, 2024 at 7:14 Comment(2)
I have an example with this approach on my GitHub profile using Spring Boot 3.2.5 and Java 21, you could use this for reference: github.com/jonathanmdr/DynamicDataSourceRoutingHopefully
I updated the application to the Spring Boot 3.3.0Hopefully

© 2022 - 2025 — McMap. All rights reserved.