springboot jpa分庫分表項目實現(xiàn)過程詳解
這篇文章主要介紹了springboot jpa分庫分表項目實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
分庫分表場景
關系型數(shù)據(jù)庫本身比較容易成為系統(tǒng)瓶頸,單機存儲容量、連接數(shù)、處理能力都有限。當單表的數(shù)據(jù)量達到1000W或100G以后,由于查詢維度較多,即使添加從庫、優(yōu)化索引,做很多操作時性能仍下降嚴重。此時就要考慮對其進行切分了,切分的目的就在于減少數(shù)據(jù)庫的負擔,縮短查詢時間。
分庫分表用于應對當前互聯(lián)網(wǎng)常見的兩個場景——大數(shù)據(jù)量和高并發(fā)。通常分為垂直拆分和水平拆分兩種。
垂直拆分是根據(jù)業(yè)務將一個庫(表)拆分為多個庫(表)。如:將經(jīng)常和不常訪問的字段拆分至不同的庫或表中。由于與業(yè)務關系密切,目前的分庫分表產(chǎn)品均使用水平拆分方式。
水平拆分則是根據(jù)分片算法將一個庫(表)拆分為多個庫(表)。如:按照ID的最后一位以3取余,尾數(shù)是1的放入第1個庫(表),尾數(shù)是2的放入第2個庫(表)等。
單純的分表雖然可以解決數(shù)據(jù)量過大導致檢索變慢的問題,但無法解決過多并發(fā)請求訪問同一個庫,導致數(shù)據(jù)庫響應變慢的問題。所以通常水平拆分都至少要采用分庫的方式,用于一并解決大數(shù)據(jù)量和高并發(fā)的問題。這也是部分開源的分片數(shù)據(jù)庫中間件只支持分庫的原因。
但分表也有不可替代的適用場景。最常見的分表需求是事務問題。同在一個庫則不需考慮分布式事務,善于使用同庫不同表可有效避免分布式事務帶來的麻煩。目前強一致性的分布式事務由于性能問題,導致使用起來并不一定比不分庫分表快。目前采用最終一致性的柔性事務居多。分表的另一個存在的理由是,過多的數(shù)據(jù)庫實例不利于運維管理。綜上所述,最佳實踐是合理地配合使用分庫+分表。
Sharding-JDBC簡介
Sharding-JDBC是當當應用框架ddframe中,從關系型數(shù)據(jù)庫模塊dd-rdb中分離出來的數(shù)據(jù)庫水平分片框架,實現(xiàn)透明化數(shù)據(jù)庫分庫分表訪問。Sharding-JDBC是繼dubbox和elastic-job之后,ddframe系列開源的第3個項目。
定位為輕量級Java框架,在Java的JDBC層提供的額外服務。 它使用客戶端直連數(shù)據(jù)庫,以jar包形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架。
- 適用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
- 基于任何第三方的數(shù)據(jù)庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
- 支持任意實現(xiàn)JDBC規(guī)范的數(shù)據(jù)庫。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
- Sharding-JDBC分片策略靈活,可支持等號、between、in等多維度分片,也可支持多分片鍵。
SQL解析功能完善,支持聚合、分組、排序、limit、or等查詢,并支持Binding Table以及笛卡爾積表查詢。
項目實踐
數(shù)據(jù)準備
準備兩個數(shù)據(jù)庫。并在兩個庫中建好表, 建表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. 有多少個數(shù)據(jù)源 (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);
//添加兩個數(shù)據(jù)庫database0和database1
dataSourceMap.put(database0Config.getDatabaseName(), database0Config.createDataSource());
dataSourceMap.put(database1Config.getDatabaseName(), database1Config.createDataSource());
//設置默認數(shù)據(jù)庫
DataSourceRule dataSourceRule = new DataSourceRule(dataSourceMap, database0Config.getDatabaseName());
//分表設置,大致思想就是將查詢虛擬表Goods根據(jù)一定規(guī)則映射到真實表中去
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?如何使用sharding?jdbc進行分庫分表
- SpringBoot實現(xiàn)分庫分表
- SpringBoot整合sharding-jdbc實現(xiàn)自定義分庫分表的實踐
- SpringBoot整合sharding-jdbc實現(xiàn)分庫分表與讀寫分離的示例
- Spring Boot 集成 Sharding-JDBC + Mybatis-Plus 實現(xiàn)分庫分表功能
- Springboot2.x+ShardingSphere實現(xiàn)分庫分表的示例代碼
- SpringBoot 2.0 整合sharding-jdbc中間件實現(xiàn)數(shù)據(jù)分庫分表
- Spring Boot 分庫分表策略示例展示
相關文章
Java 線程的優(yōu)先級(setPriority)案例詳解
這篇文章主要介紹了Java 線程的優(yōu)先級(setPriority)案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-08-08
自動配置@EnableAutoConfiguration問題
這篇文章主要介紹了自動配置@EnableAutoConfiguration問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
Mybatis-Plus實現(xiàn)自動生成代碼的操作步驟
AutoGenerator 是 MyBatis-Plus 的代碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模塊的代碼,極大的提升了開發(fā)效率,本文將給大家介紹Mybatis-Plus實現(xiàn)自動生成代碼的操作步驟2023-10-10
Java?Runnable和Thread實現(xiàn)多線程哪個更好你知道嗎
這篇文章主要為大家詳細介紹了Java?Runnable和Thread實現(xiàn)多線程哪個更好,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助<BR>2022-03-03

