前置條件,你已經配置好spring boot+mybatis,能夠參考以前的博客java
實現邏輯經過註解+aop切面編程來動態更新datasourcemysql
第一步,配置多個DataSourcegit
server: port: 8080 freezing: datasource: name: freezing url: jdbc:mysql://123.57.XXX.XXX:3636/freezingdb?characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: freezing password: Zgjkj2015 # 使用druid數據源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver ucenter: datasource: name: ucenter url: jdbc:mysql://123.57..XXX.XXX:3636/ucenter?characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: ucenter password: Zgjkj2015 # 使用druid數據源 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver ## 該配置節點爲獨立的節點,有不少同窗容易將這個配置放在spring的節點下,致使配置沒法被識別 mybatis: mapper-locations: classpath:mapping/*.xml #注意:必定要對應mapper映射xml文件的所在路徑 type-aliases-package: com.hmkx.demo.model # 注意:對應實體類的路徑
第二步,定義數據源上下文管理類spring
public class DynamicDataSourceContextHolder { private Logger logger = Logger.getLogger(DynamicDataSourceContextHolder.class); //存放當前線程使用的數據源類型信息 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); //存放數據源id 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(); } //判斷當前數據源是否存在 public static boolean isContainsDataSource(String dataSourceId) { return dataSourceIds.contains(dataSourceId); } }
第三步,經過AbstractRoutingDataSource 繼承修改這個類來動態修改dataTypesql
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
第四步,DataSource註冊數據庫
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private Logger logger = Logger.getLogger(DynamicDataSourceRegister.class); //指定數據源類型,我這裏用的druid(springboot2.0默認數據源是hikari如何想使用其餘數據源能夠本身配置) // private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource"; private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource"; //默認數據源 private DataSource defaultDataSource; //用戶自定義數據源 private Map<String, DataSource> slaveDataSources = new HashMap<>(); @Override public void setEnvironment(Environment environment) { initDefaultDataSource(environment); initslaveDataSources(environment); } private void initDefaultDataSource(Environment env) { // 讀取主數據源 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver", env.getProperty("freezing.datasource.driver-class-name")); dsMap.put("url", env.getProperty("freezing.datasource.url")); dsMap.put("username", env.getProperty("freezing.datasource.username")); dsMap.put("password", env.getProperty("freezing.datasource.password")); logger.info(dsMap); defaultDataSource = buildDataSource(dsMap); } private void initslaveDataSources(Environment env) { // 讀取配置文件獲取更多數據源 /* String dsPrefixs = env.getProperty("slave.datasource.names"); for (String dsPrefix : dsPrefixs.split(",")) { // 多個數據源 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver", env.getProperty("slave.datasource." + dsPrefix + ".driver")); dsMap.put("url", env.getProperty("slave.datasource." + dsPrefix + ".url")); dsMap.put("username", env.getProperty("slave.datasource." + dsPrefix + ".username")); dsMap.put("password", env.getProperty("slave.datasource." + dsPrefix + ".password")); DataSource ds = buildDataSource(dsMap); slaveDataSources.put(dsPrefix, ds); }*/ Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver", env.getProperty("ucenter.datasource.driver-class-name")); dsMap.put("url", env.getProperty("ucenter.datasource.url")); dsMap.put("username", env.getProperty("ucenter.datasource.username")); dsMap.put("password", env.getProperty("ucenter.datasource.password")); logger.info("ssssssssssssssssss"+dsMap); System.out.println("ssssssssssssssssss"+dsMap); DataSource ds = buildDataSource(dsMap); slaveDataSources.put("ucenter", ds); } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); //添加默認數據源 targetDataSources.put("dataSource", this.defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); //添加其餘數據源 targetDataSources.putAll(slaveDataSources); for (String key : slaveDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIds.add(key); } //建立DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); //註冊 - BeanDefinitionRegistry beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry"); } public DataSource buildDataSource(Map<String, Object> dataSourceMap) { try { Object type = dataSourceMap.get("type"); if (type == null) { type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource } 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; } }
到這裏完成了,數據庫動態切換的前置工做,接下來就是何時去切換了編程
第五步,自定義標籤springboot
import java.lang.annotation.*; /** * @Author hht * @Date 2018-12-11 * @Description 做用於類、接口或者方法上 */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String name(); }
第六步,監控標籤的aopmybatis
/** * @Author hht * @Date 2018-12-11 * @Description 動態數據源通知 */ @Aspect @Order(-1)//保證在@Transactional以前執行 @Component public class DynamicDattaSourceAspect { private Logger logger = Logger.getLogger(DynamicDattaSourceAspect.class); //改變數據源 @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { String dbid = targetDataSource.name(); if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) { //joinPoint.getSignature() :獲取鏈接點的方法簽名對象 logger.error("數據源 " + dbid + " 不存在使用默認的數據源 -> " + joinPoint.getSignature()); } else { logger.debug("使用數據源:" + dbid); DynamicDataSourceContextHolder.setDataSourceType(dbid); } } @After("@annotation(targetDataSource)") public void clearDataSource(JoinPoint joinPoint, TargetDataSource targetDataSource) { logger.debug("清除數據源 " + targetDataSource.name() + " !"); DynamicDataSourceContextHolder.clearDataSourceType(); } }
這時候基本完工,最後一步就是修改啓動配置類app
package com.hmkx; import com.hmkx.config.DynamicDataSourceRegister; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Import; @SpringBootApplication @MapperScan("com.hmkx.mapper") @Import(DynamicDataSourceRegister.class) public class SpringMybatisDemoApplication { public static void main(String[] args) { SpringApplication.run(SpringMybatisDemoApplication.class, args); } }
完成了,下面是如何使用
@Service public class ArticleServiceImpl implements ArticleService { Logger log = LoggerFactory.getLogger(ArticleServiceImpl.class); @Resource private ArticlesMapper articlesMapper; @Override public Articles getArticleById(int id) { //使用默認數據庫 Articles articles = articlesMapper.selectByPrimaryKey(501112); log.info(articles.getTitle()); return articles; } @Override @TargetDataSource(name="ucenter") public int updateByPrimaryKey(int id) { //使用ucenter數據庫 ArticlesWithBLOBs art = new ArticlesWithBLOBs(); art.setId(501112); art.setTitle("測試"); int res = articlesMapper.updateByPrimaryKeySelective(art); return res; } }
記錄完了,有錯誤或者更好的辦法歡迎指代碼下載地址 https://gitee.com/huhaitao/SpringBootMybatisForManyDatabase