springbootjpa分库分表项⽬实现过程详解
这篇⽂章主要介绍了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,
`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>
spring:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
database0:
driverClassName: sql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mazhq?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
password: 123456
databaseName: mazhq
database1:
driverClassName: sql.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
spring framework是什么框架的private Database1Config database1Config;
@Override
public String doEqualSharding(Collection<String> collection, ShardingValue<Long> shardingValue) {
Long value = Value();
if (value <= 20L) {
DatabaseName();
} else {
DatabaseName();
}
}
@Override
public Collection<String> doInSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) {
Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
for (Long value : Values()) {
if (value <= 20L) {
result.DatabaseName());
} else {
result.DatabaseName());
}
}
return result;
}
@Override
public Collection<String> doBetweenSharding(Collection<String> availableTargetNames, ShardingValue<Long> shardingValue) {    Collection<String> result = new LinkedHashSet<>(availableTargetNames.size());
Range<Long> range = ValueRange();
for (Long value = range.lowerEndpoint(); value <= range.upperEndpoint(); value++) {
if (value <= 20L) {
result.DatabaseName());
} else {
result.DatabaseName());
}
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 (Value() % 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 : Values()) {
for (String tableName : tableNames) {
if (dsWith(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 = ValueRange();
for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) {
for (String each : tableNames) {
if (dsWith(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.DatabaseName(), ateDataSource());
dataSourceMap.DatabaseName(), ateDataSource());
/
/设置默认数据库
DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, DatabaseName());
//分表设置,⼤致思想就是将查询虚拟表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 = ateDataSource(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");
userAuthEntity.setPassword("123456");

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。