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");
}
}
具體請查看官方文檔