搜 索

ShardingSphere-JDBC丨分库分表

  • 203阅读
  • 2021年11月09日
  • 0评论
首页 / 默认分类 / 正文

前言

Apache ShardingSphere 是一套开源的分布式数据库解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款既能够独立部署,又支持混合部署配合使用的产品组成。它们均提供标准化的数据水平扩展、分布式事务和分布式治理等功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。”

ShardingSphere-JDBC

ShardingSphere-JDBC 是 Apache ShardingSphere 的第一个产品,也是 Apache ShardingSphere 的前身。 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

数据分片

引入 Maven 依赖

ShardingSphere-JDBC 提供官方的 Spring Boot Starter,使开发者可以非常便捷的整合 ShardingSphere-JDBC 和 Spring Boot。

<dependency>
    <groupId>org.apache.shardingsphere</groupId>
    <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
    <version>${sharding-sphere.version}</version>
</dependency>

数据表实体类

分别新建两张数据表:职位表 position 与职位详细信息表 position_detail

CREATE TABLE `position` (
    `Id` BIGINT(11) NOT NULL auto_increment,
    `name` varchar(256) default null,
    `salary` varchar(256) default null,
    `city` varchar(256) default null,
    primary key(`Id`)
) engine=InnoDB default charset=utf8mb4;

create table `position_detail` (
    `Id` int(11) not null auto_increment,
    `pid` bigint(1) not null default '0',
    `description` text default null,
    primary key(`Id`)
) engine=InnoDB default charset=utf8mb4;

对应的 Java 文件如下:

Position.java
PositionDetail.java

@Data
@Entity
@Table(name = "position")
public class Position implements Serializable {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "name")
    private String name;

    @Column(name = "salary")
    private String salary;

    @Column(name = "city")
    private String city;
}

@Data
@Entity
@Table(name = "position_detail")
public class PositionDetail implements Serializable {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "pid")
    private long pid;

    @Column(name = "description")
    private String description;
}

规则配置

注意: 使用 JDBC 连接 MySQL 数据库,所以需要引入 JDBC、MySQL 的 Maven 依赖。
官方的配置文件是 application.properties 文件,我个人更喜欢 YAML 配置文件。

# debug=true
spring:
  shardingsphere:
    # 配置真实数据源
    datasource:
      # 配置第 1 个数据源
      ds0:
        # 与 JDBC 类似
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/lagou1
        password: 123456
        type: com.zaxxer.hikari.HikariDataSource
        username: root
      # 配置第 2 个数据源
      ds1:
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/lagou2
        password: 123456
        type: com.zaxxer.hikari.HikariDataSource
        username: root
      names: ds0,ds1
    props:
      # 是否在日志中打印 SQL
      sql-show: true
      # 是否在日志中打印简单风格的 SQL
      sql-simple: true
    # 配置分库策略
    rules:
      sharding:
        # 分布式序列算法配置
        key-generators:
          SNOWFLAKE-id:
            props:
              max-vibration-offset: 1
              worker-id: 0
            # 使用内置的雪花算法
            type: SNOWFLAKE
          SNOWFLAKE-id2:
            props:
              max-vibration-offset: 1
              worker-id: 0
            type: SNOWFLAKE
        sharding-algorithms:
          # 分片算法名称
          database-inline:
            props:
              # 表达式 `ds_$->{0..1}`枚举的数据源为读写分离配置的逻辑数据源名称
              algorithm-expression: ds$->{id % 2}
            type: INLINE
          database-inline2:
            props:
              algorithm-expression: ds$->{pid % 2}
            type: INLINE
        tables:
          position_detail:
            database-strategy:
              standard:
                # 使用上面定义的分片算法
                sharding-algorithm-name: database-inline2
                # 需要分片的列名称
                sharding-column: pid
            key-generate-strategy:
              column: id
              # 使用上面定义的
              key-generator-name: SNOWFLAKE-id2
          position:
            database-strategy:
              standard:
                sharding-algorithm-name: database-inline
                sharding-column: id
            key-generate-strategy:
              column: id
              key-generator-name: SNOWFLAKE-id

继承 JpaRepository 接口

public interface PositionRepository extends JpaRepository<Position, Long> {
    @Query(nativeQuery = true, value = "select p.id, p.name, p.salary, p.city, pd.description " +
            " from position p join position_detail pd on(p.id = pd.pid) where p.id=?1")
    List<Object> findPositionById(@Param("id") long id);
}

public interface PositionDetailRepository extends JpaRepository<PositionDetail, Long>{
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class)
public class ShardingDatabaseTest {

    @Autowired
    private PositionRepository positionRepository;

    @Autowired
    private PositionDetailRepository positionDetailRepository;

    @Test
    public void testAdd() {
        for (int i = 1; i < 20; i++) {
            Position p = new Position();
            // 不需要 p.setId(i);
            // 使用雪花算法生成 id
            p.setName("研发工程师" + i);
            p.setSalary("12000");
            p.setCity("广州");
            positionRepository.save(p);
        }
    }

    @Test
    public void testAdd2() {
        for (int i = 1; i < 20; i++) {
            Position p = new Position();
            // 使用雪花算法生成 id
            p.setName("测试工程师" + i);
            p.setSalary("8000");
            p.setCity("北京");
            PositionDetail pd = new PositionDetail();
            positionRepository.save(p);
            pd.setPid(p.getId());
            pd.setDescription("这是岗位的详细描述");
            positionDetailRepository.save(pd);
        }
    }

    @Test
    public void testLoad() {
        // 运行出错
        List<Object> objList = positionRepository.findPositionById(664901791502041088L);
        // 输出
        for (Object obj : objList){
            System.out.println(obj.toString());
        }
    }
}

正常运行后,一个库的插入数据的 id 都是奇数,另一个的 id 都是偶数。

错误与分析

表名没写对

运行时出现了以下错误:大概是表名没写正确,检查一下就行。

org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet

行表达式

官方文档中说明:行表达式标识符可以使用 ${...}$->{...},但前者与 Spring 本身的属性文件占位符冲突,因此在 Spring 环境中使用行表达式标识符建议使用 $->{...}。所以,应该是写成“ds$->{id % 2}”。

org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'database-inline' defined in null: Could not resolve placeholder 'id % 2' in value "ds_${id % 2}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder 'id % 2' in value "ds_${id % 2}"at org.springframework.beans.factory.config.PlaceholderConfigurerSupport.doProcessProperties

保留关键字

在运行 testLoad() 方法时,出现了以下错误:

Caused by: org.apache.calcite.sql.parser.SqlParseException: Encountered "position" at line 1, column 56.
Was expecting one of:
    "LATERAL" ...
    "TABLE" ...
    "UNNEST" ...
    <IDENTIFIER> ...
    <HYPHENATED_IDENTIFIER> ...
    <QUOTED_IDENTIFIER> ...
    <BACK_QUOTED_IDENTIFIER> ...
    <BRACKET_QUOTED_IDENTIFIER> ...
    <UNICODE_QUOTED_IDENTIFIER> ...
    "(" ...

SQL 语句在本地 navicat 执行没错误;经查阅,原来Apache Flink SQL有很多保留关键字,而恰巧,数据表名 “position” 就是其中一个。
官方文档:保留关键字列表
华为云中文文档:保留关键字_Flink SQL语法参考_华为云
对此,官方也给出了解决方案:

Although not every SQL feature is implemented yet, some string combinations are already reserved as keywords for future use. If you want to use one of the following strings as a field name, make sure to surround them with backticks (e.g. \`value\`, \`count\`).

那么,将 SQL 查询语句改为:

@Query(nativeQuery = true, value = "select p.id, p.name, p.salary, p.city, pd.description " +
     " from `position` p join position_detail pd on(p.id = pd.pid) where p.id=?1")
Object findPositionById(@Param("id") long id);

不支持

但是运行后依然出错,错误又不同了:

org.springframework.orm.jpa.JpaSystemException: could not extract ResultSet; nested exception is org.hibernate.exception.GenericJDBCException: could not extract ResultSet
    ...
Caused by: java.sql.SQLException: exception while executing query: unknown type 0
    ...
Caused by: java.lang.RuntimeException: unknown type 0
    at org.apache.calcite.avatica.Helper.createException(Helper.java:56)
    at org.apache.calcite.avatica.Helper.createException(Helper.java:41)
    ...

点击查看 Helper.java 的第 56 行,是一个抛出 SQLFeatureNotSupportedException 的方法。当 SQLState 类值为“null”时抛出的子类 SQLException,这表明 JDBC 驱动程序不支持可选的 JDBC 功能。
又查找了一下官方文档,里面的 “不支持项” 写着:不支持执行 native SQL,也不支持 JDBC 4.1 接口新功能。
初步怀疑是不支持引起的,目前尚未解决

参考

weixin_43394456的博客-CSDN博客
Apache ShardingSphere 5.0.0-beta 官方中文文档 - 书栈网
打 赏
  • 支付宝
  • 微信
Alipay
WeChatPay
评论区
暂无评论
avatar