springboot+jpa分库分表项目实例
分库分表场景
关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。此时就要考虑对其进行切分了,切分的目的就在于减少数据库的负担,缩短查询时间。
分库分表用于应对当前互联网常见的两个场景——大数据量和高并发。通常分为垂直拆分和水平拆分两种。
垂直拆分是根据业务将一个库(表)拆分为多个库(表)。如:将经常和不常访问的字段拆分至不同的库或表中。由于与业务关系密切,目前的分库分表产品均使用水平拆分方式。
水平拆分则是根据分片算法将一个库(表)拆分为多个库(表)。如:按照ID的最后一位以3取余,尾数是1的放入第1个库(表),尾数是2的放入第2个库(表)等。
单纯的分表虽然可以解决数据量过大导致检索变慢的问题,但无法解决过多并发请求访问同一个库,导致数据库响应变慢的问题。所以通常水平拆分都至少要采用分库的方式,用于一并解决大数据量和高并发的问题。这也是部分开源的分片数据库中间件只支持分库的原因。
但分表也有不可替代的适用场景。最常见的分表需求是事务问题。同在一个库则不需考虑分布式事务,善于使用同库不同表可有效避免分布式事务带来的麻烦。目前强一致性的分布式事务由于性能问题,导致使用起来并不一定比不分库分表快。目前采用最终一致性的柔性事务居多。分表的另一个存在的理由是,过多的数据库实例不利于运维管理。综上所述,最佳实践是合理地配合使用分库+分表。
Sharding-JDBC简介
Sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问。Sharding-JDBC是继dubbox和elastic-job之后,ddframe系列开源的第3个项目。
定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。
- 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
Sharding-JDBC分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。
SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持Binding Table以及笛卡尔积表查询。
项目实践
数据准备
准备两个数据库。并在两个库中建好表, 建表sql如下:
DROP TABLE IF EXISTS `user_auth_0`;
CREATE TABLE `user_auth_0` (
`user_id` bigint(20) NOT NULL,
`add_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`email` varchar(16) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`phone` varchar(16) DEFAULT NULL,
`remark` varchar(16) DEFAULT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `USER_AUTH_PHONE` (`phone`),
UNIQUE KEY `USER_AUTH_EMAIL` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; DROP TABLE IF EXISTS `user_auth_1`;
CREATE TABLE `user_auth_1` (
`user_id` bigint(20) NOT NULL,
`add_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`email` varchar(16) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`phone` varchar(16) DEFAULT NULL,
`remark` varchar(16) DEFAULT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `USER_AUTH_PHONE` (`phone`),
UNIQUE KEY `USER_AUTH_EMAIL` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
POM配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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> <!-- 引入jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 引入mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<!-- sharding-jdbc -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>1.5.4</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
application.yml配置
spring:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
database0:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mazhq?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
databaseName: mazhq database1:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/liugh?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
username: root
password: 123456
databaseName: liugh
分库分表最主要有几个配置
1. 有多少个数据源 (2个:database0和database1)
@Data
@ConfigurationProperties(prefix = "database0")
@Component
public class Database0Config {
private String url;
private String username;
private String password;
private String driverClassName;
private String databaseName; public DataSource createDataSource() {
DruidDataSource result = new DruidDataSource();
result.setDriverClassName(getDriverClassName());
result.setUrl(getUrl());
result.setUsername(getUsername());
result.setPassword(getPassword());
return result;
}
}
2. 用什么列进行分库以及分库算法 (一般是用具体值对2取余判断入哪个库,我采用的是判断值是否大于20)
@Component
public class DatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {
@Autowired
private Database0Config database0Config;
@Autowired
private Database1Config database1Config;
@Override
public String doEqualSharding(Collection<String> collection, ShardingValue<Long> shardingValue) {
Long value = shardingValue.getValue();
if (value <= 20L) {
return database0Config.getDatabaseName();
} else {
return database1Config.getDatabaseName();
}
} @Override
public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
for (Long value : shardingValue.getValues()) {
if (value <= 20L) {
result.add(database0Config.getDatabaseName());
} else {
result.add(database1Config.getDatabaseName());
}
}
return result;
} @Override
public Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
Range<Long> range = shardingValue.getValueRange();
for (Long value = range.lowerEndpoint(); value <= range.upperEndpoint(); value++) {
if (value <= 20L) {
result.add(database0Config.getDatabaseName());
} else {
result.add(database1Config.getDatabaseName());
}
}
return result;
}
}
3. 用什么列进行分表以及分表算法
@Component
public class TableShardingAlgorithm 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 = 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;
}
}
4. 每张表的逻辑表名和所有物理表名和集成调用
@Configuration
public class DataSourceConfig {
@Autowired
private Database0Config database0Config; @Autowired
private Database1Config database1Config; @Autowired
private DatabaseShardingAlgorithm databaseShardingAlgorithm; @Autowired
private TableShardingAlgorithm tableShardingAlgorithm; @Bean
public DataSource getDataSource() throws SQLException {
return buildDataSource();
} private DataSource buildDataSource() throws SQLException {
//分库设置
Map<String, DataSource> dataSourceMap = new HashMap<>(2);
//添加两个数据库database0和database1
dataSourceMap.put(database0Config.getDatabaseName(), database0Config.createDataSource());
dataSourceMap.put(database1Config.getDatabaseName(), database1Config.createDataSource());
//设置默认数据库
DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, database0Config.getDatabaseName()); //分表设置,大致思想就是将查询虚拟表Goods根据一定规则映射到真实表中去
TableRule orderTableRule = TableRule.builder("user_auth")
.actualTables(Arrays.asList("user_auth_0", "user_auth_1"))
.dataSourceRule(dataSourceRule)
.build(); //分库分表策略
ShardingRule shardingRule = ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Arrays.asList(orderTableRule))
.databaseShardingStrategy(new DatabaseShardingStrategy("user_id", databaseShardingAlgorithm))
.tableShardingStrategy(new TableShardingStrategy("user_id", tableShardingAlgorithm)).build();
DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
return dataSource;
} @Bean
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
接口测试代码
1、实体类
/**
* @author mazhq
* @date 2019/7/30 16:41
*/
@Entity
@Data
@Table(name = "USER_AUTH", uniqueConstraints = {@UniqueConstraint(name = "USER_AUTH_PHONE", columnNames = {"PHONE"}),
@UniqueConstraint(name = "USER_AUTH_EMAIL", columnNames = {"EMAIL"})})
public class UserAuthEntity implements Serializable {
private static final long serialVersionUID = 7230052310725727465L;
@Id
private Long userId;
@Column(name = "PHONE", length = 16)
private String phone;
@Column(name = "EMAIL", length = 16)
private String email;
private String password;
@Column(name = "REMARK",length = 16)
private String remark;
@Column(name = "ADD_DATE", nullable = false, columnDefinition = "datetime default now()")
private Date addDate;
}
2. Dao层
@Repository
public interface UserAuthDao extends JpaRepository<UserAuthEntity, Long> {
}
3. controller层
/**
* @author mazhq
* @Title: UserAuthController
* @date 2019/8/1 17:18
*/
@RestController
@RequestMapping("/user")
public class UserAuthController {
@Autowired
private UserAuthDao userAuthDao; @PostMapping("/save")
public String save(){
for (int i=0;i<40;i++) {
UserAuthEntity userAuthEntity = new UserAuthEntity();
userAuthEntity.setUserId((long)i);
userAuthEntity.setAddDate(new Date());
userAuthEntity.setEmail("test"+i+"@163.com");
userAuthEntity.setPassword("123456");
userAuthEntity.setPhone("1388888888"+i);
Random r = new Random();
userAuthEntity.setRemark(""+r.nextInt(100));
userAuthDao.save(userAuthEntity);
}
return "success";
} @PostMapping("/select")
public String select(){
return JSONObject.toJSONString(userAuthDao.findAll(Sort.by(Sort.Order.desc("remark"))));
}
}
测试方式:
先调用:http://localhost:8080/user/save
再查询:http://localhost:8080/user/select
git地址:sharding
springboot+jpa分库分表项目实例的更多相关文章
- springboot(十三)-分库分表-手动配置
sharding-jdbc简介 Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零: 可适用于任何基于java的ORM框架,如:JPA, Hib ...
- Sharding-JDBC基本使用,整合Springboot实现分库分表,读写分离
结合上一篇docker部署的mysql主从, 本篇主要讲解SpringBoot项目结合Sharding-JDBC如何实现分库分表.读写分离. 一.Sharding-JDBC介绍 1.这里引用官网上的介 ...
- 利用ShardingSphere-JDBC实现分库分表
利用ShardingSphere-JDBC实现分库分表 1. ShardingSphere概述 1.1 概述 业务发展到一定程度,分库分表是一种必然的要求,分库可以实现资源隔离,分表则可以降低单表数据 ...
- SpringBoot使用Sharding-JDBC分库分表
本文介绍SpringBoot使用当当Sharding-JDBC进行分库分表. 1.有关Sharding-JDBC 有关Sharding-JDBC介绍这里就不在多说,之前Sharding-JDBC是当当 ...
- springboot+mybatisplus+sharding-jdbc分库分表实例
项目实践 现在Java项目使用mybatis多一些,所以我也做了一个springboot+mybatisplus+sharding-jdbc分库分表项目例子分享给大家. 要是用的springboot+ ...
- 【ShardingSphere技术专题】「ShardingJDBC」SpringBoot之整合ShardingJDBC实现分库分表(JavaConfig方式)
前提介绍 ShardingSphere介绍 ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC.Sharding-Proxy和Shardin ...
- springboot(十四)-分库分表-自动配置
上一节我们是手动配置数据源的,直接在java代码里写数据库的东西,这操作我个人是不喜欢的.我觉得这些东西就应该出现在application.yml文件中. 还有,万一我们的项目在使用之后,突然需要改变 ...
- SpringBoot 2.0 整合sharding-jdbc中间件,实现数据分库分表
一.水平分割 1.水平分库 1).概念: 以字段为依据,按照一定策略,将一个库中的数据拆分到多个库中. 2).结果 每个库的结构都一样:数据都不一样: 所有库的并集是全量数据: 2.水平分表 1).概 ...
- 三、SpringBoot 整合mybatis 多数据源以及分库分表
前言 说实话,这章本来不打算讲的,因为配置多数据源的网上有很多类似的教程.但是最近因为项目要用到分库分表,所以让我研究一下看怎么实现.我想着上一篇博客讲了多环境的配置,不同的环境调用不同的数据库,那接 ...
随机推荐
- 自建KMS服务器激活Windows office
一.KMS服务器环境介绍 操作系统Centos 7.x Windows Server 也是可以的,此处以Centos7为例安装,使用默认1688端口号 二.服务端安装过程: 1.下载安装包(这 ...
- Unity3D 使用Socket处理数据并将数据 在UGUI、NGUI上显示出来
Unity3d 不支持C#的线程直接调用Unity3D 主线程才能实现的功能.例如:给UGUI text 赋值.改变Color值等.怎样解决这个问题呢?使用一个Loom脚本. 按照惯例贴上代码. 首先 ...
- 《Web Development with Go》Middleware之共享数据
这个库值得学, 好像写起来越来越溜 package main import ( "fmt" "log" "net/http" //" ...
- [C]副作用和序列点
概述 副作用: <C语言核心技术>对副作用的描述: 表达式内包含了一串的常量.标识符.运算符(指示的运算方式).表达式的目的可以是获得结果值,或者得到运算的副作用(side effect) ...
- mysqlbinlog-Note
binlog_format = mixedlog-bin = /data/mysql/mysql-binexpire_logs_days = 7 #binlog过期清理时间max_binlog_siz ...
- python-参数化-(2)(数据库判断是否存在并返回满足条件的数据)
1.根据python-参数化-(1),生成的数据号码 在数据库查询后判断是否存在若不存在返回手机号码,若存在返回该手机号码对应数据的信息,未封装成类或函数上代码 import pymysqlconn= ...
- (day68)Vue-CLI项目、页面跳转和传参、生命周期钩子
目录 一.Vue-CLI (一)环境搭建 (二)项目的创建 (三)项目目录结构 (四)Vue组件(.vue文件) (五)全局脚本文件main.js(项目入口) (六)Vue请求生命周期 二.页面跳转和 ...
- java1.8 ConcurrentHashMap 详细理解
基于 jdk1.8 Java并发包中提供的一个线程安全且高效的HashMap实现,可以完全替代HashTable,在并发编程的场景中使用频率非常之高.可能大多人只是知道它使用了多个锁代替HashTab ...
- SpringBoot 构建 REST 服务
摘要 该文章只为了说明如何整合REST服务,并不介绍如何使用,当做笔记吧. MongoDB 以MongoDB为例 maven 依赖 <dependency> <groupId> ...
- SpringBoot2.0 整合 Shiro 框架,实现用户权限管理
本文源码:GitHub·点这里 || GitEE·点这里 一.Shiro简介 1.基础概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理.作为一款安全 ...