springboot jpa分库分表项目实现过程详解

这篇文章主要介绍了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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • spring boot jpa写原生sql报Cannot resolve table错误解决方法

    错误如图 打开View→Tool Windows→Persistence选项 在弹出的Persistence窗口的项目上右键,选择Generate Persistence Mapping→By Database Schema 在此处进行数据库相关配置,配置成功后即可在下方看到数据库中的表 选择下载driver files,或者使用自己本地的connector 自定义连接 填写mysql相关的配置信息 选择data source,就可以看到数据库相关的配置了. 在弹出的Persistence窗口的

  • springboot使用JPA时间类型进行模糊查询的方法

    这个问题是我自己开发中遇到的问题  数据库使用的是mysql5.6  字段名称为checkingTime  类型为timestamp 显而易见 存到库中的是保留6位毫秒 即yyyy-MM-dd HH:mm:ss.ssssss  此时需求是精确到分钟的相同时间 不进行存储 这时候就需要进行模糊查询   搜了一圈百度 并没有什么好用的方法 我的bean类定义的是date类型 使用注解将类型更改为timestamp 存入库中 其实在做模糊查询的时候  只需要向持久层传入String类型参数即可 我的做

  • Spring Boot jpa Service层代码实例

    这篇文章主要介绍了Spring Boot jpa Service层代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 package com.fei.service.impl; import java.util.ArrayList; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criter

  • Spring Boot中使用Spring-data-jpa实现数据库增删查改

    在实际开发过程中,对数据库的操作无非就"增删改查".就最为普遍的单表操作而言,除了表和字段不同外,语句都是类似的,开发人员需要写大量类似而枯燥的语句来完成业务逻辑. 为了解决这些大量枯燥的数据操作语句,我们第一个想到的是使用ORM框架,比如:Hibernate.通过整合Hibernate之后,我们以操作Java实体的方式最终将数据改变映射到数据库表中. 为了解决抽象各个Java实体基本的"增删改查"操作,我们通常会以泛型的方式封装一个模板Dao来进行抽象简化,但是这

  • 详解SpringBoot实现JPA的save方法不更新null属性

    序言:直接调用原生Save方法会导致null属性覆盖到数据库,使用起来十分不方便.本文提供便捷方法解决此问题. 核心思路 如果现在保存某User对象,首先根据主键查询这个User的最新对象,然后将此User对象的非空属性覆盖到最新对象. 核心代码 直接修改通用JpaRepository的实现类,然后在启动类标记此实现类即可. 一.通用CRUD实现类 public class SimpleJpaRepositoryImpl<T, ID> extends SimpleJpaRepository&l

  • SpringBoot集成Spring Data JPA及读写分离

    相关代码: github OSCchina JPA是什么 JPA(Java Persistence API)是Sun官方提出的Java持久化规范,它为Java开发人员提供了一种对象/关联映射工具 来管理Java应用中的关系数据.它包括以下几方面的内容: 1.ORM映射 支持xml和注解方式建立实体与表之间的映射. 2.Java持久化API 定义了一些常用的CRUD接口,我们只需直接调用,而不需要考虑底层JDBC和SQL的细节. 3.JPQL查询语言 这是持久化操作中很重要的一个方面,通过面向对象

  • 详解spring boot jpa整合QueryDSL来简化复杂操作

    前言 使用过spring data jpa的同学,都很清楚,对于复杂的sql查询,处理起来还是比较复杂的,而本文中的QueryDSL就是用来简化JPA操作的. Querydsl定义了一种常用的静态类型语法,用于在持久域模型数据之上进行查询.JDO和JPA是Querydsl的主要集成技术.本文旨在介绍如何使用Querydsl与JPA组合使用.JPA的Querydsl是JPQL和Criteria查询的替代方法.QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查

  • Spring Boot + Jpa(Hibernate) 架构基本配置详解

    1.基于springboot-1.4.0.RELEASE版本测试 2.springBoot + hibernate + Druid + MySQL + servlet(jsp) 不废话,直接上代码 一.maven的pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi=&qu

  • SpringBoot Data JPA 关联表查询的方法

    SpringBoot Data JPA实现 一对多.多对一关联表查询 开发环境 IDEA 2017.1 Java1.8 SpringBoot 2.0 MySQL 5.X 功能需求 通过关联关系查询商店Store中所有的商品Shop,商店对商品一对多,商品对商店多对一,外键 store_id存在于多的一方.使用数据库的内连接语句. 表结构 tb_shop tb_store 实体类,通过注解实现 1.商店类Store.java package com.gaolei.Entity; import ja

  • springboot jpa分库分表项目实现过程详解

    这篇文章主要介绍了springboot jpa分库分表项目实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 分库分表场景 关系型数据库本身比较容易成为系统瓶颈,单机存储容量.连接数.处理能力都有限.当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库.优化索引,做很多操作时性能仍下降严重.此时就要考虑对其进行切分了,切分的目的就在于减少数据库的负担,缩短查询时间. 分库分表用于应对当前互联网常见的两个场景--大数

  • vue-cli3.0 脚手架搭建项目的过程详解

    1.安装vue-cli 3.0 npm install -g @vue/cli # or yarn global add @vue/cli 安装成功后查看版本:vue -V(大写的V) 2.命令变化 vue create --help 用法:create [options] <app-name> 创建一个由 `vue-cli-service` 提供支持的新项目 选项: -p, --preset <presetName>       忽略提示符并使用已保存的或远程的预设选项   -d

  • springboot使用logback文件查看错误日志过程详解

    这篇文章主要介绍了springboot使用logback文件查看错误日志过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 <?xml version="1.0" encoding="UTF-8"?> <!-- 从高到地低 OFF . FATAL . ERROR . WARN . INFO . DEBUG . TRACE . ALL --> <!-- 日志输出规则 根据当前ROOT

  • Spring-boot集成pg、mongo多数据源过程详解

    这篇文章主要介绍了Spring-boot集成pg.mongo多数据源过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 修改POM文件,增加相应Jar包 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </

  • 使用vue cli4.x搭建vue项目的过程详解

    cli-4.x已经发布好久了,斟酌了好久,还是决定将原来的cli-2.x升级到4.x,详细的升级过程可以戳这里 1.创建项目 vue create vuetest 2.选择配置方式 ? Please pick a preset: (Use arrow keys) ☜(使用箭头键) > default (babel, eslint) ☜(使用默认的配置,会安装babel和eslint) Manually select features ☜(手动配置) 这里我选择的是手动配置(使用↑ ↓箭头切换,E

  • 使用 React 和 Threejs 创建一个VR全景项目的过程详解

    最近我在学习使用 React 配合 Three.js 来搭建一个可以浏览720全景图片的项目 实现的是加载一张 2:1 的720全景 分享一下我的创建过程 一.搭建框架并安装需要的插件 npx create-react-app parano // 创建一个 React 项目 npm install -S typescript // 安装 typescript,这个是类型辅助插件,与全景项目关系不大 npm install -S @types/three // 安装 typescript 支持的

  • SpringBoot实现分库分表

    目录 一.statementHandler对象的定义 二.prepare方法 1.首先prepare方法是用来编译SQL 2.那就是之前说的那几个具体的StatementHandler对象 3.parameterize方法 4.query/update方法 方案:可以使用拦截器拦截mybatis框架,在执行SQL前对SQL语句根据路由字段进行分库分表操作,下例只做分表功能 @Intercepts:申明需要拦截的方法 拦截StatementHandler对象 一.statementHandler对

  • IDEA通过Docker插件部署SpringBoot项目的过程详解

    1.配置Docker远程连接端口 找到并编辑服务器上的docker.service文件. vi /usr/lib/systemd/system/docker.service 如下图所示的地方,添加: -H tcp://0.0.0.0:2375 2.重启Docker systemctl daemon-reload service docker restart 3.服务器开放端口 我用的阿里云服务器,在安全组的配置规则,开放端口. 4.测试远程连接是否正常 curl http://localhost

  • Jvisualvm监控远程SpringBoot项目的过程详解

    目录 JMX 1.修改SpringBoot启动参数 2.使用本地jvisualvm监控远程 jstatd 1.添加配置 2.使用本地jvisualvm监控远程 注意:VisualVM连接远程服务器有两种方式:JMX和jstatd,两种方式都不能完美支持所有功能, 例如JMX不支持VisualGC, jstatd不支持CPU监控, 实际使用可同时配置上并按需选用. jvisualvm程序添加远程连接,请关闭本机防火墙. JMX 1.修改SpringBoot启动参数 正常的springboot启动命

  • springboot学习之构建简单项目搭建步骤详解

    概述 相信对于Java开发者而言,spring和springMvc两个框架一定不陌生,这两个框架需要我们手动配置的地方非常多,各种的xml文件,properties文件,构建一个项目还是挺复杂的,在这种情况下,springboot应运而生,他能够快速的构建spring项目,而且让项目正常运行起来的配置文件非常少,甚至只需要几个注解就可以运行整个项目. 总的说来,springboot项目可以打成jar包独立运行部署,因为它内嵌servlet容器,之前spring,springMvc需要的大量依赖,

随机推荐