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");
  }
}

具体请查看官方文档

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