SpringBoot-- 动态数据源
Posted 听歌敲代码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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})
以上是关于SpringBoot-- 动态数据源的主要内容,如果未能解决你的问题,请参考以下文章
SpringBoot启动报错“Consider defining a bean of type ‘xxx.mapper.UserMapper‘ in your configuration.“(代码片段