资讯专栏INFORMATION COLUMN

springboot实践笔记之一:springboot+sharding-jdbc+mybatis全

Snailclimb / 1165人阅读

摘要:现在的分片策略是上海深圳分别建库,每个库都存各自交易所的两支股票的,且按照月分表。五配置分片策略数据库分片策略在这个实例中,数据库的分库就是根据上海和深圳来分的,在中是单键分片。

由于当当发布了最新的Sharding-Sphere,所以本文已经过时,不日将推出新的版本

</>复制代码

  1. 项目中遇到了分库分表的问题,找到了shrding-jdbc,于是就搞了一个springboot+sharding-jdbc+mybatis的增量分片的应用。今天写博客总结一下遇到的坑。

    其实,我自己写了一个increament-jdbc组件的,当我读了sharding-jdbc的源码之后,发现思路和原理差不多,sharding这个各方面要比我的强,毕竟我是一天之内赶出来的东东。

  2. 示例代码地址:https://gitee.com/spartajet/s...

  3. demo没有写日志,也没有各种异常判断,只是说明问题

一、需求背景

我的项目背景就不说了,现在举一个例子吧:A,B两支股票都在上海,深圳上市,需要实时记录这两支股票的交易tick(不懂tick也没有关系)。现在的分片策略是:上海、深圳分别建库,每个库都存各自交易所的两支股票的ticktick,且按照月分表。如图:

db_sh

tick_a_2017_01

tick_b_2017_01

........

tick_a_2017_12

tick_b_2017_12

db_sz

tick_a_2017_01

tick_b_2017_01

........

tick_a_2017_12

tick_b_2017_12

</>复制代码

  1. 分库分表就是这样的。根据这个建库。
  2. **千万不要讨论这样分库分表是否合适,这里这样分片只是举个栗子,说明分库分表这个事情。**
  3. **Sharding-jdbc是不支持建库的SQL,如果像我这样增量的数据库和数据表,那就要一次性把一段时期的数据库和数据表都要建好。**
二、建库

考虑到表确实多,所以我就只建1,2月份的表。语句见demo文件。

三、springboot集成sharding-jdbc

mvn配置pom如下:

</>复制代码

  1. com.spartajet
  2. springboot-sharding-jdbc-demo
  3. 0.0.1-SNAPSHOT
  4. jar
  5. springboot-sharding-jdbc-demo
  6. Springboot integrate Sharding-jdbc Demo
  7. UTF-8
  8. UTF-8
  9. UTF-8
  10. zh_CN
  11. 1.8
  12. ${java.version}
  13. 1.4.1.RELEASE
  14. 1.0.13
  15. 5.1.36
  16. 1.4.1
  17. 2.8.0
  18. 2.9.7
  19. 1.4
  20. 2.5
  21. 1.2.0
  22. org.springframework.boot
  23. spring-boot-starter-jdbc
  24. ${spring.boot.version}
  25. org.mybatis.spring.boot
  26. mybatis-spring-boot-starter
  27. ${mybatis-spring-boot-starter.version}
  28. commons-dbcp
  29. commons-dbcp
  30. ${commons-dbcp.version}
  31. com.dangdang
  32. sharding-jdbc-core
  33. ${sharding-jdbc.version}
  34. com.dangdang
  35. sharding-jdbc-config-spring
  36. ${sharding-jdbc.version}
  37. com.dangdang
  38. sharding-jdbc-self-id-generator
  39. ${sharding-jdbc.version}
  40. com.google.code.gson
  41. gson
  42. ${com.google.code.gson.version}
  43. org.springframework.boot
  44. spring-boot-starter-web
  45. ${spring.boot.version}
  46. org.springframework.boot
  47. spring-boot-start-logging
  48. org.springframework.boot
  49. spring-boot-starter-test
  50. ${spring.boot.version}
  51. test
  52. org.springframework.boot
  53. spring-boot-starter-log4j2
  54. ${spring.boot.version}
  55. log4j
  56. log4j
  57. org.springframework.boot
  58. spring-boot-starter
  59. ${spring.boot.version}
  60. org.springframework.boot
  61. spring-boot-start-logging
  62. logback-classic
  63. ch.qos.logback
  64. log4j-over-slf4j
  65. org.slf4j
  66. mysql
  67. mysql-connector-java
  68. ${mysql-connector-java.version}
  69. org.springframework.boot
  70. spring-boot-maven-plugin
  71. ${spring.boot.version}
  72. org.apache.maven.plugins
  73. maven-compiler-plugin
  74. 3.1
  75. ${project.build.jdk}
  76. ${project.build.jdk}
  77. ${project.build.sourceEncoding}
  78. org.apache.maven.plugins
  79. maven-jar-plugin
  80. 2.4

其实这个和sharding-jdbc的官网差不多。其实我想写一个sharding-jdbc-spring-boot-starter的pom的,等项目业务都做完再说吧。

四、配置数据源

我想将数据库做成可配置的,所以我没有在application.properties文件中直接配置数据库,而是写在了database.json文件中。

</>复制代码

  1. [
  2. {
  3. "name": "db_sh",
  4. "url": "jdbc:mysql://localhost:3306/db_sh",
  5. "username": "root",
  6. "password": "root",
  7. "driveClassName":"com.mysql.jdbc.Driver"
  8. },
  9. {
  10. "name": "db_sz",
  11. "url": "jdbc:mysql://localhost:3306/db_sz",
  12. "username": "root",
  13. "password": "root",
  14. "driveClassName":"com.mysql.jdbc.Driver"
  15. }
  16. ]

然后在springboot读取database文件,加载方式如下:

</>复制代码

  1. @Value("classpath:database.json")
  2. private Resource databaseFile;
  3. @Bean
  4. public List databases() throws IOException {
  5. String databasesString = IOUtils.toString(databaseFile.getInputStream(), Charset.forName("UTF-8"));
  6. List databases = new Gson().fromJson(databasesString, new TypeToken>() {
  7. }.getType());
  8. return databases;
  9. }

加载完database信息之后,可以通过工厂方法配置逻辑数据库:

</>复制代码

  1. @Bean
  2. public HashMap dataSourceMap(List databases) {
  3. Map dataSourceMap = new HashMap<>();
  4. for (Database database : databases) {
  5. DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
  6. dataSourceBuilder.url(database.getUrl());
  7. dataSourceBuilder.driverClassName(database.getDriveClassName());
  8. dataSourceBuilder.username(database.getUsername());
  9. dataSourceBuilder.password(database.getPassword());
  10. DataSource dataSource = dataSourceBuilder.build();
  11. dataSourceMap.put(database.getName(), dataSource);
  12. }
  13. return dataSourceMap;
  14. }

这样就把各个逻辑数据库就加载好了。

五、配置分片策略 5.1数据库分片策略

在这个实例中,数据库的分库就是根据上海(sh)和深圳(sz)来分的,在sharding-jdbc中是单键分片。根据官方文档实现接口SingleKeyDatabaseShardingAlgorithm就可以

</>复制代码

  1. @service
  2. public class DatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm {
  3. /**
  4. * 根据分片值和SQL的=运算符计算分片结果名称集合.
  5. *
  6. * @param availableTargetNames 所有的可用目标名称集合, 一般是数据源或表名称
  7. * @param shardingValue 分片值
  8. *
  9. * @return 分片后指向的目标名称, 一般是数据源或表名称
  10. */
  11. @Override
  12. public String doEqualSharding(Collection availableTargetNames, ShardingValue shardingValue) {
  13. String databaseName = "";
  14. for (String targetName : availableTargetNames) {
  15. if (targetName.endsWith(shardingValue.getValue())) {
  16. databaseName = targetName;
  17. break;
  18. }
  19. }
  20. return databaseName;
  21. }
  22. }

此接口还有另外两个方法,doInShardingdoBetweenSharding,因为我暂时不用IN和BETWEEN方法,所以就没有写,直接返回null。

5.2数据表分片策略

数据表的分片策略是根据股票和时间共同决定的,在sharding-jdbc中是多键分片。根据官方文档,实现MultipleKeysTableShardingAlgorithm接口就OK了

</>复制代码

  1. @service
  2. public class TableShardingAlgorithm implements MultipleKeysTableShardingAlgorithm {
  3. /**
  4. * 根据分片值计算分片结果名称集合.
  5. *
  6. * @param availableTargetNames 所有的可用目标名称集合, 一般是数据源或表名称
  7. * @param shardingValues 分片值集合
  8. *
  9. * @return 分片后指向的目标名称集合, 一般是数据源或表名称
  10. */
  11. @Override
  12. public Collection doSharding(Collection availableTargetNames, Collection> shardingValues) {
  13. String name = null;
  14. Date time = null;
  15. for (ShardingValue shardingValue : shardingValues) {
  16. if (shardingValue.getColumnName().equals("name")) {
  17. name = ((ShardingValue) shardingValue).getValue();
  18. }
  19. if (shardingValue.getColumnName().equals("time")) {
  20. time = ((ShardingValue) shardingValue).getValue();
  21. }
  22. if (name != null && time != null) {
  23. break;
  24. }
  25. }
  26. String timeString = new SimpleDateFormat("yyyy_MM").format(time);
  27. String suffix = name + "_" + timeString;
  28. Collection result = new LinkedHashSet<>();
  29. for (String targetName : availableTargetNames) {
  30. if (targetName.endsWith(suffix)) {
  31. result.add(targetName);
  32. }
  33. }
  34. return result;
  35. }
  36. }

这些方法的使用可以查官方文档。

5.3注入分片策略

以上只是定义了分片算法,还没有形成策略,还没有告诉shrding将哪个字段给分片算法:

</>复制代码

  1. @Configuration
  2. public class ShardingStrategyConfig {
  3. @Bean
  4. public DatabaseShardingStrategy databaseShardingStrategy(DatabaseShardingAlgorithm databaseShardingAlgorithm) {
  5. DatabaseShardingStrategy databaseShardingStrategy = new DatabaseShardingStrategy("exchange", databaseShardingAlgorithm);
  6. return databaseShardingStrategy;
  7. }
  8. @Bean
  9. public TableShardingStrategy tableShardingStrategy(TableShardingAlgorithm tableShardingAlgorithm) {
  10. Collection columns = new LinkedList<>();
  11. columns.add("name");
  12. columns.add("time");
  13. TableShardingStrategy tableShardingStrategy = new TableShardingStrategy(columns, tableShardingAlgorithm);
  14. return tableShardingStrategy;
  15. }
  16. }

这样才能形成完成的分片策略。

六、配置Sharding-jdbc的DataSource

sharding-jdbc的原理其实很简单,就是自己做一个DataSource给上层应用使用,这个DataSource包含所有的逻辑库和逻辑表,应用增删改查时,他自己再修改sql,然后选择合适的数据库继续操作。所以这个DataSource创建很重要。

</>复制代码

  1. @Bean
  2. @Primary
  3. public DataSource shardingDataSource(HashMap dataSourceMap, DatabaseShardingStrategy databaseShardingStrategy, TableShardingStrategy tableShardingStrategy) {
  4. DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap);
  5. TableRule tableRule = TableRule.builder("tick").actualTables(Arrays.asList("db_sh.tick_a_2017_01", "db_sh.tick_a_2017_02", "db_sh.tick_b_2017_01", "db_sh.tick_b_2017_02", "db_sz.tick_a_2017_01", "db_sz.tick_a_2017_02", "db_sz.tick_b_2017_01", "db_sz.tick_a_2017_02")).dataSourceRule(dataSourceRule).build();
  6. ShardingRule shardingRule = ShardingRule.builder().dataSourceRule(dataSourceRule).tableRules(Arrays.asList(tableRule)).databaseShardingStrategy(databaseShardingStrategy).tableShardingStrategy(tableShardingStrategy).build();
  7. DataSource shardingDataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
  8. return shardingDataSource;
  9. }

这里要着重说一下为什么要用@Primary这个注解,没有这个注解是会报错的,错误大致意思就是DataSource太多了,mybatis不知道用哪个。加上这个mybatis就知道用sharding的DataSource了。这里参考的是jpa的多数据源配置

七、配置mybatis 7.1 Bean

</>复制代码

  1. public class Tick {
  2. private long id;
  3. private String name;
  4. private String exchange;
  5. private int ask;
  6. private int bid;
  7. private Date time;
  8. }
7.2 Mapper

很简单,只实现一个插入方法

</>复制代码

  1. @Mapper
  2. public interface TickMapper {
  3. @Insert("insert into tick (id,name,exchange,ask,bid,time) values (#{id},#{name},#{exchange},#{ask},#{bid},#{time})")
  4. void insertTick(Tick tick);
  5. }
7.3 SessionFactory配置

还要设置一下tick的SessionFactory:

</>复制代码

  1. @Configuration
  2. @MapperScan(basePackages = "com.spartajet.shardingboot.mapper", sqlSessionFactoryRef = "sessionFactory")
  3. public class TickSessionFactoryConfig {
  4. @Bean
  5. public SqlSessionFactory sessionFactory(DataSource shardingDataSource) throws Exception {
  6. final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
  7. sessionFactory.setDataSource(shardingDataSource);
  8. return sessionFactory.getObject();
  9. }
  10. @Bean
  11. public CommonSelfIdGenerator commonSelfIdGenerator() {
  12. CommonSelfIdGenerator.setClock(AbstractClock.systemClock());
  13. CommonSelfIdGenerator commonSelfIdGenerator = new CommonSelfIdGenerator();
  14. return commonSelfIdGenerator;
  15. }
  16. }

这里添加了一个CommonSelfIdGenerator,sharding自带的id生成器,看了下代码和facebooksnowflake类似。我又不想把数据库的主键设置成自增的,否则数据双向同步会死的很惨的。

八、测试写入

</>复制代码

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @SpringBootTest
  3. public class SpringbootShardingJdbcDemoApplicationTests {
  4. @Autowired
  5. private TickMapper tickMapper;
  6. @Autowired
  7. private CommonSelfIdGenerator commonSelfIdGenerator;
  8. @Test
  9. public void contextLoads() {
  10. Tick tick = new Tick(commonSelfIdGenerator.generateId().longValue(), "a", "sh", 100, 200, new Date());
  11. this.tickMapper.insertTick(tick);
  12. }
  13. }

成功实现增量分库分表!!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71030.html

相关文章

  • java篇

    摘要:多线程编程这篇文章分析了多线程的优缺点,如何创建多线程,分享了线程安全和线程通信线程池等等一些知识。 中间件技术入门教程 中间件技术入门教程,本博客介绍了 ESB、MQ、JMS 的一些知识... SpringBoot 多数据源 SpringBoot 使用主从数据源 简易的后台管理权限设计 从零开始搭建自己权限管理框架 Docker 多步构建更小的 Java 镜像 Docker Jav...

    honhon 评论0 收藏0
  • 基于 SpringBoot2.0+优雅整合 SpringBoot+Mybatis

    摘要:基于最新的,是你学习的最佳指南。驱动程序通过自动注册,手动加载类通常是不必要。由于加上了注解,如果转账中途出了意外和的钱都不会改变。三的方式项目结构相比于注解的方式主要有以下几点改变,非常容易实现。公众号多篇文章被各大技术社区转载。 Github 地址:https://github.com/Snailclimb/springboot-integration-examples(Sprin...

    gghyoo 评论0 收藏0
  • 写这么多系列博客,怪不得找不到女朋友

    摘要:前提好几周没更新博客了,对不断支持我博客的童鞋们说声抱歉了。熟悉我的人都知道我写博客的时间比较早,而且坚持的时间也比较久,一直到现在也是一直保持着更新状态。 showImg(https://segmentfault.com/img/remote/1460000014076586?w=1920&h=1080); 前提 好几周没更新博客了,对不断支持我博客的童鞋们说声:抱歉了!。自己这段时...

    JerryWangSAP 评论0 收藏0
  • Java相关

    摘要:本文是作者自己对中线程的状态线程间协作相关使用的理解与总结,不对之处,望指出,共勉。当中的的数目而不是已占用的位置数大于集合番一文通版集合番一文通版垃圾回收机制讲得很透彻,深入浅出。 一小时搞明白自定义注解 Annotation(注解)就是 Java 提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法。Annotion(注解) 是一个接口,程序可以通过...

    wangtdgoodluck 评论0 收藏0
  • Springboot应用缓存实践之:Ehcache加持

    摘要:但本文将讲述如何将缓存应用到应用中。这是的使用注解之一,除此之外常用的还有和,分别简单介绍一下配置在方法上表示其返回值将被加入缓存。 showImg(https://segmentfault.com/img/remote/1460000016643568); 注: 本文首发于 博客 CodeSheep · 程序羊,欢迎光临 小站!本文共 851字,阅读大约需要 3分钟 ! 本文内...

    luzhuqun 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<