springboot、mysql实现读写分离

1、首先在springcloud config中配置读写数据库

  1. mysql:
  2. datasource:
  3. readSize: 1 #读库个数
  4. type: com.alibaba.druid.pool.DruidDataSource
  5. write:
  6. url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false
  7. username: root
  8. password: 123123
  9. driver-class-name: com.mysql.cj.jdbc.Driver
  10. minIdle: 5
  11. maxActive: 100
  12. initialSize: 10
  13. maxWait: 60000
  14. timeBetweenEvictionRunsMillis: 60000
  15. minEvictableIdleTimeMillis: 300000
  16. validationQuery: select 'x'
  17. testWhileIdle: true
  18. testOnBorrow: false
  19. testOnReturn: false
  20. poolPreparedStatements: true
  21. maxPoolPreparedStatementPerConnectionSize: 50
  22. removeAbandoned: true
  23. filters: stat
  24. read01:
  25. url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false
  26. username: root
  27. password: 123123
  28. driver-class-name: com.mysql.cj.jdbc.Driver
  29. minIdle: 5
  30. maxActive: 100
  31. initialSize: 10
  32. maxWait: 60000
  33. timeBetweenEvictionRunsMillis: 60000
  34. minEvictableIdleTimeMillis: 300000
  35. validationQuery: select 'x'
  36. testWhileIdle: true
  37. testOnBorrow: false
  38. testOnReturn: false
  39. poolPreparedStatements: true
  40. maxPoolPreparedStatementPerConnectionSize: 50
  41. removeAbandoned: true
  42. filters: stat
  43. read02:
  44. url: jdbc:mysql://200.200.4.34:3306/quote?characterEncoding=utf8&useSSL=false
  45. username: root
  46. password: 123123
  47. driver-class-name: com.mysql.cj.jdbc.Driver
  48. minIdle: 5
  49. maxActive: 100
  50. initialSize: 10
  51. maxWait: 60000
  52. timeBetweenEvictionRunsMillis: 60000
  53. minEvictableIdleTimeMillis: 300000
  54. validationQuery: select 'x'
  55. testWhileIdle: true
  56. testOnBorrow: false
  57. testOnReturn: false
  58. poolPreparedStatements: true
  59. maxPoolPreparedStatementPerConnectionSize: 50
  60. removeAbandoned: true
  61. filters: stat

2、编写读库注解

  1. import java.lang.annotation.Documented;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Inherited;
  4. import java.lang.annotation.Retention;
  5. import java.lang.annotation.RetentionPolicy;
  6. import java.lang.annotation.Target;
  7. @Target({ElementType.METHOD, ElementType.TYPE})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Inherited
  10. @Documented
  11. public [@interface](https://my.oschina.net/u/996807) ReadDataSource {
  12. }

3、增加数据源初始化配置

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. import javax.sql.DataSource;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. import org.springframework.beans.factory.annotation.Value;
  7. import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
  8. import org.springframework.boot.context.properties.ConfigurationProperties;
  9. import org.springframework.context.annotation.Bean;
  10. import org.springframework.context.annotation.Configuration;
  11. import org.springframework.context.annotation.Primary;
  12. /**
  13. * name:DataSourceConfiguration
  14. * <p></p>
  15. * @author:lipeng
  16. * @data:2018年6月27日 下午5:55:35
  17. * @version 1.0
  18. */
  19. @Configuration
  20. public class DataSourceConfiguration {
  21. private static Logger log = LoggerFactory.getLogger(DataSourceConfiguration.class);
  22. @Value("${mysql.datasource.type}")
  23. private Class<? extends DataSource> dataSourceType;
  24. /**
  25. * 写库 数据源配置
  26. * @return
  27. */
  28. @Bean(name = "writeDataSource")
  29. @Primary
  30. @ConfigurationProperties(prefix = "mysql.datasource.write")
  31. public DataSource writeDataSource() {
  32. log.info("-------------------- writeDataSource init ---------------------");
  33. return DataSourceBuilder.create().type(dataSourceType).build();
  34. }
  35. /**
  36. * 有多少个从库就要配置多少个
  37. * @return
  38. */
  39. @Bean(name = "readDataSource01")
  40. @ConfigurationProperties(prefix = "mysql.datasource.read01")
  41. public DataSource readDataSourceOne() {
  42. log.info("-------------------- read01 DataSourceOne init ---------------------");
  43. return DataSourceBuilder.create().type(dataSourceType).build();
  44. }
  45. @Bean(name = "readDataSource02")
  46. @ConfigurationProperties(prefix = "mysql.datasource.read02")
  47. public DataSource readDataSourceTwo() {
  48. log.info("-------------------- read01 DataSourceOne init ---------------------");
  49. return DataSourceBuilder.create().type(dataSourceType).build();
  50. }
  51. @Bean("readDataSources")
  52. public List<DataSource> readDataSources(){
  53. List<DataSource> dataSources=new ArrayList<>();
  54. dataSources.add(readDataSourceOne());
  55. dataSources.add(readDataSourceTwo());
  56. return dataSources;
  57. }
  58. }

4、增加主从配置常量

  1. /**
  2. * name:DataSourceType
  3. * <p></p>
  4. * @author:lipeng
  5. * @data:2018年6月28日 上午9:25:44
  6. * @version 1.0
  7. */
  8. public enum DataSourceType {
  9. read("read", "从库"),
  10. write("write", "主库");
  11. private String type;
  12. private String name;
  13. DataSourceType(String type, String name) {
  14. this.type = type;
  15. this.name = name;
  16. }
  17. public String getType() {
  18. return type;
  19. }
  20. public void setType(String type) {
  21. this.type = type;
  22. }
  23. public String getName() {
  24. return name;
  25. }
  26. public void setName(String name) {
  27. this.name = name;
  28. }
  29. }

5、事务内读写配置

由于涉及到事务处理,可能会遇到事务中同时用到读库和写库,可能会有延时造成脏读,所以增加了线程变量设置,来保证一个事务内读写都是同一个库

  1. /**
  2. * name:DataSourceContextHolder
  3. * <p></p>
  4. * @author:lipeng
  5. * @data:2018年6月27日 下午5:57:39
  6. * @version 1.0
  7. */
  8. public class DataSourceContextHolder {
  9. private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
  10. //线程本地环境
  11. private static final ThreadLocal<String> local = new ThreadLocal<String>();
  12. public static ThreadLocal<String> getLocal() {
  13. return local;
  14. }
  15. /**
  16. * 读库
  17. */
  18. public static void setRead() {
  19. local.set(DataSourceType.read.getType());
  20. log.info("数据库切换到读库...");
  21. }
  22. /**
  23. * 写库
  24. */
  25. public static void setWrite() {
  26. local.set(DataSourceType.write.getType());
  27. log.info("数据库切换到写库...");
  28. }
  29. public static String getReadOrWrite() {
  30. return local.get();
  31. }
  32. public static void clear(){
  33. local.remove();
  34. }
  35. }

如果在注解在service层并且声明式事务也在service层,这个得保证拦截器优先级在声明式事务前面

  1. /**
  2. * name:DataSourceAopInService
  3. * 在service层觉得数据源
  4. * 必须在事务AOP之前执行,所以实现Ordered,order的值越小,越先执行
  5. * 如果一旦开始切换到写库,则之后的读都会走写库
  6. *
  7. * @author:lipeng
  8. * @data:2018年6月27日 下午5:59:17
  9. * @version 1.0
  10. */
  11. @Aspect
  12. @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
  13. @Component
  14. public class DataSourceAopInService implements PriorityOrdered{
  15. private static Logger log = LoggerFactory.getLogger(DataSourceAopInService.class);
  16. @Before("@annotation(com.sangfor.quote.datasource.annotation.ReadDataSource) ")
  17. public void setReadDataSourceType() {
  18. //如果已经开启写事务了,那之后的所有读都从写库读
  19. if(!DataSourceType.write.getType().equals(DataSourceContextHolder.getReadOrWrite())){
  20. DataSourceContextHolder.setRead();
  21. }
  22. }
  23. @Before("@annotation(com.sangfor.quote.datasource.annotation.WriteDataSource) ")
  24. public void setWriteDataSourceType() {
  25. DataSourceContextHolder.setWrite();
  26. }
  27. @Override
  28. public int getOrder() {
  29. /**
  30. * 值越小,越优先执行
  31. * 要优于事务的执行
  32. * 在启动类中加上了@EnableTransactionManagement(order = 10)
  33. */
  34. return 1;
  35. }
  36. }

并且在启动类或者配置类中增加注解order配置 @EnableTransactionManagement(order = 10)

6、增加mybatis相关配置类

mybatis配置

  1. @Configuration
  2. @AutoConfigureAfter(DataSourceConfiguration.class)
  3. @MapperScan(basePackages = "com.sangfor.springboot")
  4. public class MybatisConfiguration {
  5. private static Logger log = LoggerFactory.getLogger(MybatisConfiguration.class);
  6. @Value("${mysql.datasource.readSize}")
  7. private String readDataSourceSize;
  8. @Autowired
  9. @Qualifier("writeDataSource")
  10. private DataSource writeDataSource;
  11. @Autowired
  12. @Qualifier("readDataSources")
  13. private List<DataSource> readDataSources;
  14. @Bean
  15. @ConditionalOnMissingBean
  16. public SqlSessionFactory sqlSessionFactory() throws Exception {
  17. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  18. sqlSessionFactoryBean.setDataSource(roundRobinDataSouceProxy());
  19. sqlSessionFactoryBean.setTypeAliasesPackage("com.sangfor.quote.model");
  20. //设置mapper.xml文件所在位置
  21. Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml");
  22. sqlSessionFactoryBean.setMapperLocations(resources);
  23. sqlSessionFactoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
  24. return sqlSessionFactoryBean.getObject();
  25. }
  26. /**
  27. * 有多少个数据源就要配置多少个bean
  28. *
  29. * @return
  30. */
  31. @Bean
  32. public AbstractRoutingDataSource roundRobinDataSouceProxy() {
  33. int size = Integer.parseInt(readDataSourceSize);
  34. MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource(size);
  35. Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
  36. // DataSource writeDataSource = SpringContextHolder.getBean("writeDataSource");
  37. // 写
  38. targetDataSources.put(DataSourceType.write.getType(), writeDataSource);
  39. // targetDataSources.put(DataSourceType.read.getType(),readDataSource);
  40. // 多个读数据库时
  41. for (int i = 0; i < size; i++) {
  42. targetDataSources.put(i, readDataSources.get(i));
  43. }
  44. proxy.setDefaultTargetDataSource(writeDataSource);
  45. proxy.setTargetDataSources(targetDataSources);
  46. return proxy;
  47. }
  48. }

多数据源切换

  1. /**
  2. * 多数据源切换
  3. * name:MyAbstractRoutingDataSource
  4. * <p></p>
  5. * @author:lipeng
  6. * @data:2018年6月27日 下午6:57:34
  7. * @version 1.0
  8. */
  9. public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
  10. private final int dataSourceNumber;
  11. private AtomicInteger count = new AtomicInteger(0);
  12. public MyAbstractRoutingDataSource(int dataSourceNumber) {
  13. this.dataSourceNumber = dataSourceNumber;
  14. }
  15. @Override
  16. protected Object determineCurrentLookupKey() {
  17. String typeKey = DataSourceContextHolder.getReadOrWrite();
  18. if(StringUtils.isBlank(typeKey)||typeKey.equals(DataSourceType.write.getType())) {
  19. return DataSourceType.write.getType();
  20. }
  21. // 读 简单负载均衡
  22. int number = count.getAndAdd(1);
  23. int lookupKey = number % dataSourceNumber;
  24. return new Integer(lookupKey);
  25. }
  26. }

事务管理配置

  1. @Configuration
  2. @EnableTransactionManagement(order = 10)
  3. @Slf4j
  4. @AutoConfigureAfter({ MybatisConfiguration.class })
  5. public class TransactionConfiguration extends DataSourceTransactionManagerAutoConfiguration {
  6. @Bean
  7. @Autowired
  8. public DataSourceTransactionManager transactionManager(MyAbstractRoutingDataSource roundRobinDataSouceProxy) {
  9. log.info("事物配置");
  10. return new DataSourceTransactionManager(roundRobinDataSouceProxy);
  11. }
  12. }

mysql读写分离之springboot集成的更多相关文章

  1. mysql读写分离(PHP类)

    mysql读写分离(PHP类) 博客分类: php mysql   自己实现了php的读写分离,并且不用修改程序 优点:实现了读写分离,不依赖服务器硬件配置,并且都是可以配置read服务器,无限扩展 ...

  2. amoeba实现MySQL读写分离

    amoeba实现MySQL读写分离 准备环境:主机A和主机B作主从配置,IP地址为192.168.131.129和192.168.131.130,主机C作为中间件,也就是作为代理服务器,IP地址为19 ...

  3. PHP代码实现MySQL读写分离

    关于MySQL的读写分离有几种方法:中间件,Mysql驱动层,代码控制 关于中间件和Mysql驱动层实现Mysql读写分离的方法,今天暂不做研究, 这里主要写一点简单的代码来实现由PHP代码控制MyS ...

  4. 转:Mysql读写分离实现的三种方式

    1 程序修改mysql操作类可以参考PHP实现的Mysql读写分离,阿权开始的本项目,以php程序解决此需求.优点:直接和数据库通信,简单快捷的读写分离和随机的方式实现的负载均衡,权限独立分配缺点:自 ...

  5. 使用Atlas实现MySQL读写分离+MySQL-(Master-Slave)配置

    参考博文: MySQL-(Master-Slave)配置  本人按照博友北在北方的配置已成功  我使用的是 mysql5.6.27版本. 使用Atlas实现MySQL读写分离 数据切分——Atlas读 ...

  6. MySQL读写分离技术

    1.简介 当今MySQL使用相当广泛,随着用户的增多以及数据量的增大,高并发随之而来.然而我们有很多办法可以缓解数据库的压力.分布式数据库.负载均衡.读写分离.增加缓存服务器等等.这里我们将采用读写分 ...

  7. php实现MySQL读写分离

    MySQL读写分离有好几种方式 MySQL中间件 MySQL驱动层 代码控制 关于 中间件 和 驱动层的方式这里不做深究  暂且简单介绍下 如何通过PHP代码来控制MySQL读写分离 我们都知道 &q ...

  8. [记录]MySQL读写分离(Atlas和MySQL-proxy)

    MySQL读写分离(Atlas和MySQL-proxy) 一.阿里云使用Atlas从外网访问MySQL(RDS) (同样的方式修改配置文件可以实现代理也可以实现读写分离,具体看使用场景) 1.在跳板机 ...

  9. docker环境 mysql读写分离 mycat maxscale

    #mysql读写分离测试 环境centos 7.4 ,docker 17.12 ,docker-compose mysql 5.7 主从 mycat 1.6 读写分离 maxscale 2.2.4 读 ...

  10. mysql读写分离总结

    随着一个网站的业务不断扩展,数据不断增加,数据库的压力也会越来越大,对数据库或者SQL的基本优化可能达不到最终的效果,我们可以采用读写分离的策略来改变现状.读写分离现在被大量应用于很多大型网站,这个技 ...

随机推荐

  1. Python中的常见方法

    Python中有三种比较常见的方法类型,如类方法和静态方法,实例方法,他们是面向对象编程中重要的概念. 1.类方法 类方法是通过使用装饰器@classmethod来定义的,他的第一个参数是cls,指向 ...

  2. gitlab角色与权限

    用户在项目中的角色 Guest:访客.可以创建issue.发表评论,不能读写版本库.(就是看不了代码-) Reporter:Git项目测试人员.可以克隆代码,不能提交.QA.PM可以赋予这个权限. D ...

  3. 实验9.单臂路由实现Vlan互通实验

    # 单臂路由实现Vlan互通实验 本实验用于测试单臂路由方式实现Vlan路由. 实验组 实验过程 SW int g0/0/1 port link-type access port default vl ...

  4. Ubuntu 18.04安装xdrp以使用远程桌面

    背景 开发环境有一台服务器默认没有屏幕(被我拿走用来拓展屏幕了),有时候需要使用到界面但嫌弃拆显示器太麻烦,因此使用远程桌面来解决这个需求. 做法 安装xrdp sudo apt install -y ...

  5. “国产双系统”出炉,RK3568J非对称AMP:Linux+RTOS/裸机

    "非对称AMP"双系统是什么 AMP(Asymmetric Multi-Processing),即非对称多处理架构."非对称AMP"双系统是指多个核心相对独立运 ...

  6. 配置hive环境步骤(zookeeper高可用集群已搭建)

    安装mysql:1. 检查当前环境是否安装mysql服务(命令:rpm -qa | grep -i mysql)2. 卸载自带的mysql3. 卸载软件:rpm -e --nodeps mysql-l ...

  7. Linux 提权-SUID/SGID_2

    本文通过 Google 翻译 SUID | SGID Part-2 – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释 ...

  8. 微信小程序车牌键盘输入组件(支持单个删除更改,支持赋值,支持新能源)

    网上一搜一大堆类似但大多都相对简单,适用的场景并不多.多数也不支持赋值 不支持单个删除更改 我就借鉴了以下文章的思路,为了达到自己想要的效果做了相对应的更改. 借鉴文章链接:> https:// ...

  9. Git常用命令汇总以及其它相关操作

    --文件目录操作命令 1 mkdir * 创建一个空目录 *指目录名 2 pwd 显示当前目录的路径. 3 cat * 查看*文件内容 4 git rm * 删除**文件 --git初始化操作 1 g ...

  10. TokenObtainPairSerialize和TokenObtainPairView

    TokenObtainPairSerializer和TokenObtainPairView是Django REST framework的SimpleJWT库提供的两个相关的类. TokenObtain ...