java实现读写分离

前言:项目启动过程中有错,未解决,这里仅是实现思路

一,业务场景:新增,修改,删除操作主库,查询操作从库

1,主,从库配置,通过配置类,分别读取主从库配置信息,创建不同数据源,并设置不同数据源

2,通过aop区分,不同标识的方法,使用ThreadLocal记录当前线程的数据源key

二,代码

1,AOP

@Component
@Aspect
public class DataSourceAop {

    //主库切点,标注了DataSourceMaster注解或者方法名为insert,update等开头的方法,走主库
    @Pointcut("@annotation(com.anji.allways.business.payment.ReadWriteDataSourceAop.DataSourceMasterAnno)" +
            "|| execution(* com.anji.allways.business.payment.controller..*.insert*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.add*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.update*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.edit*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.delete*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.remove*(..))")
    public void masterPointcut() {

    }

    //从库切点,没有标注了DataSourceMaster注解且方法名为select,get等开头的方法,走从库
    @Pointcut("!@annotation(com.anji.allways.business.payment.ReadWriteDataSourceAop.DataSourceMasterAnno)" +
            "&& (execution(* com.anji.allways.business.payment.controller..*.select*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.get*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.find*(..))" +
            "|| execution(* com.anji.allways.business.payment.controller..*.query*(..)))")
    public void slavePointcut() {

    }

    @Before("masterPointcut()")
    public void master() {
        DynamicDataSourceHolderAop.markMaster();
    }

    @Before("slavePointcut()")
    public void slave() {
        DynamicDataSourceHolderAop.markSlave();
    }
}
2,数据源配置
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = DataSourceConfig.PACKAGE)
public class DataSourceConfig {
    //public class DataSourceConfig extends MybatisAutoConfiguration {


    static final String PACKAGE = "com.anji.allways.business.sales.mapper";


/*    public DataSourceConfig(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
        super(properties, interceptorsProvider, resourceLoader, databaseIdProvider, configurationCustomizersProvider);
    }*/

    /**
     * 主库数据源
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "primary.datasource")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 从库数据源
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "secondary.datasource")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

/*    @Bean
    @Override
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        return super.sqlSessionFactory(dataSource());
    }*/

    /**
     * 自定义数据源,内部持有了主库和从库数据源
     * 通过某种机制应用程序读取不同数据源
     * @param masterDataSource
     * @param slaveDataSource
     * @return
     */
    @Bean
    public DataSource myRoutingDataSource(
            @Qualifier("masterDataSource") DataSource masterDataSource,
            @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        MyRoutingDataSource proxy = new MyRoutingDataSource();
        Map<Object, Object> targetDataResources = new HashMap<>();
        targetDataResources.put(DataSourceType.MASTER, masterDataSource);
        targetDataResources.put(DataSourceType.SLAVE, slaveDataSource);
        //当方法没有别aop拦截,默认主库
        proxy.setDefaultTargetDataSource(masterDataSource());
        proxy.setTargetDataSources(targetDataResources);
        proxy.afterPropertiesSet();
        return proxy;
    }
}

3,

数据源的key
/**
 * 使用ThreadLocal技术来记录当前线程中的数据源的key
 *
 * @author zhijun
 */
public class DynamicDataSourceHolderAop {

    //使用ThreadLocal记录当前线程的数据源key
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

    /**
     * 设置数据源key
     *
     * @param key
     */
    public static void putDataSourceKey(String key) {
        holder.set(key);
    }

    /**
     * 获取数据源key
     *
     * @return
     */
    public static String getDataSourceKey() {
        return holder.get();
    }

    /**
     * 标记写库
     */
    public static void markMaster() {
        putDataSourceKey(DataSourceType.MASTER.toString());
    }

    /**
     * 标记读库
     */
    public static void markSlave() {
        putDataSourceKey(DataSourceType.SLAVE.toString());
    }

}

4,数据源路由

public class MyRoutingDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolderAop.getDataSourceKey();
    }
}

5,自定义主数据源注解

/**
 * <pre>
 * 主数据源自定义注解
 * </pre>
 *
 * @author shenke
 * @version $Id: LoggerManage.java, v 0.1 2018年3月16日 上午9:40:56 wanglong Exp $
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceMasterAnno {

    String description();
}

6,数据源类型枚举

/**
 * 主库从库标记key
 */
public enum DataSourceType {
    MASTER, SLAVE;
}

7,测试

测试1

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class DatasourceApplicationTest {

    private static Logger logger = LoggerFactory.getLogger(DatasourceApplicationTest.class);

    @Autowired
    private TradeMapper tradeMapper;

    /**
     * 从从库中读取-开发库
     */
    @Test
    public void readData() {
        TradeVo tradeVo = new TradeVo();
        List<TradeVo> listSecond = tradeMapper.selectTrade(tradeVo);
        log.info("从库secondary dataSource size:{}", listSecond.size());
    }

    /**
     * 从主库中读取-测试库
     */
    @Test
    public void insertTest() {
        TradeVo tradeVo = new TradeVo();
        List<TradeVo> listP = tradeMapper.selectTrade(tradeVo);
        logger.info("主库primary dataSource size:{}", listP.size());
    }

}

测试二

@Slf4j
@RestController
@RequestMapping("/dataSource")
public class ReadWriteController {
    private static Logger logger = LoggerFactory.getLogger(ReadWriteController.class);

    @Autowired
    private TradeMapper tradeMapper;

    /**
     * 从从库中读取-开发库
     */
    @RequestMapping("readMaster")
    public void readData() {
        TradeVo tradeVo = new TradeVo();
        List<TradeVo> listSecond = tradeMapper.selectTrade(tradeVo);
        log.info("从库secondary dataSource size:{}", listSecond.size());
    }

    /**
     * 从主库中读取-测试库
     */
    @RequestMapping("readSlave")
    public void insertTest() {
        TradeVo tradeVo = new TradeVo();
        List<TradeVo> listP = tradeMapper.selectTrade(tradeVo);
        logger.info("主库primary dataSource size:{}", listP.size());
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值