The Play Framework, MySQL, currency, decimal fields, and BigDecimal types

I’m currently working on a Play Framework server-side application that handles money/currency. (The application UI uses Sencha ExtJS, but that doesn’t matter for this example.)

From past experience I know that this means I need to use a decimal field in my MySQL database table. (MySQL also lets you declare this field type as numeric.) I further know that the MySQL JDBC driver uses a java.math.BigDecimal field to insert and select from this field type.

On the Play Framework side I want to use a Scala BigDecimal, so there needs to be a little conversion work between Java and Scala BigDecimal types. I won’t discuss this much, but the following code shows how I work with this situation in my Scala Play Framework application.

Play Framework controller

My Play Framework controller class is named Transactions, and looks like this:

package controllers

import play.api._
import play.api.mvc._
import play.api.data._
import play.api.data.Forms._
import play.api.data.validation.Constraints._
import views._
import models._
import scala.collection.mutable.ArrayBuffer
import play.api.libs.json._
import play.api.libs.json.Json
import play.api.libs.json.Json._
import play.api.data.format.Formats._  // needed for `of[Double]` in mapping (which i initially used)
import java.util.Calendar

object Transactions extends Controller {
    
  val transactionForm: Form[Transaction] = Form(
    // the names you use in this mapping (such as 'symbol') must match the names that will be
    // POSTed to your methods in JSON.
    // note: skipping `id` field
    // note: skipping `datetime` field
    mapping(
      // verifying here creates a field-level error; if your test returns false, the error is shown
      "symbol" -> nonEmptyText,
      "ttype" -> nonEmptyText,
      "price" -> bigDecimal,
      "quantity" -> number,
      "notes" -> text
      )
      // transactionForm -> Transaction
      ((symbol, ttype, price, quantity, notes) => Transaction(0, symbol, ttype, price, quantity, Calendar.getInstance.getTime, notes))
      // Transaction -> TransactionForm
      ((t: Transaction) => Some(t.symbol, t.ttype, t.price.toDouble, t.quantity, t.notes))
)
 
  /**
   * The Sencha client will send me id, symbol, and companyName in a POST request.
   * I need to return something like this on success:
   *     { "success" : true, "msg" : "", "id" : 100 }
   */
  def add = Action { implicit request =>
    transactionForm.bindFromRequest.fold(
      errors => {
        Ok(Json.toJson(Map("success" -> toJson(false), "msg" -> toJson("Boom!"), "id" -> toJson(0))))
      },
      transaction => {
        val id = Transaction.insert(transaction)
        id match {
          case Some(autoIncrementId) =>
              val result = Map("success" -> toJson(true), "msg" -> toJson("Success!"), "id" -> toJson(autoIncrementId))
              Ok(Json.toJson(result))
          case None =>
              // TODO inserts can fail; i need to handle this properly.
              val result = Map("success" -> toJson(true), "msg" -> toJson("Success!"), "id" -> toJson(-1))
              Ok(Json.toJson(result))
        }
        
      }
    )
  }

}

The important part of that code for this discussion is that the mapping deals with the price field as a Scala BigDecimal type.

Play Framework model class

My Play Framework model code looks like this:

package models

case class Transaction (
    id: Long,
    symbol: String,
    ttype: String,
    price: BigDecimal,
    quantity: Int,
    datetime: java.util.Date,
    notes: String
)

object Transaction {

  import anorm._
  import anorm.SqlParser._
  import play.api.db._
  import play.api.Play.current

  // a parser that transforms a JDBC ResultSet row to a Transaction value.
  // names in the `get` expressions need to match the database table field names.
  // `price` field comes back from the jdbc driver as a `java.math.BigDecimal`, so convert it to a
  // scala BigDecimal as needed.
  val transaction = {
    get[Long]("id") ~
    get[String]("symbol") ~
    get[String]("ttype") ~
    get[java.math.BigDecimal]("price") ~
    get[Int]("quantity") ~
    get[java.util.Date]("date_time") ~
    get[String]("notes") map {
      case id~symbol~ttype~price~quantity~datetime~notes => Transaction(id, symbol, ttype, BigDecimal(price), quantity, datetime, notes)
    }
  }

  /**
   * This method returns the value of the auto_increment field when the transaction is inserted
   * into the database table.
   *
   * Note: Inserting `transaction.price` does not work, throws nasty exception; need to insert a Java BigDecimal.
   */
  def insert(transaction: Transaction): Option[Long] = {
    val id: Option[Long] = DB.withConnection { implicit c =>
      SQL("insert into transactions (symbol, ttype, price, quantity, notes) values ({symbol}, {ttype}, {price}, {quantity}, {notes})")
        .on("symbol" -> transaction.symbol.toUpperCase,
            "ttype" -> transaction.ttype,
            "price" -> transaction.price.bigDecimal,  //converts to java.math.BigDecimal
            "quantity" -> transaction.quantity,
            "notes" -> transaction.notes
        ).executeInsert()
      }
    id
  }
 
 
  /**
   * JSON Serializer Code
   * --------------------
   */
  import play.api.libs.json.Json
  import play.api.libs.json._

  implicit object TransactionFormat extends Format[Transaction] {

      // convert from Transaction object to JSON (serializing to JSON)
      def writes(transaction: Transaction): JsValue = {
          val transactionSeq = Seq(
              "id" -> JsNumber(transaction.id),
              "symbol" -> JsString(transaction.symbol),
              "ttype" -> JsString(transaction.ttype),
              "price" -> JsNumber(transaction.price),
              "quantity" -> JsNumber(transaction.quantity),
              "datetime" -> JsNumber(transaction.datetime.getTime),  // TODO verify
              "notes" -> JsString(transaction.notes)
          )
          JsObject(transactionSeq)
      }

      // convert from a JSON string to a Transaction object (de-serializing from JSON)
      def reads(json: JsValue): JsResult[Transaction] = {
          val id = (json \ "id").as[Long]
          val symbol = (json \ "symbol").as[String]
          val ttype = (json \ "ttype").as[String]
          val price = (json \ "price").as[BigDecimal]
          val quantity = (json \ "quantity").as[Int]
          val datetime = (json \ "datetime").as[java.util.Date]
          val notes = (json \ "notes").as[String]
          JsSuccess(Transaction(id, symbol, ttype, price, quantity, datetime, notes))
      }
  }

}

If you search for “BigDecimal” in that code, you’ll see that I convert from a Scala BigDecimal type to a Java BigDecimal in the insert method, using the bigDecimal method.

Again, I’m not going to discuss this much; I’m just sharing this code here so I can remember how I solved this problem today. There may be other bugs in this code, I don’t know, but I do know that the BigDecimal code works as desired.

As a final note, it’s important to say that if you don’t convert from a Scala BigDecimal to a Java BigDecimal during this process, you’ll generate an exception when trying to insert the data into the MySQL database table.

The MySQL database table

That last sentence reminded me that I should also share the code for my MySQL database table:

create table transactions (
  id int auto_increment not null,
  symbol varchar(10) not null,
  ttype char(1) not null,
  quantity int not null,
  price decimal(10,2) not null,
  date_time timestamp not null default now(),
  notes text,
  primary key (id)
) engine = InnoDB;

As you can see, the price field is declared as a decimal field.