1. 動態數據源使用背景
在很多應用場景的時候,我們需要用到動態數據源,比如多租戶的場景,系統登錄時需要根據用戶信息切換到用戶對應的數據庫。又比如業務A要訪問A數據庫,業務B要訪問B數據庫,業務C要訪問C數據庫等,都可以使用動態數據源方案進行解決 數據訪問問題。
2. 基於Springboot + mybatis+mysql+oracle
create table SYS_USER
(
id NUMBER,
name VARCHAR2(50),
password VARCHAR2(100),
salt VARCHAR2(40),
email VARCHAR2(100),
mobile VARCHAR2(100),
status NUMBER,
dept_id NUMBER,
create_by VARCHAR2(50),
create_time DATE,
last_update_by VARCHAR2(50),
last_update_time DATE,
del_flag NUMBER
)
3. 加載相關jar 包依賴
<!-- spring boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- spring aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--oracle驅動 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
<scope>compile</scope>
</dependency>
4. 配置數據庫屬性,yml 文件
spring:
datasource:
master:
#driver-class-name: com.mysql.jdbc.Driver
driverClassName: com.mysql.jdbc.Driver
#jdbcUrl: jdbc:mysql://localhost:3306/mysqltest?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3306/mysqltest?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
initialSize: 5
maxActive: 20
salv:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: root
initialSize: 5
maxActive: 20
oral:
url: jdbc:oracle:thin:@localhost:1521:ORCL
username: scott
password: root
driverClassName: oracle.jdbc.OracleDriver
initialSize: 5
maxActive: 20
server:
port: 8888
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
com.example.feifan.datasource.mapper: debug
5. 配置啓動類
/**
* Hello world!
* DataSourceAutoConfiguration 禁用數據源自動配置
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class SpringbootDynamicDataSourceApp
{
public static void main( String[] args )
{
SpringApplication.run(SpringbootDynamicDataSourceApp.class);
}
}
6. 創建數據庫配置類
當進行數據庫操作時,就會通過我們創建的動態數據源去獲取要操作的數據源了。
動態數據源設置到了SQL會話工廠和事務管理器,這樣在操作數據庫時就會通過動態數據源類來獲取要操作的數據源了。
動態數據源類集成了Spring提供的AbstractRoutingDataSource類,
package org.example.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.example.compent.DynamicDatasource;
import org.example.util.DynamicDatasourceContextHolder;
import org.mybatis.spring.SqlSessionFactoryBean;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @author Donald
* @create 2020-04-18 22:46
*/
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
@MapperScan(basePackages = {"org.example.**.mapper"})
public class DynamicDatasourceConfig {
private Map<String,String> master;
private Map<String,String> salv;
private Map<String,String> oral;
public void setMaster(Map<String, String> master) {
this.master = master;
}
public void setSalv(Map<String, String> salv) {
this.salv = salv;
}
public void setOral(Map<String, String> oral) {
this.oral = oral;
}
/**
* 不通過druid pool 創建鏈接
* @return
* @throws Exception
*/
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource getDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("master")
public DataSource master() throws Exception{
DataSource dataSource = DruidDataSourceFactory.createDataSource(master);
return dataSource;
}
@Bean("slave")
public DataSource slave() throws Exception{
DataSource dataSource = DruidDataSourceFactory.createDataSource(salv);
return dataSource;
}
@Bean("oracle")
public DataSource orcal() throws Exception{
DataSource dataSource = DruidDataSourceFactory.createDataSource(oral);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dynamicDatasource ) {
// 配置事務管理, 使用事務時在方法頭部添加@Transactional註解即可
return new DataSourceTransactionManager(dynamicDatasource);
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean( @Qualifier("dynamicDataSource") DataSource dynamicDatasource ) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
// 配置數據源,此處配置爲關鍵配置,如果沒有將 dynamicDataSource作爲數據源則不能實現切換
sessionFactory.setDataSource(dynamicDatasource);
sessionFactory.setTypeAliasesPackage("org.example.**.entity"); // 掃描entity
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources("classpath*:**/mapper/*Mapper.xml")); // 掃描映射文件
return sessionFactory;
}
@Bean("dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("master") DataSource master, @Qualifier("slave")DataSource sal, @Qualifier("oracle") DataSource oracle) {
DynamicDatasource dynamicDataSource = DynamicDatasource.getInstance();
Map<Object, Object> dataSourceMap = new HashMap<>(3);
dataSourceMap.put("master", master);
dataSourceMap.put("salve", sal);
dataSourceMap.put("oracle",oracle);
// 將 master 數據源作爲默認指定的數據源
dynamicDataSource.setDefaultDatasource(master);
// 將 master 和 slave 數據源作爲指定的數據源
dynamicDataSource.setTargetDataSources(dataSourceMap);
DynamicDatasourceContextHolder.addDataSourceKeys(Arrays.asList("master","salve","oracle"));
return dynamicDataSource;
}
}
動態數據源實現類:在通過determineTargetDataSource獲取目標數據源時使用,動態獲取數據源
package org.example.compent;
import org.example.util.DynamicDatasourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author Donald
* @create 2020-04-18 21:57
* 動態數據源實現類
*/
public class DynamicDatasource extends AbstractRoutingDataSource {
private static byte[] lock = new byte[0];
private static DynamicDatasource instance;
/**
* 如果希望所有數據源在啓動配置時就加載好,這裏通過設置數據源Key值來切換數據,定製這個方法
* 獲取與數據源相關的key
* 此key是Map<String,DataSource> resolvedDataSources 中與數據源綁定的key值
* 在通過determineTargetDataSource獲取目標數據源時使用
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDatasourceContextHolder.getDatasourceKey();
}
/**
* 設置默認數據源
* @param defaultDatasource
*/
public void setDefaultDatasource( Object defaultDatasource){
super.setDefaultTargetDataSource(defaultDatasource);
}
private DynamicDatasource() {
}
public static synchronized DynamicDatasource getInstance(){
if(instance == null)
{
synchronized (lock){
if (instance==null)
{
instance = new DynamicDatasource();
}
}
}
return instance;
}
}
7. 設置當前數據源上下文進行數據的切換
package org.example.util;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* @author Donald
* @create 2020-04-18 22:11
*/
public class DynamicDatasourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(){
/**
* 設置默認數據源
* @return
*/
@Override
protected String initialValue() {
return "master";
}
};
public static List<Object> datasourceKeys = new LinkedList<>();
/**
* 設置數據源
* @param key
*/
public static void setDatasourceKey(String key){
contextHolder.set(key);
}
/**
* 獲取數據源
* @param key
* @return
*/
public static String getDatasource(){
return contextHolder.get();
}
/**
* 重置數據源
*/
public static void clearDataSourceKey() {
contextHolder.remove();
}
/**
* 判斷是否包含數據源
* @param key 數據源key
* @return
*/
public static boolean containDataSourceKey(String key) {
return datasourceKeys.contains(key);
}
/**
* 添加數據源keys
* @param keys
* @return
*/
public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
return datasourceKeys.addAll(keys);
}
public static String getDatasourceKey(){
return contextHolder.get();
}
}
8. 通過註解方式實現數據源的切換,以及去請求頭方式。
通過設置上下文的value 達到動態數據切換
/**
* 設置數據源
* @param key
*/
public static void setDatasourceKey(String key){
contextHolder.set(key);
}
/**
* @author Donald
* @create 2020-04-18 22:23
*/
@Documented
@Retention(RetentionPolicy.RUNTIME )
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface DatasourceAnnotation {
String value();
}
9. 通過AOP 切面實現對頁面請求的攔截。
/**
* @author Donald
* @create 2020-04-18 22:28
*/
@Aspect
@Component
@Order(-1) // 切面優先於 @transational 執行
public class DynamicDatasourceAspect {
@Pointcut("execution(* org.example.service.impl.*.* (..))")
public void ponitcut() {
}
/**
* 設置數據源
*
* @param point
*/
//@Before("@within(dataSource) || @annotation(dataSource)||execution(* org.example.service.impl..*.* (..))")
//public void switchDatasource(JoinPoint point, DatasourceAnnotation dataSource)
@Before("ponitcut()")
public void switchDatasource(JoinPoint point) {
DatasourceAnnotation dataSource;
Object target = point.getTarget();
Method method = ((MethodSignature) point.getSignature()).getMethod();
// 1. 方法是否被數據源註解
if(method.isAnnotationPresent(DatasourceAnnotation.class)){
DatasourceAnnotation annotation = method.getAnnotation(DatasourceAnnotation.class);
if( DynamicDatasourceContextHolder.containDataSourceKey(annotation.value())){
DynamicDatasourceContextHolder.setDatasourceKey(annotation.value());
System.out.println("設置方法的數據源>>> "+ annotation.value());
return;
}
}
// 2. 類上的註解
if (target.getClass().isAnnotationPresent(DatasourceAnnotation.class)){
DatasourceAnnotation annotation = target.getClass().getAnnotation(DatasourceAnnotation.class);
if( DynamicDatasourceContextHolder.containDataSourceKey(annotation.value())){
DynamicDatasourceContextHolder.setDatasourceKey(annotation.value());
System.out.println("設置實現類的數據源>>> "+ annotation.value());
return;
}
}
// 3. 獲取請求頭裏面的 數據源
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String dataSourceName = request.getHeader("dataSourceName");
if(DynamicDatasourceContextHolder.containDataSourceKey(dataSourceName)){
DynamicDatasourceContextHolder.setDatasourceKey(dataSourceName);
System.out.println("設置請求頭的數據源>>> "+ dataSourceName);
return;
}
System.out.println("默認數據源 :"+ DynamicDatasourceContextHolder.getDatasourceKey());
}
// @After("@within(dataSource)||@annotation(dataSource)")
@After("ponitcut()")
public void resetDatasource(JoinPoint point) {
// 將數據源置爲默認數據源
DynamicDatasourceContextHolder.clearDataSourceKey();
}
}
10 控制層測試
/**
* @author Donald
* @create 2020-04-18 23:03
*/
@RestController
@RequestMapping("user")
public class SysUserController {
@Resource(name = "sysUserServiceImpl")
private SysUserService sysUserService;
@Resource(name = "sysUserServiceImpl2")
private SysUserService sysUserService2;
@Resource(name = "sysUserServiceImpl3")
private SysUserService sysUserService3;
@GetMapping(value = "selectOne")
public SysUserDto selectOne(){
return sysUserService.selectOne();
}
@GetMapping("findall")
public List<SysUserDto> findAll(){
return sysUserService.findAll();
}
@GetMapping("queryone")
public SysUserDto queryOne(){
return sysUserService.queryOne();
}
@GetMapping("query")
public SysUserDto queryDoubleDatasource(){
return sysUserService2.selectOne();
}
@GetMapping("query/salve")
public SysUserDto querySingleDatasource(){
return sysUserService2.queryOne();
}
@GetMapping("query/header")
public SysUserDto queryHeadId(){
return sysUserService3.selectOne();
}
}
11 服務實現
接口:
/**
* @author Donald
* @create 2020-04-18 23:06
*/
public interface SysUserService {
List<SysUserDto> findAll();
SysUserDto selectOne();
SysUserDto queryOne();
}
實現1:
/**
* @author Donald
* @create 2020-04-18 23:07
*/
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@DatasourceAnnotation("master")
@Override
public List<SysUserDto> findAll() {
return sysUserMapper.findAll();
}
@DatasourceAnnotation("salve")
@Override
public SysUserDto selectOne() {
return sysUserMapper.selctOne();
}
@DatasourceAnnotation("oracle")
@Override
public SysUserDto queryOne() {
return sysUserMapper.queryOne();
}
}
實現2:
**
* @author Donald
* @create 2020-04-19 16:51
*/
@Service
@DatasourceAnnotation("salve")
public class SysUserServiceImpl2 implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public List<SysUserDto> findAll() {
return null;
}
@Override
@DatasourceAnnotation("oracle")
public SysUserDto selectOne() {
return sysUserMapper.selctOne();
}
@Override
public SysUserDto queryOne() {
return sysUserMapper.queryOne();
}
}
實現3:
/**
* @author Donald
* @create 2020-04-19 18:51
*/
@Service("sysUserServiceImpl3")
public class SysUserServiceImpl3 implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public List<SysUserDto> findAll() {
return sysUserMapper.findAll();
}
@Override
public SysUserDto selectOne() {
return sysUserMapper.selctOne();
}
@Override
public SysUserDto queryOne() {
return sysUserMapper.queryOne();
}
}
12 mapper 層
@Mapper
@Repository
public interface SysUserMapper {
@Select("select * from sys_user")
List<SysUserDto> findAll();
SysUserDto selctOne();
@Select("select * from sys_user c where c.id = 1 ")
SysUserDto queryOne();
}
初始化使用僅供參考, 有不足歡迎大家提出來一起進步
源碼地址:
動態數據源 小測試 碼雲地址