当前位置: 首页 > news >正文

MySQL逗号分隔字段-历史遗留原因兼容方案

MySQL逗号分隔字段的MyBatis操作指南

📋 背景说明

字段定义:

create your_table(supplier_id varchar(255) NULL  -- (多个ID逗号分隔)数据格式示例:1,5,3,8,12
);

现状分析:

  • 字段已在生产环境使用,不能修改表结构
  • 需要支持单个ID的增删改查操作
  • 需要保持数据格式的一致性

🔍 查询操作

1.1 精确查询(推荐使用)

<!-- 方法1:使用FIND_IN_SET函数 -->
<select id="selectBySupplierId" resultType="YourEntity">SELECT * FROM your_table WHERE FIND_IN_SET(#{supplierId}, supplier_id) > 0
</select><!-- 方法2:使用LIKE(更安全,处理边界情况) -->
<select id="selectBySupplierIdSafe" resultType="YourEntity">SELECT * FROM your_table WHERE CONCAT(',', supplier_id, ',') LIKE CONCAT('%,', #{supplierId}, ',%')
</select>

两种方法的对比:

方法优点缺点适用场景
FIND_IN_SET语法简洁,易理解不能使用索引,性能较差数据量小的表
LIKE可部分利用索引,更安全语法复杂数据量较大的表

1.2 多条件查询

<!-- 查询同时包含多个supplier_id的记录 -->
<select id="selectBySupplierIds" resultType="YourEntity">SELECT * FROM your_table WHERE <foreach collection="supplierIds" item="id" separator=" AND ">FIND_IN_SET(#{id}, supplier_id) > 0</foreach>
</select><!-- 查询包含任意一个supplier_id的记录 -->
<select id="selectByAnySupplierId" resultType="YourEntity">SELECT * FROM your_table WHERE <foreach collection="supplierIds" item="id" separator=" OR ">FIND_IN_SET(#{id}, supplier_id) > 0</foreach>
</select>

✏️ 修改/更新操作

2.1 添加新的supplier_id

<!-- 安全添加,避免重复 -->
<update id="addSupplierId">UPDATE your_table SET supplier_id = CASE WHEN supplier_id IS NULL OR supplier_id = '' THEN #{newSupplierId}WHEN FIND_IN_SET(#{newSupplierId}, supplier_id) = 0 THEN CONCAT(supplier_id, ',', #{newSupplierId})ELSE supplier_idENDWHERE id = #{recordId}
</update>

2.2 移除特定的supplier_id

<!-- 安全移除,处理各种边界情况 -->
<update id="removeSupplierId">UPDATE your_table SET supplier_id = TRIM(BOTH ',' FROM REPLACE(REPLACE(CONCAT(',', supplier_id, ','), CONCAT(',', #{removeId}, ','), ','),',,', ','))WHERE FIND_IN_SET(#{removeId}, supplier_id) > 0AND id = #{recordId}
</update>

移除操作的详细处理逻辑:

  1. CONCAT(',', supplier_id, ',') - 在首尾添加逗号,统一格式
  2. REPLACE(CONCAT(...), CONCAT(',', #{removeId}, ','), ',') - 替换目标ID
  3. REPLACE(..., ',,', ',') - 处理可能产生的连续逗号
  4. TRIM(BOTH ',' FROM ...) - 移除首尾逗号

2.3 批量更新整个supplier_id列表

<update id="updateSupplierIds">UPDATE your_table SET supplier_id = <choose><when test="supplierIds != null and supplierIds.size() > 0"><foreach collection="supplierIds" item="id" separator="," open="" close="">#{id}</foreach></when><otherwise>NULL</otherwise></choose>WHERE id = #{recordId}
</update>

🗑️ 删除操作

3.1 删除包含特定supplier_id的记录

<delete id="deleteBySupplierId">DELETE FROM your_table WHERE FIND_IN_SET(#{supplierId}, supplier_id) > 0
</delete>

💻 Java代码实现

4.1 Mapper接口定义

public interface YourMapper {// 基础查询YourEntity selectById(@Param("id") Long id);// 根据supplier_id查询List<YourEntity> selectBySupplierId(@Param("supplierId") String supplierId);List<YourEntity> selectBySupplierIds(@Param("supplierIds") List<String> supplierIds);// 更新操作int addSupplierId(@Param("recordId") Long recordId, @Param("newSupplierId") String newSupplierId);int removeSupplierId(@Param("recordId") Long recordId, @Param("removeId") String removeId);int updateSupplierIds(@Param("recordId") Long recordId, @Param("supplierIds") List<String> supplierIds);// 删除操作int deleteBySupplierId(@Param("supplierId") String supplierId);
}

4.2 Service层实现

@Service
@Transactional
public class SupplierService {@Autowiredprivate YourMapper yourMapper;/*** 安全移除supplier_id*/public boolean safeRemoveSupplierId(Long recordId, String removeId) {try {YourEntity entity = yourMapper.selectById(recordId);if (entity == null || entity.getSupplierId() == null) {return false;}// 使用Java逻辑处理,更安全List<String> ids = Arrays.stream(entity.getSupplierId().split(",")).filter(id -> !id.trim().isEmpty()).collect(Collectors.toList());boolean removed = ids.remove(removeId);if (!removed) {return false; // 要移除的ID不存在}if (ids.isEmpty()) {yourMapper.updateSupplierIds(recordId, null);} else {yourMapper.updateSupplierIds(recordId, ids);}return true;} catch (Exception e) {throw new RuntimeException("移除supplier_id失败", e);}}/*** 安全添加supplier_id*/public boolean safeAddSupplierId(Long recordId, String newId) {YourEntity entity = yourMapper.selectById(recordId);if (entity == null) return false;if (entity.getSupplierId() == null || entity.getSupplierId().isEmpty()) {yourMapper.updateSupplierIds(recordId, Collections.singletonList(newId));return true;}List<String> ids = Arrays.stream(entity.getSupplierId().split(",")).filter(id -> !id.trim().isEmpty()).collect(Collectors.toList());if (ids.contains(newId)) {return false; // 已存在,不重复添加}ids.add(newId);yourMapper.updateSupplierIds(recordId, ids);return true;}/*** 获取所有的supplier_id列表*/public List<String> getSupplierIds(Long recordId) {YourEntity entity = yourMapper.selectById(recordId);if (entity == null || entity.getSupplierId() == null) {return Collections.emptyList();}return Arrays.stream(entity.getSupplierId().split(",")).filter(id -> !id.trim().isEmpty()).collect(Collectors.toList());}
}

⚠️ 重要注意事项

5.1 性能考虑

问题:

  • FIND_IN_SET 无法使用索引,全表扫描
  • 数据量大时查询性能差

优化建议:

-- 可以考虑添加全文索引(如果MySQL版本支持)
ALTER TABLE your_table ADD FULLTEXT(supplier_id);-- 查询时使用MATCH AGAINST(需要调整业务逻辑)
SELECT * FROM your_table 
WHERE MATCH(supplier_id) AGAINST('+1' IN BOOLEAN MODE);

5.2 数据一致性保障

// 在Service层添加数据校验
private void validateSupplierId(String supplierId) {if (supplierId == null || supplierId.trim().isEmpty()) {throw new IllegalArgumentException("supplierId不能为空");}if (supplierId.contains(",")) {throw new IllegalArgumentException("单个supplierId不能包含逗号");}
}// 在添加/移除时调用校验
public boolean safeAddSupplierId(Long recordId, String newId) {validateSupplierId(newId);// ... 其余逻辑
}

5.3 事务管理

@Service
@Transactional(rollbackFor = Exception.class)
public class SupplierService {/*** 批量操作,保证原子性*/@Transactionalpublic void batchUpdateSupplierIds(Long recordId, List<String> toAdd, List<String> toRemove) {for (String removeId : toRemove) {safeRemoveSupplierId(recordId, removeId);}for (String addId : toAdd) {safeAddSupplierId(recordId, addId);}}
}

🚀 长期优化建议

6.1 推荐方案:建立关联表

-- 创建关联表(推荐方案)
CREATE TABLE supplier_relation (id BIGINT AUTO_INCREMENT PRIMARY KEY,main_id BIGINT NOT NULL,supplier_id VARCHAR(255) NOT NULL,created_time DATETIME DEFAULT CURRENT_TIMESTAMP,INDEX idx_main_id (main_id),INDEX idx_supplier_id (supplier_id),UNIQUE KEY uk_main_supplier (main_id, supplier_id)
);-- 迁移现有数据(一次性操作)
INSERT INTO supplier_relation (main_id, supplier_id)
SELECT id, SUBSTRING_INDEX(SUBSTRING_INDEX(supplier_id, ',', n.digit+1), ',', -1)
FROM your_table
INNER JOIN (SELECT 0 digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3-- 根据最大数量扩展
) n ON LENGTH(REPLACE(supplier_id, ',' , '')) <= LENGTH(supplier_id)-n.digit;

6.2 渐进式改造方案

// 1. 先创建新表,双写维护
// 2. 逐步将查询迁移到新表
// 3. 最终移除旧字段@Component
public class SupplierMigrationService {@Autowiredprivate OldMapper oldMapper;@Autowiredprivate NewMapper newMapper;/*** 双写策略,保证数据一致性*/@Transactionalpublic void addSupplierId(Long recordId, String newId) {// 写入旧表(兼容现有功能)oldMapper.addSupplierId(recordId, newId);// 写入新表newMapper.insertSupplierRelation(recordId, newId);}
}

📝 总结

操作类型推荐方案注意事项
查询FIND_IN_SETLIKE注意性能问题,大数据量需要优化
添加先查询再拼接,避免重复处理空值和边界情况
移除Java逻辑处理更安全注意逗号边界处理
批量在Service层处理逻辑保证事务一致性

最佳实践:

  1. 短期:使用上述方案维持现有功能
  2. 中期:考虑添加缓存优化查询性能
  3. 长期:推动表结构重构,使用关联表方案

这种逗号分隔的字段设计虽然不符合数据库范式,但在现有系统下通过合理的技术方案可以稳定运行。

http://www.dtcms.com/a/544605.html

相关文章:

  • Bun.js + Elysia 框架实现基于 SQLITE3 的简单 CURD 后端服务
  • 做网站 怎么赚钱吗网站数据分析课程
  • Rust——迭代器适配器深度解析:函数式编程的优雅实践
  • 理解PostgreSQL中的映射表
  • Java1029 抽象类:构造方法
  • 类和对象(中)——日期类的实现取地址运算符重载
  • Linux系统编程—线程同步与互斥
  • 【笔试真题】- 百度第一套-2025.09.23
  • notion模板 | 小胡的第二大脑[特殊字符]- 使用案例
  • notion模版 | 小胡的第二大脑[特殊字符]-介绍
  • 公司网站被百度转码了银川网站建设设计
  • 链式二叉树算法精讲:前中后序、层序与完全二叉树判断
  • 项目中遇到的特殊需求所作的特殊处理
  • 会所网站建设wordpress 怎样做模版
  • vue3使用ONLYOFFICE 实现在线Word,Excel等文档
  • Python数据分析自动化:从入门到精通
  • 零依赖一键多端!用纯 Node.js 打造“IP 可访、角色隔离”的轻量化 Mock 服务器
  • Azure 监控工具怎么选?从原生局限到第三方解决方案的效率跃升
  • 湖南省人力资源网夫唯seo
  • 佛山+网站建设品牌建设发展规划
  • 0009.STM32等单片机的RAM和FLASH使用情况查询
  • CloudFront分发安全优化指南:提升性能与用户体验的完整方案
  • 分享修改文件md5的工具
  • 拓展知识:了解grid、block、thread 关系
  • 打破视频壁垒:视频融合平台EasyCVR如何实现多路视频监控上屏的高效管理?
  • 仓颉原子操作封装:从底层原理到鸿蒙高并发实战
  • BIOS 设置PC 上电自启动
  • “自然搞懂”深度学习系列(基于Pytorch架构)——03渐入佳境
  • 网站建设及推广枣强怎么做汽车网站推广方案
  • 做网站什么软件给女朋友做情侣网站的程序员