1、原理圖
2、創建枚舉類
/** * 存數據源key值 */
public enum DataSourceKey { master,salve,migration }
3、創建自定義注解類
/** * 自定義注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DBSource
{ String value() default "master"; }
4、切換數據源類
/** * @author yehui * 根據線程動態切換數據源 */ @Configuration public class DynamicDataSourceContextHolder { private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * 設置默認數據源 */
public static String DEFAULT_DS = "master"; /** *用於輪訓計數 */
private static int counter = 0; /* * 當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本, * 所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。 */
private static final ThreadLocal<String> contextHolder = ThreadLocal.withInitial(() -> DataSourceKey.master.name()); /** *用於在切換數據源時保證不會被其他線程修改 */
public static Lock lock = new ReentrantLock(); /** * 設置數據源 */
public static void setDB(String dbType){ log.info("切換到{" + dbType + "}數據源"); contextHolder.set(dbType); } /** * 得到數據源 * */
public static String getDB(){ return contextHolder.get(); } /** * 使用主數據源 */
public static void useMasterDataSource() { contextHolder.set(DataSourceKey.master.name()); } /** * 移除數據源 */
public static void removeDB(){ contextHolder.remove(); } /** * The constant slaveDataSourceKeys. */
public static List<Object> slaveDataSourceKeys = new ArrayList<>(); /** * 當使用只讀數據源時通過輪循方式選擇要使用的數據源 */
public static String getSlaveDB(){ lock.lock(); try { int datasourceKeyIndex = counter % slaveDataSourceKeys.size(); counter++; return String.valueOf(slaveDataSourceKeys.get(datasourceKeyIndex)); } catch (Exception e) { log.error(e.getMessage(), e); e.printStackTrace(); return "master"; } finally { lock.unlock(); } } }
5、獲取數據源類
/** * @author yehui * 多數據源的選擇 */
public class DynamicDataSource extends AbstractRoutingDataSource { private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class); @Override protected Object determineCurrentLookupKey() { log.info("Current DataSource is " + DynamicDataSourceContextHolder.getDB()); return DynamicDataSourceContextHolder.getDB(); } }
6、Aop類
/** * @author yehui * 自定義注解 + AOP的方式實現數據源動態切換。 */ @Aspect @Component public class DynamicDataSourceAspect { private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class); @Before("@annotation(DBSource)") public void beforeSwitchDB(JoinPoint joinPoint,DBSource DBSource){ //獲取目標類的方法
Class<?> aClass = joinPoint.getTarget().getClass(); //獲得訪問的方法名
String methodName = joinPoint.getSignature().getName(); //得到方法的參數類型
Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); String dataSource = DynamicDataSourceContextHolder.DEFAULT_DS; try { Method method = aClass.getMethod(methodName, parameterTypes); if(method.isAnnotationPresent(DBSource.class)){ DBSource db = method.getAnnotation(DBSource.class); //指定數據源
dataSource = db.value(); }else{ //輪訓設置數據源
dataSource = DynamicDataSourceContextHolder.getSlaveDB(); } } catch (NoSuchMethodException e) { log.error(e.getMessage(), e); } //設置數據源
DynamicDataSourceContextHolder.setDB(dataSource); } @After("@annotation(DBSource)") public void afterSwitchDB(DBSource DBSource){ DynamicDataSourceContextHolder.removeDB(); } }
6、application.properties文件
spring.druid.datasource.slave.password=root spring.druid.datasource.slave.username=root spring.druid.datasource.slave.jdbc-url=jdbc:mysql://localhost:3306/study
spring.druid.datasource.slave.driver-class-name=com.mysql.jdbc.Driver spring.druid.datasource.master.password=root spring.druid.datasource.master.username=root spring.druid.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/study01
spring.druid.datasource.master.driver-class-name=com.mysql.jdbc.Driver spring.druid.datasource.migration.password=root spring.druid.datasource.migration.username=root #2.0版本多數據源必須是使用jdbc-url 不能使用url,否則報錯 jdbcUrl is required with driverClassName spring.druid.datasource.migration.jdbc-url=jdbc:mysql://localhost:3306/study02
spring.druid.datasource.migration.driver-class-name=com.mysql.jdbc.Driver
7、數據源配置類
/** * @author yehui * 數據源配置類 */ @Configuration public class DataSourceConfig { /** * 主數據 * * @return data source */ @Bean("master") @Primary @ConfigurationProperties(prefix = "spring.druid.datasource.master") public DataSource master() { return DataSourceBuilder.create().build(); } /** * 從數據庫 * * @return data source */ @Bean("slave") @ConfigurationProperties(prefix ="spring.druid.datasource.slave") public DataSource slave() { return DataSourceBuilder.create().build(); } /** * 從數據庫 * * @return data source */ @Bean("migration") @ConfigurationProperties(prefix ="spring.druid.datasource.migration") public DataSource migration() { return DataSourceBuilder.create().build(); } /** * 配置動態數據源 * * @return
*/ @Bean("dynamicDataSource") public DataSource dynamicDataSource() { DynamicDataSource dynamicRoutingDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(4); dataSourceMap.put(DataSourceKey.master.name(), master()); dataSourceMap.put(DataSourceKey.salve.name(), slave()); dataSourceMap.put(DataSourceKey.master.name(), slave()); //設置默認的數據源
dynamicRoutingDataSource.setDefaultTargetDataSource(master()); // 多個slave數據源在此添加,自定義key,用於輪詢
dataSourceMap.put(DataSourceKey.salve.name() + "1", slave()); //設置目標數據源
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap); //將數據源的key放在集合中判斷是否正常
DynamicDataSourceContextHolder.slaveDataSourceKeys.addAll(dataSourceMap.keySet()); //實現負載均衡算法 將 Slave 數據源的 key 放在集合中,用於輪循
DynamicDataSourceContextHolder.slaveDataSourceKeys.addAll(dataSourceMap.keySet()); DynamicDataSourceContextHolder.slaveDataSourceKeys.remove(DataSourceKey.migration.name()); return dynamicRoutingDataSource; } /** * 設置工廠類 */ @Bean public SqlSessionFactoryBean sqlSessionFactoryBean() { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); //此處設置為了解決找不到mapper文件的問題
try { sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*Mapper.xml")); } catch (IOException e) { e.printStackTrace(); } return sqlSessionFactoryBean; } /** * 事物管理器 */ @Bean("transactionManager") public DataSourceTransactionManager transactionManager() { return new DataSourceTransactionManager(dynamicDataSource()); } }
8、啟動類
/** * springboot入口類,此類需要在所有用到的package上層 exclude = * {DataSourceAutoConfiguration.class} * 禁用springboot默認加載的application.properties單數據源配置 */ @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class StartApp { public static void main(String[] args) { SpringApplication.run(StartApp.class); } }
9、測試
mapper接口
@Mapper public interface UserDataSourceMapper { public List<TbUser> findUser(); }
mapper文件
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yehui.mapper.UserDataSourceMapper">
<select id="findUser" resultType="com.yehui.entity.TbUser"> select * from tb_user </select>
</mapper>
service類
@Service public class UserServiceImpl implements UserService { @Autowired private UserDataSourceMapper userMapper; @Override @DBSource("slave")//使用數據源打上注解即可 public List<TbUser> findUser() { return userMapper.findUser(); } }
controller類
@RestController public class UserController { @Autowired private UserService userService; @RequestMapping("/findUser") public List<TbUser> findUser(){ return userService.findUser(); } }
效果:
如圖所示,則動態數據源配置成功