Spring Boot + Mybatis Multi source and dynamic data source configuration methods

  • 2021-01-02 21:49:10
  • OfStack

Most of the articles on the web are multi-data sources or dynamic data sources. However, recent projects need to use both methods at the same time.

Application scenarios

The project needs to connect to two different databases, A and B, and they are both master and slave architectures, with one write library and several read libraries.

Multiple data sources

First disable DataSourceAutoConfiguration, which comes with spring boot, because it reads the spring.datasource. * attribute of the application.properties file and automatically assigns the list data source. Add the exclude attribute to the @SpringBootApplication annotation:


@SpringBootApplication(exclude = {
  DataSourceAutoConfiguration.class
})
public class TitanWebApplication {
 public static void main(String[] args) {
  SpringApplication.run(TitanWebApplication.class, args);
 }
}

Then configure the multi-data source connection information in ES23en.properties:


# titan library 
spring.datasource.titan-master.url=jdbc:mysql://X.X.X.X:port/titan?characterEncoding=UTF-8
spring.datasource.titan-master.username=
spring.datasource.titan-master.password=
spring.datasource.titan-master.driver-class-name=com.mysql.jdbc.Driver
#  Connection pool configuration 
#  omit 
#  Other libraries 
spring.datasource.db2.url=jdbc:mysql://X.X.X.X:port/titan2?characterEncoding=UTF-8
spring.datasource.db2.username=
spring.datasource.db2.password=
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver

Since we disable the automatic data source configuration, the next step is to manually create these data sources:


@Configuration
public class DataSourceConfig {
 @Bean(name = "titanMasterDS")
 @ConfigurationProperties(prefix = "spring.datasource.titan-master") // application.properteis , the prefix for the corresponding attribute 
 public DataSource dataSource1() {
  return DataSourceBuilder.create().build();
 }
 @Bean(name = "ds2")
 @ConfigurationProperties(prefix = "spring.datasource.db2") // application.properteis , the prefix for the corresponding attribute 
 public DataSource dataSource2() {
  return DataSourceBuilder.create().build();
 }
}

Next you need to configure two mybatis SqlSessionFactory to use different data sources:


@Configuration
@MapperScan(basePackages = {"titan.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDbAConfig {
 @Autowired
 @Qualifier("titanMasterDS")
 private DataSource ds1;
 @Bean
 public SqlSessionFactory sqlSessionFactory1() throws Exception {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(ds1); //  use titan The data source ,  The connection titan library 
  return factoryBean.getObject();
 }
 @Bean
 public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
  SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1()); //  Use the ones configured above Factory
  return template;
 }
}

After the above configuration, the Mapper interface under ES36en. mapper will use the titan data source. Similarly, the second SqlSessionFactory:


@Configuration
@MapperScan(basePackages = {"other.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDbBConfig {
 @Autowired
 @Qualifier("ds2")
 private DataSource ds2;
 @Bean
 public SqlSessionFactory sqlSessionFactory2() throws Exception {
  SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  factoryBean.setDataSource(ds2);
  return factoryBean.getObject();
 }
 @Bean
 public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
  SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
  return template;
 }
}

After completing these configurations, assume that there are two Mapper ES45en.mapper.UserMapper and other.mapper.RoleMapper, with the former automatically connecting to the titan library and the latter to the ds2 library.

Dynamic data source

The original purpose of using dynamic data sources is to achieve read-write separation in the application layer, that is, to control different query methods in the program code to connect different libraries. In addition to this approach, database middleware is also a good choice, with the advantage that the database cluster is only exposed as a single library to the application, without the need to switch the code logic of the data source.

We use custom annotations + AOP to achieve dynamic switching of data sources.

First define an ContextHolder to hold the data source name used by the current thread:


public class DataSourceContextHolder {
 public static final Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
 /**
  *  Default data source 
  */
 public static final String DEFAULT_DS = "titan-master";
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
 //  Set the data source name 
 public static void setDB(String dbType) {
  log.debug(" Switch to the {} The data source ", dbType);
  contextHolder.set(dbType);
 }
 //  Get the data source name 
 public static String getDB() {
  return (contextHolder.get());
 }
 //  Clear the data source name 
 public static void clearDB() {
  contextHolder.remove();
 }
}

Then customize the implementation of an ES66en.sql.DataSource interface. Here, we only need to inherit from Spring the pre-implemented parent class AbstractRoutingDataSource:


public class DynamicDataSource extends AbstractRoutingDataSource {
 private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
 @Override
 protected Object determineCurrentLookupKey() {
  log.debug(" The data source for {}", DataSourceContextHolder.getDB());
  return DataSourceContextHolder.getDB();
 }
}

Create dynamic data sources:


/**
  *  Dynamic data source :  through AOP Dynamic switching between different data sources 
  * @return
  */
 @Bean(name = "dynamicDS1")
 public DataSource dataSource() {
  DynamicDataSource dynamicDataSource = new DynamicDataSource();
  //  Default data source 
  dynamicDataSource.setDefaultTargetDataSource(dataSource1());
  //  Configuring multiple data sources 
  Map<Object, Object> dsMap = new HashMap(5);
  dsMap.put("titan-master", dataSource1());
  dsMap.put("ds2", dataSource2());
  dynamicDataSource.setTargetDataSources(dsMap);
  return dynamicDataSource;
 }

The custom annotation @DS is used to specify which data source the method uses at encoding time:


@Retention(RetentionPolicy.RUNTIME)
@Target({
  ElementType.METHOD
})
public @interface DS {
 String value() default "titan-master";
}

Write AOP section to achieve switching logic:


@Aspect
@Component
public class DynamicDataSourceAspect {
 @Before("@annotation(DS)")
 public void beforeSwitchDS(JoinPoint point){
  // Get current access to class
  Class<?> className = point.getTarget().getClass();
  // Gets the name of the method to access 
  String methodName = point.getSignature().getName();
  // Gets the type of the method's argument 
  Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
  String dataSource = DataSourceContextHolder.DEFAULT_DS;
  try {
   //  Gets the method object accessed 
   Method method = className.getMethod(methodName, argClass);
   //  To determine whether there is @DS annotations 
   if (method.isAnnotationPresent(DS.class)) {
    DS annotation = method.getAnnotation(DS.class);
    //  Fetch the name of the data source in the annotation 
    dataSource = annotation.value();
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
  //  Switching data sources 
  DataSourceContextHolder.setDB(dataSource);
 }
 @After("@annotation(DS)")
 public void afterSwitchDS(JoinPoint point){
  DataSourceContextHolder.clearDB();
 }
}

With the above configuration, you can happily switch data sources in Service by specifying DynamicDataSource in the previous SqlSessionFactory configuration:


# titan library 
spring.datasource.titan-master.url=jdbc:mysql://X.X.X.X:port/titan?characterEncoding=UTF-8
spring.datasource.titan-master.username=
spring.datasource.titan-master.password=
spring.datasource.titan-master.driver-class-name=com.mysql.jdbc.Driver
#  Connection pool configuration 
#  omit 
#  Other libraries 
spring.datasource.db2.url=jdbc:mysql://X.X.X.X:port/titan2?characterEncoding=UTF-8
spring.datasource.db2.username=
spring.datasource.db2.password=
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
0

conclusion


Related articles: