springboot基于Aop实现的动态数据源切换

springboot基于Aop实现的动态数据源切换

  • 编写AbstractRoutingDataSource的实现类,HandlerDataSource就是提供给我们动态选择数据源的数据的信息,我们这里编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息,也可以手动的设置当前的数据源,在编程的类中。
/**
 * @author luhong
 * @version 1.0
 * @Description 获取动态数据源
 * @date 10/22/2018
 * @updateby
 * @updatedate
 * @since 1.0
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    public Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}
  • 编写DynamicDataSourceHolder.class

/**
 * @author luhong
 * @version 1.0
 * @Description
 * @date 10/22/2018
 * @updateby
 * @updatedate
 * @since 1.0
 */
public class DynamicDataSourceHolder {

    //写库对应的数据源key
    private static final String READ = "read";

    //读库对应的数据源key
    private static final String WRITE = "write";

    //使用ThreadLocal记录当前线程的数据源key
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

     /**
     * @author: luhong
     * @Description 管理所有的数据源id; 主要是为了判断数据源是否存在;
     * @Date 5:08 PM 10/27/2018
     * @Param
     * @return:
     */
    public static List<String> dataSourceIds = new ArrayList<String>();

    /**
     * 设置数据源key
     * @param key
     */
    public static void setDataSourceKey(String key) {
        holder.set(key);
    }

    /**
     * 获取数据源key
     * @return
     */
    public static String getDataSourceKey() {
        return holder.get();
    }

    /**
     * 标记写库
     */
    public static void markRead(){
        setDataSourceKey(READ);
    }

    /**
     * 标记读库
     */
    public static void markWrite(){
        setDataSourceKey(WRITE);
    }

    //清除数据源
    public static void clearDataSourceType() {
        holder.remove();
    }

    /**
    * @author: luhong 
    * @Description 
    * @Date 4:46 PM 10/24/2018
    * @Param [dataSourceId] 
    * @return:boolean 
    */
    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}
  • 编写DynamicDataSourceRegister.class
/**
 * @author luhong
 * @version 1.0
 * @Description 动态数据源注册
 * @date 10/24/2018
 * @updateby
 * @updatedate
 * @since 1.0
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

    /**
    * 如配置文件中未指定数据源类型,使用该默认值
    */
    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";

    private ConversionService conversionService = new DefaultConversionService();

    private PropertyValues dataSourcePropertyValues;

    // 默认数据源
    private DataSource defaultDataSource;

    private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();

    /**
    * @author: luhong
    * @Description 加载多数据源配置
    * @Date 5:02 PM 10/27/2018
    * @Param [environment]
    * @return:void
    */
    @Override
    public void setEnvironment(Environment environment) {
        System.out.println("DynamicDataSourceRegister.setEnvironment()");
        initDefaultDataSource(environment);
        initCustomDataSources(environment);
    }

    /**
    * @author: luhong
    * @Description 加载主数据源配置.
    * @Date 5:03 PM 10/27/2018
    * @Param [env]
    * @return:void
    */
    private void initDefaultDataSource(Environment env){
        // 读取主数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.buz.");
        Map<String, Object> dsMap = new HashMap<String, Object>();
        dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        dsMap.put("username", propertyResolver.getProperty("username"));
        dsMap.put("password", propertyResolver.getProperty("password"));

        //创建数据源;
        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource, env);
    }

    /**
    * @author: luhong
    * @Description 初始化更多数据源
    * @Date 5:04 PM 10/27/2018
    * @Param [env]
    * @return:void
    */
    private void initCustomDataSources(Environment env) {
        // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
        String dsPrefixs = propertyResolver.getProperty("names");
        // 多个数据源
        for (String dsPrefix : dsPrefixs.split(",")) {
            Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
            DataSource ds = buildDataSource(dsMap);
            customDataSources.put(dsPrefix, ds);
            dataBinder(ds, env);
        }
    }

    /**
    * @author: luhong
    * @Description 创建datasource.
    * @Date 5:04 PM 10/27/2018
    * @Param [dsMap]
    * @return:javax.sql.DataSource
    */
    @SuppressWarnings("unchecked")
    public DataSource buildDataSource(Map<String, Object> dsMap) {
        Object type = dsMap.get("type");
        if (type == null){
            // 默认DataSource
            type = DATASOURCE_TYPE_DEFAULT;
        }
        Class<? extends DataSource> dataSourceType;
        try {
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dsMap.get("driver-class-name").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();
            DataSourceBuilder factory =   DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
    * @author: luhong
    * @Description 为DataSource绑定更多数据
    * @Date 5:05 PM 10/27/2018
    * @Param [dataSource, env]
    * @return:void
    */
    private void dataBinder(DataSource dataSource, Environment env){
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        dataBinder.setConversionService(conversionService);
        //false
        dataBinder.setIgnoreNestedProperties(false);
        //false
        dataBinder.setIgnoreInvalidFields(false);
        //true
        dataBinder.setIgnoreUnknownFields(true);

        if(dataSourcePropertyValues == null){
            Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.buz-datasource").getSubProperties(".");
            Map<String, Object> values = new HashMap<>(rpr);
            // 排除已经设置的属性
            values.remove("type");
            values.remove("driver-class-name");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put("dataSource", defaultDataSource);
        DynamicDataSourceHolder.dataSourceIds.add("dataSource");
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceHolder.dataSourceIds.add(key);
        }
        // 创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //添加属性:AbstractRoutingDataSource.defaultTargetDataSource
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
    }
}
  • dao层
      
        /**
         * 数据库操作方法加上注解@DataSourceAop(name = "buz-read")
         * 指定数据库
         */
        @DataSourceAop(name = "buz-read")
        public Integer queryIsJoin(){
           
        }

     

  • 增加DataSourceAop.class
    /**
     * @author luhong
     * @version 1.0
     * @Description
     * @date 10/22/2018
     * @updateby
     * @updatedate
     * @since 1.0
     */
    @Target({ElementType.METHOD,ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSourceAop {
        String name();
    }
    

     

  •