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