文章目录
- 一、实现原理分析
- 1. 浏览器访问方法
- 2. 判断是否添加注解
- 3. 前置通知获取value值
- 4. 获取数据库连接
- 5. 执行具体逻辑
- 二、代码实战
- 2.1. 目标方法
- 2.2. 自定义注解
- 2.3. aop拦截自定义注解
- 2.4. ThreadLocal
- 2.5. 多数据源配置
- 2.6. mapper
- 2.7. 表结构
一、实现原理分析
1. 浏览器访问方法
这里演示的是同一个方法连接2个数据库的场景。
因此请求方法上不需要添加注解,在具体的执行方法上添加注解即可。
依次连接sys-order数据库存储数据,然后连接sys-admin数据库查询数据。
2. 判断是否添加注解
先执行aop拦截判断请求方法上是否有添加多数据源注解
3. 前置通知获取value值
如果有,走aop前置通知回调方法,获取该多数据源注解中的value值,将获取多数据源的value值存放到当前线程的ThreadLocal中
4. 获取数据库连接
执行数据库查询之前spring会先获取数据库连接,执行第18行时,会走到spring的AbstractRoutingDataSource类中的determineTargetDataSource方法
这类中的determineCurrentLookupKey方法会判断是否有重写,如果重新就会进行回调
咱们类MultipleDataSource extends AbstractRoutingDataSource并重写了determineCurrentLookupKey方法,因此,会获取咱们自己存储的值。
咱们在aop的前置通知中已经将数据源信息存储到了当前线程的ThreadLocal中
了,因此,这里从ThreadLocal中获取的就是咱们提前存储的值。
获取值后,在回到spring的determineCurrentLookupKey方法中,继续走下面的逻辑
5. 执行具体逻辑
当获取数据连接后,就可以执行具体的逻辑处理了。
二、代码实战
2.1. 目标方法
@Autowired
private MainManage mainManage;
@Autowired
private OrderManage orderManage;
@GetMapping("/getTestDatasource")
@Override
public String getTestDatasource() {
// sys-order数据源 解决事务方案 jta
SysOrder sysOrder = new SysOrder("mayikt");
int result = orderManage.insert(sysOrder);
// sys-admin数据源
MayiktDictionaryData mayiktDictionaryData = mainManage.selectById(1);
return mayiktDictionaryData.getName() + result;
}
@Component
public class OrderManage {
@Autowired
private SysOrderMapper orderMapper;
@MayiktDataSource("db2")
public int insert(SysOrder sysOrder) {
// sys-order数据源 解决事务方案 jta
int result = orderMapper.insert(sysOrder);
return result;
}
}
@Component
public class MainManage {
@Autowired
private MayiktDictionaryDataMapper dictionaryDataMapper;
@MayiktDataSource
public MayiktDictionaryData selectById(Integer id) {
// sys-admin数据源
return dictionaryDataMapper.selectById(id);
}
}
2.2. 自定义注解
/**
* 多数据源注解
*
* @author gblfy
* @date 2022-09-12
*/
@Target({ElementType.METHOD,// 方法上
ElementType.TYPE})// 类上
@Retention(RetentionPolicy.RUNTIME)
public @interface MayiktDataSource {
String value() default "db1";
}
2.3. aop拦截自定义注解
package com.mayikt.ext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Aspect
@Order(-1)
public class DataSourceAspect {
@Pointcut("@within(com.mayikt.ext.MayiktDataSource)||@annotation(com.mayikt.ext.MayiktDataSource)")
public void pointCut() {
}
/**
* 前置通知
*
* @param dataSource
*/
@Before("pointCut() && @annotation(dataSource)")
public void doBefore(MayiktDataSource dataSource) {
log.info("=========选择数据源==========={}", dataSource.value());
DataSourceContextHolder.setDataSource(dataSource.value());
}
/**
* 后置通知
*/
@After("pointCut() ")
public void doAfter() {
DataSourceContextHolder.clearDataSource();
log.info("=========清除上下文数据源===========");
}
}
2.4. ThreadLocal
package com.mayikt.ext;
public class DataSourceContextHolder {
public static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();
/**
* 设置数据源
*
* @param db
*/
public static void setDataSource(String db) {
contextHolder.set(db);
}
/**
* 获取当前数据源
*
*/
public static String getDataSource() {
return contextHolder.get();
}
/**
* 清除上下文数据源
*
*/
public static void clearDataSource() {
contextHolder.remove();
}
}
2.5. 多数据源配置
package com.mayikt.config;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.mayikt.ext.MultipleDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
@MapperScan(value = {"com.mayikt.order.mapper", "com.mayikt.main.mapper"})
public class MybatisPlusConfiguration {
static final String MAPPER_LOCATION = "classpath*:/mapping/*.xml";
@Bean("db1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.admin")
public DataSource getDb1DataSource() {
return DataSourceBuilder.create().build();
}
@Bean("db2DataSource")
@ConfigurationProperties(prefix = "spring.datasource.order")
public DataSource getDb2DataSource() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源配置
*/
@Bean
@Primary
public DataSource multipleDataSource(@Qualifier("db1DataSource") DataSource db1DataSource,
@Qualifier("db2DataSource") DataSource db2DataSource) {
MultipleDataSource multipleDataSource = new MultipleDataSource();
HashMap<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("db1", db1DataSource);// sys-admin
targetDataSources.put("db2", db2DataSource);// sys-order
// 添加数据源
multipleDataSource.setTargetDataSources(targetDataSources);
// 设置默认数据源
multipleDataSource.setDefaultTargetDataSource(db1DataSource);
return multipleDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
// SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
// 整合mybatisplus时需要采用MybatisSqlSessionFactoryBean
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
sqlSessionFactory.setDataSource(multipleDataSource(getDb1DataSource(), getDb2DataSource()));
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
return sqlSessionFactory.getObject();
}
}
2.6. mapper
public interface MayiktDictionaryDataMapper extends BaseMapper<MayiktDictionaryData> {}
public interface SysOrderMapper extends BaseMapper<SysOrder> { }
2.7. 表结构
CREATE TABLE `mayikt_dictionary_data` (
`id` int NOT NULL COMMENT '字典ID',
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典名称',
`type_id` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '字典类型ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`is_delete` int DEFAULT NULL COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='字典子表';
CREATE TABLE `sys_order` (
`id` int NOT NULL COMMENT '订单ID',
`name` varchar(50) COLLATE utf8mb4_general_ci NOT NULL COMMENT '订单名称',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='订单表';