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.