springboot系列之動態數據源

springboot系列之動態數據源

在經過多數據源的配置後,本章介紹動態數據源的配置,這裏採用註解和AOP的方法實現多數據源自動切換。在使用過程中,只需要添加註解就可以使用,簡單方便。

application.properties

#mapper.xml文件
mybatis.mapper-locations=classpath:mapper/*.xml
#實體類包
mybatis.type-aliases-package=com.husy.springboot.dynamicsource.entity
#數據源配置(默認)
spring.datasource.jdbc-url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 其他數據源
custom.datasource.names=ds1,ds2
# ds1
custom.datasource.ds1.jdbc-url=jdbc:mysql://localhost:3306/test-db1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
custom.datasource.ds1.username=root
custom.datasource.ds1.password=123456
custom.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver
# ds2
custom.datasource.ds2.jdbc-url=jdbc:mysql://localhost:3306/test-db2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
custom.datasource.ds2.username=root
custom.datasource.ds2.password=123456
custom.datasource.ds2.driver-class-name=com.mysql.cj.jdbc.Driver

怎麼實現切換?,不要急

Spring提供了AbstractRoutingDataSource來實現這樣的功能,AbstractRoutingDataSource可以根據key值動態切換到具體的數據源。有興趣可以查看一下AbstractRoutingDataSource源碼,這裏不多描述

配置數據源如下:

/**
 * @description: 動態數據源路由配置
 * AbstractRoutingDataSource(每執行一次數據庫,動態獲取DataSource)
 * @author: hsy
 * @date; 2019/9/24
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    //determineCurrentLookupKey() 用於獲取數據源dataSource的key值
	@Override
	protected Object determineCurrentLookupKey() {
		return DynamicDataSourceContextHolder.getDataSourceType();
	}
}
package com.husy.springboot.dynamicsource.config;

import java.util.ArrayList;
import java.util.List;

/**
 * @description: 動態數據源上下文管理
 * @author: hsy
 * @date; 2019/9/24
 */
public class DynamicDataSourceContextHolder {
	//存放當前線程使用的數據源類型信息
	private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
	//存放數據源id
	public static List<String> dataSourceIds = new ArrayList<>();

	//設置數據源
	public static void setDataSourceType(String dataSourceType) {
		contextHolder.set(dataSourceType);
	}

	//獲取數據源
	public static String getDataSourceType() {
		return contextHolder.get();
	}

	//清除數據源
	public static void clearDataSourceType() {
		contextHolder.remove();
	}

	//判斷當前數據源是否存在
	public static boolean containsDataSource(String dataSourceId) {
		return dataSourceIds.contains(dataSourceId);
	}
}

核心代碼

package com.husy.springboot.dynamicsource.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: 動態數據源註冊
 * 實現 ImportBeanDefinitionRegistrar 實現數據源註冊
 * 實現 EnvironmentAware 用於讀取application.yml配置
 * 啓動動態數據源請在啓動類中 添加 @Import(DynamicDataSourceRegister.class)
 * @author: hsy
 * @date; 2019/9/24
 */
@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
	// 默認數據源
	private DataSource defaultDataSource;
	// 存儲我們需要註冊的數據源
	private static Map<String, DataSource> customDataSources = new HashMap<>();


	// 配置文件中未指定數據源類型,使用該默認值
	// 指定默認數據源(springboot2.0默認數據源是hikari如何想使用其他數據源可以自己配置)
//	private static final String DATASOURCE_TYPE_DEFAULT  = "com.zaxxer.hikari.HikariDataSource";
	private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";

	private static String DB_PREFIX="custom.datasource";

	/**
	 * 加載多數據源配置
	 *
	 * @param environment
	 */
	@Override
	public void setEnvironment(Environment environment) {
		// 初始化默認數據源
		initDefaultDataSource(environment);
		// 初始化自定義的數據源
		initCustomDataSources(environment);
	}

	/**
	 * 初始化默認數數據源
	 * @param env
	 */
	private void initDefaultDataSource(Environment env) {
		// 讀取主數據源
		Map<String, Object> dsMap = new HashMap<>();
		dsMap.put("type", env.getProperty ("spring.datasource.type"));
		dsMap.put("driver", env.getProperty("spring.datasource.driver-class-name"));
		dsMap.put("url", env.getProperty("spring.datasource.jdbc-url"));
		dsMap.put("username", env.getProperty("spring.datasource.username"));
		dsMap.put("password", env.getProperty("spring.datasource.password"));
		defaultDataSource = buildDataSource(dsMap);

	}

	/**
	 * 初始化更多數據源
	 * @param env
	 */
	private void initCustomDataSources(Environment env) {
		// 讀取配置文件獲取更多數據源,也可以通過defaultDataSource讀取數據庫獲取更多數據源
		String dsPrefixs = env.getProperty(DB_PREFIX + ".names" );
		for (String dsName : dsPrefixs.split(",")) {// 多個數據源
			Map<String, Object> dsMap = new HashMap<>();

			dsMap.put("type", env.getProperty(DB_PREFIX + "." +  dsName + ".type"));
			dsMap.put("driver", env.getProperty(DB_PREFIX + "."  +  dsName + ".driver-class-name"));
			dsMap.put("url", env.getProperty(DB_PREFIX + "."  +  dsName + ".jdbc-url"));
			dsMap.put("username", env.getProperty(DB_PREFIX + "."  +  dsName + ".username"));
			dsMap.put("password", env.getProperty(DB_PREFIX + "."  +  dsName + ".password"));

			DataSource ds = buildDataSource(dsMap);
			customDataSources.put(dsName, ds);
		}
	}

	/**
	 * 將動態數據源註冊到spring中
	 *
	 * @param importingClassMetadata
	 * @param registry
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		Map<String, Object> targetDataSources = new HashMap<>();
		// 添加默認數據源
		targetDataSources.put("dataSource", this.defaultDataSource);
		DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
		// 添加更多數據源
		targetDataSources.putAll(customDataSources);
		DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());

		// bean定義類 創建DynamicDataSource
		GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
		// 設置bean的類型,此處DynamicDataSource是繼承AbstractRoutingDataSource的實現類
		beanDefinition.setBeanClass(DynamicDataSource.class);

		/*
		 * 設置屬性賦值
		 * */
		// 需要注入的參數
		MutablePropertyValues mpv = beanDefinition.getPropertyValues();
		// 添加默認數據源,避免key不存在的情況沒有數據源可用
		mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
		// 添加其他數據源
		mpv.addPropertyValue("targetDataSources", targetDataSources);

		//註冊BeanDefinitionRegistry,--> 將該bean註冊爲datasource,不使用springboot自動生成的datasource
		registry.registerBeanDefinition("dataSource", beanDefinition);
		log.info("Dynamic DataSource Registry");
	}


	public DataSource buildDataSource(Map<String, Object> dataSourceMap) {
		try {
			Object type = dataSourceMap.get("type");
			if (type == null) {
				type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource
			}
			Class<? extends DataSource> dataSourceType  = (Class<? extends DataSource>) Class.forName((String) type);
			String driverClassName = dataSourceMap.get("driver").toString();
			String url = dataSourceMap.get("url").toString();
			String username = dataSourceMap.get("username").toString();
			String password = dataSourceMap.get("password").toString();
			// 自定義DataSource配置
			DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
					.username(username).password(password).type(dataSourceType);
			return factory.build();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}
}

AOP切換

/**
 * 切換數據註解 可以用於類或者方法級別 方法級別優先級 > 類級別
 */
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
	String name() ;
}
package com.husy.springboot.dynamicsource.aspect;

import com.husy.springboot.dynamicsource.annotation.TargetDataSource;
import com.husy.springboot.dynamicsource.config.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @description: 數據源切換切面
 * @author: hsy
 * @date; 2019/9/26
 */
@Slf4j
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {
	//改變數據源
	@Before("@annotation(targetDataSource)")
	public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
		String dbid = targetDataSource.name();

		if (!DynamicDataSourceContextHolder.containsDataSource(dbid)) {
			//joinPoint.getSignature() :獲取連接點的方法簽名對象
			log.info("數據源 " + dbid + " 不存在,使用默認的數據源 -> " + joinPoint.getSignature());
		} else {
			log.info("使用數據源:" + dbid);
			DynamicDataSourceContextHolder.setDataSourceType(dbid);
		}
	}

	@After("@annotation(targetDataSource)")
	public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) {
		log.info("清除數據源 " + targetDataSource.name() + " !");
		DynamicDataSourceContextHolder.clearDataSourceType();
	}
}

啓動類配置

@SpringBootApplication
@Import({DynamicDataSourceRegister.class}) // 註冊動態多數據源
@MapperScan("com.husy.springboot.dynamicsource.mapper")//將項目中對應的mapper類的路徑加進來就可以了
public class SpringbootDynamicSourceApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringbootDynamicSourceApplication.class, args);
	}

}

使用方式

/**
 * <p>
 *  服務類
 * </p>
 *
 * @author husy
 * @since 2019-09-22
 */
public interface IUsersService{
	List<Users> getAll();
	Users getOne(Long id);
	void insert(Users user);
	void update(Users user);
	void delete(Long id);
}
/**
 * <p>
 *  服務實現類
 * </p>
 *
 * @author husy
 * @since 2019-09-22
 */
@Service
public class UsersServiceImpl implements IUsersService {
	@Resource
	UsersMapper usersMapper;

	@Override
	@TargetDataSource(name="ds1")
	public List<Users> getAll() {
		return usersMapper.selectList(new QueryWrapper<>());
	}

	@Override
	@TargetDataSource(name="ds2")
	public Users getOne(Long id) {
		return usersMapper.selectById(id);
	}

	@Override
	@TargetDataSource(name="ds1")
	public void insert(Users user) {
		usersMapper.insert(user);
	}

	@Override
	@TargetDataSource(name="ds2")
	public void update(Users user) {
		usersMapper.update(user,null);
	}

	@Override
	@TargetDataSource(name="ds2")
	public void delete(Long id) {
		usersMapper.deleteById(id);
	}
}
/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author husy
 * @since 2019-09-22
 */
@RestController
@RequestMapping("users")
public class UsersController {
	@Autowired
	IUsersService usersService;

	@RequestMapping("/getUsers")
	public List<Users> getUsers() {
		List<Users> users=usersService.getAll();
		return users;
	}

	@RequestMapping("/getUser/{id}")
	public Users getUser(@PathVariable  Long id) {
		Users user=usersService.getOne(id);
		return user;
	}

	@RequestMapping("/add")
	public void save(@RequestBody Users user) {
		usersService.insert(user);
	}

	@RequestMapping(value="update")
	public void update(@RequestBody Users user) {
		usersService.update(user);
	}

	@RequestMapping(value="/delete/{id}")
	public void delete(@PathVariable("id") Long id) {
		usersService.delete(id);
	}
}

測試結果

在這裏插入圖片描述

在這裏插入圖片描述
源代碼地址

SQL腳本:

CREATE TABLE `users` (
	`user_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
	`user_name` VARCHAR(32) NOT NULL COMMENT '用戶名' COLLATE 'utf8_general_ci',
	`password` VARCHAR(32) NOT NULL COMMENT '密碼' COLLATE 'utf8_general_ci',
	`user_sex` TINYINT(1) NOT NULL DEFAULT '0' COMMENT '0:男,1:女',
	`nick_name` VARCHAR(32) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
	PRIMARY KEY (`user_id`)
)
COLLATE='utf8mb4_unicode_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1

這裏介紹一個更簡單的動態數據源配置

使用mybatis-plus 動態數據源,配置方式及其簡單!!!!!!

1、引入dynamic-datasource-spring-boot-starter

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>${version}</version>
</dependency>

2、配置數據源

spring:
  datasource:
    dynamic:
      primary: master #設置默認的數據源或者數據源組,默認值即爲master
      datasource:
        master:
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
        slave_1:
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
        slave_2:
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://xx.xx.xx.xx:3308/dynamic
       #......省略
       #以上會配置一個默認庫master,一個組slave下有兩個子庫slave_1,slave_2

配置好後直接就可以使用了,省去了自己編寫切換的代碼
如下:

@Service
@DS("slave")
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  public List<Map<String, Object>> selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("slave_1")
  public List<Map<String, Object>> selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}

具體請查看官方文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章