記得面試時(shí)候,有面試官會(huì)問道挺份,你們多數(shù)據(jù)源是怎么實(shí)現(xiàn)的呀褒翰。.......,一陣蒙蔽中,然后說(shuō)道我們之前項(xiàng)目中优训,沒有用到多數(shù)據(jù)源朵你。
所幸,目前做得項(xiàng)目中有一個(gè)業(yè)務(wù)邏輯中揣非,用到多個(gè)數(shù)據(jù)庫(kù)數(shù)據(jù)情況抡医,多數(shù)據(jù)源華麗上線。
一. mybatis plus
?因?yàn)槲覀冺?xiàng)目是springboot+mybatis plus早敬,有些人一看魂拦,mybatis還知道對(duì)吧,mybatis plus是什么鬼搁嗓,其實(shí)字面意思可以理解芯勘,就是對(duì)mybatis進(jìn)行一些功能改造,一些封裝升級(jí)腺逛,然后用起來(lái)特別方便荷愕。
? ? ?核心功能的升級(jí)主要是以下三點(diǎn):
? ? ?支持通用的 CRUD、代碼生成器與條件構(gòu)造器棍矛。
通用 CRUD:定義好 Mapper 接口后安疗,只需要繼承 BaseMapper 接口即可獲得通用的增刪改查功能,無(wú)需編寫任何接口方法與配置文件
條件構(gòu)造器:通過 EntityWrapper (實(shí)體包裝類)够委,可以用于拼接 SQL 語(yǔ)句荐类,并且支持排序、分組查詢等復(fù)雜的 SQL
代碼生成器:支持一系列的策略配置與全局配置茁帽,比 MyBatis 的代碼生成更好用
二.多數(shù)據(jù)源配置開始
? ? 思路:
1玉罐、yml中配置多個(gè)數(shù)據(jù)源信息
2、通過AOP切換不同數(shù)據(jù)源
3潘拨、配合mybatis plus使用
1吊输、yml配置
spring:
? aop:
? ? ? proxy-target-class: true
? ? ? auto: true
? datasource:
? ? druid:
? ? ? db1:
? ? ? ? url: jdbc:mysql://localhost:3306/eboot
? ? ? ? username: root
? ? ? ? password: root
? ? ? ? driver-class-name: com.mysql.jdbc.Driver
? ? ? ? initialSize: 5
? ? ? ? minIdle: 5
? ? ? ? maxActive: 20
? ? ? db2:
? ? ? ? url: jdbc:oracle:thin:@192.168.136.222:ORCL
? ? ? ? username: sa
? ? ? ? password: sa123456
? ? ? ? driver-class-name: oracle.jdbc.OracleDriver
? ? ? ? initialSize: 5
? ? ? ? minIdle: 5
? ? ? ? maxActive: 20
2、啟動(dòng)多個(gè)數(shù)據(jù)源
@EnableTransactionManagement //開啟事務(wù)
@Configuration? //spring中常用到注解铁追,與xml配置相對(duì)立季蚂。是兩種加載bean方式
@MapperScan("com.df.openapi.**.mapper.db*") // 掃描mapperdao的地址
public class MybatisPlusConfig {
? ? @Bean
? ? public PaginationInterceptor paginationInterceptor() {
? ? ? ? PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
//? ? ? ? paginationInterceptor.setLocalPage(true); // 由于版本問題,有些類可能招不到這個(gè)方法琅束,需要升級(jí)jar包
? ? ? ? return paginationInterceptor;
? ? }
? ? @Bean(name = "db1")
? ? @ConfigurationProperties(prefix = "spring.datasource.druid.db1")
? ? public DataSource db1() {
? ? ? ? return DruidDataSourceBuilder.create().build();
? ? }
? ? @Bean(name = "db2")
? ? @ConfigurationProperties(prefix = "spring.datasource.druid.db2")
? ? public DataSource db2() {
? ? ? ? return DruidDataSourceBuilder.create().build();
? ? }
? ? /**
? ? * 動(dòng)態(tài)數(shù)據(jù)源配置
? ? *
? ? * @return
? ? */
? ? @Bean
? ? @Primary
? ? public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @Qualifier("db2") DataSource db2) {
? ? ? ? DynamicDataSource dynamicDataSource = new DynamicDataSource();
? ? ? ? Map<Object, Object> targetDataSources = new HashMap<>();
? ? ? ? targetDataSources.put(DBTypeEnum.db1.getValue(), db1);
? ? ? ? targetDataSources.put(DBTypeEnum.db2.getValue(), db2);
? ? ? ? dynamicDataSource.setTargetDataSources(targetDataSources);
? ? ? ? dynamicDataSource.setDefaultTargetDataSource(db2); // 程序默認(rèn)數(shù)據(jù)源扭屁,這個(gè)要根據(jù)程序調(diào)用數(shù)據(jù)源頻次,經(jīng)常把常調(diào)用的數(shù)據(jù)源作為默認(rèn)
? ? ? ? return dynamicDataSource;
? ? }
? ? @Bean("sqlSessionFactory")
? ? public SqlSessionFactory sqlSessionFactory() throws Exception {
? ? ? ? MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
? ? ? ? sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2()));
? ? ? ? MybatisConfiguration configuration = new MybatisConfiguration();
? ? ? ? configuration.setJdbcTypeForNull(JdbcType.NULL);
? ? ? ? configuration.setMapUnderscoreToCamelCase(true);
? ? ? ? configuration.setCacheEnabled(false);
? ? ? ? sqlSessionFactory.setConfiguration(configuration);
? ? ? ? //PerformanceInterceptor(),OptimisticLockerInterceptor()
? ? ? ? //添加分頁(yè)功能
? ? ? ? sqlSessionFactory.setPlugins(new Interceptor[]{
? ? ? ? ? ? ? ? paginationInterceptor()
? ? ? ? });
//? ? ? ? sqlSessionFactory.setGlobalConfig(globalConfiguration()); //注釋掉全局配置涩禀,因?yàn)樵趚ml中讀取就是全局配置
? ? ? ? return sqlSessionFactory.getObject();
? ? }
/*? @Bean
? ? public GlobalConfiguration globalConfiguration() {
? ? ? ? GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
? ? ? ? conf.setLogicDeleteValue("-1");
? ? ? ? conf.setLogicNotDeleteValue("1");
? ? ? ? conf.setIdType(0);
? ? ? ? conf.setMetaObjectHandler(new MyMetaObjectHandler());
? ? ? ? conf.setDbColumnUnderline(true);
? ? ? ? conf.setRefresh(true);
? ? ? ? return conf;
? ? }*/
}
3料滥、DBType枚舉類
package com.df.openapi.config.db;
public enum DBTypeEnum {
? ? db1("db1"), db2("db2");
? ? private String value;
? ? DBTypeEnum(String value) {
? ? ? ? this.value = value;
? ? }
? ? public String getValue() {
? ? ? ? return value;
? ? }
}
4、動(dòng)態(tài)數(shù)據(jù)源決策
package com.df.openapi.config.db;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
String datasource = DataSourceContextHolder.getDbType();
LOGGER.debug("使用數(shù)據(jù)源 {}", datasource);
return datasource;
}
}
5埋泵、設(shè)置幔欧、獲取數(shù)據(jù)源
public class DataSourceContextHolder {
? ? private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceContextHolder.class);
? ? private static final ThreadLocal contextHolder = new ThreadLocal<>(); //實(shí)際上就是開啟多個(gè)線程罪治,每個(gè)線程進(jìn)行初始化一個(gè)數(shù)據(jù)源
? ? /**
? ? * 設(shè)置數(shù)據(jù)源
? ? * @param dbTypeEnum
? ? */
? ? public static void setDbType(DBTypeEnum dbTypeEnum) {
? ? ? ? contextHolder.set(dbTypeEnum.getValue());
? ? }
? ? /**
? ? * 取得當(dāng)前數(shù)據(jù)源
? ? * @return
? ? */
? ? public static String getDbType() {
? ? ? ? return (String) contextHolder.get();
? ? }
? ? /**
? ? * 清除上下文數(shù)據(jù)
? ? */
? ? public static void clearDbType() {
? ? ? ? contextHolder.remove();
? ? }
}
6、AOP實(shí)現(xiàn)的數(shù)據(jù)源切換
@Order設(shè)置的足夠小是為了讓他先執(zhí)行
/** * aop的實(shí)現(xiàn)的數(shù)據(jù)源切換
* aop切點(diǎn)礁蔗,實(shí)現(xiàn)mapper類找尋觉义,找到所屬大本營(yíng)以后,如db1Aspect(),則會(huì)調(diào)用
* db1()前面之前的操作浴井,進(jìn)行數(shù)據(jù)源的切換晒骇。 */@Component@Order(value = -100)@Slf4j@Aspectpublic class DataSourceAspect {? ? @Pointcut("execution(* com.zwyl.bazhong.dao.mapper.db1..*.*(..))")? ? private void db1Aspect() {? ? }? ? @Pointcut("execution(* com.zwyl.bazhong.dao.mapper.db2..*.*(..))")? ? private void db2Aspect() {? ? }? ? @Before("db1Aspect()")? ? public void db1() {? ? ? ? log.info("切換到db1 數(shù)據(jù)源...");? ? ? ? DataSourceContextHolder.setDbType(DBTypeEnum.db1);? ? }? ? @Before("db2Aspect()")? ? public void db2() {? ? ? ? log.info("切換到db2 數(shù)據(jù)源...");? ? ? ? DataSourceContextHolder.setDbType(DBTypeEnum.db2);? ? }}
7、mapper層結(jié)構(gòu)
8磺浙、寫一個(gè)service測(cè)試一下
@Service
public class DictServiceImpl implements IDictService {
? ? @Resource
? ? private PtDictMapper ptDictMapper; //來(lái)自db1
? ? @Resource
? ? private SysDictMapper sysDictMapper; // 來(lái)自db2
? ? @Override
? ? public void getById(String id) {
? ? ? ? PtDict dict = ptDictMapper.selectById("2bf6257fc8fe483c84c1ad7e89d632f6");
? ? ? ? SysDict sysDict = sysDictMapper.getById("49");
? ? ? ? System.out.println("123");
? ? }
}
9洪囤、測(cè)試結(jié)果
總結(jié): 其實(shí)整個(gè)過程可以理解成,配置多數(shù)據(jù)源 xml中 ?-------> 然后通過加載多數(shù)源到spring工廠中-------->然后創(chuàng)建多線程撕氧,每個(gè)數(shù)據(jù)源對(duì)應(yīng)一個(gè)數(shù)據(jù)源--------->然后實(shí)際調(diào)用時(shí)候瘤缩,會(huì)先通過aop匹配到某一具體數(shù)據(jù)源------------->然后實(shí)例化當(dāng)前數(shù)據(jù)源