Backend Development 19 min read

How to Configure Master‑Slave Data Sources in Spring Boot with JPA & MyBatis

This guide walks through setting up a Spring Boot 2.1.4 application with JDK 1.8, Oracle, and HikariCP to use separate master and slave data sources, covering Maven dependencies, YAML configuration, custom property classes, JPA EntityManager factories, MyBatis SqlSession factories, mapper scanning, and sample entity definitions.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
How to Configure Master‑Slave Data Sources in Spring Boot with JPA & MyBatis

Development environment: JDK 1.8+, Spring Boot 2.1.4.RELEASE, Oracle.

Assume two data sources: master and slave .

pom.xml dependencies

<code>&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
        &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;com.github.noraui&lt;/groupId&gt;
        &lt;artifactId&gt;ojdbc7&lt;/artifactId&gt;
        &lt;version&gt;12.1.0.2&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
        &lt;artifactId&gt;commons-lang3&lt;/artifactId&gt;
    &lt;/dependency&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.mybatis.spring.boot&lt;/groupId&gt;
        &lt;artifactId&gt;mybatis-spring-boot-starter&lt;/artifactId&gt;
        &lt;version&gt;1.1.1&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</code>

application.yml configuration for master and slave data sources, MyBatis and JPA settings

<code>server:
  port: 50000
---
spring:
  jpa:
    hibernate:
      ddlAuto: update
    openInView: true
    showSql: false
    databasePlatform: org.hibernate.dialect.Oracle10gDialect
---
# master datasource
master:
  datasource:
    driverClassName: oracle.jdbc.driver.OracleDriver
    url: jdbc:oracle:thin:@localhost:1521/orcl
    username: t0
    password: t0
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
      autoCommit: true
      idleTimeout: 30000
      poolName: MasterDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1 FROM DUAL
---
# slave datasource
slave:
  datasource:
    driverClassName: oracle.jdbc.driver.OracleDriver
    url: jdbc:oracle:thin:@localhost:1521/orcl
    username: t1
    password: t1
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
      autoCommit: true
      idleTimeout: 30000
      poolName: SlaveDatabookHikariCP
      maxLifetime: 1800000
      connectionTimeout: 30000
      connectionTestQuery: SELECT 1 FROM DUAL
---
# MyBatis configuration for each datasource
master:
  mybatis:
    config-location: classpath:/MyBatis-conf.xml
    type-aliases-package: com.pack.domain
    mapper-locations:
      - classpath:/com/pack/mapper/oracle/*.xml
slave:
  mybatis:
    config-location: classpath:/MyBatis-conf.xml
    type-aliases-package: com.pack.slave.domain
    mapper-locations:
      - classpath:/com/pack/slave/mapper/oracle/*.xml
---
# JPA configuration for each datasource
master:
  jpa:
    repos: com.pack.base.repository
    domain: com.pack.domain
slave:
  jpa:
    repos: com.pack.slave.repository
    domain: com.pack.slave.domain</code>

BaseDataSourceProperties class (core property handling for a datasource)

<code>public class BaseDataSourceProperties implements BeanClassLoaderAware, InitializingBean {
    private ClassLoader classLoader;
    private String name;
    private boolean generateUniqueName;
    private Class<? extends DataSource> type;
    private String driverClassName;
    private String url;
    private String username;
    private String password;
    private String jndiName;
    private DataSourceInitializationMode initializationMode = DataSourceInitializationMode.EMBEDDED;
    private String platform = "all";
    private List<String> schema;
    private String schemaUsername;
    private String schemaPassword;
    private List<String> data;
    private String dataUsername;
    private String dataPassword;
    private boolean continueOnError = false;
    private String separator = ";";
    private Charset sqlScriptEncoding;
    private EmbeddedDatabaseConnection embeddedDatabaseConnection = EmbeddedDatabaseConnection.NONE;
    private Xa xa = new Xa();
    private String uniqueName;
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; }
    @Override
    public void afterPropertiesSet() throws Exception {
        this.embeddedDatabaseConnection = EmbeddedDatabaseConnection.get(this.classLoader);
    }
    public DataSourceBuilder<?> initializeDataSourceBuilder() {
        return DataSourceBuilder.create(getClassLoader()).type(getType())
                .driverClassName(determineDriverClassName()).url(determineUrl())
                .username(determineUsername()).password(determinePassword());
    }
    // getters, setters and helper methods omitted for brevity
    public static class Xa {
        private String dataSourceClassName;
        private Map<String, String> properties = new LinkedHashMap<>();
        public String getDataSourceClassName() { return this.dataSourceClassName; }
        public void setDataSourceClassName(String dataSourceClassName) { this.dataSourceClassName = dataSourceClassName; }
        public Map<String, String> getProperties() { return this.properties; }
        public void setProperties(Map<String, String> properties) { this.properties = properties; }
    }
    static class DataSourceBeanCreationException extends BeanCreationException {
        private static final long serialVersionUID = 1L;
        private final BaseDataSourceProperties properties;
        private final EmbeddedDatabaseConnection connection;
        DataSourceBeanCreationException(String message, BaseDataSourceProperties properties,
                EmbeddedDatabaseConnection connection) {
            super(message);
            this.properties = properties;
            this.connection = connection;
        }
        public BaseDataSourceProperties getProperties() { return this.properties; }
        public EmbeddedDatabaseConnection getConnection() { return this.connection; }
    }
}</code>

BaseMybatisProperties class (holds MyBatis configuration)

<code>public class BaseMybatisProperties {
    private String configLocation;
    private String[] mapperLocations;
    private String typeAliasesPackage;
    private String typeHandlersPackage;
    private boolean checkConfigLocation = false;
    private ExecutorType executorType;
    private Configuration configuration;
    // getters and setters omitted for brevity
    public Resource[] resolveMapperLocations() {
        List<Resource> resources = new ArrayList<>();
        if (this.mapperLocations != null) {
            for (String mapperLocation : this.mapperLocations) {
                try {
                    Resource[] mappers = new PathMatchingResourcePatternResolver()
                            .getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                } catch (IOException e) { }
            }
        }
        Resource[] mapperLocations = new Resource[resources.size()];
        return resources.toArray(mapperLocations);
    }
}</code>

Properties classes for master and slave data sources

<code>@Component
@ConfigurationProperties(prefix = "master.datasource")
public class MasterDataSourceProperties extends BaseDataSourceProperties {}

@Component
@ConfigurationProperties(prefix = "slave.datasource")
public class SlaveDataSourceProperties extends BaseDataSourceProperties {}

@Component
@ConfigurationProperties(prefix = "master.mybatis")
public class MasterMybatisProperties extends BaseMybatisProperties {}

@Component
@ConfigurationProperties(prefix = "slave.mybatis")
public class SlaveMybatisProperties extends BaseMybatisProperties {}</code>

HikariDataSourceConfig – creates the master and slave Hikari data sources

<code>@Configuration
public class HikariDataSourceConfig {
    @Bean
    @Primary
    public HikariDataSource masterDataSource(MasterDataSourceProperties properties) {
        HikariDataSource ds = createDataSource(properties, HikariDataSource.class);
        if (StringUtils.hasText(properties.getName())) { ds.setPoolName(properties.getName()); }
        return ds;
    }
    @Bean
    public HikariDataSource slaveDataSource(SlaveDataSourceProperties properties) {
        HikariDataSource ds = createDataSource(properties, HikariDataSource.class);
        if (StringUtils.hasText(properties.getName())) { ds.setPoolName(properties.getName()); }
        return ds;
    }
    @SuppressWarnings("unchecked")
    protected static <T> T createDataSource(BaseDataSourceProperties properties, Class<? extends DataSource> type) {
        return (T) properties.initializeDataSourceBuilder().type(type).build();
    }
}</code>

EntityManagerFactoryConfig – configures JPA EntityManagerFactory and transaction manager for both datasources

<code>public class EntityManagerFactoryConfig {
    @Configuration
    @EnableJpaRepositories(basePackages = {"${master.jpa.repos}"},
        entityManagerFactoryRef = "masterEntityManagerFactory",
        transactionManagerRef = "masterJPATransactionManager")
    static class MasterEntityManagerFactory {
        @Resource(name = "masterDataSource")
        private DataSource masterDataSource;
        @Value("${master.jpa.domain}")
        private String masterDomainPkg;
        @Bean
        @Primary
        public LocalContainerEntityManagerFactoryBean masterEntityManagerFactory(EntityManagerFactoryBuilder builder) {
            Map<String, Object> props = new HashMap<>();
            props.put("hibernate.hbm2ddl.auto", "update");
            props.put("hibernate.id.new_generator_mappings", true);
            props.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
            return builder.dataSource(masterDataSource)
                .packages(masterDomainPkg)
                .persistenceUnit("master")
                .properties(props)
                .build();
        }
        @Bean
        @Primary
        public PlatformTransactionManager masterJPATransactionManager(EntityManagerFactoryBuilder builder) {
            return new JpaTransactionManager(masterEntityManagerFactory(builder).getObject());
        }
    }
    @Configuration
    @EnableJpaRepositories(basePackages = {"${slave.jpa.repos}"},
        entityManagerFactoryRef = "slaveEntityManagerFactory",
        transactionManagerRef = "slaveJPATransactionManager")
    @ConditionalOnProperty(name = "multiple.ds.enabled", havingValue = "true")
    static class SlaveEntityManagerFactory {
        @Resource(name = "slaveDataSource")
        private DataSource slaveDataSource;
        @Value("${slave.jpa.domain}")
        private String slaveDomainPkg;
        @Bean
        public LocalContainerEntityManagerFactoryBean slaveEntityManagerFactory(EntityManagerFactoryBuilder builder) {
            Map<String, Object> props = new HashMap<>();
            props.put("hibernate.hbm2ddl.auto", "update");
            props.put("hibernate.id.new_generator_mappings", true);
            props.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
            return builder.dataSource(slaveDataSource)
                .packages(slaveDomainPkg)
                .persistenceUnit("slave")
                .properties(props)
                .build();
        }
        @Bean
        public PlatformTransactionManager slaveJPATransactionManager(EntityManagerFactoryBuilder builder) {
            return new JpaTransactionManager(slaveEntityManagerFactory(builder).getObject());
        }
    }
}</code>

SqlSessionFactoryConfig – creates MyBatis SqlSessionFactory, SqlSessionTemplate and transaction manager for master and slave

<code>public class SqlSessionFactoryConfig {
    @Configuration
    static class MasterSqlSessionFactory {
        @Resource
        private MasterMybatisProperties properties;
        @Autowired(required = false)
        private Interceptor[] interceptors;
        @Autowired
        private ResourceLoader resourceLoader = new DefaultResourceLoader();
        @Autowired(required = false)
        private DatabaseIdProvider databaseIdProvider;
        @Bean
        public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
            factory.setDataSource(dataSource);
            factory.setVfs(SpringBootVFS.class);
            if (StringUtils.hasText(this.properties.getConfigLocation())) {
                factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
            }
            factory.setConfiguration(this.properties.getConfiguration());
            if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); }
            if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); }
            if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
                factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
            }
            if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
                factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
            }
            if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
                factory.setMapperLocations(this.properties.resolveMapperLocations());
            }
            return factory.getObject();
        }
        @Bean
        public DataSourceTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource ds) {
            return new DataSourceTransactionManager(ds);
        }
        @Bean
        public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sf) {
            ExecutorType et = this.properties.getExecutorType();
            return (et != null) ? new SqlSessionTemplate(sf, et) : new SqlSessionTemplate(sf);
        }
    }
    @Configuration
    @ConditionalOnProperty(name = "multiple.ds.enabled", havingValue = "true")
    static class SlaveSqlSessionFactory {
        @Resource
        private SlaveMybatisProperties properties;
        @Autowired(required = false)
        private Interceptor[] interceptors;
        @Autowired
        private ResourceLoader resourceLoader = new DefaultResourceLoader();
        @Autowired(required = false)
        private DatabaseIdProvider databaseIdProvider;
        @Bean
        public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
            factory.setDataSource(dataSource);
            factory.setVfs(SpringBootVFS.class);
            if (StringUtils.hasText(this.properties.getConfigLocation())) {
                factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
            }
            factory.setConfiguration(this.properties.getConfiguration());
            if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); }
            if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); }
            if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
                factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
            }
            if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
                factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
            }
            if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
                factory.setMapperLocations(this.properties.resolveMapperLocations());
            }
            return factory.getObject();
        }
        @Bean
        public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource ds) {
            return new DataSourceTransactionManager(ds);
        }
        @Bean
        public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sf) {
            ExecutorType et = this.properties.getExecutorType();
            return (et != null) ? new SqlSessionTemplate(sf, et) : new SqlSessionTemplate(sf);
        }
    }
}</code>

MapperScanConfig – registers mapper scanning for master and slave packages

<code>public class MapperScanConfig {
    @Configuration
    @MapperScan(basePackages = {"com.pack.base.mapper"}, sqlSessionTemplateRef = "masterSqlSessionTemplate")
    static class MasterMapper {}
    @Configuration
    @MapperScan(basePackages = {"com.pack.slave.mapper"}, sqlSessionTemplateRef = "slaveSqlSessionTemplate")
    @ConditionalOnProperty(name = "multiple.ds.enabled", havingValue = "true")
    static class SlaveMapper {}
}</code>

Sample entity classes used for testing

<code>@Entity
@Table(name = "T_USERS")
@Data
public class Users {
    @Id
    private Long id;
    private String username;
    private String password;
    private String realName;
    private String phone;
    private String idNo;
    @Column(length = 4000)
    private String authority;
    @Column(columnDefinition = "int default 0")
    private Integer status = 0;
}

@Entity
@Table(name = "T_PERSON")
@Data
public class Person {
    @Id
    private Long id;
    private String name;
    private String email;
}</code>

After creating the

com.pack.domain

and

com.pack.slave.domain

packages with the above entity classes, start the Spring Boot application. If the tables

T_USERS

and

T_PERSON

are created in the master and slave Oracle databases respectively, the multi‑datasource configuration is successful.

Spring BootMyBatisHikariCPOracleJPAmultiple data sources
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.