Using Anorm RowParser

2019-05-30 13:22发布

I have been using Play framework 2.0 for about 6 months, I have been wondering why they use so many boilerplate code to parse from my SQL query returns, like below:

case class Journal_accountDetail(amount: Double, states: Boolean)

val Journal_AccountParser: RowParser[Journal_accountDetail] = {
    get[Double] ("amount") ~
    get[Boolean] ("states") map({
        case amount~states => Journal_accountDetail(amount,states)
    })
}

Is it something that boost Play framework performance ??

标签: scala anorm
2条回答
做自己的国王
2楼-- · 2019-05-30 13:41

anorm.SqlParser also provides convinent parser functions, like .str, .int, .float, .double, ... instead of .get[String], .get[Int], .get[Float], .get[Double]. Best regards.

查看更多
乱世女痞
3楼-- · 2019-05-30 14:00

The parsing API can seem a bit tedious at first, but it's quite powerful when you start combining and re-using the parsers, and much less ugly than pattern matching in every function that returns an SQL result.

Imagine something like this:

case class User(id: Int, name: String, address: Address)
case class Address(id: Int, street: String, city: String, state: State, country: Country)
case class State(id: Int, abbrev: String, name: String)
case class Country(id: Int, code: String, name: String)

To construct the User you need to parse a result with multiple JOINs. Rather than having one large parser, we construct one for each class in it's companion object:

object User {
    val parser: RowParser[User] = {
        get[Int]("users.id") ~
        get[String]("users.name") ~ 
        Address.parser map {
            case id~name~address => User(id, name, address)
        }
    }
}

object Address {
    val parser: RowParser[Address] = {
        get[Int]("address.id") ~
        get[String]("address.street) ~
        get[String]("address.city") ~ 
        State.parser ~
        Country.parser map {
            case id~street~city~state~country => Address(id, street, city, state, country)
        }
    }
}

object State {
    val parser: RowParser[State] = {
        get[Int]("states.id") ~
        get[String]("states.abbrev") ~ 
        get[String]("states.name") map {
            case id~abbrev~name => State(id, abbrev, name)
        }
    }
}

object Country {
    val parser: RowParser[Country] = {
        get[Int]("countries.id") ~
        get[String]("countries.code") ~ 
        get[String]("countries.name") map {
            case id~code~name => Country(id, code, name)
        }
    }
}

Note how I'm using the full table space in the parsers, in order to avoid column name collisions.

Altogether, this looks like a lot of code, but for each source file it's only a small footprint. And the largest benefit is that our User parser is quite clean despite it's complex structure. Let's say in User the address is actually Option[Address]. Then accounting for that change is as simple as changing Address.parser in the User parser to (Address.parser ?).

For parsing simple queries, yes it does seem like a lot of boilerplate. But I'm quite thankful for the parsing API when it comes to parsing examples like the one above (and much more complex ones).

查看更多
登录 后发表回答