Anorm

Anorm is a module in Play that supports interactions with the database using a plain SQL.

Anorm exposes methods to query the SQL database and parse the result as Scala objects, built in as well as custom.

The objective behind Anorm as stated on the Play website (https://www.playframework.com/documentation/2.3.x/ScalaAnorm) is:

Using JDBC is a pain, but we provide a better API

We agree that using the JDBC API directly is tedious, particularly in Java. You have to deal with checked exceptions everywhere and iterate over and over around the ResultSet to transform this raw dataset into your own data structure.

We provide a simpler API for JDBC; using Scala you don't need to bother with exceptions, and transforming data is really easy with a functional language. In fact, the goal of the Play Scala SQL access layer is to provide several APIs to effectively transform JDBC data into other Scala structures.

You don't need another DSL to access relational databases

SQL is already the best DSL for accessing relational databases. We don't need to invent something new. Moreover the SQL syntax and features can differ from one database vendor to another.

If you try to abstract this point with another proprietary SQL like DSL you will have to deal with several dialects dedicated for each vendor (like Hibernate ones), and limit yourself by not using a particular database's interesting features.

Play will sometimes provide you with pre-filled SQL statements, but the idea is not to hide the fact that we use SQL under the hood. Play just saves typing a bunch of characters for trivial queries, and you can always fall back to plain old SQL.

A typesafe DSL to generate SQL is a mistake

Some argue that a type safe DSL is better since all your queries are checked by the compiler. Unfortunately the compiler checks your queries based on a meta-model definition that you often write yourself by mapping your data structure to the database schema.

There are no guarantees that this meta-model is correct. Even if the compiler says that your code and your queries are correctly typed, it can still miserably fail at runtime because of a mismatch in your actual database definition.

Take control of your SQL code

Object Relational Mapping works well for trivial cases, but when you have to deal with complex schemas or existing databases, you will spend most of your time fighting with your ORM to make it generate the SQL queries you want.

Writing SQL queries yourself can be tedious for a simple 'Hello World' application, but for any real-life application, you will eventually save time and simplify your code by taking full control of your SQL code.

Note

When developing an application using Anorm, its dependency should be specified explicitly, since it is a separate module in Play (starting from Play 2.1):

val appDependencies = Seq(
      jdbc,
      anorm
)

Let's picture our user model in MySQL. The table can be defined as follows:

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `login_id` varchar(45) NOT NULL,
  `password` varchar(50) NOT NULL,
  `name` varchar(45) DEFAULT NULL,
  `dob` bigint(20) DEFAULT NULL,
  `is_active` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`),
  UNIQUE KEY `login_id_UNIQUE` (`login_id`),
  UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB

Now let's look at the different queries we will make in this table. The queries will be as follows:

  • Insert: This query includes adding a new user
  • Update: This query includes updating the profile, password, and so on
  • Select: This query includes fetching one or more user's details, based on particular criteria

Assume that when a user requests to delete his account from our application, we do not delete the user from the database, but instead mark the user's status as inactive. Therefore, we will not use any delete queries.

Using Anorm, we could have the userId autogenerated as follows:

DB.withConnection {
  implicit connection =>
  val userId  = SQL"""INSERT INTO user(login_id,password,name,
  dob) VALUES($loginId,$password,$name,$dob)""".executeInsert()
userId
}

Here, loginId, password, name, and dob are variables that are replaced in the query at runtime. Anorm builds only java.sql.PreparedStatements, which prevents SQL injection.

The SQL method returns an object of the SimpleSql type and is defined as follows:

implicit class SqlStringInterpolation(val sc: StringContext) extends AnyVal {
  def SQL(args: ParameterValue*) = prepare(args)

  private def prepare(params: Seq[ParameterValue]) = {
    // Generates the string query with "%s" for each parameter placeholder
    val sql = sc.parts.mkString("%s")

    val (ns, ps): (List[String], Map[String, ParameterValue]) =
        namedParams(params)

      SimpleSql(SqlQuery(sql, ns), ps,
        defaultParser = RowParser(row => Success(row)))
    }
  }

SimpleSql is used to represent a query in an intermediate format. Its constructor is as follows:

case class SimpleSql[T](sql: SqlQuery, params: Map[String, ParameterValue], defaultParser: RowParser[T]) extends Sql { … }

The executeInsert method fetches PreparedStatement from the SimpleSql object using its getFilledStatement method. Then the getGeneratedKeys() method of PreparedStatement is executed.

The getGeneratedKeys method results in an autogenerated key, created as a result of executing the statement in which it is called. If no key is created, it returns an empty object.

Now let's use Anorm to update a user's password:

DB.withConnection {
    implicit connection =>
SQL"""UPDATE user SET password=$password WHERE id = $userId""".executeUpdate()
}

The executeUpdate method works similar to executeInsert. The difference is that it calls the executeUpdate method of the PreparedStatement, instead of getGeneratedKeys.

The executeUpdate method returns a count of affected rows for the Data Manipulation Language (DML) statements. If the SQL statement is of the other types, such as Data Definition Language (DDL), it returns 0.

Now let's try to fetch the details of all registered users. If we want the resulting rows to be parsed as user objects, we should define a parser. The parser for a user will be as follows:

def userRow:RowParser[User] = {
    get[Long]("id") ~
      get[String]("login_id") ~
      get[Option[String]]("name") map {
      case id ~ login_id ~ name  =>  User(id, login_id, name)
    }
  }

In most queries, we will not need the password and date of birth, so we can exclude them from the user RowParser default.

A query using this parser can be shown in this way:

DB.withConnection {
  implicit connection =>
  val query = "SELECT id,login_id,name FROM user"
  SQL(query).as(userRow.*)
}

The .* symbol indicates that the result should have one or more rows similar to its common interpretation in regular expressions. Similarly, the .+ symbol can be used when we expect the result to consist of zero or more rows.

Tip

If you're using an older version of Scala where string interpolations are not supported, the queries would be written in this way:

DB.withConnection {
  implicit connection =>
val insertQuery  = """INSERT INTO user(login_id,password,name,
  |dob) VALUES({loginId},{password},{name},{dob})""".stripMargin
val userId = SQL(insertQuery).on(
  'loginId -> loginId,
  'password -> password,
  'name -> name,
  'dob -> dob).executeInsert()
userId
}

The on method updates the query with the parameter map passed to it. It is defined for SimpleSql in the following way:

 def on(args: NamedParameter*): SimpleSql[T] =
    copy(params = this.params ++ args.map(_.tupled))

Please refer to the Play documentation (http://www.playframework.com/documentation/2.3.x/ScalaAnorm) and the Anorm API documentation (http://www.playframework.com/documentation/2.3.x/api/scala/index.html#anorm.package) for more use casess and details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.165.62