前言
“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 文件如下:
规则配置
注意: 使用 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语法参考_华为云
对此,官方也给出了解决方案:
那么,将 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\`).
@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 官方中文文档 - 书栈网