0
点赞
收藏
分享

微信扫一扫

自定义注解实现多数据源+实现原理分析_02


文章目录

  • ​​一、实现原理分析​​
  • ​​1. 浏览器访问方法​​
  • ​​2. 判断是否添加注解​​
  • ​​3. 前置通知获取value值​​
  • ​​4. 获取数据库连接​​
  • ​​5. 执行具体逻辑​​
  • ​​二、代码实战​​
  • ​​2.1. 目标方法​​
  • ​​2.2. 自定义注解​​
  • ​​2.3. aop拦截自定义注解​​
  • ​​2.4. ThreadLocal​​
  • ​​2.5. 多数据源配置​​
  • ​​2.6. mapper​​
  • ​​2.7. 表结构​​
一、实现原理分析
1. 浏览器访问方法

自定义注解实现多数据源+实现原理分析_02_数据库


自定义注解实现多数据源+实现原理分析_02_spring_02

这里演示的是同一个方法连接2个数据库的场景。
因此请求方法上不需要添加注解,在具体的执行方法上添加注解即可。
依次连接sys-order数据库存储数据,然后连接sys-admin数据库查询数据。

自定义注解实现多数据源+实现原理分析_02_spring_03


自定义注解实现多数据源+实现原理分析_02_数据源_04

2. 判断是否添加注解

先执行aop拦截判断请求方法上是否有添加多数据源注解

自定义注解实现多数据源+实现原理分析_02_spring_05

自定义注解实现多数据源+实现原理分析_02_数据库_06

自定义注解实现多数据源+实现原理分析_02_sql_07

3. 前置通知获取value值

如果有,走aop前置通知回调方法,获取该多数据源注解中的value值,将获取多数据源的value值存放到当前线程的ThreadLocal中

自定义注解实现多数据源+实现原理分析_02_spring_08

4. 获取数据库连接

执行数据库查询之前spring会先获取数据库连接,执行第18行时,会走到spring的AbstractRoutingDataSource类中的determineTargetDataSource方法

自定义注解实现多数据源+实现原理分析_02_sql_09


自定义注解实现多数据源+实现原理分析_02_数据库_10


这类中的determineCurrentLookupKey方法会判断是否有重写,如果重新就会进行回调

自定义注解实现多数据源+实现原理分析_02_sql_11


咱们类MultipleDataSource extends AbstractRoutingDataSource并重写了determineCurrentLookupKey方法,因此,会获取咱们自己存储的值。

咱们在aop的前置通知中已经将数据源信息存储到了当前线程的ThreadLocal中

了,因此,这里从ThreadLocal中获取的就是咱们提前存储的值。

自定义注解实现多数据源+实现原理分析_02_sql_12


获取值后,在回到spring的determineCurrentLookupKey方法中,继续走下面的逻辑

自定义注解实现多数据源+实现原理分析_02_java_13


自定义注解实现多数据源+实现原理分析_02_数据库_14

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='订单表';


举报

相关推荐

0 条评论