作了這麼多年的Java程序員,好像使用多數據源的狀況很是少,特別是現現在微服務這麼火的狀況下,不一樣的業務訪問一個數據庫是常態,並且Java訪問數據源真沒有PHP等腳本語言來得那麼簡單方便,可是在特殊業務狀況下,還不得不使用多數據源,今天咱們就來說講這方面的話題程序員
咱們的數據庫A爲主庫,其餘數據庫配置在主庫中,從庫B,C,D的數量是不固定的,會根據業務的須要動態的把配置寫入到主庫中並動態在建立新的數據庫,也就是說在項目中咱們只須要配置主庫的數據源,其餘從庫都須要從主庫中讀出配置並動態建立數據源,動態的注入到Spring容器中,在使用的時候動態的切換數據源以實現相應的功能邏輯spring
Springboot:2.0.4
Mybatis-plus:3.0.7.1
JDK:1.8數據庫
在啓動類添加@Import({DynamicDataSourceRegister.class})註解用於代替默認的數據源配置springboot
代碼結構以下:app
@Component public class ApplicationContextUtil implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { ApplicationContextUtil.applicationContext = applicationContext; } /** * 取得存儲在靜態變量中的ApplicationContext. */ public static ApplicationContext getApplicationContext() { checkApplicationContext(); return applicationContext; } /** * 從靜態變量ApplicationContext中取得Bean, 自動轉型爲所賦值對象的類型. */ public static <T> T getBean(String name) { checkApplicationContext(); if (applicationContext.containsBean(name)) { return (T) applicationContext.getBean(name); } return null; } /** * 從靜態變量ApplicationContext中取得Bean, 自動轉型爲所賦值對象的類型. */ public static <T> T getBean(Class<T> clazz) { checkApplicationContext(); return (T) applicationContext.getBeansOfType(clazz); } private static void checkApplicationContext() { if (applicationContext == null) throw new IllegalStateException("applicaitonContext未注入,請在applicationContext.xml中定義SpringContextUtil"); } public synchronized static void registerSingletonBean(String beanName,Class clzz,Map<String,Object> original) { checkApplicationContext(); DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory(); if(beanFactory.containsBean(beanName)){ removeBean(beanName); } GenericBeanDefinition definition = new GenericBeanDefinition(); //類class definition.setBeanClass(clzz); //屬性賦值 definition.setPropertyValues(new MutablePropertyValues(original)); //註冊到spring上下文 beanFactory.registerBeanDefinition(beanName, definition); } public synchronized static void registerSingletonBean(String beanName, Object obj, Map<String,Object> original) { checkApplicationContext(); DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) ApplicationContextUtil.getApplicationContext().getAutowireCapableBeanFactory(); if(beanFactory.containsBean(beanName)){ removeBean(beanName); } GenericBeanDefinition definition = new GenericBeanDefinition(); //類class definition.setBeanClass(obj.getClass()); //屬性賦值 definition.setPropertyValues(new MutablePropertyValues(original)); //註冊到spring上下文 beanFactory.registerBeanDefinition(beanName, definition); } public synchronized static void registerSingletonBean(String beanName,Object obj) { registerSingletonBean(beanName,obj,BeanUtils.transBean2Map(obj)); } /** * 刪除spring中管理的bean * @param beanName */ public static void removeBean(String beanName){ ApplicationContext ctx = ApplicationContextUtil.getApplicationContext(); DefaultListableBeanFactory acf = (DefaultListableBeanFactory) ctx.getAutowireCapableBeanFactory(); if(acf.containsBean(beanName)) { acf.removeBeanDefinition(beanName); } } }
public class BeanUtils { public static Map<String, Object> transBean2Map(Object obj) { if(obj == null){ return null; } Map<String, Object> map = new HashMap<>(); try { BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass()); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); for (PropertyDescriptor property : propertyDescriptors) { String key = property.getName(); // 過濾class屬性 if (!key.equals("class")) { // 獲得property對應的getter方法 Method getter = property.getReadMethod(); Object value = getter.invoke(obj); map.put(key, value); } } } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } return map; } }
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } public void updateTargetDataSource(Map<String,DataSource> customDataSources){ Map<Object,Object> customDS=new HashMap<Object, Object>(); customDS.putAll(customDataSources); setTargetDataSources(customDS); afterPropertiesSet(); } }
public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static List<String> dataSourceIds = new ArrayList<>(); public static String getDataSourceType() { return contextHolder.get(); } public static void setDataSourceType(String dataSourceType) { if(!containsDataSource(dataSourceType)){ DynamicDataSourceRegister.addSlaveDataSource(dataSourceType); } contextHolder.set(dataSourceType); } public static void clearDataSourceType() { contextHolder.remove(); } /** * 判斷指定DataSrouce當前是否存在 */ public static boolean containsDataSource(String dataSourceId) { return dataSourceIds.contains(dataSourceId); } }
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); //默認數據源 public static DataSource defaultDataSource; //用戶自定義數據源 public static Map<String, DataSource> slaveDataSources = new HashMap<>(); public static BeanDefinitionRegistry beanDefinitionRegistry=null; public static String driverName; public static String userName; public static String password; public static String type; public static String url; @Override public void setEnvironment(Environment environment) { initDefaultDataSource(environment); } private void initDefaultDataSource(Environment env) { // 讀取主數據源 driverName=env.getProperty("spring.datasource.driver-class-name"); userName=env.getProperty("spring.datasource.username"); password=env.getProperty("spring.datasource.password"); type=env.getProperty("spring.datasource.type"); url=env.getProperty("spring.datasource.url"); Constant.defaultDbName="a"; Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver",driverName); dsMap.put("url",url); dsMap.put("username",userName); dsMap.put("password",password); dsMap.put("type",type); defaultDataSource = buildDataSource(dsMap); } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); //添加默認數據源 targetDataSources.put("dataSource", this.defaultDataSource); this.beanDefinitionRegistry=beanDefinitionRegistry; beanDefinitionRegistry(defaultDataSource,targetDataSources); logger.info("Dynamic DataSource Registry"); } public static void addSlaveDataSource(String dataSourceType){ BeanDefinition beanDefinition=beanDefinitionRegistry.getBeanDefinition("dataSource"); PropertyValue propertyValue=beanDefinition.getPropertyValues().getPropertyValue("targetDataSources"); Map<String,DataSource> oldTargetDataSource=(Map<String,DataSource>) propertyValue.getValue(); String newUrl=firstStr+dataSourceType+secondStr; Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver",driverName); dsMap.put("url",newUrl); dsMap.put("username",userName); dsMap.put("password",password); dsMap.put("type",type); DataSource ds = buildDataSource(dsMap); oldTargetDataSource.put(dataSourceType,ds); DynamicDataSource dynamicDataSource =ApplicationContextUtil.getBean("dataSource"); dynamicDataSource.updateTargetDataSource(oldTargetDataSource); DynamicDataSourceContextHolder.dataSourceIds.add(dataSourceType); } public void beanDefinitionRegistry(DataSource defaultDataSource,Map<Object,Object> targetDataSources){ //建立DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); if(targetDataSources.size()>0){ mpv.addPropertyValue("targetDataSources", targetDataSources); } //註冊 - BeanDefinitionRegistry beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition); } public static DataSource buildDataSource(Map<String, Object> dataSourceMap) { try { Object type = dataSourceMap.get("type"); Class<? extends DataSource> dataSourceType; 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; } }
DynamicDataSourceContextHolder.setDataSourceType("B"); Integer lProductUv=dataVisitCollectionMapper.getProductUv(dDate); DynamicDataSourceContextHolder.setDataSourceType(Constant.defaultDbName);
在setDataSourceType的時候判斷是否存在此數據源,若是存在就直接切換,不存在就動態建立並加入到Spring容器中,從而實現動態建立數據源的目的ide
本文與衆多其餘springboot多數據源的文章區別之處主要在於,子庫的配置是在使用的時候根據狀況動態根據在主庫中的配置,連接到各從庫,從而實現更加靈活的數據源訪問體驗,若是你有更好的方法和建議能夠私信,我是李正凡,謝謝你們微服務