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
二、pox.xml
<!-- aspectj --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjtools</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
三、使用aop自定义注解,实现动态切换数据源
1.动态数据源注册器

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

package com.xsjt.dynamicDataSource; import java.util.ArrayList; import java.util.List; /** * ClassName:DynamicDataSourceContextHolder * Date: 2017年11月13日 下午7:41:49 * @author Joe * @version * @since JDK 1.8 */ public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceIds = new ArrayList<String>(); public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } public static String getDataSourceType() { return contextHolder.get(); } public static void clearDataSourceType() { contextHolder.remove(); } /** * 判断指定DataSrouce当前是否存在 * * @param dataSourceId * @return */ public static boolean containsDataSource(String dataSourceId){ return dataSourceIds.contains(dataSourceId); } }
3.自定义注解

/** * ClassName:TargetDataSource * Date: 2017年11月13日 下午7:42:15 * @author Joe * @version * @since JDK 1.8 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name(); }
4.动态数据源切面

package com.xsjt.dynamicDataSource; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * ClassName:DynamicDataSourceAspect * Date: 2017年11月13日 下午7:44:09 * @author Joe * @version * @since JDK 1.8 */ @Aspect //保证该AOP在@Transactional之前执行 @Order(-1) @Component public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); /** * @Description 在方法执行之前执行 @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 * @param @param point * @param @param ds * @param @throws Throwable 参数 * @return void 返回类型 * @throws */ @Before("@annotation(ds)") public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable { String dsId = ds.name(); if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) { logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature()); } else { logger.debug("Use DataSource : {} > {}", ds.name(),point.getSignature()); DynamicDataSourceContextHolder.setDataSourceType(ds.name()); } } /** * @Description 在方法执行之后执行 @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 * @param @param point * @param @param ds 参数 * @return void 返回类型 * @throws */ @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, TargetDataSource ds) { logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); } }
5.继承Spring AbstractRoutingDataSource实现路由切换

package com.xsjt.dynamicDataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * ClassName:DynamicDataSource * 继承Spring AbstractRoutingDataSource实现路由切换 * Date: 2017年11月13日 下午7:49:49 * @author Joe * @version * @since JDK 1.8 */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
四、怎么使用自定义的注解动态的切换数据源
只需要在service实现类中 对需要切换数据源的方法上 加上 自定义的注解即可,如:@TargetDataSource(name = "ds2")
五、启动类上添加@Import注解
//注册动态多数据源
@Import({DynamicDataSourceRegister.class})
六.源码下载
https://gitee.com/xbq168/spring-boot-learn