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(); } }
效果:
如图所示,则动态数据源配置成功