Spring 简单实现读写分离

读写分离,基本原理是让主数据库处理事务性增、改、删操作,而从数据库处理查询操作。数据库复制被用来把事务性操作导致的变更同步到集群的从数据库。

一般常用实现方式有以下两种:
1,主从分离,更新操作主数据库,查询操作从数据库
2,动态数据源切换。

下面利用Spring的AbstractRoutingDataSource 类简单实现主从分离。

首先DynamicDataSource 继承AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSource.class);
    //线程安全的加减
    private AtomicInteger counter = new AtomicInteger();

    /**
     * master库 dataSource
     */
    private DataSource master;

    /**
     * slaves
     */
    private List<DataSource> slaves;

    @Override
    protected Object determineCurrentLookupKey() {
        //该方法可以返回数据源标识
        // return DataSourceHolder.getDataSource();
        return null;
    }

    @Override
    public void afterPropertiesSet() {
        //do nothing
    }

    /**
     * 根据标识
     * 获取数据源
     */
    @Override
    protected DataSource determineTargetDataSource() {
        DataSource returnDataSource;
        if (DataSourceHolder.isMaster()) {
            returnDataSource = master;
        } else if (DataSourceHolder.isSlave()) {
            int count = counter.incrementAndGet();
            if (count > 1000000) {
                counter.set(0);
            }
            int n = slaves.size();
            int index = count % n;
            returnDataSource = slaves.get(index);
        } else {
            returnDataSource = master;
        }
//        if (returnDataSource instanceof BoneCPDataSource) {
//            BoneCPDataSource source = (BoneCPDataSource) returnDataSource;
//            String jdbcUrl = source.getJdbcUrl();
//            logger.error("jdbcUrl:" + jdbcUrl);
//        }
        return returnDataSource;
    }

    public DataSource getMaster() {
        return master;
    }

    public void setMaster(DataSource master) {
        this.master = master;
    }

    public List<DataSource> getSlaves() {
        return slaves;
    }

    public void setSlaves(List<DataSource> slaves) {
        this.slaves = slaves;
    }
}

上面示例重写了determineTargetDataSource,该方法根据标识获取数据源。(determineCurrentLookupKey获取数据源标识)源代码如下:

protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        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 + "]");
        }
        return dataSource;
    }

重写后根据DataSourceHolder 进行数据源判断,DataSourceHolder类如下:

public class DataSourceHolder {
    private static final String MASTER = "master";

    private static final String SLAVE = "slave";

    /**
     * dataSource master or slave
     */
    private static final ThreadLocal<String> dataSources = new ThreadLocal<>();

    /**
     * master local
     */
    private static final ThreadLocal<DataSource> masterLocal = new ThreadLocal<>();

    /**
     * master local
     */
    private static final ThreadLocal<DataSource> slaveLocal = new ThreadLocal<>();

    /**
     * 设置数据源
     *
     * @param dataSourceKey
     * @author
     */
    private static void setDataSource(String dataSourceKey) {
        dataSources.set(dataSourceKey);
    }

    /**
     * 获取数据源
     *
     * @return
     * @author
     */
    private static String getDataSource() {
        return dataSources.get();
    }

    /**
     * 标志为master
     */
    public static void setMaster() {
        setDataSource(MASTER);
    }

    /**
     * 标志为slave
     */
    public static void setSlave() {
        setDataSource(SLAVE);
    }

    /**
     * 将master放入threadlocal
     *
     * @param master
     */
    public static void setMaster(DataSource master) {
        masterLocal.set(master);
    }

    /**
     * 将slave放入threadlocal
     *
     * @param slave
     */
    public static void setSlave(DataSource slave) {
        slaveLocal.set(slave);
    }


    public static boolean isMaster() {
        return getDataSource() == MASTER;
    }

    public static boolean isSlave() {
        return getDataSource() == SLAVE;
    }

    /**
     * 清除thread local中的数据源
     *
     * @author
     */
    public static void clearDataSource() {
        dataSources.remove();
        masterLocal.remove();
        slaveLocal.remove();
    }
}

DataSourceHolder 定义了一些数据源判断及操作方法。并且通过ThreadLocal保证了线程安全。

下面需要继承DataSourceTransactionManager进行事务管理

public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
    /**
     * 只读事务到从库
     * 读写事务到主库
     */
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        boolean readOnly = definition.isReadOnly();
        if (readOnly) {
            DataSourceHolder.setSlave();
        } else {
            DataSourceHolder.setMaster();
        }
        super.doBegin(transaction, definition);
    }

    /**
     * 清理本地线程的数据源
     */
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        super.doCleanupAfterCompletion(transaction);
        DataSourceHolder.clearDataSource();
    }
}

至此,有关主从分离的逻辑处理已经完成,下面需要定义两个数据源。

<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        <property name="driverClass" value="${jdbc.driverClassName}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
        <property name="idleMaxAge" value="${jdbc.idleMaxAge}"/>
        <property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}"/>
        <property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}"/>
        <property name="partitionCount" value="${jdbc.partitionCount}"/>
        <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
        <property name="statementsCacheSize" value="${jdbc.statementsCacheSize}"/>
        <property name="releaseHelperThreads" value="${jdbc.releaseHelperThreads}"/>
    </bean>

    <bean id="slaveDataSource1" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
        <property name="driverClass" value="${jdbc.driverClassName}"/>
        <property name="jdbcUrl" value="${jdbc.slave1.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="idleConnectionTestPeriod" value="${jdbc.idleConnectionTestPeriod}"/>
        <property name="idleMaxAge" value="${jdbc.idleMaxAge}"/>
        <property name="maxConnectionsPerPartition" value="${jdbc.maxConnectionsPerPartition}"/>
        <property name="minConnectionsPerPartition" value="${jdbc.minConnectionsPerPartition}"/>
        <property name="partitionCount" value="${jdbc.partitionCount}"/>
        <property name="acquireIncrement" value="${jdbc.acquireIncrement}"/>
        <property name="statementsCacheSize" value="${jdbc.statementsCacheSize}"/>
        <property name="releaseHelperThreads" value="${jdbc.releaseHelperThreads}"/>
    </bean>

同时需要将数据放入DynamicDataSource中,并使用自定义的类进行事务管理

<bean id="dynamicDataSource" class="com.nzs.datasource.DynamicDataSource">
        <property name="master" ref="masterDataSource"/>
        <property name="slaves">
            <list>
                <ref bean="slaveDataSource1"/>
            </list>
        </property>
    </bean>

    <!-- 配置mybitasSqlSessionFactoryBean -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dynamicDataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!-- 配置SqlSessionTemplate -->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <!-- 事务配置 -->
    <bean id="transactionManager" class="com.nzs.datasource.DynamicDataSourceTransactionManager">
        <property name="dataSource" ref="dynamicDataSource"/>
    </bean>

    <!-- 使用annotation注解方式配置事务  -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值