ShardingSphere5.2.1 + Seata1.6.1

本文最后更新于:2025年5月30日 下午

ShardingSphere5.2.1 + Seata1.6.1

准备

外围环境

1、Nacos服务

nacos官网地址:https://nacos.io/zh-cn/

Nacos服务下载地址:https://github.com/alibaba/nacos/releases?page=2

详情请移步nacos笔记查看。

2、Seata服务 1.6.1

Seata官网地址:https://seata.apache.org/zh-cn/release-history/seata-server

springboot2.7.18对应的springcloud alibaba下的seata就是1.6.1版本

详情请移步seata笔记查看。

3、mysql 5.7(搭配MGR实现高可用集群)

高可用是搭配动态数据源策略使用的,这里也可以不用高可用,那样的话就采取静态策略即可。

详情请移步mysqlMGR集群笔记查看。

步骤

Maven依赖

父类依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
</parent>


<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- spring-cloud-alibaba 版本号 -->
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>
<!-- spring-cloud 版本号 -->
<spring-cloud.version>2021.0.5</spring-cloud.version>
<snakeyaml.version>1.33</snakeyaml.version>
<druid.version>1.2.6</druid.version>
<knife4j-openapi2-spring-boot-starter.version>4.4.0</knife4j-openapi2-spring-boot-starter.version>
<!-- hutool-all 工具包 -->
<hutool.version>5.8.34</hutool.version>
</properties>



<dependencyManagement>
<dependencies>
<!-- spring-cloud 微服务依赖(如 Eureka、Consul、Zuul、OpenFeign等)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<!-- 用于初始化数据库表和数据 -->
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>${liquibase-core.version}</version> <!-- 2023年最新稳定版 -->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>${knife4j-openapi2-spring-boot-starter.version}</version>
</dependency>
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

子类依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<dependencies>
<!-- nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- 负载均衡 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- nacos配置管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>org.antlr</groupId>
<artifactId>antlr4-runtime</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- 读取bootstrap文件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- yml解析 > 解决shardingshpere找不到算法的更新配置 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</dependency>

<!-- druid 数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>

<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>

<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>

<!-- shardingsphere-jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
<version>5.2.1</version>
</dependency>

<!-- transaction 用于整合seata分布式事务 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>shardingsphere-transaction-core</artifactId>
<version>5.2.1</version>
</dependency>

<!-- spring-aop 代理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>

<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
</dependency>
</dependencies>

application.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
server:
port: 10010
spring:
application:
name: user-service

shardingsphere:
props:
sql-show: true
datasource:
names: ds_0,ds_1,ds_2
ds_0:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/db_01?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

ds_1:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/db_01?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

ds_2:
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://127.0.0.1:3306/db_01?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver

rules:
readwrite-splitting:
data-sources:
rw_1:
# 静态数据源策略
# static-strategy:
# write-data-source-name: ds_0
# read-data-source-names: ds_1,ds_2
# 动态数据源策略
dynamic-strategy:
# 数据源名称
auto-aware-data-source-name: auto_datasource_1
# 是否开启自动获取数据源
write-data-source-query-enabled: true
load-balancer-name: round-robin

# 负载均衡
load-balancers:
# 轮询
round-robin:
type: ROUND_ROBIN
# 随机
random:
type: RANDOM
props:
# 权重
weight:
type: WEIGHT
props:
weight-data-sources: ds_1=1,ds_2=1
# 事务随机
transaction-random:
type: TRANSACTION_RANDOM
# 事务轮询
transaction_round_robin:
type: TRANSACTION_ROUND_ROBIN
# 事务权重
transaction_weight:
type: TRANSACTION_WEIGHT
props:
weight-data-sources: ds_1=1,ds_2=1
# 固定副本随机
fixed_replica_random:
type: FIXED_REPLICA_RANDOM
# 固定副本轮询
fixed_replica_round_robin:
type: FIXED_REPLICA_ROUND_ROBIN
# 固定副本权重
fixed_replica_weight:
type: FIXED_REPLICA_WEIGHT
props:
weight-data-sources: ds_1=1,ds_2=1
# 固定主库
fixed_primary:
type: FIXED_PRIMARY
# 数据库发现(高可用) : 这里会检查数据源host和port是否与数据库查到的host和port一致,不一致就会报MGR组信息不可用错误
database-discovery:
data-sources:
auto_datasource_1:
data-source-names:
- ds_0
- ds_1
- ds_2
discovery-heartbeat-name: hrb_1
discovery-type-name: mgr_1
discovery-heartbeats:
hrb_1:
props:
keep-alive-corn: '0/5 * * * * ?'
discovery-types:
mgr_1:
type: 'MySQL.MGR'
props:
group-name: 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
sharding:

# 数据表分片配置
tables:
t_user:
# 数据源名
actual-data-nodes: rw_1.t_user_$->{0..1}
# 分库策略,缺省表示使用默认分库策略,以下的分片策略只能选其一
table-strategy:
# 用于单分片键的标准分片场景
standard:
# 分片列名称
sharding-column: id
# 分片算法名称
sharding-algorithm-name: t-user-inline
# 主键生成策略
key-generate-strategy:
# 分片列名称
column: id
# 主键生成器名称
key-generator-name: snowflake
# 分片算法配置
sharding-algorithms:
# 表达式分片算法
t-user-inline:
type: INLINE
props:
algorithm-expression: t_user_$->{id % 2}
# 主键生成配置
key-generators:
snowflake:
type: SNOWFLAKE
props:
worker-id: 1
# nacos配置
cloud:
nacos:
# 配置中心
config:
group: SEATA_GROUP
namespace: public
# 注册中心
discovery:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP

# seata配置
seata:
# 是否开启seata
enabled: true
# Seata 这里对应seata内的配置
application-id: seata-server
# Seata 事务组编号,用于 TC 集群名
tx-service-group: test-tx-group
# 是否开启数据源代理
enable-auto-data-source-proxy: false
data-source-proxy-mode: AT
# 服务配置项
service:
# 虚拟组和分组的映射
vgroup-mapping:
test-tx-group: default
# 分组和 Seata 服务的映射
grouplist:
default: 127.0.0.1:8091

关键配置

在java目录下创建以下文件夹

1
org.apache.shardingsphere.transaction.base.seata.at
新建三个文件
文件1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shardingsphere.transaction.base.seata.at;

import com.google.common.base.Preconditions;
import io.seata.config.FileConfiguration;
import io.seata.core.context.RootContext;
import io.seata.core.exception.TransactionException;
import io.seata.core.rpc.netty.RmNettyRemotingClient;
import io.seata.core.rpc.netty.TmNettyRemotingClient;
import io.seata.rm.RMClient;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.tm.TMClient;
import io.seata.tm.api.GlobalTransaction;
import io.seata.tm.api.GlobalTransactionContext;
import lombok.SneakyThrows;
import org.apache.shardingsphere.infra.database.type.DatabaseType;
import org.apache.shardingsphere.transaction.core.ResourceDataSource;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.apache.shardingsphere.transaction.spi.ShardingSphereTransactionManager;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
* Seata AT transaction manager.
*/
public final class SeataATShardingSphereTransactionManager implements ShardingSphereTransactionManager {

private final Map<String, DataSource> dataSourceMap = new HashMap<>();

private final String applicationId;

private final String transactionServiceGroup;

private final boolean enableSeataAT;

private final int globalTXTimeout;

public SeataATShardingSphereTransactionManager() {
FileConfiguration config = new FileConfiguration("seata.conf");
enableSeataAT = config.getBoolean("sharding.transaction.seata.at.enable", true);
applicationId = config.getConfig("client.application.id");
transactionServiceGroup = config.getConfig("client.transaction.service.group", "default");
globalTXTimeout = config.getInt("sharding.transaction.seata.tx.timeout", 60);
}

@Override
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources, final String providerType) {
if (enableSeataAT) {
initSeataRPCClient();
resourceDataSources.forEach(each -> dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource())));
}
}

private void initSeataRPCClient() {
Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file.");
TMClient.init(applicationId, transactionServiceGroup);
RMClient.init(applicationId, transactionServiceGroup);
}

@Override
public TransactionType getTransactionType() {
return TransactionType.BASE;
}

@Override
public boolean isInTransaction() {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
//return null != RootContext.getXID();
return null != SeataTransactionHolder.get();
}

@Override
public Connection getConnection(final String databaseName, final String dataSourceName) throws SQLException {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
return dataSourceMap.get(databaseName + "." + dataSourceName).getConnection();
}

@Override
public void begin() {
begin(globalTXTimeout);
}

@Override
@SneakyThrows(TransactionException.class)
public void begin(final int timeout) {
if (timeout < 0) {
throw new TransactionException("timeout should more than 0s");
}
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
GlobalTransaction globalTransaction = GlobalTransactionContext.getCurrentOrCreate();
globalTransaction.begin(timeout * 1000);
SeataTransactionHolder.set(globalTransaction);
}

@Override
@SneakyThrows(TransactionException.class)
public void commit(final boolean rollbackOnly) {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
try {
SeataTransactionHolder.get().commit();
} finally {
SeataTransactionHolder.clear();
RootContext.unbind();
}
}

@Override
@SneakyThrows(TransactionException.class)
public void rollback() {
Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
try {
SeataTransactionHolder.get().rollback();
} finally {
SeataTransactionHolder.clear();
RootContext.unbind();
}
}

@Override
public void close() {
dataSourceMap.clear();
SeataTransactionHolder.clear();
RmNettyRemotingClient.getInstance().destroy();
TmNettyRemotingClient.getInstance().destroy();
}
}
文件2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shardingsphere.transaction.base.seata.at;

import io.seata.tm.api.GlobalTransaction;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

/**
* Seata transaction holder.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SeataTransactionHolder {

private static final ThreadLocal<GlobalTransaction> CONTEXT = new ThreadLocal<>();

/**
* Set seata global transaction.
*
* @param transaction global transaction context
*/
public static void set(final GlobalTransaction transaction) {
CONTEXT.set(transaction);
}

/**
* Get seata global transaction.
*
* @return global transaction
*/
public static GlobalTransaction get() {
return CONTEXT.get();
}

/**
* Clear global transaction.
*/
public static void clear() {
CONTEXT.remove();
}
}


文件3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.shardingsphere.transaction.base.seata.at;

import io.seata.core.context.RootContext;
import org.apache.shardingsphere.infra.database.metadata.DataSourceMetaData;
import org.apache.shardingsphere.infra.executor.kernel.model.ExecutorDataMap;
import org.apache.shardingsphere.infra.executor.sql.hook.SQLExecutionHook;

import java.util.List;
import java.util.Map;

/**
* Seata transactional SQL execution hook.
*/
public final class TransactionalSQLExecutionHook implements SQLExecutionHook {

private static final String SEATA_TX_XID = "SEATA_TX_XID";

private boolean seataBranch;

@Override
public void start(final String dataSourceName, final String sql, final List<Object> parameters,
final DataSourceMetaData dataSourceMetaData, final boolean isTrunkThread, final Map<String, Object> shardingExecuteDataMap) {
if (isTrunkThread) {
if (RootContext.inGlobalTransaction()) {
ExecutorDataMap.getValue().put(SEATA_TX_XID, RootContext.getXID());
}
} else if (!RootContext.inGlobalTransaction() && shardingExecuteDataMap.containsKey(SEATA_TX_XID)) {
RootContext.bind((String) shardingExecuteDataMap.get(SEATA_TX_XID));
seataBranch = true;
}
}

@Override
public void finishSuccess() {
if (seataBranch) {
RootContext.unbind();
}
}

@Override
public void finishFailure(final Exception cause) {
if (seataBranch) {
RootContext.unbind();
}
}
}


在resources目录下创建以下文件夹

这种是使用了SPI机制

1
/META-INF/services
新建两个文件
文件1
1
2
3
文件名称:org.apache.shardingsphere.infra.executor.sql.hook.SQLExecutionHook
文件内容:
org.apache.shardingsphere.transaction.base.seata.at.TransactionalSQLExecutionHook
文件2
1
2
3
文件名称:org.apache.shardingsphere.transaction.spi.ShardingSphereTransactionManager
文件内容:
org.apache.shardingsphere.transaction.base.seata.at.SeataATShardingSphereTransactionManager

使用

调用方

1
2
3
4
5
6
7
// 模拟用户下单并立即支付的场景
@GlobalTransactional(rollbackFor = Exception.class)
@ShardingSphereTransactionType(TransactionType.BASE)
public void buyItem(OrderDTO orderDTO) {
// 通过openfeign的核心调用,其他的语句已省略。
itemClient.update(orderDTO.getItemId(), orderDTO.getAmount());
}

被调用方

1
2
3
4
5
6
7
8
9
// 扣减用户余额
@Transactional(rollbackFor = Exception.class)
@ShardingSphereTransactionType(TransactionType.BASE)
public void updateUserMoney(BigDecimal money, Long id) throws SQLException {
int i = userMapper.updateMoney(money, id);
if (i == 0){
throw new SQLException("更新失败!");
}
}

注意事项:

枚举类异常问题

shardingspheremybatisplus一起使用时,对于枚举类可能会出现异常:InvalidDataAccessApiUsageException: Error attempting to get column 'status' from result set. Cause: java.sql.SQLFeatureNotSupportedException: getObject with type。这主要是因为shardingsphere与当前版本的mybatisplus不兼容。

根据参考资料2,mybatisplus3.4.1虽然不会出现这个问题,但是这个版本查询出来的数据,对应的枚举类是null值,也是有问题,3.4.3以上没有这问题,但是又与shardingsphere冲突导致出现枚举类异常,而且mybatisplus官方说不做这个组件的兼容。所以需要自行处理。

方法一:不使用枚举类

既然是枚举类异常,那就不使用枚举类不就好了,这样倒是简单粗暴,但是并不是解决问题的好方法

方法二:自定义类型处理器方法

1、枚举类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.order.enums;

import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;

/**
* @author peter
*/
@Getter
public enum OrderStatusEnums{

WAITING_PAYMENT(0, "待付款"),
PAID(1, "已付款"),
WAITING_DELIVER(2, "待发货"),
WAITING_RECEIVE(3, "待收货"),
FINISHED(4, "已完成"),
CANCELED(5, "已取消");

@EnumValue
private final int code;

@JsonValue
private final String message;


OrderStatusEnums(int code, String message) {
this.code = code;
this.message = message;
}

// 根据数据库值获取枚举
public static OrderStatusEnums fromDbCode(String dbCode) {
for (OrderStatusEnums orderStatusEnums : values()) {
if (orderStatusEnums.code == Integer.parseInt(dbCode) || orderStatusEnums.name().equals(dbCode)) {
return orderStatusEnums;
}
}
throw new IllegalArgumentException("Unknown color code: " + dbCode);
}
}
2、枚举类处理方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.order.enums.handler;

import com.alibaba.druid.util.StringUtils;
import com.order.enums.OrderStatusEnums;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedJdbcTypes;
import org.apache.ibatis.type.MappedTypes;
import org.springframework.stereotype.Component;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
* @author peter
*/
@Component
// 定义转换器支持的JAVA类型
@MappedTypes(OrderStatusEnums.class)
// 定义转换器支持的数据库类型
@MappedJdbcTypes(value = JdbcType.INTEGER, includeNullJdbcType = true)
public class OrderStatusEnumsTypeHandler extends BaseTypeHandler<OrderStatusEnums> {

// 写入数据库(必须)
@Override
public void setNonNullParameter(PreparedStatement ps, int i, OrderStatusEnums parameter, JdbcType jdbcType)
throws SQLException {
if (parameter != null) {
ps.setString(i, String.valueOf(parameter.getCode()));
}
}

// 按列名读取(最常用)
@Override
public OrderStatusEnums getNullableResult(ResultSet rs, String columnName) throws SQLException {
String code = rs.getString(columnName);
if (StringUtils.isEmpty(code)) {
return null;
}
return OrderStatusEnums.fromDbCode(code);
}

// 按列序号读取(必须)
@Override
public OrderStatusEnums getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String code = rs.getString(columnIndex);
if (StringUtils.isEmpty(code)) {
return null;
}
return OrderStatusEnums.fromDbCode(code);
}

// 存储过程读取(必须)
@Override
public OrderStatusEnums getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String code = cs.getString(columnIndex);
if (StringUtils.isEmpty(code)) {
return null;
}
return OrderStatusEnums.fromDbCode(code);
}
}

3、注册到mybatis

1
2
mybatis-plus:
type-handlers-package: com.order.domain.enums.handler

4、指定需要处理的属性(可选)

若无法正常转换的话,指定一下试试,我试过的情况是,自定义的类型处理器只要被扫描到,就可以生效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.order.mapper.OrderMapper">

<resultMap id="BaseResultMap" type="com.order.domain.Order">
<result property="id" column="id" jdbcType="BIGINT"/>
<result property="userId" column="userId" jdbcType="BIGINT"/>
<result property="itemId" column="itemId" jdbcType="BIGINT"/>
<result property="amount" column="amount" jdbcType="INTEGER"/>
<result property="price" column="price" jdbcType="DECIMAL"/>
<result property="status" column="status" jdbcType="INTEGER" typeHandler="com.order.enums.handler.OrderStatusEnumsTypeHandler"/>
</resultMap>

<sql id="Base_Column_List">
<if test="true">
id,user_id,item_id,amount,price,`status`
</if>
</sql>

</mapper>

日期类异常

在搜索枚举类异常问题解决方法时,很多人都出现过日期时间类型也会报错的问题。同样也是缺少对应的处理器,可通过引入依赖解决日期相关问题。可参考参考资料3。

这个依赖中,包含了多种日期相关的类型处理器。

此依赖是直接加入到了mybatis的类型处理中,所以无需额外配置即可生效。

1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>

数据源不能为空异常

对于无需直接访问数据库的服务,作为入口分布式调用入口,会出现数据源不能为空的错误,所以需要在启动类中排除对应的自动装配,参考资料4。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @author peter
*/
@EnableFeignClients(basePackages = "com.business.feign", defaultConfiguration = DefaultFeignConfig.class)
@EnableDiscoveryClient
// 由于sharding-jdbc的starter会自动引入数据源,但是这个服务并不需要直接与数据库沟通,而是访问其他服务,所以需要排除掉
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class, SpringBootConfiguration.class})
public class BusinessApplication {

public static void main(String[] args) {
SpringApplication.run(BusinessApplication.class, args);
}

}

备注

若可以的话,更推荐使用sharding-jdbc4.1.1的spring starter。因为这个为了解决与seata整合时出现的问题使用的解决方法不是很推荐,像deepseek说在自己项目里创建与sharding依赖内的包相同容易产生违反开源协议的问题。若不是必须要用sharding5.x版本的新特性的话,更推荐4.1.1版本,详情查看4.1.1版本的整合记录。

参考资料:

  1. Shardingsphere-jdbc整合Feign、Seata AT 模式实现分布式事务的解决方案
  2. 整合sharding jdbc查询时,枚举类型出错
  3. sharding-jdbc 出现 java.sql.SQLFeatureNotSupportedException: getObject with type 异常处理
  4. sharding-jdbc Data sources cannot be empty.

ShardingSphere5.2.1 + Seata1.6.1
https://superlovelace.top/2025/05/20/ShardingSphere5.2.1 + Seata1.6.1/
作者
棱境
发布于
2025年5月20日
更新于
2025年5月30日
许可协议