springboot jpa分库分表项目实现过程详解
这篇文章主要介绍了springbootjpa分库分表项目实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
分库分表场景
关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到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,SpringJDBCTemplate或直接使用JDBC。
- 基于任何第三方的数据库连接池,如:DBCP,C3P0,BoneCP,Druid,HikariCP等。
- 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
- Sharding-JDBC分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。
SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持BindingTable以及笛卡尔积表查询。
项目实践
数据准备
准备两个数据库。并在两个库中建好表,建表sql如下:
DROPTABLEIFEXISTS`user_auth_0`; CREATETABLE`user_auth_0`( `user_id`bigint(20)NOTNULL, `add_date`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP, `email`varchar(16)DEFAULTNULL, `password`varchar(255)DEFAULTNULL, `phone`varchar(16)DEFAULTNULL, `remark`varchar(16)DEFAULTNULL, PRIMARYKEY(`user_id`), UNIQUEKEY`USER_AUTH_PHONE`(`phone`), UNIQUEKEY`USER_AUTH_EMAIL`(`email`) )ENGINE=InnoDBDEFAULTCHARSET=utf8mb4; DROPTABLEIFEXISTS`user_auth_1`; CREATETABLE`user_auth_1`( `user_id`bigint(20)NOTNULL, `add_date`datetimeNOTNULLDEFAULTCURRENT_TIMESTAMP, `email`varchar(16)DEFAULTNULL, `password`varchar(255)DEFAULTNULL, `phone`varchar(16)DEFAULTNULL, `remark`varchar(16)DEFAULTNULL, PRIMARYKEY(`user_id`), UNIQUEKEY`USER_AUTH_PHONE`(`phone`), UNIQUEKEY`USER_AUTH_EMAIL`(`email`) )ENGINE=InnoDBDEFAULTCHARSET=utf8mb4;
POM配置
org.springframework.boot spring-boot-starter org.springframework.boot spring-boot-devtools runtime org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-jpa mysql mysql-connector-java com.alibaba druid 1.1.9 com.dangdang sharding-jdbc-core 1.5.4 com.alibaba fastjson 1.2.51
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
publicclassDatabase0Config{
privateStringurl;
privateStringusername;
privateStringpassword;
privateStringdriverClassName;
privateStringdatabaseName;
publicDataSourcecreateDataSource(){
DruidDataSourceresult=newDruidDataSource();
result.setDriverClassName(getDriverClassName());
result.setUrl(getUrl());
result.setUsername(getUsername());
result.setPassword(getPassword());
returnresult;
}
}
2.用什么列进行分库以及分库算法(一般是用具体值对2取余判断入哪个库,我采用的是判断值是否大于20)
@Component publicclassDatabaseShardingAlgorithmimplementsSingleKeyDatabaseShardingAlgorithm{ @Autowired privateDatabase0Configdatabase0Config; @Autowired privateDatabase1Configdatabase1Config; @Override publicStringdoEqualSharding(Collection collection,ShardingValue shardingValue){ Longvalue=shardingValue.getValue(); if(value<=20L){ returndatabase0Config.getDatabaseName(); }else{ returndatabase1Config.getDatabaseName(); } } @Override publicCollection doInSharding(Collection availableTargetNames,ShardingValue shardingValue){ Collection result=newLinkedHashSet<>(availableTargetNames.size()); for(Longvalue:shardingValue.getValues()){ if(value<=20L){ result.add(database0Config.getDatabaseName()); }else{ result.add(database1Config.getDatabaseName()); } } returnresult; } @Override publicCollection doBetweenSharding(Collection availableTargetNames,ShardingValue shardingValue){ Collection result=newLinkedHashSet<>(availableTargetNames.size()); Range range=shardingValue.getValueRange(); for(Longvalue=range.lowerEndpoint();value<=range.upperEndpoint();value++){ if(value<=20L){ result.add(database0Config.getDatabaseName()); }else{ result.add(database1Config.getDatabaseName()); } } returnresult; } }
3.用什么列进行分表以及分表算法
@Component publicclassTableShardingAlgorithmimplementsSingleKeyTableShardingAlgorithm{ @Override publicStringdoEqualSharding(Collection tableNames,ShardingValue shardingValue){ for(Stringeach:tableNames){ if(each.endsWith(shardingValue.getValue()%2+"")){ returneach; } } thrownewIllegalArgumentException(); } @Override publicCollection doInSharding(Collection tableNames,ShardingValue shardingValue){ Collection result=newLinkedHashSet<>(tableNames.size()); for(Longvalue:shardingValue.getValues()){ for(StringtableName:tableNames){ if(tableName.endsWith(value%2+"")){ result.add(tableName); } } } returnresult; } @Override publicCollection doBetweenSharding(Collection tableNames,ShardingValue shardingValue){ Collection result=newLinkedHashSet<>(tableNames.size()); Range range=shardingValue.getValueRange(); for(Longi=range.lowerEndpoint();i<=range.upperEndpoint();i++){ for(Stringeach:tableNames){ if(each.endsWith(i%2+"")){ result.add(each); } } } returnresult; } }
4.每张表的逻辑表名和所有物理表名和集成调用
@Configuration
publicclassDataSourceConfig{
@Autowired
privateDatabase0Configdatabase0Config;
@Autowired
privateDatabase1Configdatabase1Config;
@Autowired
privateDatabaseShardingAlgorithmdatabaseShardingAlgorithm;
@Autowired
privateTableShardingAlgorithmtableShardingAlgorithm;
@Bean
publicDataSourcegetDataSource()throwsSQLException{
returnbuildDataSource();
}
privateDataSourcebuildDataSource()throwsSQLException{
//分库设置
MapdataSourceMap=newHashMap<>(2);
//添加两个数据库database0和database1
dataSourceMap.put(database0Config.getDatabaseName(),database0Config.createDataSource());
dataSourceMap.put(database1Config.getDatabaseName(),database1Config.createDataSource());
//设置默认数据库
DataSourceRuledataSourceRule=newDataSourceRule(dataSourceMap,database0Config.getDatabaseName());
//分表设置,大致思想就是将查询虚拟表Goods根据一定规则映射到真实表中去
TableRuleorderTableRule=TableRule.builder("user_auth")
.actualTables(Arrays.asList("user_auth_0","user_auth_1"))
.dataSourceRule(dataSourceRule)
.build();
//分库分表策略
ShardingRuleshardingRule=ShardingRule.builder()
.dataSourceRule(dataSourceRule)
.tableRules(Arrays.asList(orderTableRule))
.databaseShardingStrategy(newDatabaseShardingStrategy("user_id",databaseShardingAlgorithm))
.tableShardingStrategy(newTableShardingStrategy("user_id",tableShardingAlgorithm)).build();
DataSourcedataSource=ShardingDataSourceFactory.createDataSource(shardingRule);
returndataSource;
}
@Bean
publicKeyGeneratorkeyGenerator(){
returnnewDefaultKeyGenerator();
}
接口测试代码
1、实体类
/**
*@authormazhq
*@date2019/7/3016:41
*/
@Entity
@Data
@Table(name="USER_AUTH",uniqueConstraints={@UniqueConstraint(name="USER_AUTH_PHONE",columnNames={"PHONE"}),
@UniqueConstraint(name="USER_AUTH_EMAIL",columnNames={"EMAIL"})})
publicclassUserAuthEntityimplementsSerializable{
privatestaticfinallongserialVersionUID=7230052310725727465L;
@Id
privateLonguserId;
@Column(name="PHONE",length=16)
privateStringphone;
@Column(name="EMAIL",length=16)
privateStringemail;
privateStringpassword;
@Column(name="REMARK",length=16)
privateStringremark;
@Column(name="ADD_DATE",nullable=false,columnDefinition="datetimedefaultnow()")
privateDateaddDate;
}
2.Dao层
@Repository publicinterfaceUserAuthDaoextendsJpaRepository{ }
3.controller层
/**
*@authormazhq
*@Title:UserAuthController
*@date2019/8/117:18
*/
@RestController
@RequestMapping("/user")
publicclassUserAuthController{
@Autowired
privateUserAuthDaouserAuthDao;
@PostMapping("/save")
publicStringsave(){
for(inti=0;i<40;i++){
UserAuthEntityuserAuthEntity=newUserAuthEntity();
userAuthEntity.setUserId((long)i);
userAuthEntity.setAddDate(newDate());
userAuthEntity.setEmail("test"+i+"@163.com");
userAuthEntity.setPassword("123456");
userAuthEntity.setPhone("1388888888"+i);
Randomr=newRandom();
userAuthEntity.setRemark(""+r.nextInt(100));
userAuthDao.save(userAuthEntity);
}
return"success";
}
@PostMapping("/select")
publicStringselect(){
returnJSONObject.toJSONString(userAuthDao.findAll(Sort.by(Sort.Order.desc("remark"))));
}
}
测试方式:
先调用:http://localhost:8080/user/save
再查询:http://localhost:8080/user/select
git地址:sharding
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。