关于spring-hibernate中,多数据源配置的一种实现方法(通过AbstractRoutingDataSource)

关于spring-hibernate中,多数据源配置的一种实现方法(通过AbstractRoutingDataSource)

最近项目中需要用到动态切换数据源且希望做到最少的操作配置,于是研究了一番(网上copy),自己总结了一些容易出问题的地方:

  • AbstractRoutingDataSource 实现切换数据源的原理
  • 数据源配置
  • 代码示例

AbstractRoutingDataSource 实现切换数据源的原理

查看这个类可以发现。它继承了DataSource,那么找到他的getConnection方法

public Connection getConnection() throws SQLException {
    return this.determineTargetDataSource().getConnection();
}

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    Object lookupKey = this.determineCurrentLookupKey();
    DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
    if(dataSource == null && (this.lenientFallback || lookupKey == null)) {
        dataSource = this.resolvedDefaultDataSource;
    }

    if(dataSource == null) {
        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
    } else {
        return dataSource;
    }
}

可以看出关键代码determineTargetDataSource中,这里处理了数据源切换,那么只需要写一个子类,将determineCurrentLookupKey这个方法重写下,即可做到数据源切换。

数据源配置

用到的部分代码如下,其余的用项目中的即可:
-数据库配置文件

jdbc.driverClassName = com.mysql.jdbc.Driver
hibernate.connection.url = jdbc:mysql://xxxx?useUnicode=true&characterEncoding=UTF-8
hibernate.connection.username = xxxx
hibernate.connection.password = xxxx
hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
hibernate.default_schema = xxxx

openData.jdbc.driverClassName = com.mysql.jdbc.Driver
openData.hibernate.connection.url = jdbc:mysql://xxxx?useUnicode=true&characterEncoding=UTF-8
openData.hibernate.connection.username = xxxx
openData.hibernate.connection.password = xxxx
openData.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
openData.hibernate.default_schema = xxxx

applicationContext.xml

<!-- 配置数据源 -->
    <bean id="dataDefaultSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${hibernate.connection.url}" />
        <property name="username" value="${hibernate.connection.username}" />
        <property name="password" value="${hibernate.connection.password}" />
        <property name="maxActive" value="256" />
        <property name="initialSize" value="16" />
        <property name="maxWait" value="60000" />
        <property name="minIdle" value="16" />
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 1 FROM DUAL" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <!-- mysql 不支持 poolPreparedStatements <property name="poolPreparedStatements" 
            value="true" /> <property name="maxPoolPreparedStatementPerConnectionSize" 
            value="20" /> -->
        <!-- 配置过滤器:wall——防注入攻击(WallFilter默认的防注入配置,也可以自己另外配置),stat-监控统计功能 -->
        <!-- <property name="filters" value="wall,stat" /> -->
    </bean>

    <!-- 配置数据源用于存放访问统计数据 -->
    <bean id="dataSiteMainSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${siteMain.hibernate.connection.url}" />
        <property name="username" value="${siteMain.hibernate.connection.username}" />
        <property name="password" value="${siteMain.hibernate.connection.password}" />
        <property name="maxActive" value="256" />
        <property name="initialSize" value="16" />
        <property name="maxWait" value="60000" />
        <property name="minIdle" value="16" />
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 1 FROM DUAL" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
    </bean>

    <bean id="dataSource" class="cn.test.developer.ChooseDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry value-ref="dataDefaultSource" key="default"></entry>
                <entry value-ref="dataSiteMainSource" key="siteMain"></entry>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataDefaultSource">
        </property>
    </bean>

    <!-- 配置hibernate SessionFactory -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="namingStrategy" ref="customNamingStrategy" />
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <!--<prop key="hibernate.default_schema">${hibernate.default_schema}</prop>-->
                <prop key="hibernate.show_sql">${hibernate.connection.show_sql}</prop>
                <prop key="hibernate.max_fetch_depth">${hibernate.max_fetch_depth}</prop>
                <prop key="hibernate.jdbc.batch_size">${hibernate.jdbc.batch_size}</prop>
                <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <prop key="hiberante.format_sql">${hibernate.connection.format_sql}</prop>
                <!-- 二级缓存EhCache配置 -->
                <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory_class}</prop>
                <!-- 设置二级缓存插件EHCache的Provider类-->
                <prop key="hibernate.cache.provider_class">${hibernate.cache.provider_class}</prop>
                <prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
                <prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}</prop>
                <prop key="hibernate.cache.provider_configuration_file_resource_path">${ehcache.file_resource_path}</prop>
                <!-- 强制Hibernate以更人性化的格式将数据存入二级缓存 -->
                <prop key="hibernate.cache.use_structured_entries">${hibernate.cache.use_structured_entries}</prop>  
                <!-- Hibernate将收集有助于性能调节的统计数据 -->  
                <prop key="hibernate.generate_statistics">${hibernate.generate_statistics}</prop>  
                <!-- 项目启动时初始化CurrentSessionContext,用于获取session(sessionFactory.getCurrentSession()) -->
                <prop key="hibernate.current_session_context_class">${hibernate.current_session_context_class}</prop>
            </props>
        </property>
        <property name="packagesToScan" value="cn.test.**.entity" />
    </bean>

    <!-- 事务管理器 -->
    <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true" propagation="REQUIRED" />
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    <tx:annotation-driven transaction-manager="txManager"/>
    <aop:config expose-proxy="true">
        <aop:advisor advice-ref="txAdvice" pointcut="execution(* cn.test..service..*(..))" order="2"/>
    </aop:config>

    <bean id="uniqueTableApp" class="cn.test.key.impl.HibernateUniqueTableApp">
    </bean>

    <bean class="cn.test.key.SequenceFactory">
        <property name="uniqueTableApp" ref="uniqueTableApp"></property>
        <!--<property name="transactionTemplate" ref="transactionTemplate"></property>-->
        <property name="cacheKeyNum" value="1"></property>
    </bean>

    <bean id="dataSourceAspect" class="cn.test.aop.DataSourceAspect" />
    <aop:config>
        <aop:aspect id="c" ref="dataSourceAspect" order="1">
            <aop:pointcut id="tx" expression="execution(* cn.test..service..*(..))"/>
            <aop:before pointcut-ref="tx" method="before"/>
        </aop:aspect>
    </aop:config>

此处代码中需要主义的地方有:dataSource配置的key要和后面修改数据库是的名称保持一致,
-切换数据源和事务管理的先后顺序:这里通过order 来设置优先级,保证切换数据源的切面在事务管理之前
-sessionFactory配置注意的地方的地方:将默认的数据库配置取消,不然会无法切换

代码示例:(与配置文件相匹配)

1:动态选择数据源 方法类

package cn.test.developer;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class ChooseDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = HandleDataSource.getDataSource();
        HandleDataSource.clearDataSource();//每次设置完清除,保证获取默认数据库
        return dataSource;
    }
}

2:数据源切换类

package cn.test.developer;

/**
 * @version 2018-08-07 19:48
 */
public class HandleDataSource {
    // 数据源名称线程池,ThreadLocal是线程安全的,并且不能在多线程之间共享
    public static final ThreadLocal<String> holder = new ThreadLocal<String>();

    public static void setDataSource(String dataSource){
        holder.set(dataSource);
    }

    public static String getDataSource(){
        return ((String)holder.get());
    }

    public static void clearDataSource() {
        holder.remove();
    }
}

3:切面类

package cn.test.aop;

import cn.test.developer.DataSource;
import cn.test.developer.DataSourceConst;
import cn.test.developer.HandleDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Before;

/**
 * 反射获取获取数据库名并设置数据库类
 * @version 2018-08-07 19:59
 */
public class DataSourceAspect {

    public void pointCut(){};

    /**
     * 在事务开始之前插入到service上,通过反射获取注解上的数据库
     * @param point
     */
    @Before(value="pointCut()")
    public void before(JoinPoint point){
        //拦截的实体类
        Object target = point.getTarget();
        //拦截的方法名称
        //String methodName = point.getSignature().getName();
        Class<?>[] classes = target.getClass().getInterfaces();
        //拦截的放参数类型
//        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
//                .getMethod().getParameterTypes();
        //默认设置为默认库
        HandleDataSource.setDataSource(DataSourceConst.DEFAULT);
        try {
            if (classes.length > 0) {
//                Method m = classes[0].getMethod(methodName, parameterTypes);
                if (classes[0] != null && classes[0].isAnnotationPresent(DataSource.class)) {
                    DataSource data = classes[0].getAnnotation(DataSource.class);
                    HandleDataSource.setDataSource(data.value());
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4:数据源名称配置类

package cn.test.developer;

/**
 * 数据源配置名称--与配置文件中的key同名
 * @version 2018-08-08 11:21
 */
public class DataSourceConst {
    public static final String DEFAULT = "default";
    public static final String SITE_MAIN = "siteMain";
}

5:自定义注解类

package cn.test.developer;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 用注解来反射读取注解中输入的数据源来确定要操作的数据源
 * @version 2018-08-07 19:53
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DataSource {

    String value();

}

如何使用

我这里使用的方式是注解加在service类上,可根据需求切换到方法上也可以。

package cn.test.system.sitechart.service;

@DataSource(DataSourceConst.SITE_MAIN)
public interface ISiteChartMainService extends IBaseService<SiteChartMainEO> {
    void getSiteChart(SiteChartMainEO mainEO);
}

以上就是使用AbstractRoutingDataSource 实现动态数据源切换的方式,如果有什么没有注意到的地方,请各路大神评论指正。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值