SpringBoot(十一)-- 动态数据源

SpringBoot中使用动态数据源可以实现分布式中的分库技术,比如查询用户 就在用户库中查询,查询订单 就在订单库中查询。

一、配置文件application.properties

# 默认数据源
spring.datasource.url=jdbc:mysql://localhost:3306/consult
spring.datasource.username=myConsult
spring.datasource.password=123456
spring.datasource.driver-class-name=org.gjt.mm.mysql.Driver
# 更多数据源
custom.datasource.names=ds1,ds2
custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1
custom.datasource.ds1.username=root
custom.datasource.ds1.password=123456
custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2
custom.datasource.ds2.username=root
custom.datasource.ds2.password=123456

二、使用aop自定义注解,实现动态切换数据源

1.动态数据源注册器

  1 import java.util.HashMap;
  2 import java.util.Map;
  3 import javax.sql.DataSource;
  4 import org.slf4j.Logger;
  5 import org.slf4j.LoggerFactory;
  6 import org.springframework.beans.MutablePropertyValues;
  7 import org.springframework.beans.PropertyValues;
  8 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
  9 import org.springframework.beans.factory.support.GenericBeanDefinition;
 10 import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
 11 import org.springframework.boot.bind.RelaxedDataBinder;
 12 import org.springframework.boot.bind.RelaxedPropertyResolver;
 13 import org.springframework.context.EnvironmentAware;
 14 import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
 15 import org.springframework.core.convert.ConversionService;
 16 import org.springframework.core.convert.support.DefaultConversionService;
 17 import org.springframework.core.env.Environment;
 18 import org.springframework.core.type.AnnotationMetadata;
 19
 20 public class DynamicDataSourceRegister implements
 21         ImportBeanDefinitionRegistrar, EnvironmentAware {
 22     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
 23
 24     private ConversionService conversionService = new DefaultConversionService();
 25
 26     private PropertyValues dataSourcePropertyValues;
 27
 28     // 如配置文件中未指定数据源类型,使用该默认值
 29     private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
 30
 31     // private static final Object DATASOURCE_TYPE_DEFAULT =
 32     // "com.zaxxer.hikari.HikariDataSource";
 33
 34     // 数据源
 35     private DataSource defaultDataSource;
 36
 37     private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
 38
 39     public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
 40         Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
 41         // 将主数据源添加到更多数据源中
 42         targetDataSources.put("dataSource", defaultDataSource);
 43         DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
 44         // 添加更多数据源
 45         targetDataSources.putAll(customDataSources);
 46         for (String key : customDataSources.keySet()) {
 47             DynamicDataSourceContextHolder.dataSourceIds.add(key);
 48         }
 49
 50         // 创建DynamicDataSource
 51         GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
 52         beanDefinition.setBeanClass(DynamicDataSource.class);
 53         beanDefinition.setSynthetic(true);
 54         MutablePropertyValues mpv = beanDefinition.getPropertyValues();
 55         mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
 56         mpv.addPropertyValue("targetDataSources", targetDataSources);
 57         registry.registerBeanDefinition("dataSource", beanDefinition); // 注册到Spring容器中
 58
 59         logger.info("Dynamic DataSource Registry");
 60     }
 61
 62     /**
 63      * 创建DataSource
 64      * @param type
 65      * @param driverClassName
 66      * @param url
 67      * @param username
 68      * @param password
 69      * @return
 70      */
 71     @SuppressWarnings("unchecked")
 72     public DataSource buildDataSource(Map<String, Object> dsMap) {
 73         try {
 74             Object type = dsMap.get("type");
 75             if (type == null)
 76                 type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
 77
 78             Class<? extends DataSource> dataSourceType;
 79             dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
 80
 81             String driverClassName = dsMap.get("driver-class-name").toString();
 82             String url = dsMap.get("url").toString();
 83             String username = dsMap.get("username").toString();
 84             String password = dsMap.get("password").toString();
 85
 86             DataSourceBuilder factory = DataSourceBuilder.create()
 87                     .driverClassName(driverClassName)
 88                     .url(url)
 89                     .username(username)
 90                     .password(password)
 91                     .type(dataSourceType);
 92             return factory.build();
 93         }
 94         catch (ClassNotFoundException e) {
 95             e.printStackTrace();
 96         }
 97         return null;
 98     }
 99
100     /**
101      * 加载多数据源配置
102      */
103     public void setEnvironment(Environment env) {
104         initDefaultDataSource(env);
105         initCustomDataSources(env);
106     }
107
108     /**
109      * 初始化主数据源
110      */
111     private void initDefaultDataSource(Environment env) {
112         // 读取主数据源
113         RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(
114                 env, "spring.datasource.");
115         Map<String, Object> dsMap = new HashMap<String, Object>();
116         dsMap.put("type", propertyResolver.getProperty("type"));
117         dsMap.put("driver-class-name",propertyResolver.getProperty("driver-class-name"));
118         dsMap.put("url", propertyResolver.getProperty("url"));
119         dsMap.put("username", propertyResolver.getProperty("username"));
120         dsMap.put("password", propertyResolver.getProperty("password"));
121         defaultDataSource = buildDataSource(dsMap);
122         dataBinder(defaultDataSource, env);
123     }
124
125     /**
126      * 为DataSource绑定更多数据
127      * @param dataSource
128      * @param env
129      */
130     private void dataBinder(DataSource dataSource, Environment env) {
131         RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
132         //dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
133         dataBinder.setConversionService(conversionService);
134         dataBinder.setIgnoreNestedProperties(false);//false
135         dataBinder.setIgnoreInvalidFields(false);//false
136         dataBinder.setIgnoreUnknownFields(true);//true
137         if (dataSourcePropertyValues == null) {
138             Map<String, Object> rpr = new RelaxedPropertyResolver(env,
139                     "spring.datasource").getSubProperties(".");
140             Map<String, Object> values = new HashMap<String, Object>(rpr);
141             // 排除已经设置的属性
142             values.remove("type");
143             values.remove("driver-class-name");
144             values.remove("url");
145             values.remove("username");
146             values.remove("password");
147             dataSourcePropertyValues = new MutablePropertyValues(values);
148         }
149         dataBinder.bind(dataSourcePropertyValues);
150     }
151
152     /**
153      * 初始化更多数据源
154      *
155      */
156     private void initCustomDataSources(Environment env) {
157         // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
158         RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(
159                 env, "custom.datasource.");
160         String dsPrefixs = propertyResolver.getProperty("names");
161         for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
162             Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix
163                     + ".");
164             DataSource ds = buildDataSource(dsMap);
165             customDataSources.put(dsPrefix, ds);
166             dataBinder(ds, env);
167         }
168     }
169 }

2.动态数据源适配器

 1 import java.util.ArrayList;
 2 import java.util.List;
 3
 4 public class DynamicDataSourceContextHolder {
 5     private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
 6     public static List<String> dataSourceIds = new ArrayList<String>();
 7
 8     public static void setDataSourceType(String dataSourceType) {
 9         contextHolder.set(dataSourceType);
10     }
11
12     public static String getDataSourceType() {
13         return contextHolder.get();
14     }
15
16     public static void clearDataSourceType() {
17         contextHolder.remove();
18     }
19
20     /**
21      * 判断指定DataSrouce当前是否存在
22      *
23      * @param dataSourceId
24      * @return
25      */
26     public static boolean containsDataSource(String dataSourceId){
27         return dataSourceIds.contains(dataSourceId);
28     }
29 }

3.自定义注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String name();
}

4.动态数据源切面

 1 import org.aspectj.lang.JoinPoint;
 2 import org.aspectj.lang.annotation.After;
 3 import org.aspectj.lang.annotation.Aspect;
 4 import org.aspectj.lang.annotation.Before;
 5 import org.slf4j.Logger;
 6 import org.slf4j.LoggerFactory;
 7 import org.springframework.core.annotation.Order;
 8 import org.springframework.stereotype.Component;
 9
10 @Aspect
11 //保证该AOP在@Transactional之前执行
12 @Order(-1)
13 @Component
14 public class DynamicDataSourceAspect {
15     private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
16
17     /**
18      * @Description 在方法执行之前执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
19      * @param @param point
20      * @param @param ds
21      * @param @throws Throwable 参数
22      * @return void 返回类型
23      * @throws
24      */
25     @Before("@annotation(ds)")
26     public void changeDataSource(JoinPoint point, TargetDataSource ds)
27             throws Throwable {
28         String dsId = ds.name();
29         if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
30             logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
31         }
32         else {
33             logger.debug("Use DataSource : {} > {}", ds.name(),point.getSignature());
34             DynamicDataSourceContextHolder.setDataSourceType(ds.name());
35         }
36     }
37
38     /**
39      * @Description 在方法执行之后执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
40      * @param @param point
41      * @param @param ds 参数
42      * @return void 返回类型
43      * @throws
44      */
45     @After("@annotation(ds)")
46     public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
47         logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
48         DynamicDataSourceContextHolder.clearDataSourceType();
49     }
50 }

5.继承Spring AbstractRoutingDataSource实现路由切换

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* 继承Spring AbstractRoutingDataSource实现路由切换
*/
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

三、怎么使用自定义的注解动态的切换数据源

只需要在service实现类中 对需要切换数据源的方法上 加上 自定义的注解即可,如:@TargetDataSource(name = "ds2")

四、启动类上添加@Import注解

  //注册动态多数据源
  @Import({DynamicDataSourceRegister.class})

时间: 2024-11-08 23:41:51

SpringBoot(十一)-- 动态数据源的相关文章

SpringBoot和Mycat动态数据源项目整合

SpringBoot项目整合动态数据源(读写分离) 1.配置多个数据源,根据业务需求访问不同的数据,指定对应的策略:增加,删除,修改操作访问对应数据,查询访问对应数据,不同数据库做好的数据一致性的处理.由于此方法相对易懂,简单,不做过多介绍. 2. 动态切换数据源,根据配置的文件,业务动态切换访问的数据库:此方案通过Spring的AOP,AspactJ来实现动态织入,通过编程继承实现Spring中的AbstractRoutingDataSource,来实现数据库访问的动态切换,不仅可以方便扩展,

SpringBoot与动态多数据源切换

本文简单的介绍一下基于SpringBoot框架动态多数据源切换的实现,采用主从配置的方式,配置master.slave两个数据库. 一.配置主从数据库 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.cj.jdbc.Driver druid: # 主库数据源 master: url: jdbc:mysql://localhost:3306/practice?us

搞定SpringBoot多数据源(2):动态数据源

目录 1. 引言 2. 动态数据源流程说明 3. 实现动态数据源 3.1 说明及数据源配置 3.1.1 包结构说明 3.1.2 数据库连接信息配置 3.1.3 数据源配置 3.2 动态数据源设置 3.2.1 动态数据源配置 3.2.2 动态选择数据源 3.2.3 动态数据源使用 3.3 使用 AOP 选择数据源 3.3.1 定义数据源注解 3.3.2 定义数据源切面 3.3.3 使用 AOP 进行数据源切换 4. 再思考一下 5. 总结 参考资料 往期文章 一句话概括:使用动态数据源对多个数据库

springboot添加多数据源 以及 动态添加数据源动态切换数据源

<!-- Druid 数据连接池依赖 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> //指定使用Druid做数据源spring.datasource.type=com.alibaba.druid.pool.Dru

43. Spring Boot动态数据源(多数据源自动切换)【从零开始学Spring Boot】

[视频&交流平台] àSpringBoot视频 http://study.163.com/course/introduction.htm?courseId=1004329008&utm_campaign=commission&utm_source=400000000155061&utm_medium=share à SpringCloud视频 http://study.163.com/course/introduction.htm?courseId=1004638001&a

第八章 springboot + mybatis + 多数据源(转载)

本篇博客转发自:http://www.cnblogs.com/java-zhao/p/5413845.html 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)DatabaseType列出所有的数据源的key---key 2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法 3)DynamicDataSource继承AbstractRoutin

第八章 springboot + mybatis + 多数据源

在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1)DatabaseType列出所有的数据源的key---key 2)DatabaseContextHolder是一个线程安全的DatabaseType容器,并提供了向其中设置和获取DatabaseType的方法 3)DynamicDataSource继承AbstractRoutingDataSource并重写其中的方法determineCurrentLookupKey(),在该方法中使用Da

Spring Boot 动态数据源(Spring 注解数据源)

本文实现案例场景:某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库. 为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便. 一配置二使用1. 启动类注册动态数据源2. 配置文件中配置多个数据源3. 在需要的方法上使用注解指定数据源 1.在启动类添加 @Import({DynamicD

Spring主从数据库的配置和动态数据源切换原理

原文:https://www.liaoxuefeng.com/article/00151054582348974482c20f7d8431ead5bc32b30354705000 在大型应用程序中,配置主从数据库并使用读写分离是常见的设计模式.在Spring应用程序中,要实现读写分离,最好不要对现有代码进行改动,而是在底层透明地支持. Spring内置了一个AbstractRoutingDataSource,它可以把多个数据源配置成一个Map,然后,根据不同的key返回不同的数据源.因为Abst