1.什么是 ShardingSphere?
Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。
它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。 它站在数据库的上层视角,关注它们之间的协作多于数据库自身。
ShardingSphere官网
ShardingSphere-JDBC
ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。
ShardingSphere-Proxy
ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。
特性定义数据分片数据分片,是应对海量数据存储与计算的有效手段。ShardingSphere 基于底层数据库提供分布式数据库解决方案,可以水平扩展计算和存储。分布式事务事务能力,是保障数据库完整、安全的关键技术,也是数据库的核心技术。基于 XA 和 BASE 的混合事务引擎,ShardingSphere 提供在独立数据库上的分布式事务功能,保证跨数据源的数据安全。读写分离读写分离,是应对高压力业务访问的手段。基于对 SQL 语义理解及对底层数据库拓扑感知能力,ShardingSphere 提供灵活的读写流量拆分和读流量负载均衡。数据迁移数据迁移,是打通数据生态的关键能力。ShardingSphere 提供跨数据源的数据迁移能力,并可支持重分片扩展。联邦查询联邦查询,是面对复杂数据环境下利用数据的有效手段。ShardingSphere 提供跨数据源的复杂查询分析能力,实现跨源的数据关联与聚合。数据加密数据加密,是保证数据安全的基本手段。ShardingSphere 提供完整、透明、安全、低成本的数据加密解决方案。影子库在全链路压测场景下,ShardingSphere 支持不同工作负载下的数据隔离,避免测试数据污染生产环境。
2.整合 shardingsphere-jdbc
为方便演示,示例(水平分片-分表)使用 H2 内存数据库,直接启动项目即可,其余示例需要自行配置数据库
2.1 引入依赖
示例使用最新版本 5.4.1(后期同步更新)
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core</artifactId>
<version>${sharding.version}</version>
</dependency>
这里有两个坑,网上搜一堆都是老版本整合,差距较大,问题较多;
坑一、需要引入额外依赖:
<!-- 高版本中已独立,需要单独添加依赖,否则启动报错 -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb.version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>${jaxb.version}</version>
</dependency>
坑二、覆盖原路径重写 snakeyaml 中的类
/**
* 高版本 snakeyaml 在 spring-boot-autoconfigure 加载配置文件时报错
* java.lang.NoSuchMethodError: org.yaml.snakeyaml.***.***: method <init>()V not found
* 覆盖源码,添加无参构造函数
* <p>
* public Representer() {
* super(new DumperOptions());
* this.representers.put(null, new RepresentJavaBean());
* }
* </p>
*/
public class Representer extends SafeRepresenter {
protected Map<Class<? extends Object>, TypeDescription> typeDefinitions = Collections.emptyMap();
public Representer() {
super(new DumperOptions());
this.representers.put(null, new RepresentJavaBean());
}
public Representer(DumperOptions options) {
super(options);
this.representers.put(null, new RepresentJavaBean());
}
public TypeDescription addTypeDescription(TypeDescription td) {
if (Collections.EMPTY_MAP == typeDefinitions) {
typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
}
if (td.getTag() != null) {
addClassTag(td.getType(), td.getTag());
}
td.setPropertyUtils(getPropertyUtils());
return typeDefinitions.put(td.getType(), td);
}
@Override
public void setPropertyUtils(PropertyUtils propertyUtils) {
super.setPropertyUtils(propertyUtils);
Collection<TypeDescription> tds = typeDefinitions.values();
for (TypeDescription typeDescription : tds) {
typeDescription.setPropertyUtils(propertyUtils);
}
}
protected class RepresentJavaBean implements Represent {
public Node representData(Object data) {
return representJavaBean(getProperties(data.getClass()), data);
}
}
/**
* Tag logic: - explicit root tag is set in serializer - if there is a predefined class tag it is
* used - a global tag with class name is always used as tag. The JavaBean parent of the specified
* JavaBean may set another tag (tag:yaml.org,2002:map) when the property class is the same as
* runtime class
*
* @param properties JavaBean getters
* @param javaBean instance for Node
* @return Node to get serialized
*/
protected MappingNode representJavaBean(Set<Property> properties, Object javaBean) {
List<NodeTuple> value = new ArrayList<NodeTuple>(properties.size());
Tag tag;
Tag customTag = classTags.get(javaBean.getClass());
tag = customTag != null ? customTag : new Tag(javaBean.getClass());
// flow style will be chosen by BaseRepresenter
MappingNode node = new MappingNode(tag, value, FlowStyle.AUTO);
representedObjects.put(javaBean, node);
FlowStyle bestStyle = FlowStyle.FLOW;
for (Property property : properties) {
Object memberValue = property.get(javaBean);
Tag customPropertyTag = memberValue == null ? null : classTags.get(memberValue.getClass());
NodeTuple tuple = representJavaBeanProperty(javaBean, property, memberValue, customPropertyTag);
if (tuple == null) {
continue;
}
if (!((ScalarNode) tuple.getKeyNode()).isPlain()) {
bestStyle = FlowStyle.BLOCK;
}
Node nodeValue = tuple.getValueNode();
if (!(nodeValue instanceof ScalarNode && ((ScalarNode) nodeValue).isPlain())) {
bestStyle = FlowStyle.BLOCK;
}
value.add(tuple);
}
if (defaultFlowStyle != FlowStyle.AUTO) {
node.setFlowStyle(defaultFlowStyle);
} else {
node.setFlowStyle(bestStyle);
}
return node;
}
/**
* Represent one JavaBean property.
*
* @param javaBean - the instance to be represented
* @param property - the property of the instance
* @param propertyValue - value to be represented
* @param customTag - user defined Tag
* @return NodeTuple to be used in a MappingNode. Return null to skip the property
*/
protected NodeTuple representJavaBeanProperty(Object javaBean, Property property, Object propertyValue, Tag customTag) {
ScalarNode nodeKey = (ScalarNode) representData(property.getName());
// the first occurrence of the node must keep the tag
boolean hasAlias = this.representedObjects.containsKey(propertyValue);
Node nodeValue = representData(propertyValue);
if (propertyValue != null && !hasAlias) {
NodeId nodeId = nodeValue.getNodeId();
if (customTag == null) {
if (nodeId == NodeId.scalar) {
// generic Enum requires the full tag
if (property.getType() != Enum.class) {
if (propertyValue instanceof Enum<?>) {
nodeValue.setTag(Tag.STR);
}
}
} else {
if (nodeId == NodeId.mapping) {
if (property.getType() == propertyValue.getClass()) {
if (!(propertyValue instanceof Map<?, ?>)) {
if (!nodeValue.getTag().equals(Tag.SET)) {
nodeValue.setTag(Tag.MAP);
}
}
}
}
checkGlobalTag(property, nodeValue, propertyValue);
}
}
}
return new NodeTuple(nodeKey, nodeValue);
}
/**
* Remove redundant global tag for a type safe (generic) collection if it is the same as defined
* by the JavaBean property
*
* @param property - JavaBean property
* @param node - representation of the property
* @param object - instance represented by the node
*/
@SuppressWarnings("unchecked")
protected void checkGlobalTag(Property property, Node node, Object object) {
// Skip primitive arrays.
if (object.getClass().isArray() && object.getClass().getComponentType().isPrimitive()) {
return;
}
Class<?>[] arguments = property.getActualTypeArguments();
if (arguments != null) {
if (node.getNodeId() == NodeId.sequence) {
// apply map tag where class is the same
Class<? extends Object> t = arguments[0];
SequenceNode snode = (SequenceNode) node;
Iterable<Object> memberList = Collections.emptyList();
if (object.getClass().isArray()) {
memberList = Arrays.asList((Object[]) object);
} else if (object instanceof Iterable<?>) {
// list
memberList = (Iterable<Object>) object;
}
Iterator<Object> iter = memberList.iterator();
if (iter.hasNext()) {
for (Node childNode : snode.getValue()) {
Object member = iter.next();
if (member != null) {
if (t.equals(member.getClass())) {
if (childNode.getNodeId() == NodeId.mapping) {
childNode.setTag(Tag.MAP);
}
}
}
}
}
} else if (object instanceof Set) {
Class<?> t = arguments[0];
MappingNode mnode = (MappingNode) node;
Iterator<NodeTuple> iter = mnode.getValue().iterator();
Set<?> set = (Set<?>) object;
for (Object member : set) {
NodeTuple tuple = iter.next();
Node keyNode = tuple.getKeyNode();
if (t.equals(member.getClass())) {
if (keyNode.getNodeId() == NodeId.mapping) {
keyNode.setTag(Tag.MAP);
}
}
}
} else if (object instanceof Map) { // NodeId.mapping ends-up here
Class<?> keyType = arguments[0];
Class<?> valueType = arguments[1];
MappingNode mnode = (MappingNode) node;
for (NodeTuple tuple : mnode.getValue()) {
resetTag(keyType, tuple.getKeyNode());
resetTag(valueType, tuple.getValueNode());
}
} else {
// the type for collection entries cannot be
// detected
}
}
}
private void resetTag(Class<? extends Object> type, Node node) {
Tag tag = node.getTag();
if (tag.matches(type)) {
if (Enum.class.isAssignableFrom(type)) {
node.setTag(Tag.STR);
} else {
node.setTag(Tag.MAP);
}
}
}
/**
* Get JavaBean properties to be serialised. The order is respected. This method may be overridden
* to provide custom property selection or order.
*
* @param type - JavaBean to inspect the properties
* @return properties to serialise
*/
protected Set<Property> getProperties(Class<? extends Object> type) {
if (typeDefinitions.containsKey(type)) {
return typeDefinitions.get(type).getProperties();
}
return getPropertyUtils().getProperties(type);
}
}
注意:实体类ID生成算法使用分布式雪花算法,mapper层、service层照旧即可。
@Getter
@Setter
@ToString
@Accessors(chain = true)
@TableName("tb_user")
@Schema(description = "用户")
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 1129496589110730436L;
@TableId(value = "id", type = IdType.ASSIGN_ID)
@Schema(description = "主键ID")
private Long id;
@TableField("create_time")
@Schema(description = "创建时间")
private Long createTime;
@TableField("name")
@Schema(description = "姓名")
private String name;
}
示例表结构(此处以水平分片-分表为例)
DROP TABLE IF EXISTS tb_user;
CREATE TABLE `tb_user`
(
`id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
`create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
`name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
);
DROP TABLE IF EXISTS tb_user0;
CREATE TABLE `tb_user0`
(
`id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
`create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
`name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
);
DROP TABLE IF EXISTS tb_user1;
CREATE TABLE `tb_user1`
(
`id` BIGINT PRIMARY KEY NOT NULL COMMENT '主键ID',
`create_time` BIGINT DEFAULT NULL COMMENT '创建时间',
`name` VARCHAR(32) DEFAULT NULL COMMENT '姓名'
)
配置文件(此处以水平分片-分表为例),其他可自行配置数据源进行测试:
# 数据源配置
dataSources:
dsA:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: org.h2.Driver
url: jdbc:h2:mem:db_sharding
username: sa # 用户名,用于控制台登录
password: 123456 # 密码,用于控制台登录
dsB0:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: org.h2.Driver
url: jdbc:h2:mem:db_sharding
username: sa # 用户名,用于控制台登录
password: 123456 # 密码,用于控制台登录
dsB1:
dataSourceClassName: com.zaxxer.hikari.HikariDataSource
driverClassName: org.h2.Driver
url: jdbc:h2:mem:db_sharding
username: sa # 用户名,用于控制台登录
password: 123456 # 密码,用于控制台登录
# 规则配置
rules:
- !SINGLE
tables:
- "*.*"
- !SHARDING
tables:
# 逻辑表名
tb_user:
actualDataNodes: dsB0.tb_user${0..1}
# 分库策略
tableStrategy:
standard:
# 分片列名称
shardingColumn: id
# 分片算法名称
shardingAlgorithmName: inline_id
# 分片算法配置
shardingAlgorithms:
# 标准分片算法-行表达式分片算法
inline_id:
# 基于行表达式的分片算法
type: INLINE
props:
algorithm-expression: tb_user${id % 2}
# 属性配置
props:
sql-show: true
最后,启动项目
访问 http://localhost:8080/doc.html 接口文档进行测试
访问 http://localhost:8080/h2 数据库控制台,输入用户名、密码,查看数据库信息
整合DEMO仓库地址
版权归原作者 ゞ註﹎錠oo 所有, 如有侵权,请联系我们删除。