Java实战:教你如何进行数据库分库分表

我们知道,当前的应用都离不开数据库,随着数据库中的数据越来越多,单表突破性能上限记录时,如MySQL单表上线估计在近千万条内,当记录数继续增长时,从性能考虑,则需要进行拆分处理。而拆分分为横向拆分和纵向拆分。一般来说,采用横向拆分较多,这样的表结构是一致的,只是不同的数据存储在不同的数据库表中。其中横向拆分也分为分库和分表。

1.示例数据库准备

为了说清楚如何用Java语言和相关框架实现业务表的分库和分表处理。这里首先用MySQL数据库中创建两个独立的数据库实例,名字为mydb和mydb2,此可演示分库操作。另外在每个数据库实例中,创建12个业务表,按年月进行数据拆分。具体的创建表脚本如下:

 
 
 
 
  1. CREATE TABLE `t_bill_2021_1` ( 
  2.   `order_id` bigint(20) NOT NULL  COMMENT '订单id', 
  3.   `user_id` int(20) NOT NULL COMMENT '用户id', 
  4.   `address_id` bigint(20) NOT NULL COMMENT '地址id', 
  5.   `status` char(1) DEFAULT NULL COMMENT '订单状态', 
  6.   `create_time` datetime DEFAULT NULL COMMENT '创建时间', 
  7.   PRIMARY KEY (`order_id`) USING BTREE 
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 
  9.  
  10. CREATE TABLE `t_bill_2021_2` ( 
  11.   `order_id` bigint(20) NOT NULL  COMMENT '订单id', 
  12.   `user_id` int(20) NOT NULL COMMENT '用户id', 
  13.   `address_id` bigint(20) NOT NULL COMMENT '地址id', 
  14.   `status` char(1) DEFAULT NULL COMMENT '订单状态', 
  15.   `create_time` datetime DEFAULT NULL COMMENT '创建时间', 
  16.   PRIMARY KEY (`order_id`) USING BTREE 
  17. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 
  18. -- 省略.... 
  19. CREATE TABLE `t_bill_2021_12` ( 
  20.   `order_id` bigint(20) NOT NULL  COMMENT '订单id', 
  21.   `user_id` int(20) NOT NULL COMMENT '用户id', 
  22.   `address_id` bigint(20) NOT NULL COMMENT '地址id', 
  23.   `status` char(1) DEFAULT NULL COMMENT '订单状态', 
  24.   `create_time` datetime DEFAULT NULL COMMENT '创建时间', 
  25.   PRIMARY KEY (`order_id`) USING BTREE 
  26. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; 

成功执行脚本后,在MySQL管理工具中可以看到如下的示例界面:

2.分库分表实现

在Java语言下的框架中,有众多的开源框架,其中关于分库分表的框架,可以选择Apache ShardingSphere,其官网介绍说:ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。 它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。Apache ShardingSphere 5.x 版本开始致力于可插拔架构。 目前,数据分片、读写分离、数据加密、影子库压测等功能,以及 MySQL、PostgreSQL、SQLServer、Oracle 等 SQL 与协议的支持,均通过插件的方式织入项目。官网地址为: https://shardingsphere.apache.org/index_zh.html 。

下面的示例采用Spring Boot框架来实现,相关的库通过Maven进行管理。首先给出pom.xml配置文件的定义:

 
 
 
 
  1.  
  2.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 
  3.     4.0.0 
  4.      
  5.         org.springframework.boot 
  6.         spring-boot-starter-parent 
  7.         2.5.3 
  8.           
  9.      
  10.     com.example 
  11.     wyd 
  12.     0.0.1-SNAPSHOT 
  13.     wyd 
  14.     Demo project for Spring Boot 
  15.      
  16.         1.8 
  17.         3.1.1 
  18.         4.0.0-RC2 
  19.         5.0.0-beta 
  20.      
  21.      
  22.          
  23.             org.springframework.boot 
  24.             spring-boot-starter-web 
  25.          
  26.          
  27.             org.mybatis.spring.boot 
  28.             mybatis-spring-boot-starter 
  29.             2.0.1 
  30.          
  31.          
  32.             com.baomidou 
  33.             mybatis-plus-boot-starter 
  34.             ${mybatis-plus.version} 
  35.          
  36.          
  37.             org.projectlombok 
  38.             lombok 
  39.             true 
  40.          
  41.          
  42.             joda-time 
  43.             joda-time 
  44.             2.9.8 
  45.          
  46.          
  47.             org.apache.shardingsphere 
  48.             sharding-jdbc-spring-boot-starter 
  49.             ${sharding-sphere.version} 
  50.          
  51.          
  52.             org.apache.shardingsphere 
  53.             sharding-jdbc-spring-namespace 
  54.             ${sharding-sphere.version} 
  55.          
  56.          
  57.             mysql 
  58.             mysql-connector-java 
  59.             runtime 
  60.          
  61.          
  62.             org.postgresql 
  63.             postgresql 
  64.             runtime 
  65.          
  66.          
  67.             org.springframework.boot 
  68.             spring-boot-starter-test 
  69.             test 
  70.          
  71.      
  72.      
  73.          
  74.              
  75.                 org.springframework.boot 
  76.                 spring-boot-maven-plugin 
  77.              
  78.          
  79.      
  80.  

其次,给出一个实体类,它对应于上述创建的数据库表t_bill,其定义如下:

 
 
 
 
  1. package com.example.wyd.dao; 
  2. import com.baomidou.mybatisplus.annotation.TableName; 
  3. import lombok.Data; 
  4. import java.util.Date; 
  5. @Data 
  6. @TableName("t_bill") 
  7. public class Bill { 
  8.     private Long orderId; 
  9.     private Integer userId; 
  10.     private Long addressId; 
  11.     private String status; 
  12.     private Date createTime; 
  13.     public void setOrderId(Long orderId) { 
  14.         this.orderId = orderId; 
  15.     } 
  16.     public void setUserId(Integer userId) { 
  17.         this.userId = userId; 
  18.     } 
  19.     public void setAddressId(Long addressId) { 
  20.         this.addressId = addressId; 
  21.     } 
  22.     public void setStatus(String status) { 
  23.         this.status = status; 
  24.     } 
  25.     public void setCreateTime(Date createTime) { 
  26.         this.createTime = createTime; 
  27.     } 
  28. 映射类BillMapper定义如下: 
  29.  
  30. package com.example.wyd.mapper; 
  31. import com.baomidou.mybatisplus.core.mapper.BaseMapper; 
  32. import com.example.wyd.dao.Bill; 
  33. public interface BillMapper extends BaseMapper { 
  34.  

服务类接口定义如下:

 
 
 
 
  1. package com.example.wyd.service; 
  2. import com.baomidou.mybatisplus.extension.service.IService; 
  3. import com.example.wyd.dao.Bill; 
  4. public interface BillService extends IService { 
  5.  

服务类接口的实现类定义如下:

 
 
 
 
  1. package com.example.wyd.service; 
  2. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 
  3. import com.example.wyd.dao.Bill; 
  4. import com.example.wyd.mapper.BillMapper; 
  5. import org.springframework.stereotype.Service; 
  6. @Service 
  7. public class BillServiceImpl extends ServiceImpl implements BillService { 
  8.  

这里我们采用了MybatisPlus框架,它可以很方便的进行数据库相关操作,而无需过多写SQL来实现具体业务逻辑。通过上述定义,通过继承接口的方式,并提供实体类的定义,MybatisPlus框架会通过反射机制来根据数据库设置来生成SQL语句,其中包含增删改查接口,具体的实现我们并未具体定义。

下面定义一个自定义的分库算法,具体实现如下:

 
 
 
 
  1. package com.example.wyd; 
  2. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm; 
  3. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue; 
  4. import java.util.Collection; 
  5. //自定义数据库分片算法 
  6. public class DBShardingAlgorithm implements PreciseShardingAlgorithm { 
  7.     @Override 
  8.     public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) { 
  9.         //真实数据库节点 
  10.         availableTargetNames.stream().forEach((item) -> { 
  11.            System.out.println("actual db:" + item); 
  12.         }); 
  13.         //逻辑表以及分片的字段名 
  14.         System.out.println("logicTable:"+shardingValue.getLogicTableName()+";shardingColumn:"+ shardingValue.getColumnName()); 
  15.         //分片数据字段值 
  16.         System.out.println("shardingColumn value:"+ shardingValue.getValue().toString()); 
  17.         //获取字段值 
  18.         long orderId = shardingValue.getValue(); 
  19.         //分片索引计算 0 , 1 
  20.         long db_index = orderId & (2 - 1); 
  21.         for (String each : availableTargetNames) { 
  22.             if (each.equals("ds"+db_index)) { 
  23.                 //匹配的话,返回数据库名 
  24.                 return each; 
  25.             } 
  26.         } 
  27.         throw new IllegalArgumentException(); 
  28.     } 

下面给出数据的分表逻辑,这个定义稍显复杂一点,就是根据业务数据的日期字段值,根据月份落入对应的物理数据表中。实现示例代码如下:

 
 
 
 
  1. package com.example.wyd; 
  2. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm; 
  3. import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue; 
  4. import java.util.Collection; 
  5. import java.util.Date; 
  6. //表按日期自定义分片 
  7. public class TableShardingAlgorithm implements PreciseShardingAlgorithm { 
  8.     @Override 
  9.     public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) { 
  10.         //真实数据库节点 
  11.         availableTargetNames.stream().forEach((item) -> { 
  12.             System.out.println("actual db:" + item); 
  13.         }); 
  14.         //逻辑表以及分片的字段名 
  15.         System.out.println("logicTable:"+shardingValue.getLogicTableName()+";shardingColumn:"+ shardingValue.getColumnName()); 
  16.         //分片数据字段值 
  17.         System.out.println("shardingColumn value:"+ shardingValue.getValue().toString()); 
  18.         //获取表名前缀 
  19.         String tb_name = shardingValue.getLogicTableName() + "_"; 
  20.         //根据日期分表 
  21.         Date date = shardingValue.getValue(); 
  22.         String year = String.format("%tY", date); 
  23.         String mon =String.valueOf(Integer.parseInt(String.format("%tm", date))); 
  24.         //String dat = String.format("%td", date); //也可以安装年月日来分表 
  25.         // 选择表 
  26.         tb_name = tb_name + year + "_" + mon; 
  27.         //实际的表名 
  28.         System.out.println("tb_name:" + tb_name); 
  29.         for (String each : availableTargetNames) { 
  30.             //System.out.println("availableTableName:" + each); 
  31.             if (each.equals(tb_name)) { 
  32.                 //返回物理表名 
  33.                 return each; 
  34.             } 
  35.         } 
  36.         throw new IllegalArgumentException(); 
  37.     } 

数据的分库分表可以在Spring Boot的属性配置文件中进行设( application.properties ):

 
 
 
 
  1. server.port=8080 
  2. ######################################################################################################### 
  3. # 配置ds0 和ds1两个数据源 
  4. spring.shardingsphere.datasource.names = ds0,ds1 
  5.  
  6. #ds0 配置 
  7. spring.shardingsphere.datasource.ds0.type = com.zaxxer.hikari.HikariDataSource 
  8. spring.shardingsphere.datasource.ds0.driver-class-name = com.mysql.cj.jdbc.Driver 
  9. spring.shardingsphere.datasource.ds0.jdbc-url = jdbc:mysql://127.0.0.1:3306/mydb?characterEncoding=utf8 
  10. spring.shardingsphere.datasource.ds0.username = uname 
  11. spring.shardingsphere.datasource.ds0.password = pwd 
  12.  
  13. #ds1 配置 
  14. spring.shardingsphere.datasource.ds1.type = com.zaxxer.hikari.HikariDataSource 
  15. spring.shardingsphere.datasource.ds1.driver-class-name = com.mysql.cj.jdbc.Driver 
  16. spring.shardingsphere.datasource.ds1.jdbc-url = jdbc:mysql://127.0.0.1:3306/mydb2characterEncoding=utf8 
  17. spring.shardingsphere.datasource.ds1.username = uname 
  18. spring.shardingsphere.datasource.ds1.password = pwd 
  19. ######################################################################################################### 
  20. # 默认的分库策略:id取模 
  21. spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column = id 
  22. spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression = ds$->{id % 2} 
  23. ######################################################################################################### 
  24. spring.shardingsphere.sharding.tables.t_bill.actual-data-nodes=ds$->{0..1}.t_bill_$->{2021..2021}_$->{1..12} 
  25. #数据库分片字段 
  26. spring.shardingsphere.sharding.tables.t_bill.database-strategy.standard.sharding-column=order_id 
  27. #自定义数据库分片策略 
  28. spring.shardingsphere.sharding.tables.t_bill.database-strategy.standard.precise-algorithm-class-name=com.example.wyd.DBShardingAlgorithm 
  29. #表分片字段 
  30. spring.shardingsphere.sharding.tables.t_bill.table-strategy.standard.sharding-column=create_time 
  31. #自定义表分片策略 
  32. spring.shardingsphere.sharding.tables.t_bill.table-strategy.standard.precise-algorithm-class-name=com.example.wyd.TableShardingAlgorithm 
  33. ######################################################################################################### 
  34. # 使用SNOWFLAKE算法生成主键 
  35. spring.shardingsphere.sharding.tables.t_bill.key-generator.column = order_id 
  36. spring.shardingsphere.sharding.tables.t_bill.key-generator.type = SNOWFLAKE 
  37. spring.shardingsphere.sharding.tables.t_bill.key-generator.props.worker.id=123 
  38. ######################################################################################################### 
  39. spring.shardingsphere.props.sql.show = true 

最后,我们给出一个定义的Controller类型,来测试分库分表的查询和保存操作是否正确。HomeController类定义如下:

 
 
 
 
  1. package com.example.wyd.controller; 
  2. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 
  3. import com.example.wyd.dao.Bill; 
  4. import com.example.wyd.service.BillService; 
  5. import org.joda.time.DateTime; 
  6. import org.springframework.beans.factory.annotation.Autowired; 
  7. import org.springframework.web.bind.annotation.RequestMapping; 
  8. import org.springframework.web.bind.annotation.RequestParam; 
  9. import org.springframework.web.bind.annotation.RestController; 
  10. import java.text.ParseException; 
  11. import java.text.SimpleDateFormat; 
  12. import java.util.Date; 
  13. import java.util.List; 
  14. @RestController 
  15. @RequestMapping("/api") 
  16. public class HomeController { 
  17.     @Autowired 
  18.     private BillService billService; 
  19.     //http://localhost:8080/api/query?start=2021-02-07%2000:00:00&end=2021-03-07%2000:00:00 
  20.     @RequestMapping("/query") 
  21.     public List queryList(@RequestParam("start") String start, @RequestParam("end") String end) { 
  22.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
  23.         try { 
  24.             Date date = sdf.parse(start); 
  25.             Date date2 = sdf.parse(end); 
  26.             QueryWrapper queryWrapper = new QueryWrapper<>(); 
  27.             queryWrapper.ge("create_time",date) 
  28.                     .and(qw-> qw.le("create_time", date2)).last("limit 1,10"); 
  29.             List billIPage = billService.list(queryWrapper); 
  30.             System.out.println(billIPage.size()); 
  31.             billIPage.forEach(System.out::println); 
  32.             return billIPage; 
  33.         } catch (ParseException e) { 
  34.             e.printStackTrace(); 
  35.         } 
  36.         return null; 
  37.     } 
  38.     //http://localhost:8080/api/save?userid=999&addressId=999&status=M&date=2021-03-07%2000:00:00 
  39.     @RequestMapping("/save") 
  40.     public String Save(@RequestParam("userid") int userId, @RequestParam("addressId") long AddressId, 
  41.                        @RequestParam("status") String status 
  42.             ,@RequestParam("date") String strDate) { 
  43.         String ret ="0"; 
  44.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
  45.         try { 
  46.             Date date = sdf.parse(strDate); 
  47.             Bill bill = new Bill(); 
  48.             bill.setUserId(userId); 
  49.             bill.setAddressId(AddressId); 
  50.             bill.setStatus(status); 
  51.             bill.setCreateTime(date); 
  52.             boolean isOk = billService.save(bill); 
  53.             if (isOk){ 
  54.                 ret ="1"; 
  55.             } 
  56.         } catch (ParseException e) { 
  57.             e.printStackTrace(); 
  58.         } 
  59.         return ret; 
  60.     } 

至此,我们可以用测试类初始化一些数据,并做一些初步的数据操作测试:

 
 
 
 
  1. package com.example.wyd; 
  2.  
  3. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; 
  4. import com.example.wyd.dao.Bill; 
  5. import com.example.wyd.dao.Order; 
  6. import com.example.wyd.service.BillService; 
  7. import com.example.wyd.service.OrderService; 
  8. import org.joda.time.DateTime; 
  9. import org.junit.jupiter.api.Test; 
  10. import org.springframework.beans.factory.annotation.Autowired; 
  11.  
  12. import java.text.ParseException; 
  13. import java.text.SimpleDateFormat; 
  14. import java.util.*; 
  15.  
  16. public class OrderServiceImplTest extends WydApplicationTests { 
  17.     @Autowired 
  18.     private BillService billService; 
  19.     @Test 
  20.     public void testBillSave(){ 
  21.         for (int i = 0 ; i< 120 ; i++){ 
  22.             Bill bill = new Bill(); 
  23.             bill.setUserId(i); 
  24.             bill.setAddressId((long)i); 
  25.             bill.setStatus("K"); 
  26.             bill.setCreateTime((new Date(new DateTime(2021,(i % 11)+1,7,00, 00,00,000).getMillis()))); 
  27.             billService.save(bill); 
  28.         } 
  29.     } 
  30.     @Test 
  31.     public void testGetByOrderId(){ 
  32.         long id = 626038622575374337L; //根据数据修改,无数据会报错 
  33.         QueryWrapper queryWrapper = new QueryWrapper<>(); 
  34.         queryWrapper.eq("order_id", id); 
  35.         Bill bill = billService.getOne(queryWrapper); 
  36.         System.out.println(bill.toString()); 
  37.     } 
  38.  
  39.     @Test 
  40.     public void testGetByDate(){ 
  41.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
  42.         try { 
  43.             Date date = sdf.parse("2021-02-07 00:00:00"); 
  44.             QueryWrapper queryWrapper = new QueryWrapper<>(); 
  45.             queryWrapper.eq("create_time",date); 
  46.             List billIPage = billService.list(queryWrapper); 
  47.             System.out.println(billIPage.size()); 
  48.             System.out.println(billIPage.toString()); 
  49.         } catch (ParseException e) { 
  50.             e.printStackTrace(); 
  51.         } 
  52.  
  53.     } 
  54.  
  55.     @Test 
  56.     public void testGetByDate2(){ 
  57.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 
  58.         try { 
  59.             Date date = sdf.parse("2021-02-07 00:00:00"); 
  60.             Date date2 = sdf.parse("2021-03-07 00:00:00"); 
  61.             QueryWrapper queryWrapper = new QueryWrapper<>(); 
  62.             queryWrapper.ge("create_time",date) 
  63.             .and(qw-> qw.le("create_time", date2)); 
  64.             List billIPage = billService.list(queryWrapper); 
  65.             System.out.println(billIPage.size()); 
  66.             billIPage.forEach(System.out::println); 
  67.  
  68.         } catch (ParseException e) { 
  69.             e.printStackTrace(); 
  70.         } 
  71.  
  72.     } 

执行上述测试,通过后会生成测试数据。

3.验证

打开浏览器,输入网址进行查询测试:http://localhost:8080/api/query?start=2021-02-07%2000:00:00&end=2021-03-07%2000:00:00

输入如下网址进行数据新增测试:http://localhost:8080/api/save?userid=999&addressId=999&status=M&date=2021-03-07%2000:00:00

通过跟踪分析,此数据落入如下的表中,SQL语句如下:

 
 
 
 
  1. SELECT * FROM mydb2.t_bill_2021_3 LIMIT 0, 1000 

这里还需要注意, ShardingSphere 还支持分布式事务 ,感兴趣的可以阅读官网相关资料进行学习。

当前名称:Java实战:教你如何进行数据库分库分表
当前URL:http://www.hantingmc.com/qtweb/news0/431900.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联