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();
    }

}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值