经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring Boot » 查看文章
Spring Boot+MyBatis+MySQL读写分离
来源:cnblogs  作者:xiaoxin101  时间:2019/6/18 9:03:16  对本文有异议

读写分离要做的事情就是对于一条sql语句该选择去哪个数据库执行,至于谁来做选择数据库的事情,无非两个,1:中间件(比如MyCat);二:程序自己去做分离操作。 

  但是从程序成眠去做读写分离最大的弱点就是无法的动态去增加数据库节点,因为数据源配置都是卸载配置中的,新增数据库意味着新加一个数据源,必须改配置,并重启应用,好处是简单

  

2.AbstractRoutingDataSource

  基于特定的key路由到特定的数据源,他内部维护了一组目标数据源,并且做了路由key与目标源之间的映射,提供基于key查找数据源的方法。

  

 

3.实践

 关于数据库主从配置参考:

  www.cnblogs.com/cjsblog/p/9706370.html

3.1Maven

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5.  
  6. <groupId>com.cjs.example</groupId>
  7. <artifactId>cjs-datasource-demo</artifactId>
  8. <version>0.0.1-SNAPSHOT</version>
  9. <packaging>jar</packaging>
  10.  
  11. <name>cjs-datasource-demo</name>
  12. <description></description>
  13.  
  14. <parent>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-parent</artifactId>
  17. <version>2.0.5.RELEASE</version>
  18. <relativePath/> <!-- lookup parent from repository -->
  19. </parent>
  20.  
  21. <properties>
  22. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  23. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  24. <java.version>1.8</java.version>
  25. </properties>
  26.  
  27. <dependencies>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-aop</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-jdbc</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-web</artifactId>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.mybatis.spring.boot</groupId>
  42. <artifactId>mybatis-spring-boot-starter</artifactId>
  43. <version>1.3.2</version>
  44. </dependency>
  45. <dependency>
  46. <groupId>org.apache.commons</groupId>
  47. <artifactId>commons-lang3</artifactId>
  48. <version>3.8</version>
  49. </dependency>
  50.  
  51. <dependency>
  52. <groupId>mysql</groupId>
  53. <artifactId>mysql-connector-java</artifactId>
  54. <scope>runtime</scope>
  55. </dependency>
  56. <dependency>
  57. <groupId>org.springframework.boot</groupId>
  58. <artifactId>spring-boot-starter-test</artifactId>
  59. <scope>test</scope>
  60. </dependency>
  61. </dependencies>
  62.  
  63. <build>
  64. <plugins>
  65. <plugin>
  66. <groupId>org.springframework.boot</groupId>
  67. <artifactId>spring-boot-maven-plugin</artifactId>
  68. </plugin>
  69.  
  70.  
  71. <!--<plugin>
  72. <groupId>org.mybatis.generator</groupId>
  73. <artifactId>mybatis-generator-maven-plugin</artifactId>
  74. <version>1.3.5</version>
  75. <dependencies>
  76. <dependency>
  77. <groupId>mysql</groupId>
  78. <artifactId>mysql-connector-java</artifactId>
  79. <version>5.1.46</version>
  80. </dependency>
  81. </dependencies>
  82. <configuration>
  83. <configurationFile>${basedir}/src/main/resources/myBatisGeneratorConfig.xml</configurationFile>
  84. <overwrite>true</overwrite>
  85. </configuration>
  86. <executions>
  87. <execution>
  88. <id>Generate MyBatis Artifacts</id>
  89. <goals>
  90. <goal>generate</goal>
  91. </goals>
  92. </execution>
  93. </executions>
  94. </plugin>-->
  95.  
  96. </plugins>
  97. </build>
  98. </project>

  3.2数据源配置

application.yml

  1. spring:
  2. datasource:
  3. master:
  4. jdbc-url: jdbc:mysql://192.168.102.31:3306/test
  5. username: root
  6. password: 123456
  7. driver-class-name: com.mysql.jdbc.Driver
  8. slave1:
  9. jdbc-url: jdbc:mysql://192.168.102.56:3306/test
  10. username: pig # 只读账户
  11. password: 123456
  12. driver-class-name: com.mysql.jdbc.Driver
  13. slave2:
  14. jdbc-url: jdbc:mysql://192.168.102.36:3306/test
  15. username: pig # 只读账户
  16. password: 123456
  17. driver-class-name: com.mysql.jdbc.Driver

  多数据源配置

  1. /**
  2. * 关于数据源配置,参考SpringBoot官方文档第79章《Data Access》
  3. * 79. Data Access
  4. * 79.1 Configure a Custom DataSource
  5. * 79.2 Configure Two DataSources
  6. */
  7.  
  8. @Configuration
  9. public class DataSourceConfig {
  10.  
  11. @Bean
  12. @ConfigurationProperties("spring.datasource.master")
  13. public DataSource masterDataSource() {
  14. return DataSourceBuilder.create().build();
  15. }
  16.  
  17. @Bean
  18. @ConfigurationProperties("spring.datasource.slave1")
  19. public DataSource slave1DataSource() {
  20. return DataSourceBuilder.create().build();
  21. }
  22.  
  23. @Bean
  24. @ConfigurationProperties("spring.datasource.slave2")
  25. public DataSource slave2DataSource() {
  26. return DataSourceBuilder.create().build();
  27. }
  28.  
  29. @Bean
  30. public DataSource myRoutingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
  31. @Qualifier("slave1DataSource") DataSource slave1DataSource,
  32. @Qualifier("slave2DataSource") DataSource slave2DataSource) {
  33. Map<Object, Object> targetDataSources = new HashMap<>();
  34. targetDataSources.put(DBTypeEnum.MASTER, masterDataSource);
  35. targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource);
  36. targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource);
  37. MyRoutingDataSource myRoutingDataSource = new MyRoutingDataSource();
  38. myRoutingDataSource.setDefaultTargetDataSource(masterDataSource);
  39. myRoutingDataSource.setTargetDataSources(targetDataSources);
  40. return myRoutingDataSource;
  41. }
  42.  
  43. }

  这里,我们配置了4个数据源,1个master,2两个slave,1个路由数据源。前3个数据源都是为了生成第4个数据源,而且后续我们只用这最后一个路由数据源。

MyBatis配置

  1. @EnableTransactionManagement
  2. @Configuration
  3. public class MyBatisConfig {
  4.  
  5. @Resource(name = "myRoutingDataSource")
  6. private DataSource myRoutingDataSource;
  7.  
  8. @Bean
  9. public SqlSessionFactory sqlSessionFactory() throws Exception {
  10. SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
  11. sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
  12. sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
  13. return sqlSessionFactoryBean.getObject();
  14. }
  15.  
  16. @Bean
  17. public PlatformTransactionManager platformTransactionManager() {
  18. return new DataSourceTransactionManager(myRoutingDataSource);
  19. }
  20. }

  由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。

3.3设置路由key/查找数据源

  

目标数据源就是那前3个这个我们是知道的,但是使用的时候是如果查找数据源的呢?

首先,我们定义一个枚举来代表这三个数据源

 

  1. package com.cjs.example.enums;
  2.  
  3. public enum DBTypeEnum {
  4. MASTER, SLAVE1, SLAVE2;
  5. }

  接下来,通过ThreadLocal将数据源设置到每个线程上下文中

  1. public class DBContextHolder {
  2.  
  3. private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>();
  4.  
  5. private static final AtomicInteger counter = new AtomicInteger(-1);
  6.  
  7. public static void set(DBTypeEnum dbType) {
  8. contextHolder.set(dbType);
  9. }
  10.  
  11. public static DBTypeEnum get() {
  12. return contextHolder.get();
  13. }
  14.  
  15. public static void master() {
  16. set(DBTypeEnum.MASTER);
  17. System.out.println("切换到master");
  18. }
  19.  
  20. public static void slave() {
  21. // 轮询
  22. int index = counter.getAndIncrement() % 2;
  23. if (counter.get() > 9999) {
  24. counter.set(-1);
  25. }
  26. if (index == 0) {
  27. set(DBTypeEnum.SLAVE1);
  28. System.out.println("切换到slave1");
  29. }else {
  30. set(DBTypeEnum.SLAVE2);
  31. System.out.println("切换到slave2");
  32. }
  33. }
  34.  
  35. }

 获取路由key 

 

  1. public class MyRoutingDataSource extends AbstractRoutingDataSource {
  2. @Nullable
  3. @Override
  4. protected Object determineCurrentLookupKey() {
  5. return DBContextHolder.get();
  6. }
  7. }

  

设置路由key

默认情况下,所有的查询都走从库,插入/修改/删除走主库。我们通过方法名来区分操作类型(CRUD)

  1. @Aspect
  2. @Component
  3. public class DataSourceAop {
  4.  
  5. @Pointcut("!@annotation(com.cjs.example.annotation.Master) " +
  6. "&& (execution(* com.cjs.example.service..*.select*(..)) " +
  7. "|| execution(* com.cjs.example.service..*.get*(..)))")
  8. public void readPointcut() {
  9.  
  10. }
  11.  
  12. @Pointcut("@annotation(com.cjs.example.annotation.Master) " +
  13. "|| execution(* com.cjs.example.service..*.insert*(..)) " +
  14. "|| execution(* com.cjs.example.service..*.add*(..)) " +
  15. "|| execution(* com.cjs.example.service..*.update*(..)) " +
  16. "|| execution(* com.cjs.example.service..*.edit*(..)) " +
  17. "|| execution(* com.cjs.example.service..*.delete*(..)) " +
  18. "|| execution(* com.cjs.example.service..*.remove*(..))")
  19. public void writePointcut() {
  20.  
  21. }
  22.  
  23. @Before("readPointcut()")
  24. public void read() {
  25. DBContextHolder.slave();
  26. }
  27.  
  28. @Before("writePointcut()")
  29. public void write() {
  30. DBContextHolder.master();
  31. }
  32.  
  33.  
  34. /**
  35. * 另一种写法:if...else... 判断哪些需要读从数据库,其余的走主数据库
  36. */
  37. // @Before("execution(* com.cjs.example.service.impl.*.*(..))")
  38. // public void before(JoinPoint jp) {
  39. // String methodName = jp.getSignature().getName();
  40. //
  41. // if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
  42. // DBContextHolder.slave();
  43. // }else {
  44. // DBContextHolder.master();
  45. // }
  46. // }
  47. }

  有一般情况就有特殊情况,特殊情况是某些情况下我们需要强制读主库,针对这种情况,我们定义一个主键,用该注解标注的就读主库

  1. package com.cjs.example.annotation;
  2.  
  3. public @interface Master {
  4. }

  例如,假设我们有一张表member

  1. @Service
  2. public class MemberServiceImpl implements MemberService {
  3.  
  4. @Autowired
  5. private MemberMapper memberMapper;
  6.  
  7. @Transactional
  8. @Override
  9. public int insert(Member member) {
  10. return memberMapper.insert(member);
  11. }
  12.  
  13. @Master
  14. @Override
  15. public int save(Member member) {
  16. return memberMapper.insert(member);
  17. }
  18.  
  19. @Override
  20. public List<Member> selectAll() {
  21. return memberMapper.selectByExample(new MemberExample());
  22. }
  23.  
  24. @Master
  25. @Override
  26. public String getToken(String appId) {
  27. // 有些读操作必须读主数据库
  28. // 比如,获取微信access_token,因为高峰时期主从同步可能延迟
  29. // 这种情况下就必须强制从主数据读
  30. return null;
  31. }
  32. }

  4.测试

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest
  3. public class CjsDatasourceDemoApplicationTests {
  4.  
  5. @Autowired
  6. private MemberService memberService;
  7.  
  8. @Test
  9. public void testWrite() {
  10. Member member = new Member();
  11. member.setName("zhangsan");
  12. memberService.insert(member);
  13. }
  14. @Test
  15. public void testRead() {
  16. for (int i = 0; i < 4; i++) {
  17. memberService.selectAll();
  18. }
  19. }
  20. @Test
  21. public void testSave() {
  22. Member member = new Member();
  23. member.setName("wangwu");
  24. memberService.save(member);
  25. }
  26. @Test
  27. public void testReadFromMaster() {
  28. memberService.getToken("1234");
  29. }
  30. }

  查看控制台

5.工程结构

原文链接:http://www.cnblogs.com/xiaoxin101/p/11041304.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号