Spring WebFlux R2DBC下如何将多Tag ID存入Post的tagId字段?
问题分析与解决方案
为什么tagId返回null?
你的Post实体里的tagId字段标注了@Column("tag_id"),但实际数据库的post表根本没有tag_id列——这个字段只存在于tag表中,用于关联post_id。同时R2DBC不像JPA那样自动处理一对多关联关系,不会主动帮你从tag表查询关联的标签ID并映射到Post的集合字段,所以查询时自然返回null。
方案一:关联查询聚合标签ID(推荐,符合关系型数据库设计)
通过SQL关联查询+聚合函数,把每个Post对应的所有tag_id聚合为集合,再映射到Post实体。
1. 修正Post实体
去掉错误的@Column("tag_id")注解,因为tagId不是数据库post表的字段,而是关联查询的结果:
@Data @AllArgsConstructor @NoArgsConstructor @Builder @Table public class Post { @Id @Column("post_id") private Long postId; @Column private String title; @Column private String body; // 仅作为业务字段,无需数据库列映射 private Collection<Long> tagId; }
2. 编写关联查询SQL
使用MySQL的JSON_ARRAYAGG函数聚合tag_id(MySQL 5.7+支持):
SELECT p.post_id, p.title, p.body, JSON_ARRAYAGG(t.tag_id) AS tag_ids FROM post p LEFT JOIN tag t ON p.post_id = t.post_id GROUP BY p.post_id, p.title, p.body;
如果是旧版本MySQL,可用GROUP_CONCAT替代,后续在Java中拆分字符串转为集合:
SELECT p.post_id, p.title, p.body, GROUP_CONCAT(t.tag_id) AS tag_ids FROM post p LEFT JOIN tag t ON p.post_id = t.post_id GROUP BY p.post_id, p.title, p.body;
3. 在R2DBC Repository中实现查询并映射结果
自定义Repository方法,配合RowMapper处理JSON数组到集合的转换:
public interface PostRepository extends R2dbcRepository<Post, Long> { @Query(""" SELECT p.post_id, p.title, p.body, JSON_ARRAYAGG(t.tag_id) AS tag_ids FROM post p LEFT JOIN tag t ON p.post_id = t.post_id GROUP BY p.post_id, p.title, p.body """) Flux<Post> findAllWithTagIds(); }
自定义RowMapper处理JSON转换:
public class PostRowMapper implements RowMapper<Post> { @Override public Post mapRow(Row row, RowMetadata metadata) { return Post.builder() .postId(row.get("post_id", Long.class)) .title(row.get("title", String.class)) .body(row.get("body", String.class)) .tagId(convertJsonToTagIds(row.get("tag_ids", JsonNode.class))) .build(); } private Collection<Long> convertJsonToTagIds(JsonNode jsonNode) { if (jsonNode == null || jsonNode.isNull()) { return Collections.emptyList(); } List<Long> tagIds = new ArrayList<>(); jsonNode.forEach(node -> tagIds.add(node.asLong())); return tagIds; } }
在服务层调用时使用该RowMapper,或者通过R2DBC配置全局转换器。
方案二:在Post表中存储JSON数组(不推荐,易导致数据不一致)
如果一定要在Post的单个字段存储多个tagId,可以修改数据库表添加JSON类型字段,但需要手动维护数据一致性:
1. 修改数据库表
ALTER TABLE post ADD COLUMN tag_ids JSON;
更新数据时手动同步标签ID:
UPDATE post SET tag_ids = '[1,2]' WHERE post_id = 1; UPDATE post SET tag_ids = '[3,4]' WHERE post_id = 2;
2. 配置R2DBC JSON转换器
让R2DBC自动将JSON数组转为Collection<Long>,添加配置类:
@Configuration public class R2dbcJsonConfig { @Bean public R2dbcCustomConversions r2dbcCustomConversions(ObjectMapper objectMapper) { List<Converter<?, ?>> converters = new ArrayList<>(); converters.add(new JsonNodeToTagIdsConverter(objectMapper)); converters.add(new TagIdsToJsonNodeConverter(objectMapper)); return R2dbcCustomConversions.of(MYSQL, converters); } static class JsonNodeToTagIdsConverter implements Converter<JsonNode, Collection<Long>> { private final ObjectMapper objectMapper; public JsonNodeToTagIdsConverter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public Collection<Long> convert(JsonNode source) { if (source == null || source.isNull()) { return Collections.emptyList(); } return objectMapper.convertValue(source, new TypeReference<List<Long>>() {}); } } static class TagIdsToJsonNodeConverter implements Converter<Collection<Long>, JsonNode> { private final ObjectMapper objectMapper; public TagIdsToJsonNodeConverter(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public JsonNode convert(Collection<Long> source) { return objectMapper.valueToTree(source); } } }
3. 修正Post实体字段
@Column("tag_ids") private Collection<Long> tagId;
总结
优先选择方案一,它符合关系型数据库的设计规范,避免了数据不一致的风险;方案二仅适用于特殊场景,需要额外维护标签与Post的关联同步。
内容的提问来源于stack exchange,提问作者Joseph Hwang
相关产品推荐
相关产品推荐

