TypeORM在PostgreSQL下save方法对bigint主键实体错误插入而非更新
TypeORM 执行 save 时对 bigint 主键实体错误触发插入的问题
问题表现
- 针对主键为 PostgreSQL bigint 类型的
RegisteredItem实体调用getRepository(RegisteredItem).save(item)时,即便主键对应记录已经存在,TypeORM 仍错误执行插入操作,而非预期的更新逻辑 - 同代码逻辑下,字符串主键的
SomethingSimple实体调用相同save方法时,插入/更新分支判断完全符合预期 - 调试日志显示 TypeORM 确实在分支判断前执行了存在性校验的 SELECT 查询,但无法根据查询结果匹配到已存在的记录
- 手动实现分支判断(通过
hasId校验后分别走update或insert逻辑)可以正常执行;即使预先通过findOne加载实体再执行 save,依然会触发主键冲突报错
问题根源
核心是 PostgreSQL 驱动的 bigint 字段返回值与 TypeORM 预期的类型不匹配:
- PostgreSQL 中的
bigint类型字段,通过数据库驱动查询返回时默认会被解析为字符串类型——这是因为 JS 原生 Number 类型无法安全承载超过 2^53-1 范围的大整数,驱动做了兼容性设计 - 出问题的
RegisteredItem实体定义中,将itemId的 JS 类型标注为number,实际数据库返回的主键值是字符串格式。TypeORM 判断实体是新增还是更新的逻辑,是严格比对传入实体的主键值和数据库查询返回的主键值:
例如传入的主键是数字格式的123456,数据库返回的同值是字符串格式的"123456",严格相等比对不成立,TypeORM 就会判定当前实体是数据库中不存在的新实体,直接触发插入逻辑 - 这也解释了为什么调试日志中能看到 SELECT 查询执行成功,却依然触发插入逻辑:查询确实拿到了结果,但在比对传入主键和返回结果的主键时,因为类型不一致,严格比对不成立,TypeORM 依然判定没有匹配到已存在的记录
- 对比运行正常的
SomethingSimple实体:它的主键本身就是字符串类型,传入值和数据库返回值的类型完全一致,比对可以正常通过,save的分支判断自然符合预期
预加载实体执行 save 仍报错的原因
如果预加载得到的实体,其主键是数据库返回的字符串格式,但后续操作中你用数字格式的同值覆盖了实体的主键字段,类型不一致的比对依然会让 TypeORM 误判为新实体,触发插入逻辑最终报主键冲突。
修复方式
- 优先适配驱动的返回值格式:将 bigint 主键在实体中的 JS 类型调整为字符串类型,与数据库返回值类型对齐
@PrimaryColumn({name: 'itemid', type: 'bigint'}) itemId?: string;
- 如果业务场景确定主键值不会超出 JS 安全整数范围,需要保留数字类型映射,可以在 PostgreSQL 数据源配置中添加驱动参数,让驱动将 bigint 字段解析为 JS 数字类型,消除类型错配
内容的提问来源于stack exchange,提问作者Manjabes
相关产品推荐
相关产品推荐

