springboot JPA 多数据源配置方式(二)动态方式

2019-03-27  本文已影响0人  TheUnforgiven

1.多数据源动态方式配置原理利用springAOP,每次请求操作数据库之前,将当前要访问的数据库对应的bean注入到entityManager中。
2.利用spring boot提供的抽象类AbstractRoutingDataSource

1.实现AOP

注解标记方法或者类,表明方法应该访问哪个数据库,
ActionLogAspect 类标记切面

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SlaveDataSource {
    String value() default "";
}
@Aspect
@Order(-1)//保证在@Transactional之前执行
@Component
@Slf4j
public class DynamicDataSourceAspect {
    //改变数据源
    @Before("@annotation(slaveDataSource)")
    public void changeDataSource(JoinPoint joinPoint, SlaveDataSource slaveDataSource) {
        String key = slaveDataSource.value();
        if (!DynamicDataSourceContextHolder.isContainsDataSource(key)) {
            //joinPoint.getSignature() :获取连接点的方法签名对象
            log.error("数据源 " + key + " 不存在,使用默认的数据源 -> " + joinPoint.getSignature());
        } else {
            log.debug("使用数据源:" + key);
            DynamicDataSourceContextHolder.setDataSourceType(key);
        }
    }

    @After("@annotation(slaveDataSource)")
    public void clearDataSource(JoinPoint joinPoint, SlaveDataSource slaveDataSource) {
        log.debug("清除数据源 " + slaveDataSource.value() + " !");
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

2.动态数据源配置

实现AbstractRoutingDataSource,重写的方法实际上返回的是@SlaveDataSource注解中的Value

public class DynamicDataSource  extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

存放当前线程的数据源信息和所有数据源的id(key值)

public class DynamicDataSourceContextHolder {
    //存放当前线程使用的数据源类型信息
    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);
    }
}

数据源注册

@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final String DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";

    //默认数据源
    private DataSource defaultDataSource = null;

    private Map<Object, Object> dataSources = new HashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
        String sourceStr = "spring.datasource.";
        // 读取主数据源
        defaultDataSource = buildDataSource(getDataSourceCfg(environment, sourceStr));
        dataSources.put("master", buildDataSource(getDataSourceCfg(environment, sourceStr)));
        DynamicDataSourceContextHolder.dataSourceIds.add("master");
        String dsPrefix = environment.getProperty("slave.datasource.names");
        for (String pre : dsPrefix.split(",")) {
            // 多个数据源
            DataSource ds = buildDataSource(getDataSourceCfg(environment, sourceStr + pre + "."));
            DynamicDataSourceContextHolder.dataSourceIds.add(pre);
            dataSources.put(dsPrefix, ds);
        }
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        //创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        DynamicDataSource dynamicDataSource=new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
        dynamicDataSource.setTargetDataSources(dataSources);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", dataSources);
        //注册 - BeanDefinitionRegistry
        registry.registerBeanDefinition("dataSource", beanDefinition);
        log.info("注册数据源成功,一共注册{}个数据源", dataSources.size());


    }

    private Map<String, Object> getDataSourceCfg(Environment env, String prefix) {
        Map<String, Object> dsMap = new HashMap<>();
        dsMap.put("driver", env.getProperty(prefix + "driver-class-name"));
        dsMap.put("url", env.getProperty(prefix + "jdbc-url"));
        dsMap.put("username", env.getProperty(prefix + "username"));
        dsMap.put("password", env.getProperty(prefix + "password"));
        return dsMap;
    }


    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;
    }
}
启动方法需要修改
@Import(DynamicDataSourceRegister.class)
public class AppOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppOrderApplication.class, args);
    }
}

最后只需要在service方法上加上 @SlaveDataSource("slave")就可以访问从数据库了

上一篇下一篇

猜你喜欢

热点阅读