这样玩儿spring多数据源

 

分布式服务,经常是一个服务对应一个库,但也有的时候一个服务需要用到两个库,这个时候,一个服务就需要配置两个数据源,来支持业务的需要。


单数据源配置如下:

/**
 * Druid的配置文件
 * 用于监控数据库SQL
 */
@Configuration
@Slf4j
@RefreshScope
public class DruidConfiguration {

    @Value("${spring.datasource.url}")
    private String dbUrl;
    ………………………………

    @Value("${spring.datasource.username}")
    private String username;

   

    /**
     * 配置数据库连接池
     * @return
     */
//    @Bean     //声明其为Bean实例
//    @Primary  //在同样的DataSource中,首先使用被标注的DataSource
    public DataSource dataSource(){
        DruidDataSource datasource = new DruidDataSource();

        log.info(">>>>>>>>>>>>>开始配置druid连接池<<<<<<<<<<<<<<<<<<");

        log.info(">>>>>>>>>>>>>>>>>>>>数据库连接 : dbUrl<<<<<<<<<<<<<<<<<<<"+dbUrl);

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties(connectionProperties);

        return datasource;
    }

}

单数据源时,在服务启动时,会加载数据源配置类,将config服务中的数据源配置信息加载出来。


Q:多数据源是不应该配置多个数据源类嘞?

A:当然是配置多个喽,多数据源类如下:

@Configuration
@Slf4j
@RefreshScope
public class SecondConfig {


    @Value("${spring.appprofiledatasource.url}")
    private String dbUrl;
    …………………………………………

    @Value("${spring.appprofiledatasource.username}")
    private String username;


    private DataSource ds = null;

    /**
     * 配置数据库连接池
     * @return
     */

    public DataSource dataSource(){
        if(ds != null) return ds;
        DruidDataSource datasource = new DruidDataSource();

        log.info(">>>>>>>>>>>>>开始配置AppProfileDataSource连接池<<<<<<<<<<<<<<<<<<");

        log.info(">>>>>>>>>>>>>>>>>>>>数据库连接 : dbUrl<<<<<<<<<<<<<<<<<<<"+dbUrl);

        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            log.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties(connectionProperties);

        ds=datasource;
        return ds;
    }

}

Q:此时有两数据源后,他俩谁首先加载呢?

A:由主数据源登场来指挥

@Configuration
@ConditionalOnClass({DruidConfiguration.class, AppProfileConfig.class})
public class PrimaryDataSource {

    @Autowired
    DruidConfiguration mysql;
    @Autowired
    SecondConfig odps;

    @Bean
    @Primary
    public DataSource dataSource() {
        MultipleDataSource dynamicDataSource = new MultipleDataSource();
        // 默认数据源
        DataSource o = odps.dataSource();
        DataSource m = mysql.dataSource();
        dynamicDataSource.setDefaultTargetDataSource(m);
        // 配置多数据源
        Map<Object, Object> datasources = new HashMap<>();
        //datasources.put("dataSource", mysql);
        datasources.put("secondDataSource", o);
        dynamicDataSource.setTargetDataSources(datasources);
        return dynamicDataSource;
    }

}

如何保证主数据源可以指挥DruidOnfiguration和SecondConfig呢?此时就靠@Primary了,单数据源时,原本也有@Primary注解的,但是配置了多数据源后,@Primary注解就改放到主数据源中了。


Q:接下来的问题就是在使用时如何切换数据源呢?

A:继承spring的AbstractRountingDataSource来定义自己的动态数据源,这样就可以根据需要动态切换不同数据库的数据源了。

public class MultipleDataSource extends AbstractRoutingDataSource {
    /**
	 * 实现AbstractRoutingDataSource的抽象方法,获取与数据源相关的key
	 * 跟进源码可以发现,此key与数据源绑定的key值,然后用于获取连接
	 */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSourceType();
    }
}

与数据源相关的key,我们是在DataSourceHolder类中用ThreadLocal来保存的,

/**
 * 保存当前线程数据源的key
 */
public final class DataSourceHolder {

    private static final ThreadLocal<String> HOLDER = new InheritableThreadLocal<>();

    /**
     * 绑定当前线程数据源路由的key
     * 在使用完成之后,必须调用removeRouteKey()方法删除
     */
    public static void setDataSourceType(String dataSourceType) {
        HOLDER.set(dataSourceType);
    }

    /**
     * 获取当前线程的数据源路由的key
     * @return
     */
    public static String getDataSourceType() {
        return HOLDER.get();
    }

    public static void clearDataSourceType() {
        HOLDER.remove();
    }
}

Q:那么问题又来了,数据源key,啥时候传进来嘞?

A:此时我们就需要使用spring的AOP编程在业务逻辑方法运行前将当前方法使用数据源的key从业务逻辑方法上自定义注解中解析数据源key,并添加到DataSourceHolder中。

切面类如下:

@Aspect
@Order(1)
@Component
public class MyAspect {


    //指定要切的自定义注解类
    @Pointcut("@annotation(cn.aa.common.aspect.TargetDataSource)")
    public void pointcut() {
    }

//    @Before("execution(* cn.lefull.service..*(..))")
    @Before("@annotation(cn.lefull.common.aspect.TargetDataSource)")
    public void before(JoinPoint point) throws Exception {
        TargetDataSource targetDataSource = AnnotationUtils.findAnnotation(((MethodSignature) point.getSignature()).getMethod(), TargetDataSource.class);
        if (Objects.isNull(targetDataSource)) {
            targetDataSource = AnnotationUtils.findAnnotation(point.getClass(), TargetDataSource.class);
            if (Objects.isNull(targetDataSource)) {
                Object proxy = point.getThis();
                Field h = FieldUtils.getDeclaredField(proxy.getClass().getSuperclass(), "h", true);
                AopProxy aopProxy = (AopProxy) h.get(proxy);
                ProxyFactory advised = (ProxyFactory) FieldUtils.readDeclaredField(aopProxy, "advised", true);
                Class<?> targetClass = advised.getProxiedInterfaces()[0];
                targetDataSource = AnnotationUtils.findAnnotation(targetClass, TargetDataSource.class);
            }
        }
        DataSourceHolder.setDataSourceType(Objects.nonNull(targetDataSource) ? targetDataSource.value() : "dataSource");
    }

    @After("pointcut()")
    public void after(JoinPoint point) {
        DataSourceHolder.clearDataSourceType();
    }
}

自定义注解类:

@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
    String value() default "dataSource";
}

应用

//在方法上加上自定义注解,值即为数据源key
@Override
@TargetDataSource("secondDataSource")
public int updateByPrimaryKey(IdfaInfo idfaInfo) {
     return idfaInfoRepository.updateByPrimaryKey(idfaInfo);
}

使用时,在方法上加上自定义注解,并将值赋为第二数据源,然后在方法执行时,便可以加载第二数据源。

以上的配置,切面注解只能加载方法上,加到类上无法获取数据源,想要加到类上,需要修改切面类。

 

### 回答1: 结婚的小游戏有很多种,以下是一些可以玩的小游戏: 1. 谁更了解新郎/新娘:在游戏中,新郎/新娘会回答一些问题,例如最喜欢的食物、最爱的电影等,然后参加游戏的人会猜测新郎/新娘的答案,看谁猜得最准。 2. 新婚猜谜:这个游戏需要准备一些有关新婚的谜语,让参加游戏的人猜测答案。 3. 找出新娘的鞋子:在婚礼上,新娘通常会穿上一双特别的婚鞋,游戏中会将其藏起来,然后参加游戏的人需要找出新娘的鞋子,找到的人会获得奖励。 4. 谁抛的花束最远:在这个游戏中,新娘会抛出她的花束,然后参加游戏的单身女性会尽可能远地抓住花束,抓到花束的人会成为下一个结婚的新娘。 5. 猜婚礼歌曲:在这个游戏中,会播放一些与婚礼相关的歌曲,参加游戏的人需要猜出这些歌曲的名称或歌手。 ### 回答2: 结婚是一段充满喜悦和甜蜜的时刻,为了增添婚礼的欢乐氛围,可以进行一些有趣的小游戏。以下是一些结婚小游戏的建议: 1. 猜新娘:新郎闭上眼睛,由新娘和一些女性嘉宾站在一起,新郎需要摸出新娘,以测试他对新娘的认识。 2. 装扮新娘:将新娘的婚纱分成若干块,要求新郎将这些块组装回来,以检验他的耐心和对新娘外貌的记忆。 3. 感情真假:为每对新婚夫妇准备一些问题,让新郎和新娘轮流回答。问题可以涉及他们的未来计划、感情故事等,以检验他们对彼此了解的程度。 4. 隔空传情:准备一些纸板,新郎和新娘分别站在一段距离的两端,各自用嘴巴夹住纸板,然后通过用纸板传送信件或者吻信,以增加互动和竞争的乐趣。 5. 拍拖大作战:请一些单身朋友参与比赛,让他们模仿新郎和新娘的甜蜜动作或场景,由新郎和新娘评选最像的一对。 6. 献花传情:要求嘉宾们分成两队,每队选出一名代表,站在一段距离的两端。每名代表手持一朵花束,通过传递花束的方式,以最快的速度将花束传至对方代表手中。 这些小游戏旨在加强新郎和新娘之间的默契和互动,同时给婚礼增添欢乐和独特的回忆。 ### 回答3: 结婚的小游戏有很多种,可以根据新娘和新郎的喜好、婚礼主题以及场地条件来选择。以下是一些常见的结婚小游戏: 1. 猜新娘或新郎的年龄、生肖、星座等:在婚宴上,主持人可以播放照片或简短视频,让宾客猜测新娘或新郎的基本信息,这样可以增加互动和欢乐氛围。 2. 问题游戏:新娘和新郎事先准备一些问题,让宾客回答,例如他们相识的地点、第一次约会的情景等等。这是一个有趣的互动环节,使宾客更了解新人的爱情故事。 3. 婚戒传递游戏:将新娘和新郎的婚戒放在一系列纸杯或袋子中,宾客之间传递婚戒,最后由新郎把婚戒套在新娘的手指上。这是一个欢乐的游戏,可以增加气氛。 4. 玩转绳的游戏:让新郎和新娘手持一根绳子,游戏主持人念出一些有关新婚生活的情景,两人根据情景变化来调整绳子的形状。这个游戏考验新人的默契和协作能力。 5. 尬聊游戏:在宾客席上放置一些聊天题,例如“最难忘的一次旅行”、“最喜欢的一本书”等,让宾客自由交流。这样可以拉近宾客之间的距离,增加互动氛围。 总的来说,结婚的小游戏可以通过增加互动和娱乐性,使宾客更好地融入婚礼,与新人共享喜庆的氛围。当然还可以根据实际情况发挥创意,设计出更多有趣的结婚小游戏,以让婚礼更加精彩难忘。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子松的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值