使用springboot实现读写分离+项目实战

使用springboot实现读写分离

这也是闲着无聊然后去了解了一下然后写了一个项目,如有不足,欢迎来指出来
因为我们读写分离至少是有两个数据源,有可能是这样的在这里插入图片描述
但是我们理想的状态并不是这样,而是这种状态
在这里插入图片描述
这时候保证在Spring提交事务之前确定数据源,利用AOP写个切换数据源的切面,让他的优先级高于Spring事务切面的优先级。
mybatis最终是要通过sqlsessionfactory获取数据连接,创建sqlsession并提交到数据库的。所以我们入手的地方有两点:

  1. 通过创建多种sqlsessionfactory比如masterFactory,slaverFactory来实现读写分离。
  2. 让sqlsessionfactory直接可以动态获取到只读或者写的数据源。

但是怎么切换数据源呢,spring中有一个AbstractRoutingDataSourc,这是DataSource的抽象,我们关注其中的四个方法
setTargetDataSources,setDefaultTargetDataSource,determineCurrentLookupKey,determineTargetDataSource,这四个方法:setTargetDataSources设置备选的数据源集合。 setDefaultTargetDataSource设置默认数据源,determineCurrentLookupKey决定当前数据源的对应的key,determineTargetDataSource方法,它才是获取数据源的实现,方法如下。

在这里插入图片描述
根据determineCurrentLookupKey获取的key,在resolvedDataSources这个Map中查找对应的datasource!,注意determineTargetDataSource方法竟然不使用的targetDataSources!

那一定存在resolvedDataSources与targetDataSources的对应关系。我接着翻阅代码,发现一个afterPropertiesSet方法(Spring源码中InitializingBean接口中的方法),这个方法将targetDataSources的值赋予了resolvedDataSources。源码如下:
在这里插入图片描述
afterPropertiesSet 方法,熟悉Spring的都知道,它在bean实例已经创建好,且属性值和依赖的其他bean实例都已经注入以后执行,也就是说调用,targetDataSources,defaultTargetDataSource的赋值一定要在afterPropertiesSet前边执行

好了,现在开始写,这里只列出部分重要代码,有兴趣可以下载项目
首先定义枚举类,
在这里插入图片描述
配置文件也不能少
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

读取配置文件
在这里插入图片描述
这里使用线程控制数据源切换
由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成
在这里插入图片描述
这里继承AbstractRoutingDataSource,并且对读进行简单的负载均衡,如果你是一个读库可以不用,判断返回当前数据源key
在这里插入图片描述
这里是mybatis配置,给sessionfactory设置数据源

@Configuration
@ConditionalOnClass({EnableTransactionManagement.class})
@Import({ DataBaseConfiguration.class})
public class MybatisConfiguration {

/**   mybatis 配置路径     */
@Value("${spring.mybatis.configLocation:mybatis/mybatis.xml}")
private String MYBATIS_CONFIG;
/**   mapper路径     */
@Value("${spring.mybatis.mapperLocations:mapper/*.xml}")
private String MAPPER_LOCATION;

@Value("${spring.datasource.type}")
private Class<? extends DataSource> dataSourceType;
@Value("${spring.datasource.readSize}")
private String dataSourceSize;
@Resource(name = "writeDataSource")
private DataSource dataSource;
@Resource(name = "readDataSources")
private List<DataSource> readDataSources;

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    /** 设置mybatis configuration 扫描路径 */
    sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(MYBATIS_CONFIG));
    sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
   AbstractRoutingDataSource s= roundRobinDataSouceProxy();
    /** 添加mapper 扫描路径 */
    sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
            .getResources(MAPPER_LOCATION));
    return sqlSessionFactoryBean.getObject();
}
/**
 * 有多少个数据源就要配置多少个bean
 * @return
 */
@Bean
public AbstractRoutingDataSource roundRobinDataSouceProxy() {
    int size = Integer.parseInt(dataSourceSize);
    MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
    Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
    // DataSource writeDataSource = SpringContextHolder.getBean("writeDataSource");
    // 写
    targetDataSources.put(DataSourceType.write.getType(),dataSource);
    //1个读数据库时
    // targetDataSources.put(DataSourceType.read.getType(),readDataSource);
    //多个读数据库时
    for (int i = 0; i < size; i++) {
        targetDataSources.put(i, readDataSources.get(i));
    }
    proxy.setDefaultTargetDataSource(dataSource);
    proxy.setTargetDataSources(targetDataSources);
    return proxy;
}

}

``最后使用aop实现拦截,判断是读还是写,当然你也可以使用注解,方式不唯一

@Aspect
@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DataSourceAop {
    private static Logger logger = Logger.getLogger(DataSourceAop.class);
    @Before("execution(* com.ganinfo.*.mapper..*.select*(..)) || execution(* com.ganinfo.*.mapper..*.get*(..))|| execution(* com.ganinfo.*.mapper..*.query*(..))")
    public void setReadDataSourceType() {
        DataSourceContextHolder.read();
        logger.info("dataSource 切换到:Read");
    }

    @Before("execution(* com.ganinfo.*.mapper..*.insert*(..)) || execution(* com.ganinfo.*.mapper..*.update*(..))")
    public void setWriteDataSourceType() {
        DataSourceContextHolder.write();
        logger.info("dataSource 切换到:Write");
    }
    @After("execution(* com.ganinfo.*.mapper..*.*(..))")
    public void remove(){
        DataSourceContextHolder.clearDB();
        logger.info("dataSource clear");
    }
}
参考:https://blog.csdn.net/yunzhaji3762/article/details/79750343

这两天如果有需要项目可以评论,过两天也会把项目发出来

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值