Sharding-jdbc4.1.1 + Seata1.6.1

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

Sharding-jdbc4.1.1 + Seata1.6.1

官方文档:https://shardingsphere.apache.org/document/4.1.1/cn/manual/sharding-jdbc/usage/sharding/#使用spring

核心依赖

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
<dependencies>
<!-- nacos注册中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

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

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

<!-- sentinel 服务保护 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<!-- typehandlers 解决日期类异常 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.2</version>
</dependency>

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

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

<!-- okhttp 代理 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<!-- hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>

<!-- 远程二级缓存:Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Redis 专属连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>

<!-- 分布式锁 官方兼容springboot2.7.x的写法 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.45.1</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-34</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-27</artifactId>
<version>3.45.1</version>
</dependency>
<!-- 消息队列 rabbitmq -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<!-- spring json解析 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

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

<!-- sharding集成seata -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>4.1.1</version>
</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
server:
port: 10040
spring:
application:
name: business-service
redis:
redisson:
file: classpath:redisson.yaml
host: localhost
port: 6379
timeout: 10000
database: 0
lettuce:
pool:
max-idle: 8
max-active: 8
max-wait: 100ms
min-idle: 0
enabled: true
rabbitmq:
host: localhost # RabbitMQ服务地址
port: 5672 # 端口
username: user # 用户名
password: 123 # 密码
virtual-host: user # 虚拟机

listener: # 监听
type: simple # 监听类型
simple:
prefetch: 1 # 每个消费者一次只处理一条消息,处理完才能获取下一条消息
acknowledge-mode: auto
retry:
# 开启消费者失败重试
enabled: true
# 初始的失败等待时长为1秒
initial-interval: 1000ms
# 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
multiplier: 1
# 最大重试次数
max-attempts: 3
# true无状态;false有状态。如果业务中包含事务,这里改为false
stateless: true

# 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 应用编号,默认为 ${spring.application.name}
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

feign:
httpclient:
enabled: false
okhttp:
enabled: true
sentinel:
enabled: false
client:
config:
default: # 默认设置,未具体设置的所有客户端都生效
connectTimeout: 5000 # 连接超时 5秒(5000毫秒) 【默认60秒】
readTimeout: 3000 # 读取超时 5秒
loggerLevel: full # 日志级别 所有

test02: # 服务名称[或是contextId指定的服务]
connectTimeout: 5000 # 连接超时 5秒(5000毫秒) 【默认60秒】
readTimeout: 5000 # 读取超时 5秒
loggerLevel: full # 日志级别 所有

mybatis-plus:
# 开启别名的包
type-aliases-package: com.business.domain
# mapper类的XML接口实现
# 默认位置 private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};
mapper-locations: classpath:/mapper/*.xml
configuration:
# 日志实现类
# log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 自动映射行为 针对resultMap 可不必写除id之外的属性 不开启 NONE , 映射没有嵌套的 PARTIAL【默认】, 全映射 FULL
auto-mapping-behavior: full
# 驼峰命名
map-underscore-to-camel-case: true
# 枚举类型处理器
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
# 全局配置
global-config:
# 数据库配置
db-config:
# 配置MyBatis-Plus的主键策略
id-type: assign_id
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-field: deleted
# 逻辑已删除值(默认为 1)
logic-delete-value: 1
# 逻辑未删除值(默认为 0)
logic-not-delete-value: 0

使用

调用方

1
2
3
4
5
6
7
// 模拟用户下单并立即支付的场景
@GlobalTransactional(rollbackFor = Exception.class)
@ShardingTransactionType(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)
@ShardingTransactionType(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);
}

}

参考资料:

  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.

Sharding-jdbc4.1.1 + Seata1.6.1
https://superlovelace.top/2025/05/22/Sharding-jdbc4.1.1 + Seata1.6.1/
作者
棱境
发布于
2025年5月22日
更新于
2025年5月30日
许可协议