Datomic中添加实体及一对多引用的高效实现方法
在Datomic中高效添加关联实体的方法
场景说明
实体A(如教师John)已存入Datomic数据库,该实体有一个cardinality:many类型的ref属性:teacher/students,指向实体B(如学生Brandon)。需要找到一次性完成实体B添加并关联到实体A的高效方法,替代先添加B再查询关联的繁琐流程。
现有实体与待添加实体
已提交的实体A(John):
(def john {:teacher/first "John" :teacher/last "Doe" :teacher/email "johndoe@university.edu"})
待添加的实体B(Brandon):
(def brandon {:student/id (d/squuid) :student/first "Brandon" :student/last "Smith"})
失败的尝试
直接在事务中通过引用字段关联B到A,首先属性名写错(应为:teacher/students而非:user/students),其次这种嵌套实体的写法不符合Datomic事务规则,报错:db.error/not-an-entity Unable to resolve entity: :user/students:
(d/transact conn [(conj john {:user/students brandon})]) ;; ==> ":db.error/not-an-entity Unable to resolve entity: :user/students"
当前可行但繁琐的方案
先提交实体B,查询其ID后再关联到A:
(let [brandon-tx (d/transact conn [brandon]) new-db (:db-after @brandon-tx) brandon-id (-> (d/q '[:find (pull ?e [*]) :in $ ?bid :where [?e :student/id ?bid]] new-db (:student/id brandon)) ffirst :db/id) tx [:db/add john-id :teacher/students brandon-id] result (d/transact conn [tx])] result)
优化方案:使用临时ID(TempIDs)在单事务中完成
Datomic支持临时ID,可以在同一个事务中创建新实体并建立关联,无需分两次提交。核心逻辑:
- 为待添加的学生实体分配临时ID(比如
-1,Datomic会自动替换为真实ID) - 通过教师的唯一属性
:teacher/email直接定位已存在的John实体,无需提前查询ID - 在同一事务中完成学生创建与关联操作
示例代码:
@(d/transact conn [;; 定义带临时ID的学生实体 {:db/id -1 :student/id (d/squuid) :student/first "Brandon" :student/last "Smith"} ;; 建立教师与学生的关联:通过唯一属性定位John,关联到临时ID的学生 [:db/add [:teacher/email "johndoe@university.edu"] :teacher/students -1]])
方案优势
- 仅需一次事务完成创建与关联,减少数据库交互次数
- 无需提前查询实体ID,通过唯一属性直接定位目标实体
- 临时ID由Datomic自动管理,无需手动处理ID映射
完整可运行代码
(require '[datomic.api :as d]) (def uri "datomic:mem://db") (d/create-database uri) (def conn (d/connect uri)) (def db (d/db conn)) (def schema ;; teachers [{:db/ident :teacher/first :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :teacher/last :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :teacher/email :db/valueType :db.type/string :db/cardinality :db.cardinality/one :db/unique :db.unique/identity} {:db/ident :teacher/students :db/valueType :db.type/ref :db/cardinality :db.cardinality/many} ;; students {:db/ident :student/first :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :student/last :db/valueType :db.type/string :db/cardinality :db.cardinality/one} {:db/ident :student/id :db/valueType :db.type/uuid :db/cardinality :db.cardinality/one :db/unique :db.unique/identity}]) (d/transact conn schema) ;; 提交教师实体 (def john {:teacher/first "John" :teacher/last "Doe" :teacher/email "johndoe@university.edu"}) (def jane {:teacher/first "Jane" :teacher/last "Doe" :teacher/email "janedoe@university.edu"}) @(d/transact conn [john jane]) ;; 单事务添加学生并关联到John @(d/transact conn [{:db/id -1 :student/id (d/squuid) :student/first "Brandon" :student/last "Smith"} [:db/add [:teacher/email "johndoe@university.edu"] :teacher/students -1]]) ;; 验证结果 (-> (d/q '[:find (pull ?e [:teacher/first :teacher/last :teacher/students]) :where [?e :teacher/email "johndoe@university.edu"]] (d/db conn)) ffirst) ;; 输出应包含Brandon的关联信息
内容的提问来源于stack exchange,提问作者Unsatisfied Zebra
相关产品推荐
相关产品推荐

