Active-Slick关联关系实现示例及Active Record模式合规性咨询
嘿,很高兴你已经跑通了Active-Slick的示例项目!关于关联关系的实现,我给你梳理一下具体的方案,同时也点评下你之前的代码思路~
一、先说说你现有代码的合理性
先看你贴的这段代码片段:
val book= Book(None,"Harry Potter") val action = for { id <- bookDao.insert(acc) y<-authorDao.insert(new Author(No...
这种分步插入的方式功能上是完全可行的,但如果要严格遵循Active Record模式,其实可以调整得更贴合模式的核心思想——Active Record模式要求模型本身具备持久化能力(比如直接调用book.save()),而不是依赖单独的Dao层来执行插入操作。不过Active-Slick本身是基于Slick的Active Record风格封装,我们可以很容易调整到更贴合的写法。
二、关联关系实现示例(以Book和Author一对多为例)
假设我们的业务模型是:一个作者可以有多本书,每本书属于一个作者。下面是具体的实现步骤:
1. 定义带关联字段的模型类
首先定义模型,Book需要包含authorId字段来关联对应的Author:
// Author模型,继承ActiveRecord特质,指定主键类型为Long case class Author(id: Option[Long], name: String) extends ActiveRecord[Long, Author] // Book模型,通过authorId关联到Author的主键 case class Book(id: Option[Long], title: String, authorId: Long) extends ActiveRecord[Long, Book]
2. 实现ActiveRecord的数据库表映射
接下来创建对应的Table映射类,同时可以定义数据库级的外键约束(可选,但推荐用于数据一致性):
object Authors extends ActiveRecordTable[Long, Author]("authors") { def name = column[String]("name", O.SqlType("VARCHAR(255)")) // 定义表字段和模型的映射关系 override def * = (id.?, name) <> (Author.tupled, Author.unapply) } object Books extends ActiveRecordTable[Long, Book]("books") { def title = column[String]("title", O.SqlType("VARCHAR(255)")) def authorId = column[Long]("author_id") // 定义外键关联,指定删除作者时级联删除其书籍 def author = foreignKey("fk_book_author", authorId, Authors)(_.id.get, onDelete = ForeignKeyAction.Cascade) override def * = (id.?, title, authorId) <> (Book.tupled, Book.unapply) }
3. 贴合Active Record模式的关联操作
(1)保存关联数据(先存作者,再存书籍)
用Active Record的风格,直接调用模型自身的insert方法,而不是依赖外部Dao:
// 创建未持久化的作者和书籍实例 val author = Author(None, "J.K. Rowling") val book = Book(None, "Harry Potter and the Philosopher's Stone", authorId = 0) // 先占位,后续替换 // 组合数据库操作:先插入作者,再用返回的作者ID更新书籍后插入 val saveAction = for { savedAuthor <- author.insert // 插入后返回带生成ID的作者实例 savedBook <- book.copy(authorId = savedAuthor.id.get).insert // 替换authorId后插入书籍 } yield (savedAuthor, savedBook) // 执行异步操作(这里用await是示例,实际项目推荐用flatMap处理异步结果) val (savedAuthor, savedBook) = db.run(saveAction).await
这种写法更贴合Active Record模式的核心,模型自己负责处理持久化,不需要额外的Dao层。
(2)查询关联数据(获取某作者的所有书籍)
我们可以给Author模型添加一个方法,直接查询其关联的书籍,让模型具备关联查询的能力:
case class Author(id: Option[Long], name: String) extends ActiveRecord[Long, Author] { // 获取当前作者的所有书籍 def books: DBIO[Seq[Book]] = { Books.filter(_.authorId === id.get).result } } // 使用示例:查询指定作者及其所有书籍 val authorWithBooks = db.run(for { author <- Authors.filter(_.name === "J.K. Rowling").result.head books <- author.books } yield (author, books)).await
三、对你原有代码的总结
你之前用Dao层分别插入Book和Author的方式功能上没问题,但如果要严格遵循Active Record模式,建议把持久化逻辑放到模型本身(利用Active-Slick提供的ActiveRecord特质自带的insert/update/delete等方法),这样更符合模式的设计思想。
另外要注意:插入操作返回的实例会包含数据库生成的主键ID,一定要用这个返回的ID来设置关联字段,避免手动硬编码或占位符导致的错误哦~
内容的提问来源于stack exchange,提问作者priyanka goel

