Spring+Hibernate+MySQL的主从分离/读写分离问题(2)

本文介绍了一种基于Spring AOP和ThreadLocal实现的动态数据源切换方案。通过自定义注解和切面类,在不同的业务场景下灵活切换主从数据源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里上具体代码,因为没时间做单独的可运行实例,所以这里只贴关键的代码。

DataSource的配置

<bean id="parentDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="defaultAutoCommit" value="${hibernate.defaultAutoCommit}" />
        <property name="maxTotal" value="${hibernate.maxTotal}" />
        <property name="maxIdle" value="${hibernate.maxIdle}" />
        <property name="initialSize" value="${hibernate.initialSize}" />
        <property name="testOnBorrow" value="${hibernate.testOnBorrow}" />
        <property name="validationQuery" value="${hibernate.validationQuery}" />
        <property name="connectionInitSqls" value="set names utf8mb4" />
    </bean>

    <bean id="masterDataSource" class="org.apache.commons.dbcp2.BasicDataSource"  parent="parentDataSource">
        <property name="driverClassName" value="${jdbc.master.driver}" />
        <property name="url" value="${jdbc.master.url}" />
        <property name="username" value="${jdbc.master.username}" />
        <property name="password" value="${jdbc.master.password}" />
    </bean>

    <bean id="slaveDataSource" class="org.apache.commons.dbcp2.BasicDataSource" parent="parentDataSource">
        <property name="driverClassName" value="${jdbc.slave.driver}" />
        <property name="url" value="${jdbc.slave.url}" />
        <property name="username" value="${jdbc.slave.username}" />
        <property name="password" value="${jdbc.slave.password}" />
    </bean>

ReplicationRoutingDataSource为AbstractRoutingDataSource的实现

<bean id="dataSource" class="com.xxx.xxx.ReplicationRoutingDataSource">    
        <property name="targetDataSources">    
            <map key-type="java.lang.String">    
               <entry key="MASTER" value-ref="masterDataSource"/>    
               <entry key="SLAVE" value-ref="slaveDataSource"/>    
            </map>    
        </property>    
        <property name="defaultTargetDataSource" ref="masterDataSource"/>    
   </bean>

ReplicationRoutingDataSource 写法

public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {

    private Map<Object, Object> tempTargetDataSources = null;

    public ReplicationRoutingDataSource(){
        DynamicDataSourceAspect.replicationRoutingDataSource = this;
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        tempTargetDataSources = targetDataSources;
    }

    public Map<Object, Object> getTargetDataSources() {
        return tempTargetDataSources;
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }

}

DataSource的动态管理类通过ThreadLocal保证线程安全

public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static List<String> dataSourceIds = new ArrayList<>();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

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

    /**
     * 判断指定DataSrouce当前是否存在
     *
     * @param dataSourceId
     * @return
     * @author zxl
     * @create  
     */
    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}

自定义注解

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

切面类,通过自定义注解TargetDataSource作为切入点

public class DynamicDataSourceAspect {
    private Logger logger = Logger.getLogger(DynamicDataSourceAspect.class);

    public static ReplicationRoutingDataSource replicationRoutingDataSource = null;

    public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {

        String dsId = ds.name();

        if (replicationRoutingDataSource!=null
                &&!replicationRoutingDataSource.getTargetDataSources().containsKey(dsId)) {
            logger.error("DataSource not exist "+ds.name() + point.getSignature());
        } else {
            logger.debug("Use DataSource : {} > {}"+ds.name()+ point.getSignature());
            DynamicDataSourceContextHolder.setDataSourceType(ds.name());
        }
    }

    public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
        logger.debug("Revert DataSource : {} > {}"+ds.name()+ point.getSignature());
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}

AOP配置,这里请注意,由于配置到Contorller层的缘故,如果发现配置无效,请检查是否出现AOP配置冲突

<!-- 指定DataSourceAOP,用于动态切换DataSource -->
    <bean id="dataSourceAspect" class="com.xxx.xxx.DynamicDataSourceAspect" />
    <aop:config>
        <aop:aspect id="aspectDataSourceConfig" ref="dataSourceAspect" >

            <aop:before method="changeDataSource" pointcut="@annotation(ds)"/>
            <aop:after method="restoreDataSource" pointcut="@annotation(ds)"/>
        </aop:aspect>
    </aop:config>

使用方法

@Controller
@RequestMapping(value = "/test")
public class TestController extends BaseController {
    @RequestMapping(value = "/testmaster", method = {RequestMethod.POST, RequestMethod.GET })
    @ResponseBody
    @TargetDataSource(name="SLAVE")
    public String onTestSlave(HttpServletRequest request, HttpServletResponse response) {
        //具体业务
        return "result";
    }
    @RequestMapping(value = "/testslave", method = {RequestMethod.POST, RequestMethod.GET })
    @ResponseBody
    @TargetDataSource(name="MASTER")
    public String onTestMaster(HttpServletRequest request, HttpServletResponse response) {
        //具体业务
        return "result";
    }
}

参考
1. http://blog.youkuaiyun.com/caomiao2006/article/details/38989789
2. http://stackoverflow.com/questions/30023704/database-routing-using-abstractroutingdatasource

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值