Scala/Play framework

Play Framework 2.5 + Play Slick 2.1.0. 외래 키, 조인

partner_jun 2017. 4. 12. 17:17

외래키를 작성하고 조인해 결과를 얻어내 보자. 


1. Foreign Key 함수 작성

외래 키를 가진 테이블을 만들기 위해서는 기본 값 테이블의 테이블쿼리가 필요하다. 코드를 보면 이해하기 쉽다.

case class Article(articleSeq: Long, userSeq: Long, title: String, createDate: Timestamp)

class ArticleTable(tag: Tag) extends Table[Article](tag, "ARTICLE") {
def articleSeq = column[Long]("article_seq", O.PrimaryKey, O.AutoInc)
def userSeq = column[Long]("user_seq")
def title = column[String]("title")
def createDate = column[Timestamp]("create_date")

val users = TableQuery[UserTable] // User 클래스 테이블쿼리
def userSeqFK = foreignKey("ARTICLE_USER_FK", userSeq, users)(_.userSeq)
// foreignKey("외래키명", 외래키로 설정할 컬럼, 외래키의의 이블쿼리)(테이블쿼리의 외래키)

override def * = (articleSeq, userSeq, title, createDate) <> ((Article.apply _).tupled, Article.unapply)
}


이전 글에서 작성한 User 테이블의 userSeq를 외래키로 가지는 Article 테이블이다.

기본적인 컬럼 설정에 이어 UserTable의 테이블쿼리 객체를 선언하고 외래키 함수를 작성했다. 객체간 탐색을 지원하지 않기 때문에 JPA의 OneToMany / ManyToOne과 같은 설정을 하지 않는다는 점이 특이하다.




2. Join

Slick은 for문을 사용해 조인 쿼리문을 작성한다. 조인은 크게 두 가지 방법이 있다.


1) Monadic join

스칼라 for문의 free monad적인 속성을 이용해 조건을 쌓아놓은 후 쿼리를 만드는 방식이다.

def simpleJoin1(userId: String): Future[Seq[(User, Article)]] = {
val query = for { user <- userTableQuery if user.id === userId
article <- articleTableQuery if article.userSeq === user.userSeq
} yield (user, article)

db.run(query.result)
}

유저 아이디를 입력받아 User 테이블에서 아이디에 해당하는 userSeq를 얻어낸 후, 얻어낸 userSeq로 작성된 모든 게시물을 찾는 쿼리문이다.


특이한 점은 스칼라의 for 문법과 마찬가지로 위와 같이 if문을 사용해도 되지만 테이블 쿼리 객체에 filter나 sortBy같은 함수를 사용해도 된다는 것이다. 그래서 같은 결과를 얻는 다양한 방법이 존재한다. 


또, 결과를 종합하는(SQL에서 select 이후 컬럼명을 작성하는) yield문은 결과를 원하는 형태로 모을 수 있기 때문에 ORM에서 흔히 사용하듯 새 객체로 만들거나 위와 같이 튜플로 모을 수 있다.



2) Applicative join

직접 join 함수를 사용하는 방법이다. Monadic join과 비슷하지만 inner join인  join  함수 외에도 left outer join인   joinLeft 같은 함수들이 지원된다.

def simpleJoin2(userId: String): Future[Seq[(User, Article)]] = {
val query = for { (user, article) <- userTableQuery.filter(_.id === userId)
.join(articleTableQuery)
.on(_.userSeq === _.userSeq)
} yield (user, article)

db.run(query.result)
}

소괄호나 .를 생략하고 띄어쓰기로 간략하게 표현할 수 있다.



Slick의 Join은 ORM들이 자랑하는 객체 탐색보다 SQL문을 직접 작성하는 예전 방식과 유사하다. 하지만 JPA같은 경우 N+1 문제를 해결하기 위해 fetch join문을 작성해야 했었는데, 그런 점에서 생각한다면 딱히 불편하다고만 볼 수는 없다. 


결과를 담을 자바빈 객체들을 만들어 두어야 하는 JPA와 달리 for문의 yield를 이용해 원하는 형태로 데이터를 얻어낼 수 있다. 하지만 튜플의 특성상 자료가 많아지면 가독성이 더 떨어지기 때문에 결국 객체를 만들어야 하는 경우도 있을 것이다.

늘 그렇듯 장단점을 생각하며 사용해야 할 것이다.