1. 前言
最近做項目碰到了一個需要連4個不同數據庫的需求,其中db1、db2表結構都不相同;另外兩個數據庫same_db_private、same_db_public表結構完全相同,一個對內一個對外,只是從物理上隔離了數據而已。
所以打算通過靜態(tài)配置包路徑的方式來實現(xiàn)db1、db2的操作,并且通過擴展Spring的AbstractRoutingDataSource的方式來實現(xiàn)same_db_private、same_db_public的動態(tài)切換數據源。
2. 數據準備
-
db1創(chuàng)建表并準備數據
create table goods_info ( id bigint auto_increment comment '主鍵' primary key, name varchar(50) not null )collate=utf8mb4_bin; insert into db1.goods_info (id, name) values (1, '商品1'), (2, '商品2'), (3, '商品3');
-
db1創(chuàng)建表并準備數據
create table user_info ( id bigint auto_increment comment '主鍵' primary key, name varchar(50) not null )collate=utf8mb4_bin; insert into db2.user_info (id, name) values (1, '用戶1'), (2, '用戶2'), (3, '用戶3');
-
same_db_private創(chuàng)建表并準備數據
create table brand_info ( id bigint auto_increment comment '主鍵' primary key, name varchar(50) not null )collate=utf8mb4_bin; insert into brand_info (id, name) values (1, '內部品牌1');
-
same_db_public創(chuàng)建表并準備數據
create table brand_info ( id bigint auto_increment comment '主鍵' primary key, name varchar(50) not null )collate=utf8mb4_bin; insert into brand_info (id, name) values (1, '外部品牌1');
3. 代碼細節(jié)
3.1 工程目錄
3.2 配置文件
spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db1.username=root
spring.datasource.db1.password=xxxxxx
spring.datasource.db1.jdbc-url=jdbc:mysql://localhost:3306/db1
spring.datasource.db2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.db2.username=root
spring.datasource.db2.password=xxxxxx
spring.datasource.db2.jdbc-url=jdbc:mysql://localhost:3306/db2
spring.datasource.same-db-private.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.same-db-private.username=root
spring.datasource.same-db-private.password=xxxxxx
spring.datasource.same-db-private.jdbc-url=jdbc:mysql://localhost:3306/same_db_private
spring.datasource.same-db-public.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.same-db-public.username=root
spring.datasource.same-db-public.password=xxxxxx
spring.datasource.same-db-public.jdbc-url=jdbc:mysql://localhost:3306/same_db_public
3.3 靜態(tài)配置數據源
在config中創(chuàng)建db1和db2的靜態(tài)數據源配置
package com.aresbf.multi.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.aresbf.multi.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSource1 {
/**
* 配置db1數據庫
*
* @return
*/
@Bean(name = "db1Datasource")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource testDatasource() {
return DataSourceBuilder.create().build();
}
/**
* 創(chuàng)建SqlSessionFactory
*
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "db1SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("db1Datasource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}
/**
* 配置事務管理
*
* @param dataSource
* @return
*/
@Bean(name = "db1TransactionManager")
public DataSourceTransactionManager testTransactionManager(
@Qualifier("db1Datasource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "db1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("db1SqlSessionFactory")
SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package com.aresbf.multi.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
@MapperScan(basePackages = "com.aresbf.multi.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")
public class DataSource2 {
/**
* 配置db2數據庫
*
* @return
*/
@Bean(name = "db2Datasource")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource testDatasource() {
return DataSourceBuilder.create().build();
}
/**
* 創(chuàng)建SqlSessionFactory
*
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "db2SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("db2Datasource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return bean.getObject();
}
/**
* 配置事務管理
*
* @param dataSource
* @return
*/
@Bean(name = "db2TransactionManager")
public DataSourceTransactionManager testTransactionManager(
@Qualifier("db2Datasource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "db2SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("db2SqlSessionFactory")
SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
3.4 動態(tài)切換數據源
-
創(chuàng)建動態(tài)數據源配置
package com.aresbf.multi.config; import com.aresbf.multi.dynamicdatasource.DataSourceRouting; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; 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.jdbc.datasource.DataSourceTransactionManager; import javax.sql.DataSource; /** * @description:sameDb動態(tài)數據源 * @author: aresbf * @createDate: 2022/10/21 */ @Configuration @Slf4j @MapperScan(basePackages = "com.aresbf.multi.mapper.same_db", sqlSessionTemplateRef = "sameDbDynamicSqlSessionTemplate") public class SameDbDynamicDataSourceConfig { /** * 自定義動態(tài)datasource * * @param sameDbPrivateDataSource 對內datasource * @param sameDbPublicDataSource 對外datasource * @return DataSource */ @Bean(name = "sameDbDynamicDataSource") @Primary public DataSource dataSource(@Qualifier("sameDbPrivateDataSource") DataSource sameDbPrivateDataSource, @Qualifier("sameDbPublicDataSource") DataSource sameDbPublicDataSource) { DataSourceRouting routingDataSource = new DataSourceRouting(); routingDataSource.initDatasource(sameDbPrivateDataSource, sameDbPublicDataSource); return routingDataSource; } /** * 對內datasource * * @return DataSource */ @Bean(name = "sameDbPrivateDataSource") @ConfigurationProperties(prefix = "spring.datasource.same-db-private") public DataSource sameDbPrivateDataSource() { return DataSourceBuilder.create().build(); } /** * 對外datasource * * @return DataSource */ @Bean(name = "sameDbPublicDataSource") @ConfigurationProperties(prefix = "spring.datasource.same-db-public") public DataSource sameDbPublicDataSource() { return DataSourceBuilder.create().build(); } /** * 自定義SqlSessionFactory * * @param dataSource 自定義datasource * @return SqlSessionFactory * @throws Exception */ @Bean(name = "sameDbDynamicSqlSessionFactory") public SqlSessionFactory customSqlSessionFactory(@Qualifier("sameDbDynamicDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //實現(xiàn)數據庫下劃線字段到POJO類駝峰形式的自動映射 bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true); return bean.getObject(); } /** * 自定義DataSourceTransactionManager * * @param dataSource 自定義datasource * @return DataSourceTransactionManager */ @Bean(name = "sameDbDynamicTransactionManager") @Primary public DataSourceTransactionManager customTransactionManager(@Qualifier("sameDbDynamicDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 自定義SqlSessionTemplate * * @param sqlSessionFactory 自定義SqlSessionFactory * @return SqlSessionTemplate */ @Bean(name = "sameDbDynamicSqlSessionTemplate") @Primary public SqlSessionTemplate customSqlSessionTemplate(@Qualifier("sameDbDynamicSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } }
-
創(chuàng)建區(qū)分動態(tài)數據源枚舉項
package com.aresbf.multi.dynamicdatasource; import lombok.Getter; /** * @description: 動態(tài)數據源枚舉 * @author: aresbf * @createDate: 2022/10/26 */ public enum DynamicDataSourceEnum { /** * 對內系統(tǒng)數據庫 */ SAME_DB_PRIVATE("SAME_DB_PRIVATE", "對內系統(tǒng)數據庫"), /** * 對外系統(tǒng)數據庫 */ SAME_DB_PUBLIC("SAME_DB_PUBLIC", "對外系統(tǒng)數據庫"), ; /** * 數據源code */ @Getter private final String dataSource; /** * 描述 */ private final String description; /** * 構造器 * * @param dataSource 數據源標識 * @param description 描述 */ DynamicDataSourceEnum(String dataSource, String description) { this.dataSource = dataSource; this.description = description; } }
-
動態(tài)數據源切換上下文
package com.aresbf.multi.dynamicdatasource; import lombok.extern.slf4j.Slf4j; /** * @description: 動態(tài)數據源切換用上下文 * @author: aresbf * @createDate: 2022/10/26 */ @Slf4j public class DataSourceContextHolder { /** * 用于存在數據源切換標識 */ private static ThreadLocal<DynamicDataSourceEnum> datasourceContext = new ThreadLocal<>(); /** * 切換數據源 * * @param dynamicDataSourceEnum 要切換的數據源標識 */ public static void switchDataSource(DynamicDataSourceEnum dynamicDataSourceEnum) { log.debug("switchDataSource: {}", dynamicDataSourceEnum.getDataSource()); datasourceContext.set(dynamicDataSourceEnum); } /** * 獲取當前的數據源標識 * * @return */ public static DynamicDataSourceEnum getDataSource() { return datasourceContext.get(); } /** * 清理上下文中的數據源標識 */ public static void clear() { datasourceContext.remove(); } }
-
動態(tài)路由
package com.aresbf.multi.dynamicdatasource; import lombok.extern.slf4j.Slf4j; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @description:動態(tài)數據源路由 * @author: aresbf * @createDate: 2022/10/26 */ @Slf4j public class DataSourceRouting extends AbstractRoutingDataSource { /** * 決定使用哪個數據源標識 * * @return 數據源標識 */ @Override protected Object determineCurrentLookupKey() { DynamicDataSourceEnum dynamicDataSourceEnum = DataSourceContextHolder.getDataSource(); //如果沒有設置數據源標識,默認使用對內數據源標識 if (dynamicDataSourceEnum == null) { dynamicDataSourceEnum = DynamicDataSourceEnum.SAME_DB_PRIVATE; } log.debug("use{}", dynamicDataSourceEnum.getDataSource()); return dynamicDataSourceEnum; } /** * 初始化數據源列表 * * @param sameDbPrivate 對內數據源 * @param sameDbPublic 對外數據源 */ public void initDatasource(DataSource sameDbPrivate, DataSource sameDbPublic) { Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(DynamicDataSourceEnum.SAME_DB_PRIVATE, sameDbPrivate); dataSourceMap.put(DynamicDataSourceEnum.SAME_DB_PUBLIC, sameDbPublic); this.setTargetDataSources(dataSourceMap); this.setDefaultTargetDataSource(sameDbPrivate); } }
-
自定義動態(tài)數據源注解
package com.aresbf.multi.dynamicdatasource; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @description:自定義動態(tài)數據源注解 * @author: aresbf * @createDate: 2022/10/26 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface SameDbDynamicDataSource { /** * 數據源標識 * * @return DynamicDataSourceEnum */ DynamicDataSourceEnum name() default DynamicDataSourceEnum.SAME_DB_PRIVATE; }
-
動態(tài)數據源攔截文章來源:http://www.zghlxwxcb.cn/news/detail-700221.html
package com.aresbf.multi.dynamicdatasource; import org.aspectj.lang.JoinPoint; 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.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /** * @description:動態(tài)數據源攔截 * @author: aresbf * @createDate: 2022/10/26 */ @Aspect @Component public class HandleDatasourceAspect { /** * 所有標識sameDbDynamicDataSource注解的類和方法 */ @Pointcut("@annotation(com.aresbf.multi.dynamicdatasource.SameDbDynamicDataSource)||@within(com.aresbf.multi.dynamicdatasource.SameDbDynamicDataSource)") public void pointcut() { } /** * 方法執(zhí)行前 * * @param joinPoint 攔截點 */ @Before("pointcut()") public void beforeExecute(JoinPoint joinPoint) { Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); SameDbDynamicDataSource annotation = method.getAnnotation(SameDbDynamicDataSource.class); if (null == annotation) { annotation = joinPoint.getTarget().getClass().getAnnotation(SameDbDynamicDataSource.class); } if (null != annotation) { // 切換數據源 DataSourceContextHolder.switchDataSource(annotation.name()); } } /** * 方法執(zhí)行后 */ @After("pointcut()") public void afterExecute() { DataSourceContextHolder.clear(); } }
3.5 編寫測試類
通過SameDbDynamicDataSource動態(tài)指定需要訪問的數據源,避免相同的mapper代碼重復寫文章來源地址http://www.zghlxwxcb.cn/news/detail-700221.html
package com.aresbf.multi.controller;
import com.aresbf.multi.dynamicdatasource.DynamicDataSourceEnum;
import com.aresbf.multi.dynamicdatasource.SameDbDynamicDataSource;
import com.aresbf.multi.entity.BrandInfoPO;
import com.aresbf.multi.entity.GoodsInfoPO;
import com.aresbf.multi.entity.UserInfoPO;
import com.aresbf.multi.service.BrandInfoService;
import com.aresbf.multi.service.GoodsInfoService;
import com.aresbf.multi.service.UserInfoService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
/**
* @description: 測試
* @author: aresbf
* @createDate: 2022/10/24
*/
@RestController
@RequestMapping
@Slf4j
public class TestController {
@Autowired
private GoodsInfoService goodsInfoService;
@Autowired
private UserInfoService userInfoService;
@Autowired
private BrandInfoService brandInfoService;
/**
* 查內部
*
* @return ok
*/
@GetMapping("/queryPrivate")
@SameDbDynamicDataSource(name = DynamicDataSourceEnum.SAME_DB_PRIVATE)
public String queryPrivate() throws JsonProcessingException {
List<GoodsInfoPO> goodsInfoPOS = goodsInfoService.queryAll();
List<UserInfoPO> userInfoPOS = userInfoService.queryAll();
List<BrandInfoPO> brandInfoPOS = brandInfoService.queryAll();
HashMap<String, List> resultMap = new HashMap<>();
resultMap.put("goodsInfo", goodsInfoPOS);
resultMap.put("userInfo", userInfoPOS);
resultMap.put("brandInfo", brandInfoPOS);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(resultMap);
}
/**
* 查外部
*
* @return ok
*/
@GetMapping("/queryPublic")
@SameDbDynamicDataSource(name = DynamicDataSourceEnum.SAME_DB_PUBLIC)
public String queryPublic() throws JsonProcessingException {
List<GoodsInfoPO> goodsInfoPOS = goodsInfoService.queryAll();
List<UserInfoPO> userInfoPOS = userInfoService.queryAll();
List<BrandInfoPO> brandInfoPOS = brandInfoService.queryAll();
HashMap<String, List> resultMap = new HashMap<>();
resultMap.put("goodsInfo", goodsInfoPOS);
resultMap.put("userInfo", userInfoPOS);
resultMap.put("brandInfo", brandInfoPOS);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(resultMap);
}
}
3.6 測試結果
3.6.1 請求http://localhost:8080/queryPublic
{"userInfo":[{"id":1,"name":"用戶1"},{"id":2,"name":"用戶2"},{"id":3,"name":"用戶3"}],"brandInfo":[{"id":1,"name":"外部品牌1"}],"goodsInfo":[{"id":1,"name":"商品1"},{"id":2,"name":"商品2"},{"id":3,"name":"商品3"}]}
3.6.2 請求http://localhost:8080/queryPrivate
{"userInfo":[{"id":1,"name":"用戶1"},{"id":2,"name":"用戶2"},{"id":3,"name":"用戶3"}],"brandInfo":[{"id":1,"name":"內部品牌1"}],"goodsInfo":[{"id":1,"name":"商品1"},{"id":2,"name":"商品2"},{"id":3,"name":"商品3"}]}
到了這里,關于springboot+mybatis實現(xiàn)多數據源的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!