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();
}
初始化使用仅供参考, 有不足欢迎大家提出来一起进步
源码地址:
动态数据源 小测试 码云地址