mysql主從複製(讀寫分離)java實現

代碼中建議在service(業務)層進行主從分離。同一個service方法內部不建議再進行主從分離。這裏是事務切面層,我們知道,在同一個事務中會使用同一條鏈接進行處理,在業務層方法內部邏輯不再建議進行主從分離,避免數據不一致問題的出現

以下方案通過 繼承 AbstractRoutingDataSource類+註解+aop+ThreadLocal 實現註解方式的數據源的動態切換

 

1、讀數據庫切換註解類,在service註解表示開啓讀庫操作,自動方法完成後自動清空數據源數據

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
@Inherited
public @interface SlaveDataSource {

}

2、spring aop切面類

@Aspect
public class DataSourceAspect {
    private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceAspect.class);
    /**
     * 寫庫對應的數據源key 默認此庫
     */
    public static final String MASTER = "master";

    /**
     * 讀庫對應的數據源key
     */
    public static final String SLAVE = "slave";

    /**
     * 在service層方法獲取datasource對象之前,在切面中指定當前線程數據源slave
     */
    @Before(value = "execution(* *(..)) && @annotation(SlaveDataSource)")
    public void before(JoinPoint point) {
        String clazz = point.getTarget().getClass().getName();
        String name = point.getSignature().getName();
        LOGGER.debug(clazz+"."+name+" --- datasource---> :"+SLAVE);
        DataSourceHolder.putDataSource(SLAVE);
    }

    @After(value = "execution(* *(..)) && @annotation(SlaveDataSource)")
    public void after(JoinPoint joinPoint) {
        DataSourceHolder.clearDataSourceType();
    }

注意,需要開啓springAop配置,同時將對象反向注入

 <!-- aop註解支持 使用aspectj代理 不使用java動態代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
    <!--動態數據源-->
    <bean id="dataSourceAspect" class="com.konka.utils.database.DataSourceAspect"/>

3、動態數據源類,繼承 org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {


    /**
     * 獲取與數據源相關的key
     * 此key是Map<String,DataSource> resolvedDataSources 中與數據源綁定的key值
     * 在通過determineTargetDataSource獲取目標數據源時使用
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceHolder.getDataSource();
    }

}

4、數據源處理類

public class DataSourceHolder {

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

    /**
     * 綁定當前線程數據源
     */
    public static void putDataSource(String datasource) {
        HOLDER.set(datasource);
    }

    /**
     * 獲取當前線程的數據源
     *
     * @return
     */
    public static String getDataSource() {
        return HOLDER.get();
    }

    /**
     * 移除數據源
     */
    public static void clearDataSourceType() {
        HOLDER.remove();
    }
}

springMybatis.xml的配置,注:使用的爲阿里的druid數據庫連接池,如果使用dbcp請自行切換

<!--阿里監控配置-->
    <bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
        <property name="slowSqlMillis" value="10000"/>
        <property name="logSlowSql" value="true"/>
        <property name="mergeSql" value="true"/>
    </bean>
    <!-- 配置主數據源 -->
    <bean name="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${master_jdbc_driverClassName}"/>
        <property name="url" value="${master_jdbc_url}"/>
        <property name="username" value="${master_jdbc_username}"/>
        <property name="password" value="${master_jdbc_password}"/>
        <property name="validationQuery" value="${master_validationQuery}"/>

        <!--連接池啓動時創建的初始化連接數量(默認值爲0)-->
        <property name="initialSize" value="${master_sql_initial_size}"/>
        <!--連接池中最小的空閒的連接數,低於這個數量會被創建新的連接
        (默認爲0,調整爲5,該參數越接近maxIdle,性能越好,
        因爲連接的創建和銷燬,都是需要消耗資源的;但是不能太大,
        因爲在機器很空閒的時候,也會創建低於minidle個數的連接,
        類似於jvm參數中的Xmn設置)-->
        <property name="minIdle" value="${master_sql_min_idle}"/>
        <!--連接池中可同時連接的最大的連接數
        (默認值爲8,調整爲20,高峯單機器在20併發左右,自己根據應用場景定)-->
        <property name="maxActive" value="${master_sql_max_active}"/>
        <!--最大等待時間,當沒有可用連接時,連接池等待連接釋放的最大時間,
        超過該時間限制會拋出異常,如果設置-1表示無限等待
        (默認爲無限,調整爲60000ms,避免因線程池不夠用,而導致請求被無限制掛起)-->
        <property name="maxWait" value="30000"/>
        <!--起了一個Evict的TimerTask定時線程進行控制
        (可通過設置參數timeBetweenEvictionRunsMillis>0),
        定時對線程池中的鏈接進行validateObject校驗,
        對無效的鏈接進行關閉後,會調用ensureMinIdle,
        適當建立鏈接保證最小的minIdle連接數。-->
        <property name="testWhileIdle" value="true"/>
        <!--借出連接時不要測試,否則很影響性能-->
        <property name="testOnBorrow" value="false"/>
        <!--返回連接時不要測試,否則很影響性能-->
        <property name="testOnReturn" value="false"/>
        <!--每30秒運行一次空閒連接回收器-->
        <property name="timeBetweenEvictionRunsMillis" value="30000"/>
        <!--池中的連接空閒30分鐘後被回收 -->
        <property name="minEvictableIdleTimeMillis" value="210000"/>
        <!--超過removeAbandonedTimeout時間後,是否進行沒用連接(廢棄)的回收
        (默認爲false,調整爲true)  當可用連接數少於3個時才執行-->
        <property name="removeAbandoned" value="true"/>
        <!--連接泄漏回收參數,180秒,泄露的連接可以被刪除的超時值-->
        <property name="removeAbandonedTimeout" value="10"/>
        <!--標記當連接被回收時是否打印程序的stack traces日誌(默認爲false,未調整)-->
        <property name="logAbandoned" value="true"/>
        <!--監控-->
        <property name="proxyFilters">
            <list>
                <ref bean="stat-filter"/>
            </list>
        </property>
        <property name="useGlobalDataSourceStat" value="true"/>
    </bean>

    <!-- 配置從數據源 -->
    <bean name="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${slave_jdbc_driverClassName}"/>
        <property name="url" value="${slave_jdbc_url}"/>
        <property name="username" value="${slave_jdbc_username}"/>
        <property name="password" value="${slave_jdbc_password}"/>
        <property name="validationQuery" value="${slave_validationQuery}"/>
        <!--連接池啓動時創建的初始化連接數量(默認值爲0)-->
        <property name="initialSize" value="${slave_sql_initial_size}"/>
        <!--連接池中最小的空閒的連接數,低於這個數量會被創建新的連接
        (默認爲0,調整爲5,該參數越接近maxIdle,性能越好,
        因爲連接的創建和銷燬,都是需要消耗資源的;但是不能太大,
        因爲在機器很空閒的時候,也會創建低於minidle個數的連接,
        類似於jvm參數中的Xmn設置)-->
        <property name="minIdle" value="${slave_sql_min_idle}"/>
        <!--連接池中可同時連接的最大的連接數
        (默認值爲8,調整爲20,高峯單機器在20併發左右,自己根據應用場景定)-->
        <property name="maxActive" value="${slave_sql_max_active}"/>
        <!--最大等待時間,當沒有可用連接時,連接池等待連接釋放的最大時間,
        超過該時間限制會拋出異常,如果設置-1表示無限等待
        (默認爲無限,調整爲60000ms,避免因線程池不夠用,而導致請求被無限制掛起)-->
        <property name="maxWait" value="30000"/>
        <!--起了一個Evict的TimerTask定時線程進行控制
        (可通過設置參數timeBetweenEvictionRunsMillis>0),
        定時對線程池中的鏈接進行validateObject校驗,
        對無效的鏈接進行關閉後,會調用ensureMinIdle,
        適當建立鏈接保證最小的minIdle連接數。-->
        <property name="testWhileIdle" value="true"/>
        <!--借出連接時不要測試,否則很影響性能-->
        <property name="testOnBorrow" value="false"/>
        <!--返回連接時不要測試,否則很影響性能-->
        <property name="testOnReturn" value="false"/>
        <!--每30秒運行一次空閒連接回收器-->
        <property name="timeBetweenEvictionRunsMillis" value="30000"/>
        <!--池中的連接空閒30分鐘後被回收 -->
        <property name="minEvictableIdleTimeMillis" value="210000"/>
        <!--超過removeAbandonedTimeout時間後,是否進行沒用連接(廢棄)的回收
        (默認爲false,調整爲true)  當可用連接數少於3個時才執行-->
        <property name="removeAbandoned" value="true"/>
        <!--連接泄漏回收參數,180秒,泄露的連接可以被刪除的超時值-->
        <property name="removeAbandonedTimeout" value="10"/>
        <!--標記當連接被回收時是否打印程序的stack traces日誌(默認爲false,未調整)-->
        <property name="logAbandoned" value="true"/>
        <!--監控-->
        <property name="proxyFilters">
            <list>
                <ref bean="stat-filter"/>
            </list>
        </property>
        <property name="useGlobalDataSourceStat" value="true"/>
    </bean>

    <!-- 動態數據源,根據service接口上的註解來決定取哪個數據源 -->
    <bean id="dataSource" class="com.konka.utils.database.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- read or slave -->
                <entry key="slave" value-ref="slaveDataSource"/>
                <!-- write or master   -->
                <entry key="master" value-ref="masterDataSource"/>
            </map>
        </property>

        <property name="defaultTargetDataSource" ref="masterDataSource"/>
    </bean>

使用方式:對service方法上添加註解切換數據源,默認爲主庫

@Service
public class PingServiceImpl implements PingService {


    private final BasicConfigDao basicConfigDao;

    @Autowired
    public PingServiceImpl(BasicConfigDao basicConfigDao) {
        this.basicConfigDao = basicConfigDao;
    }


    @Override
    public void pingMaster() {
        basicConfigDao.ping();
    }

    @SlaveDataSource
    @Override
    public void pingSalve() {
        basicConfigDao.ping();
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章