java项目配置多数据源

有些web应用需要连接多个数据源,本文讲解一下如何使用多个数据源,大题思路是这样的,系统启动的时候创建多个数据源,然后具体执行sql的时候去切换数据源执行对应的sql。如何切换数据源呢?spring提供了一个AbstractRoutingDataSource抽象类,只要继承这个类就可以了,这个类需要设置多个数据源,每个数据源有一个key对应,继承这个类必须实现determineCurrentLookupKey()方法,这个方法返回一个Object值,这个值应该是数据源的key,执行sql的时候会调用这个方法 获取key,然后根据这个key获取到的数据源执行sql。下面看具体的例子。
前面说了determineCurrentLookupKey()方法的返回值决定,选择什么数据库,方法执行的时候如何动态设置返回值呢?为了不影响其他线程的使用,使用线程本地变量是最好的,这样只会影响当前线程。看如下类

public class DataSourceRouter {
    //默认数据源的key
    public final static String DEFAULT = "default";
    //数据源1的key
    public final static String KEY_ONE = "key_one";
    private final static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> DEFAULT);

    /**
     * 获取线程本地变量
     *
     * @return
     */
    static String getCurrentDataSourceKey() {
        return threadLocal.get();
    }

    /**
     * @param key      数据源对应的key
     * @param supplier sql执行的方法
     * @param <T>
     * @return sql执行的结果
     */
    public static <T> T doWithKey(String key, Supplier<T> supplier) {
        threadLocal.set(key);
        T t = supplier.get();
        threadLocal.set(DEFAULT);
        return t;
    }
}

这里通过doWithKey执行sql,通过determineCurrentLookupKey动态获取当前数据源。
下面来实现AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Autowired
    ApplicationContext applicationContext;

    /**
     * 是否在创建数据源时,立即初始化连接
     */
    private boolean initConnectionsOnCreate = false;

    /**
     * 返回配置的数据源的key,这样下面执行的sql就是使用该数据源的
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceRouter.getCurrentDataSourceKey();
    }

    public void init() {
        DruidDataSource defaultTargetDataSource = applicationContext.getBean("defaultDataSource", DruidDataSource.class);
        DruidDataSource dataSource1 = applicationContext.getBean("dataSourceTemplate", DruidDataSource.class);
        dataSource1.setUrl("jdbc:mysql://localhost:3306/social?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
        dataSource1.setUsername("root");
        dataSource1.setPassword("998973");
        if (initConnectionsOnCreate) {
            try {
                defaultTargetDataSource.init();
                dataSource1.init();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceRouter.DEFAULT, defaultTargetDataSource);
        map.put(DataSourceRouter.KEY_ONE, dataSource1);
        setTargetDataSources(map);
        //默认先执行配置文件中的targetDataSources属性的数据源,这里设置的数据源不会生效,必须调用afterPropertiesSet
        afterPropertiesSet();
    }

    public void setInitConnectionsOnCreate(boolean initConnectionsOnCreate) {
        this.initConnectionsOnCreate = initConnectionsOnCreate;
    }
}

这里说明了,配置文件中配置的targetDataSources是必配的,对象创建的时候会读取这个配置然后调用afterPropertiesSet加载数据源,因为我们这里的数据源可能通过数据库等其他途径获取,所以没有写在配置文件中,这里需要手动调用一下afterPropertiesSet重新设置一下数据源。
配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 数据源模板,动态增加数据源时需要用到,scope是prototype,非单例对象 -->
    <bean id="dataSourceTemplate" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="filters" value="stat"/>
        <property name="maxActive" value="20"/>
        <property name="initialSize" value="1"/>
        <property name="maxWait" value="60000"/>
        <property name="minIdle" value="1"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxOpenPreparedStatements" value="20"/>
        <property name="asyncInit" value="true"/>
    </bean>

    <bean id="defaultDataSource" parent="dataSourceTemplate">
        <property name="url"
                  value="jdbc:mysql://localhost:3306/easytour?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="998973"/>
    </bean>

    <!--统一的dataSource-->
    <bean id="dynamicDataSource" class="com.zhan.design.config.datasource.DynamicDataSource" init-method="init">
        <!-- 设置true时,随便一个key,找不到就走默认数据源,很可能带来不好的效果 -->
        <property name="lenientFallback" value="false"/>
        <property name="targetDataSources">
            <map></map>
        </property>
        <!--设置默认的dataSource-->
        <property name="defaultTargetDataSource" ref="defaultDataSource">
        </property>
        <!--是否在创建数据源时,立即初始化连接-->
        <property name="initConnectionsOnCreate" value="true"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dynamicDataSource"/>
        <!--扫描mybatis配置文件,在哪里可以做细配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--扫描映射文件所在目录-->
        <property name="mapperLocations" value="classpath:com/zhan/design/mapper/**/*.xml"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--扫描接口的基础包,会把该包下面的所有接口注册为spring的bean-->
        <property name="basePackage" value="com.zhan.design.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!--配置spring的事务-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dynamicDataSource"/>
    </bean>

    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

这样就完成了
下面看下具体使用的例子

    @GetMapping("/test")
    public Map<String, String> test() {
        //通过调用doWithKey就完成对数据源的切换了
        String label1 = DataSourceRouter.doWithKey(DataSourceRouter.DEFAULT, () -> dictionaryService.getByTypeAndKey("1", "1"));
        String label2 = DataSourceRouter.doWithKey(DataSourceRouter.KEY_ONE, () -> dictionaryService.getByTypeAndKey("1", "1"));
        Map<String, String> map = new HashMap<>();
        map.put("default_label", label1);
        map.put("one_label", label2);
        return map;
    }

至此就完成了。

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值