关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。此时就要考虑对其进行切分了,切分的目的就在于减少数据库的负担,缩短查询时间。
数据库分布式核心内容无非就是数据切分(Sharding),以及切分后对数据的定位、整合。数据切分就是将数据分散存储到多个数据库中,使得单一数据库中的数据量变小,通过扩充主机的数量缓解单一数据库的性能问题,从而达到提升数据库操作性能的目的。
数据切分根据其切分类型,可以分为两种方式:垂直(纵向)切分和水平(横向)切分
1、垂直(纵向)切分
垂直切分常见有垂直分库和垂直分表两种。
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。如图:
垂直分表是基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题,MySQL底层是通过数据页存储的,一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。
垂直切分的优点:
· 解决业务系统层面的耦合,业务清晰
· 与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
· 高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈
缺点:
· 部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度
· 分布式事务处理复杂
· 依然存在单表数据量过大的问题(需要水平切分)
2、水平(横向)切分
当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
水平切分分为库内分表和分库分表,是根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果。如图所示:
库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻MySQL数据库的压力来说,帮助不是很大,大家还是竞争同一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。
水平切分的优点:
· 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
· 应用端改造较小,不需要拆分业务模块
缺点:
· 跨分片的事务一致性难以保证
· 跨库的join关联查询性能较差
· 数据多次扩展难度和维护量极大
水平切分后同一张表会出现在多个数据库/表中,每个库/表的内容不同。
mysql的水平分表和垂直分表的区别
1,水平分割:
例:QQ的登录表。假设QQ的用户有100亿,如果只有一张表,每个用户登录的时候数据库都要从这100亿中查找,会很慢很慢。如果将这一张表分成100份,每张表有1亿条,就小了很多,比如qq0,qq1,qq1...qq99表。
用户登录的时候,可以将用户的id%100,那么会得到0-99的数,查询表的时候,将表名qq跟取模的数连接起来,就构建了表名。比如123456789用户,取模的89,那么就到qq89表查询,查询的时间将会大大缩短。
这就是水平分割。
2,垂直分割:
垂直分割指的是:表的记录并不多,但是字段却很长,表占用空间很大,检索表的时候需要执行大量的IO,严重降低了性能。这时需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。
例如学生答题表tt:有如下字段:
Id name 分数 题目 回答
其中题目和回答是比较大的字段,id name 分数比较小。
如果我们只想查询id为8的学生的分数:select 分数 from tt where id = 8;虽然知识查询分数,但是题目和回答这两个大字段也是要被扫描的,很消耗性能。但是我们只关心分数,并不想查询题目和回答。这就可以使用垂直分割。我们可以把题目单独放到一张表中,通过id与tt表建立一对一的关系,同样将回答单独放到一张表中。这样我们插叙tt中的分数的时候就不会扫描题目和回答了。
3,其他要点:
1)存放图片、文件等大文件用文件系统存储。数据库只存储路径,图片和文件存放在文件系统,甚至单独存放在一台服务器(图床)。
2)数据参数配置。
最重要的参数就是内存,我们主要用的innodb引擎,所以下面两个参数调的很大:
innodb_additional_mem_pool_size=64M
innodb_buffer_pool_size=1G
对于MyISAM,需要调整key_buffer_size,当然调整参数还是要看状态,用show status语句可以看到当前状态,以决定该调整哪些参数。
分库分表之后全局id的生成
1. 数据库自增ID——来自Flicker的解决方案
因为MySQL本身支持auto_increment操作,很自然地,我们会想到借助这个特性来实现这个功能。Flicker在解决全局ID生成方 案里就采用了MySQL自增长ID的机制(auto_increment + replace into + MyISAM)。一个生成64位ID方案具体就是这样的: 先创建单独的数据库(eg:ticket),然后创建一个表:
CREATE TABLE Tickets64 (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default '',
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=MyISAM
当我们插入记录后,执行SELECT * from Tickets64,查询结果就是这样的:
+-------------------+------+| id | stub |
+-------------------+------+| 72157623227190423 | a |
+-------------------+------+
在我们的应用端需要做下面这两个操作,在一个事务会话里提交:
REPLACE INTO Tickets64 (stub) VALUES ('a');SELECT LAST_INSERT_ID();
这样我们就能拿到不断增长且不重复的ID了。 到上面为止,我们只是在单台数据库上生成ID,从高可用角度考虑,接下来就要解决单点故障问题:Flicker启用了两台数据库服务器来生成ID,通过区分auto_increment的起始值和步长来生成奇偶数的ID。
TicketServer1:auto-increment-increment = 2auto-increment-offset = 1
TicketServer2:auto-increment-increment = 2auto-increment-offset = 2
最后,在客户端只需要通过轮询方式取ID就可以了。
· 优点:充分借助数据库的自增ID机制,提供高可靠性,生成的ID有序。
· 缺点:占用两个独立的MySQL实例,有些浪费资源,成本较高。
2. 独立的应用程序——来自Twitter的解决方案
Twitter在把存储系统从MySQL迁移到Cassandra的过程中由于Cassandra没有顺序ID生成机制,于是自己开发了一套全局唯一ID生成服务:Snowflake。根据twitter的业务需求,snowflake系统生成64位的ID。由3部分组成:
41位的时间序列(精确到毫秒,41位的长度可以使用69年)
10位的机器标识(10位的长度最多支持部署1024个节点)
12位的计数顺序号(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
最高位是符号位,始终为0。
· 优点:高性能,低延迟;独立的应用;按时间有序。
· 缺点:需要独立的开发和部署。
Spring Boot集成sharding-jdbc实现按时间分库分表
1
前篇内容已经实现了spring boot集成mybatis访问MySQL,本篇在此基础上集成sharding-jdbc实现分库分表等功能。
集成sharding-jdbc
首先创建创建数据库和表。这是sharding-jdbc所要求的。
create database db_201810;
create database db_201811;
use db_201810;
create table gps_20181014(id int not null auto_increment,gprs varchar(16) NOT NULL,sys_time datetime,PRIMARY KEY (id));
create table gps_20181015(id int not null auto_increment,gprs varchar(16) NOT NULL,sys_time datetime,PRIMARY KEY (id));
insert into gps_20181014 values(0,'0123456789012345','2018-10-14 0:0:0');
insert into gps_20181015 values(0,'0123456789012345','2018-10-15 0:0:0');
use db_201811;
create table gps_20181114(id int not null auto_increment,gprs varchar(16) NOT NULL,sys_time datetime,PRIMARY KEY (id));
create table gps_20181115(id int not null auto_increment,gprs varchar(16) NOT NULL,sys_time datetime,PRIMARY KEY (id));
insert into gps_20181114 values(0,'0123456789012345','2018-11-14 0:0:0');
insert into gps_20181115 values(0,'0123456789012345','2018-11-15 0:0:0');
接着是pom.xml文件,添加sharding-jdbc到工程中。主要是下面两个依赖:
<!--sharding-jdbc -->
<dependency>
<groupId>io.shardingjdbc</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.3</version>
</dependency>
完整的pom.xml文件内容如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>SpringBootDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>SpringBootDemo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--sharding-jdbc -->
<dependency>
<groupId>io.shardingjdbc</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
剩下就是代码了。首先是数据源配置和库策略、表策略:
DataSourceConfig.java
package com.net.config;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.net.config.DatabaseShardingAlgorithm;
import com.net.config.TableShardingAlgorithm;
import io.shardingjdbc.core.api.config.ShardingRuleConfiguration;
import io.shardingjdbc.core.api.config.TableRuleConfiguration;
import io.shardingjdbc.core.api.config.strategy.StandardShardingStrategyConfiguration;
import io.shardingjdbc.core.jdbc.core.datasource.ShardingDataSource;
@Configuration
@MapperScan(basePackages = "com.net.domain", sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
//配置sharding-jdbc的DataSource,给上层应用使用,这个DataSource包含所有的逻辑库和逻辑表,应用增删改查时,修改对应sql
//然后选择合适的数据库继续操作。因此这个DataSource创建很重要。
@Bean
@Primary
public DataSource shardingDataSource() throws SQLException {
ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
//用户表配置,可以添加多个配置
shardingRuleConfig.getTableRuleConfigs().add(getUserTableRuleConfiguration());
shardingRuleConfig.getTableRuleConfigs().add(getUserTableRuleConfiguration1());
shardingRuleConfig.getBindingTableGroups().add("gps");
//设置数据库策略,传入的是sys_time
shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("sys_time", DatabaseShardingAlgorithm.class.getName()));
//设置数据表策略,传入的是sys_time
shardingRuleConfig.setDefaultTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("sys_time", TableShardingAlgorithm.class.getName()));
return new ShardingDataSource(shardingRuleConfig.build(createDataSourceMap()));
}
//创建用户表规则
@Bean
TableRuleConfiguration getUserTableRuleConfiguration() {
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
orderTableRuleConfig.setLogicTable("gps");
//设置数据节点,格式为dbxx.tablexx。这里的名称要和map的别名一致。下面两种方式都可以
//orderTableRuleConfig.setActualDataNodes("db_${0..1}.gps_${0..1}");
orderTableRuleConfig.setActualDataNodes("db_201810.gps_20181014,db_201810.gps_20181015,db_201811.gps_20181114,db_201811.gps_20181115");
//设置纵列名称
orderTableRuleConfig.setKeyGeneratorColumnName("sys_time");
return orderTableRuleConfig;
}
@Bean
TableRuleConfiguration getUserTableRuleConfiguration1() {
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
//设置用户表逻辑
orderTableRuleConfig.setLogicTable("tb_user");
//设置数据节点,格式为dbxx.tablexx。这里的名称要和map的别名一致
orderTableRuleConfig.setActualDataNodes("gps_com.tb_user");
return orderTableRuleConfig;
}
//下面函数是获取数据源,即包含有多少个数据库,读入到系统中存放于map中
private Map<String, DataSource> createDataSourceMap() {
Map<String, DataSource> result = new HashMap<>();
result.put("gps_com", createDataSource("jdbc:mysql://localhost:3306/gps_com?characterEncoding=utf8&useSSL=false"));
result.put("db_201810", createDataSource("jdbc:mysql://localhost:3306/db_201810?characterEncoding=utf8&useSSL=false"));
result.put("db_201811", createDataSource("jdbc:mysql://localhost:3306/db_201811?characterEncoding=utf8&useSSL=false"));
return result;
}
private DataSource createDataSource(final String dataSourceName) {
//使用默认连接池
BasicDataSource result = new BasicDataSource();
result.setDriverClassName(com.mysql.jdbc.Driver.class.getName());
//设置数据库路径
result.setUrl(dataSourceName);
//设置数据库用户名
result.setUsername("root");
//设置数据密码
result.setPassword("123456");
return result;
}
/**
* 需要手动配置事务管理器
*/
@Bean
public DataSourceTransactionManager transactitonManager(DataSource shardingDataSource) {
return new DataSourceTransactionManager(shardingDataSource);
}
//下面是SessionFactory配置,二选一,由类前语句选择 @MapperScan(basePackages = "com.net.domain", sqlSessionFactoryRef = "sqlSessionFactory")
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory(DataSource shardingDataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(shardingDataSource);
return sessionFactory.getObject();
}
}
DatabaseShardingAlgorithm.java
package com.net.config;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
public class DatabaseShardingAlgorithm implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
String db_name="db_";
try {
Date date = (Date) new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(preciseShardingValue.getValue());
String year = String.format("%tY", date);
String mon = String.format("%tm",date);
db_name=db_name+year+mon;
System.out.println("db_name:" + db_name);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (String each : collection) {
System.out.println("db:" + each);
if (each.equals(db_name)) {
return each;
}
}
throw new IllegalArgumentException();
}
}
TableShardingAlgorithm.java
package com.net.config;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import io.shardingjdbc.core.api.algorithm.sharding.PreciseShardingValue;
import io.shardingjdbc.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
public class TableShardingAlgorithm implements PreciseShardingAlgorithm<String> {
@Override
public String doSharding(Collection<String> collection, PreciseShardingValue<String> preciseShardingValue) {
String tb_name=preciseShardingValue.getLogicTableName() + "_";
try {
Date date = (Date) new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(preciseShardingValue.getValue());
String year = String.format("%tY", date);
String mon = String.format("%tm",date);
String dat = String.format("%td",date);
tb_name=tb_name+year+mon+dat;
System.out.println("tb_name:" + tb_name);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (String each : collection) {
System.out.println("tb:" + each);
if (each.equals(tb_name)) {
return each;
}
}
throw new IllegalArgumentException();
}
}
这时工程结构如图所示:
运行程序,没有问题,浏览器触发访问数据库也能正常访问gps_com的tb_user表。
以上说明集成sharding-jdbc没有问题。至于能访问数据库是由于DataSourceConfig.java中加上了下面代码。
shardingRuleConfig.getTableRuleConfigs().add(getUserTableRuleConfiguration1());
@Bean
TableRuleConfiguration getUserTableRuleConfiguration1() {
TableRuleConfiguration orderTableRuleConfig = new TableRuleConfiguration();
//设置用户表逻辑
orderTableRuleConfig.setLogicTable("tb_user");
//设置数据节点,格式为dbxx.tablexx。这里的名称要和map的别名一致
orderTableRuleConfig.setActualDataNodes("gps_com.tb_user");
return orderTableRuleConfig;
}
测试并使用sharding-jdbc
上面完成了sharding-jdbc的集成,但是还没有真正发挥作用,也就是分库分表的功能。
继续添加代码,最后工程结构图是:
GpsData.java
package com.net.domain;
import java.io.Serializable;
public class GpsData implements Serializable{
private static final long serialVersionUID = 1L;
String gprs;
String sys_time;
public void setGprs(String gprs)
{
this.gprs = gprs;
}
public String getGprs()
{
return this.gprs;
}
public void setSystime(String sys_time)
{
this.sys_time = sys_time;
}
public String getSystime()
{
return this.sys_time;
}
}
GpsDataMapper.java
package com.net.domain;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import com.net.domain.GpsData;
@Mapper
public interface GpsDataMapper {
@Select("select * from gps where sys_time = #{sys_time}")
List<GpsData> findAll(@Param("sys_time") String sys_time);
}
GpsDataService.java
package com.net.service;
import java.util.List;
import com.net.domain.GpsData;
public interface GpsDataService {
List<GpsData> getAll(String sys_time);
}
GpsDataServiceImpl.java
package com.net.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.net.domain.GpsData;
import com.net.domain.GpsDataMapper;
import com.net.service.GpsDataService;
@Service("gpsDataService")
public class GpsDataServiceImpl implements GpsDataService{
@Autowired
private GpsDataMapper gpsDataMapper;
@Override
public List<GpsData> getAll(String sys_time)
{
return gpsDataMapper.findAll(sys_time);
}
}
最后是测试代码
TestWithWeb.java
package com.net.web;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.net.domain.User;
import com.net.domain.GpsData;
import com.net.service.GpsDataService;
import com.net.service.UserService;
@RestController
public class TestWithWeb {
@Autowired
@Qualifier("userService")
private UserService userService;
@Autowired
@Qualifier("gpsDataService")
private GpsDataService gpsDataService;
@RequestMapping("/log")
public String login()
{
System.out.println("hello");
User user = userService.login("root", "123456");
System.out.println(user.toString());
List<GpsData> GpsData_list = gpsDataService.getAll("2018-11-14 0:0:0");
System.out.println(GpsData_list.get(0).getGprs());
System.out.println(GpsData_list.get(0).getSystime());
return "OK";
}
}
代码展示结束,开始运行测试。
测试语句:gpsDataService.getAll("2018-11-14 0:0:0");
结果如下:
由于在代码里面直接配置了数据源,所以application.properties里面关于数据库的信息不起作用,可以直接去掉。
- 背景
最近在研究Mysql的分库分表,前面的博客已经详细介绍了分库分表!
由于sharding-jdbc是不支持动态进行建库的SQL,那么就需要一次把需要的数据库和数据表都建好
- 建库、建表
考虑到这只是一个测试的demo,所以,只建了两个库和两个表
CREATE TABLE `t_order_0` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
`order_id` VARCHAR(32) NULL DEFAULT NULL COMMENT '顺序编号',
`user_id` VARCHAR(32) NULL DEFAULT NULL COMMENT '用户编号',
`userName` VARCHAR(32) NULL DEFAULT NULL COMMENT '用户名',
`passWord` VARCHAR(32) NULL DEFAULT NULL COMMENT '密码',
`nick_name` VARCHAR(32) NULL DEFAULT NULL,- SpringBoot+Mybatis+sharding-jdbc框架搭建(pom文件)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--sharding-jdbc -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>1.5.4</version>
</dependency>
</dependencies>- 配置文件
mybatis.config-locations=classpath:mybatis/mybatis-config.xml
#datasource
spring.devtools.remote.restart.enabled=false
#data source1
spring.datasource.test1.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test1.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test_msg1?serverTimezone=UTC
spring.datasource.test1.username=root
spring.datasource.test1.password=123456
#data source2
spring.datasource.test2.driverClassName=com.mysql.jdbc.Driver
spring.datasource.test2.jdbcUrl=jdbc:mysql://127.0.0.1:3306/test_msg2?serverTimezone=UTC
spring.datasource.test2.username=root
spring.datasource.test2.password=123456- 启动文件
@SpringBootApplication
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class}) //排除DataSourceConfiguratrion
@EnableTransactionManagement(proxyTargetClass = true) //开启事物管理功能
public class ShardingJdbcApplication {
public static void main(String[] args) {
SpringApplication.run(ShardingJdbcApplication.class, args);
}
}- 实体类(Data注解可免去set/get方法)
@Data
public class User {
private Long id;
private Long order_id;
private Long user_id;
private String userName;
private String passWord;
private String nickName;
}- Service层
@Slf4j
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public void insert(User user) {
userMapper.insert(user);
}
}- Mapper层
public interface UserMapper {
void insert(User user);
}- 数据源配置和Mybatis配置和分库分表规则(重要)
这里,我们是将多个数据源交给sharding-jdbc进行管理,并且有默认的数据源,当没有设置分库分表规则的时候就可以使用默认的数据源
分表:user_id%2 = 0的数据存储到test_msg1,为1的存储到test_msg0
分表:order_id%2 = 0的数据存储到t_order_0,为1的存储到t_order_1
/**
* @Auther: Tinko
* @Date: 2018/12/19 16:27
* @Description: 数据源配置和Mybatis配置和分库分表规则
*/
@Configuration
@MapperScan(basePackages = "com.example.shardingjdbc.mapper", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class DataSourceConfig {
/**
* 配置数据源0,数据源的名称最好要有一定的规则,方便配置分库的计算规则
* @return
*/
@Bean(name="dataSource0")
@ConfigurationProperties(prefix = "spring.datasource.test1")
public DataSource dataSource0(){
return DataSourceBuilder.create().build();
}
/**
* 配置数据源1,数据源的名称最好要有一定的规则,方便配置分库的计算规则
* @return
*/
@Bean(name="dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.test2")
public DataSource dataSource1(){
return DataSourceBuilder.create().build();
}
/**
* 配置数据源规则,即将多个数据源交给sharding-jdbc管理,并且可以设置默认的数据源,
* 当表没有配置分库规则时会使用默认的数据源
* @param dataSource0
* @param dataSource1
* @return
*/
@Bean
public DataSourceRule dataSourceRule(@Qualifier("dataSource0") DataSource dataSource0,
@Qualifier("dataSource1") DataSource dataSource1){
Map<String, DataSource> dataSourceMap = new HashMap<>(); //设置分库映射
dataSourceMap.put("dataSource0", dataSource0);
dataSourceMap.put("dataSource1", dataSource1);
return new DataSourceRule(dataSourceMap, "dataSource0"); //设置默认库,两个库以上时必须设置默认库。默认库的数据源名称必须是dataSourceMap的key之一
}
/**
* 配置数据源策略和表策略,具体策略需要自己实现
* @param dataSourceRule
* @return
*/
@Bean
public ShardingRule shardingRule(DataSourceRule dataSourceRule){
//具体分库分表策略
TableRule orderTableRule = TableRule.builder("t_order")
.actualTables(Arrays.asList("t_order_0", "t_order_1"))
.tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
.dataSourceRule(dataSourceRule)
.build();
//绑定表策略,在查询时会使用主表策略计算路由的数据源,因此需要约定绑定表策略的表的规则需要一致,可以一定程度提高效率
List<BindingTableRule> bindingTableRules = new ArrayList<BindingTableRule>();
bindingTableRules.add(new BindingTableRule(Arrays.asList(orderTableRule)));
return ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Arrays.asList(orderTableRule))
.bindingTableRules(bindingTableRules)
.databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
.tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
.build();
}
/**
* 创建sharding-jdbc的数据源DataSource,MybatisAutoConfiguration会使用此数据源
* @param shardingRule
* @return
* @throws SQLException
*/
@Bean(name="dataSource")
public DataSource shardingDataSource(ShardingRule shardingRule) throws SQLException {
return ShardingDataSourceFactory.createDataSource(shardingRule);
}
/**
* 需要手动配置事务管理器
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager transactitonManager(@Qualifier("dataSource") DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "test1SqlSessionFactory")
@Primary
public SqlSessionFactory testSqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/*.xml"));
return bean.getObject();
}
@Bean(name = "test1SqlSessionTemplate")
@Primary
public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}- 分库规则
/**
* @Auther: Tinko
* @Date: 2018/12/19 16:31
* @Description: 分库规则
*/
public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {
@Override
public String doEqualSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
for (String each : databaseNames) {
if (each.endsWith(Long.parseLong(shardingValue.getValue().toString()) % 2 + "")) {
return each;
}
}
throw new IllegalArgumentException();
}
@Override
public Collection<String> doInSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(databaseNames.size());
for (Long value : shardingValue.getValues()) {
for (String tableName : databaseNames) {
if (tableName.endsWith(value % 2 + "")) {
result.add(tableName);
}
}
}
return result;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> databaseNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(databaseNames.size());
Range<Long> range = (Range<Long>) shardingValue.getValueRange();
for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : databaseNames) {
if (each.endsWith(i % 2 + "")) {
result.add(each);
}
}
}
return result;
}
}
- 分表规则
/**
* @Auther: Tinko
* @Date: 2018/12/19 16:30
* @Description: 分表规则
*/
public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {
@Override
public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
for (String each : tableNames) {
if (each.endsWith(shardingValue.getValue() % 2 + "")) {
return each;
}
}
throw new IllegalArgumentException();
}
@Override
public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(tableNames.size());
for (Long value : shardingValue.getValues()) {
for (String tableName : tableNames) {
if (tableName.endsWith(value % 2 + "")) {
result.add(tableName);
}
}
}
return result;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(tableNames.size());
Range<Long> range = (Range<Long>) shardingValue.getValueRange();
for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : tableNames) {
if (each.endsWith(i % 2 + "")) {
result.add(each);
}
}
}
return result;
}
}- 与Mysql交互的配置的文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.shardingjdbc.mapper.UserMapper" >
<sql id="Base_Column_List" >
id, userName, passWord, user_sex, nick_name
</sql>
<insert id="insert" parameterType="com.example.shardingjdbc.entity.User" >
INSERT INTO
t_order
(order_id,user_id,userName,passWord)
VALUES
(#{order_id},#{user_id},#{userName}, #{passWord})
</insert>
</mapper>
然后,测试可行!!!