Slickで削除済みテーブルを使った削除を実装

Slickにて、レコード削除の際、削除したレコードを削除済みテーブルに追加する方法と
それを復活させる方法、
また管理の際に、通常テーブルと削除済みデーブルを結合して取得する方法について。

環境は

  • play2.4
  • Slick3.0
  • mysql

SQLのレコードを削除したいけど、削除済みのデータも残しておきたい。
そんなことも度々あると思うんですが、論理削除として削除フラグをテーブルに作るのも後々管理がしにくい、、ということで
通常テーブルと削除済みテーブルを2つ作ってしまえばいいじゃない!
そして、通常の処理では通常テーブルだけを参照して、
管理画面なんかで見る時だ通常テーブルと削除済みテーブルをUnionするという方法をとっています。
SQLで書くなら

通常の処理

  SELECT * From Account;

管理画面での処理

 (select *, false as Exist from Account) 
   union
 ( select *, true as Exist from AccountDelete)
   order by userId Desc;

こんな感じにしたいわけです。


ここから本題。
まず通常テーブルと削除済みテーブルの定義は以下のとおりで、通常テーブルについているO.AutoInc以外はほとんど同じです。

//通常テーブル
  val table = TableQuery[AccountTable]
  class AccountTable(tag: Tag) extends Table[Account](tag, "Account") {
    def userId = column[Int]("userId", O.PrimaryKey, O.AutoInc)
    def email = column[String]("email")
    def password = column[String]("password")

    def * = (userId.?, email, password) <>(Account.tupled, Account.unapply _)
  }

//削除済みテーブル
  val deleteTable = TableQuery[AccountDeleteTable]
  class AccountDeleteTable(tag: Tag) extends Table[Account](tag, "AccountDelete") {
    def userId = column[Int]("userId", O.PrimaryKey)
    def email = column[String]("email")
    def password = column[String]("password")

    def * = (userId.?, username, email, password) <>(Account.tupled, Account.unapply _)
  }

まず削除処理のSlick3での書き方

  def delete(userId: Int): Future[Unit] = {
    val act = {
      for {
        rows <- table.filter(_.userId === userId).take(1).result
        _ <- DBIO.seq(deleteTable += rows.head,
                      table.filter(_.userId === userId).delete)
      } yield()
    }
    db.run(act.transactionally)
  }

userIdに相当するレコードを探してきて、それを削除済みテーブルに追加して、通常テーブルから削除しています。
単純に3つのクリエをそれぞれに実行してもできますが、
一連の処理を一つのトランザクションで行いたいのでこういう形になります。


レコード復活の場合もほぼ同様ですが、UserIdをそのまま戻したいので、insertOrUpdateを利用しています。

  def restore(userId: Int): Future[Unit] = {
    val act = {
      for {
        rows <- deleteTable.filter(_.userId === userId).take(1).result
        _ <-DBIO.seq( table.insertOrUpdate(rows.head),
                         deleteTable.filter(_.userId === userId).delete)
      } yield ()
    }
    db.run(act.transactionally)
  }

通常の処理ではAccountTableのみを見ればよいですが、管理画面などでは削除されたものとそうでないものをまとめて、しかもID順に並び替えて閲覧したかったので、次のようにしました。
まず二つのテーブルを存在フラグのカラムを追加したうえで結合して、userId順に並び替えています。

  def selectAllWithDelete(): Future[Seq[AccountWithDeleteFrag]] ={
    val tbl = table.map(x => (x.userId.?, x.username, x.email, x.password, LiteralColumn[Boolean](true)) )
    val tblDelete = deleteTable.map(x => (x.userId.?, x.username, x.email, x.password, LiteralColumn[Boolean](false)))
    val union = (tbl union tblDelete).sortBy(_._1.desc)
    db.run(union.result).map(_.map(AccountWithDeleteFrag.tupled(_)))
  }
LiteralColumn[Boolean](true))

を使って、テーブルの定義に対し値を設定したカラムを1つ追加したレコードを取得しています。
それぞれののテーブルからの取得クエリを union して sortby しています。名前がないので、1番目を指定。
取得できるレコードは元のAccountレコードとは構造が変わってきますのでタプルのまま取得してもいいですし、
上記のように新しくbooleanのプロパティを増やしたcase class を作っても良いと思います


トラックバックURL  -  http://mashi.exciton.jp/archives/177/trackback