如何使用Vapor Fluent实现Profile模型的Upsert操作?
Vapor Fluent 实现 Profile 模型的按需更新与Upsert优化
背景信息
我有一张名为profile的表,对应的Fluent模型代码如下:
final class Profile: Model, Content { struct Keys { static let userId: FieldKey = "user_id" static let nickname: FieldKey = "nickname" static let bio: FieldKey = "bio" } static let schema = "profile" @ID(key: .id) var id: UUID? @Field(key: Keys.nickname) var nickname: String? @Field(key: Keys.bio) var bio: String? @Parent(key: Keys.userId) var user: User init() {} init(id: UUID? = nil, nickname: String?, bio: String?, userId: User.IDValue) { self.id = id self.nickname = nickname self.bio = bio self.$user.id = userId } }
客户端传来的请求体示例:
{ "bio": "my new bio" }
我希望能根据客户端传递的任意属性进行更新,当前的实现代码如下:
let userId = try req.auth.require(User.self).requireID() guard let oldProfile = try await Profile.query(on: req.db).filter(\.$user.$id == userId).first() else { let newProfile = Profile(nickname: edit.nickname, bio: edit.bio, userId: userId) try await newProfile.save(on: req.db) return GlobalResponse(code: .ok, msg: .custom("save success")) } if let b = edit.bio { oldProfile.bio = b } if let n = edit.nickname { oldProfile.nickname = n } try await oldProfile.update(on: req.db) return GlobalResponse(code: .ok, msg: .custom("save success"))
这段代码可以正常运行,但我想知道有没有更优或更简便的实现方式?我还没找到Vapor Fluent中实现Upsert的便捷方法,作为Vapor新手,我尝试查找相关Upsert代码但没有结果。
优化方案
1. 利用Fluent的upsert方法实现原子性插入/更新
Fluent支持基于唯一约束的Upsert操作,首先需要给Profile模型的user_id字段添加唯一约束,确保一个用户只能有一条Profile记录:
先在迁移文件中添加约束(如果还未配置):
struct CreateProfile: AsyncMigration { func prepare(on database: Database) async throws { try await database.schema(Profile.schema) .id() .field(Profile.Keys.userId, .uuid, .required, .references(User.schema, .id)) .field(Profile.Keys.nickname, .string) .field(Profile.Keys.bio, .string) .unique(on: Profile.Keys.userId) // 添加user_id唯一约束 .create() } func revert(on database: Database) async throws { try await database.schema(Profile.schema).delete() } }
之后就可以用upsert方法替代原有逻辑,数据库会原子性处理“存在则更新、不存在则插入”的操作:
let userId = try req.auth.require(User.self).requireID() let edit = try req.content.decode(Profile.self) // 整理需要更新的字段 var updateFields: [FieldKey: DatabaseQuery.Value] = [:] if let nickname = edit.nickname { updateFields[Profile.Keys.nickname] = .string(nickname) } if let bio = edit.bio { updateFields[Profile.Keys.bio] = .string(bio) } // 执行Upsert try await Profile.query(on: req.db) .filter(\.$user.$id == userId) .upsert( update: updateFields, create: Profile(nickname: edit.nickname, bio: edit.bio, userId: userId) ) return GlobalResponse(code: .ok, msg: .custom("save success"))
2. 简化原有查询更新逻辑(不使用Upsert的情况)
如果暂时不想用Upsert,也可以简化属性更新的判断逻辑:
let userId = try req.auth.require(User.self).requireID() let edit = try req.content.decode(Profile.self) if let oldProfile = try await Profile.query(on: req.db).filter(\.$user.$id == userId).first() { // 仅覆盖客户端传来的非空属性 oldProfile.nickname = edit.nickname ?? oldProfile.nickname oldProfile.bio = edit.bio ?? oldProfile.bio try await oldProfile.update(on: req.db) } else { let newProfile = Profile(nickname: edit.nickname, bio: edit.bio, userId: userId) try await newProfile.save(on: req.db) } return GlobalResponse(code: .ok, msg: .custom("save success"))
关键说明
- Upsert的核心依赖唯一约束,必须保证
user_id字段唯一,否则无法正确匹配目标记录 - 使用
upsert可以减少一次数据库查询,由数据库层面保证操作的原子性,性能更优 - 按需更新时,只需将客户端传来的非空字段加入更新字典,数据库只会修改指定字段
内容的提问来源于stack exchange,提问作者yxk
相关产品推荐
相关产品推荐

