In many applications, it may be necessary to use multiple databases or data sources to handle different business needs. Spring Boot provides an easy way to configure and use multiple data sources, enabling developers to easily handle multiple database connections. If you may need to switch data sources at any time in your project, then my article may help you
: I won’t say much about the introduction of coordinates in the pom file here
Configuration file
1: in the properties file
# data source configuration spring.datasource.mysql.primary.url=jdbc:mysql://127.0.0.1:3351/tally_book?characterEncoding=utf8 &serverTimezone=UTC spring.datasource.mysql.primary.username=root spring.datasource.mysql.primary.password=123456 spring.datasource.mysql.primary.driver-class-name=com.mysql.cj.jdbc.Driver # Data source configuration spring.datasource.mysql.slave1.url=jdbc:mysql://127.0.0.1:3351/dingding_mid?characterEncoding=utf8 &serverTimezone=UTC spring.datasource.mysql.slave1.username=root spring.datasource.mysql.slave1.password=123456 spring.datasource.mysql.slave1.driver-class-name=com.mysql.cj.jdbc.Driver
In the above configuration file, I only wrote two sources, and they are both mysql, primary and slave1 are the distinction
2: The configuration class implements multi-data source configuration
package com.todoitbo.tallybookdasmart.config; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.todoitbo.tallybookdasmart.multiDataSource.DataSourceType; import com.todoitbo.tallybookdasmart.multiDataSource.DynamicDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.*; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author xiaobo * @date 2023/5/19 */ @Configuration @Slf4j public class MultiDataSourceConfig {<!-- --> @Bean public PlatformTransactionManager platformTransactionManager(DataSource dynamicDataSource) {<!-- --> return new DataSourceTransactionManager(dynamicDataSource); } @Bean @Primary @DependsOn("primaryDataSource") public DataSource dynamicDataSource(@Qualifier(DataSourceType. PRIMARY) DataSource primaryDataSource, @Qualifier(DataSourceType. SECOND) DataSource secondDataSource) {<!-- --> DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 1. Set the default data source dynamicDataSource.setDefaultTargetDataSource(primaryDataSource); // 2. Configure multiple data sources Map<Object, Object> map = new HashMap<>(); map.put(DataSourceType.PRIMARY, primaryDataSource); map.put(DataSourceType.SECOND, secondDataSource); // 3. Store the data source set dynamicDataSource.setTargetDataSources(map); return dynamicDataSource; } @Bean(name = DataSourceType. PRIMARY) @ConfigurationProperties(prefix = "spring.datasource.mysql.primary") public DataSource primaryDataSource() {<!-- --> log.info("The main database connection pool is being created......"); return DruidDataSourceBuilder.create().build(); } @Bean(name = DataSourceType. SECOND) @ConfigurationProperties(prefix = "spring.datasource.mysql.slave1") public DataSource secondDataSource() {<!-- --> log.info("second database connection pool creation......"); return DruidDataSourceBuilder.create().build(); } }
3: Custom annotation implementation, you can use custom annotations to switch data sources
package com.todoitbo.tallybookdasmart.multiDataSource; import java.lang.annotation.*; /** * description: custom annotation, mark data source * * @author bo * @version 1.0 * @date 2023/5/19 08:45 */ @Retention(RetentionPolicy. RUNTIME) @Target(ElementType. METHOD) @Documented public @interface DataSource {<!-- --> String value() default DataSourceType.PRIMARY; }
4: Define an aspect class
This code is an aspect class DataSourceAspect
, which is used to switch the data source before and after the method call. Here is an explanation of the code:
@Aspect
: Specify this class as an aspect class, which is used to define the entry point of the aspect and enhance the logic.@Order(value=1)
: Specify the execution order of the aspect, the smaller the value, the higher the priority.@Component
: Declare the aspect class as a Spring component, so that it can be automatically scanned and assembled into the Spring container.@Pointcut(value = "execution(* com.todoitbo.tallybookdasmart.service.*.*(..)) || execution(* com.todoitbo.tallybookdasmart.*.*(..))" )
: Define the pointcut expression and specify the target method that needs to be cut in.@Around("dataSourcePointCut()")
: defines the surround advice, which means that the aspect logic is executed before and after the execution of the target method.public Object around(ProceedingJoinPoint joinPoint) throws Throwable
: surround notification method, including aspect logic.- Obtain the annotation information of the target method through reflection in the method, determine whether there is a
@DataSource
annotation, and obtain the data source name set in the annotation. - Call the
DataSourceContextHolder.setDataSource(dataSource)
method to set the obtained data source name to the context of the current thread. - Call the
joinPoint.proceed()
method to continue executing the target method. - Call the
DataSourceContextHolder.clearDataSourceType()
method in thefinally
block to clear the data source information stored in the current thread.
package com.todoitbo.tallybookdasmart.multiDataSource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @author xiaobo */ @Aspect @Order(value=1) @Component @Slf4j public class DataSourceAspect {<!-- --> /** Define pointcut expression */ @Pointcut(value = "execution(* com.todoitbo.tallybookdasmart.service.*.*(..)) || execution(* com.todoitbo.tallybookdasmart.*.*(..))") public void dataSourcePointCut() {<!-- --> } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable {<!-- --> Object target = joinPoint. getTarget(); String method = joinPoint. getSignature(). getName(); Class<?> classz = target. getClass(); Class<?>[] parameterTypes = ((MethodSignature) joinPoint. getSignature()). getMethod(). getParameterTypes(); try {<!-- --> // Use reflection to get the method object of the specified method name and parameter type in the target class Method m = classz. getMethod(method, parameterTypes); // Set the default data source name String dataSource = DataSourceType. PRIMARY; // Determine whether the method is marked with the @DataSource annotation. if (m.isAnnotationPresent(DataSource.class)) {<!-- --> // Obtain the @DataSource annotation object on the method through the getAnnotation() method. DataSource ds = m. getAnnotation(DataSource. class); // Get the data source name set in the annotation object dataSource = ds. value(); } // Set the obtained data source name to the context of the current thread DataSourceContextHolder.setDataSource(dataSource); // Continue to execute the target method return joinPoint. proceed(); } finally {<!-- --> DataSourceContextHolder. clearDataSourceType(); } } }
5: The context tool class for storing and obtaining the data source of the current thread
This code is a context tool class for storing and getting the data source of the current thread. It uses Netty’s FastThreadLocal
to achieve thread-local fast access.
- Create
FastThreadLocal
object: AFastThreadLocal
object namedCONTEXT_HOLDER
is defined in the class to store the data source information of the current thread. - Set data source: The
setDataSource
method is used to set the data source name to the context of the current thread. Store the data source name in the current thread by callingCONTEXT_HOLDER.set(dataSource)
. - Get Data Source: The
getDataSource
method is used to get the data source name from the context of the current thread. By callingCONTEXT_HOLDER.get()
, you can get the data source name of the current thread. - Clear data source: The
clearDataSourceType
method is used to clear the data source information stored in the current thread. By callingCONTEXT_HOLDER.remove()
, the data source information in the current thread can be cleared.
package com.todoitbo.tallybookdasmart.multiDataSource; import io.netty.util.concurrent.FastThreadLocal; /** * description: The context tool class for storing and obtaining the data source of the current thread * * @author bo * @version 1.0 * @date 2023/5/19 08:44 */ public class DataSourceContextHolder {<!-- --> /** * Create a FastThreadLocal object to store the data source information of the current thread */ private static final FastThreadLocal<String> CONTEXT_HOLDER = new FastThreadLocal<String>(); /** * Set the data source */ public static void setDataSource(String dataSource) {<!-- --> CONTEXT_HOLDER.set(dataSource); } /** * Get data source */ public static String getDataSource() {<!-- --> return CONTEXT_HOLDER. get(); } /** * Clear data source */ public static void clearDataSourceType() {<!-- --> CONTEXT_HOLDER. remove(); } }
6: data source type
package com.todoitbo.tallybookdasmart.multiDataSource; /** * @author xiaobo */ public class DataSourceType {<!-- --> public static final String PRIMARY = "primaryDataSource"; public static final String SECOND = "secondDataSource"; }
7: Get the corresponding data source according to the data source context in the current thread
package com.todoitbo.tallybookdasmart.multiDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * description: Get the corresponding data source according to the data source context in the current thread. * * @author bo * @version 1.0 * @date 2023/5/19 08:46 */ public class DynamicDataSource extends AbstractRoutingDataSource {<!-- --> @Override protected Object determineCurrentLookupKey() {<!-- --> return DataSourceContextHolder. getDataSource(); } }
Concrete implementation
Just add annotations to the method of the service implementation class
package com.todoitbo.tallybookdasmart.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.todoitbo.tallybookdasmart.entity.TbConfig; import com.todoitbo.tallybookdasmart.mapper.TbConfigMapper; import com.todoitbo.tallybookdasmart.multiDataSource.DataSource; import com.todoitbo.tallybookdasmart.multiDataSource.DataSourceType; import com.todoitbo.tallybookdasmart.service.ITbConfigService; import com.todoitbo.tallybookdasmart.service.base.BaseServiceImpl; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; /** * (TbConfig) service * * @author bo * @since 2023-04-18 21:13:14 */ @Service public class TbConfigServiceImpl extends BaseServiceImpl<TbConfigMapper,TbConfig> implements ITbConfigService {<!-- --> @Resource protected TbConfigMapper mapper; @Override @DataSource(DataSourceType. SECOND) public List<TbConfig> testList() {<!-- --> return mapper. selectList(new QueryWrapper<>()); } }
Renderings:
: This is just to show that he is indeed gone from the data source