У меня есть структура базы данных со отношением «многие ко многим» между Dream
с и Tag
с.
Dream
с и Tag
с сохраняются в отдельных таблицах, и между ними, как обычно, в такой ситуации есть таблица соединения, с классом DreamTag
, представляющим соединение:
protected class DreamTagTable(tag: Tag) extends Table[DreamTag](tag, "dreamtags") {
def dreamId = column[Long]("dream_id")
def dream = foreignKey("dreams", dreamId, dreams)(_.id)
def tagId = column[Long]("tag_id")
def tag = foreignKey("tags", tagId, tags)(_.id)
// default projection
def * = (dreamId, tagId) <> ((DreamTag.apply _).tupled, DreamTag.unapply)
}
Мне удалось выполнить соответствующий дубль JOIN
чтобы получить Dream
с Tag
s, но Я изо всех сил пытался сделать это полностью неблокирующим образом .
Вот мой код для выполнения поиска, как это может пролить свет на некоторые вещи:
def createWithTags(form: DreamForm): Future[Seq[Int]] = db.run {
logger.info(s"Creating dream [$form]")
// action to put the dream
val dreamAction: DBIO[Dream] =
dreams.map(d => (d.title, d.content, d.date, d.userId, d.emotion))
.returning(dreams.map(_.id))
.into((fields, id) => Dream(id, fields._1, fields._2, fields._3, fields._4, fields._5))
.+=((form.title, form.content, form.date, form.userId, form.emotion))
// action to put any tags that don't already exist (create a single action)
val tagActions: DBIO[Seq[MyTag]] =
DBIO.sequence(form.tags.map(text => createTagIfNotExistsAction(text)))
// zip allows us to get the results of both actions in a tuple
val zipAction: DBIO[(Dream, Seq[MyTag])] = dreamAction.zip(tagActions)
// put the entries into the join table, if the zipAction succeeds
val dreamTagsAction = exec(zipAction.asTry) match {
case Success(value) => value match {
case (dream, tags) =>
DBIO.sequence(tags.map(tag => createDreamTagAction(dream, tag)))
}
case Failure(exception) => throw exception
}
dreamTagsAction
}
private def createTagIfNotExistsAction(text: String): DBIO[MyTag] = {
tags.filter(_.text === text).result.headOption.flatMap {
case Some(t: MyTag) => DBIO.successful(t)
case None =>
tags.map(t => (t.text))
.returning(tags.map(_.id))
.into((text, id) => MyTag(id, text)) += text
}
}
private def createDreamTagAction(dream: Dream, tag: MyTag): DBIO[Int] = {
dreamTags += DreamTag(dream.id, tag.id)
}
/**
* Helper method for executing an async action in a blocking way
*/
private def exec[T](action: DBIO[T]): T = Await.result(db.run(action), 2.seconds)
Сценарий
Теперь я нахожусь на этапе, когда я хочу иметь возможность обновить Dream
и список Tag
s , и я изо всех сил.
Учитывая ситуацию, когда существующий список тегов ["one", "two", "three"]
и обновляется до ["two", "three", "four"]
Я хочу:
- Удалить
Tag
для «один», если другие Dream
s не ссылаются на него. - Не трогайте записи для «два» и «три», как
Tag
и DreamTag
записи уже существуют. - Создайте
Tag
"четверку", если она не существует, и добавьте для нее запись в таблицу соединений.
Я думаю, что мне нужно сделайте что-то вроде list1.diff(list2)
и list2.diff(list1)
, но для этого потребуется сначала выполнить get, что кажется неправильным.
Возможно, я ошибаюсь - Лучше всего просто удалить все записи в объединении таблицу для этого Dream
, а затем создать каждый элемент в новом списке, или есть хороший способ изменить два списка (предыдущий и существующий) и выполнить удаление / добавление в зависимости от ситуации?
Спасибо за помощь.
NB Да, Tag
- это очень раздражающее имя класса, поскольку оно конфликтует с slick.lifted.Tag
!
Обновление - Мое решение:
Я выбрал вариант 2, упомянутый Ричардом в его ответе ...
// action to put any tags that don't already exist (create a single action)
val tagActions: DBIO[Seq[MyTag]] =
DBIO.sequence(form.tags.map(text => createTagIfNotExistsAction(text)))
// zip allows us to get the results of both actions in a tuple
val zipAction: DBIO[(Int, Seq[MyTag])] = dreamAction.zip(tagActions)
// first clear away the existing dreamtags
val deleteExistingDreamTags = dreamTags
.filter(_.dreamId === dreamId)
.delete
// put the entries into the join table, if the zipAction succeeds
val dreamTagsAction = zipAction.flatMap {
case (_, tags) =>
DBIO.sequence(tags.map(tag => createDreamTagAction(dreamId, tag)))
}
deleteExistingDreamTags.andThen(dreamTagsAction)