0

I have a simple scala and play code for inserting product into database.

My database config in application.conf looks like:

db.default.hikaricp.connectionTestQuery = "SELECT 1"

db.default.driver=org.postgresql.Driver
db.default.url="jdbc:postgresql://localhost:5432/shop"
db.default.user="postgres"
db.default.password="root"

Table definition and crud operations:

case class Product(id: Long, name: String, description: String, price: BigDecimal, amount: Int)

case class ProductFormData(name: String, description: String, price: BigDecimal, amount: Int)

object ProductForm {

  val form = Form(
    mapping(
      "name" -> nonEmptyText,
      "description" -> nonEmptyText,
      "price" -> bigDecimal,
      "amount" -> number
    )(ProductFormData.apply)(ProductFormData.unapply)
  )
}

class ProductTableDef(tag: Tag) extends Table[Product](tag, "product") {

  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

  def name = column[String]("name")

  def description = column[String]("description")

  def price = column[BigDecimal]("price")

  def amount = column[Int]("amount")

  override def * =
    (id, name, description, price, amount) <> (Product.tupled, Product.unapply)
}

object Products {

  val products = TableQuery[ProductTableDef]

  private def db: Database = Database.forDataSource(DB.getDataSource())

  def add(product: Product): Future[Int] = {
    try db.run(products += product)
    finally db.close
  }

  def delete(id: Long): Future[Int] = {
    db.run(products.filter(_.id === id).delete)
  }

  def get(id: Long): Future[Option[Product]] = {
    db.run(products.filter(_.id === id).result.headOption)
  }

  def listAll: Future[Seq[Product]] = {
    db.run(products.result)
  }
}

service:

object ProductService {
  def addProduct(product: Product): Future[Int] = {
    Products.add(product)
  }
}

and controller:

def create() = Action(parse.json) { request =>
    val name = (request.body \ "name").as[String]
    val description = (request.body \ "description").as[String]
    val price = (request.body \ "price").as[BigDecimal]
    val amount = (request.body \ "amount").as[Int]

    val product = Product(0, name, description, price, amount)
    ProductService.addProduct(product)

    Ok("name : " + product.name)
  }

Everything looks good, no errors in process (I use postman, creating json and send it to server). But after all there is no data in databse. Even table is not created in database. I really don't know why this cannot be add to database.

EDIT:

create table "Product" ("id" BIGSERIAL NOT NULL PRIMARY KEY,"name" VARCHAR(254) NOT NULL,"description" VARCHAR(254) NOT NULL,"price" Decimal, "amount" BIGINT NOT NULL);

This is a script which I use to create table manually, then I try to save data frm request into database. From request everything is read fine (object Product is created) but no data still is safe into database.

EDIT 2:

case class Product(id: Option[Long], name: String, description: String, price: BigDecimal, amount: Int)

class ProductTableDef(tag: Tag) extends Table[Product](tag, "product") {

  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

  def name = column[String]("name")

  def description = column[String]("description")

  def price = column[BigDecimal]("price")

  def amount = column[Int]("amount")

  override def * =
    (id.?, name, description, price, amount) <> (Product.tupled, Product.unapply)
}

I updated models and dao with Option auto increment field, but It didn't help.

Developus
  • 1,400
  • 2
  • 14
  • 50
  • Have you read up on how Evolutions work with Play? https://www.playframework.com/documentation/2.5.x/Evolutions – sheunis May 27 '17 at 12:05
  • Yes, but my code is created with some tutorials where evolutions were not used. Can you explain what should I add instead of alone "evolutions" into build.sbt to trigger queries? – Developus May 27 '17 at 12:13
  • In your question you stated "Even table is not created in database.". Without Evolutions the table won't be created for you. You need to manually create the table in your postgres db using a SQL statement. Make sure it corresponds to your ProductTableDef. – sheunis May 27 '17 at 12:22
  • I created manually table "Product" in database but still not data is add there. – Developus May 27 '17 at 12:36
  • if you need a tutorial on play+slick have a look at this tutorial I wrote (for play2.4.x) a couple of months ago: http://pedrorijo.com/blog/play-slick/ the code is available at Github: https://github.com/pedrorijo91/play-slick3-steps/tree/step3 and there's even a play2.5.x branch: https://github.com/pedrorijo91/play-slick3-steps/tree/play2.5 hope it helps – pedrorijo91 May 27 '17 at 19:58

2 Answers2

2

Play action in controller asynchronous by default. So rendering finished before call to database finished. Call to database is a slow operation and this is condisidered as side effect: network + IO operation. Simple way to check this is to put next code before rendering:

 Thread.sleep(2000)

What you actually should do is something like:

def add = Action.async( parse.json(userReads) ) { request =>
    val results = userRepo.insert(  UserData( None, request.body.name, request.body.note ) )
    results.map(_ => Ok("done") )

For creating required tables you should use something like:

val setup = DBIO.seq(
  // Create the tables, including primary and foreign keys
  (suppliers.schema ++ coffees.schema).create,


  //
)

val setupFuture = db.run(setup)

Here is slick api documentation: http://slick.lightbend.com/doc/3.0.0/gettingstarted.html#schema

Not sure there you will place this logic in you webApp

Try to see SQL generated by Slick:

Update you method accordingly def add(product: Product): Future[Int] = { val action = products += product

   val sql = action.result.statements.toString()

   // this is SQL query which slick will try run against postGreed 
   // you should be able to run it manually from SQL console to see reason why this failing 
   println(sql)

db.run( action )

}

Pavel
  • 1,519
  • 21
  • 29
  • Thanks, but did you create a table manually or something? It works for you when you added Thread.sleep()? – Developus May 27 '17 at 12:55
  • Why you are expecting table to be created? Where logic for this ?? This should be done by other logic if you have some or manually. I would suggest flyway migration: https://flywaydb.org/ works good for me and simple to use as project evolves. – Pavel May 27 '17 at 12:57
  • I do not expect. I think it should be automatically created after I trigger insert function. – Developus May 27 '17 at 12:59
  • Yes, you can create schema in Slick: http://slick.lightbend.com/doc/3.0.0/gettingstarted.html#schema , will update my answer – Pavel May 27 '17 at 13:02
  • If you look at my code from first post, you can see I created everything, but data is not saved into database. I should add Thread.sleep() before `Ok("name : " + product.name)`? – Developus May 27 '17 at 13:04
  • Please make sure you have table with required structure in the db and there is no connection exception etc and then yes, try add Thread.sleep() before Ok(...) I did have this problem in the past, its quite painful to identify it ... – Pavel May 27 '17 at 13:07
  • I created table `Product` in database, but it still not work. I do not get any data there. – Developus May 27 '17 at 13:08
  • its sounds strange .. but try then 2 things (temporary: ) val product = Product(0, name, description, price, amount) put some data manualy and in def add(product: Product): Future[Int] = { try db.run(products += product) finally db.close } remove try/finally .. otherwise I am not sure what is the reason .. :(( – Pavel May 27 '17 at 13:13
  • I edited my first post. It still not works, even with this temporary solution. Moreover, I do not see anything in logs when I trying insert into database. – Developus May 27 '17 at 13:21
  • Well, downgrade this logic to console application, just to see if slick works fine for main part of logic, that the way I was learning slick – Pavel May 27 '17 at 13:25
  • Sorry, one more thing, change autoIncr field to be Optional: something like: case class UserData(id : Option[Int], name: String, desc: String) In your case case class Product(id: Optional[Long], ... – Pavel May 27 '17 at 13:27
  • I change field to be optional, but it didn't help... I cannot downgrade it to console application. I totally do not understand why this data is not saved intop db. – Developus May 27 '17 at 13:41
  • 1
    Well, there is an option to see SQL generated by Slick: could be printed to terminal this way: val sql = query.result.statements.toString() println(sql) I will update my answer for you examples – Pavel May 27 '17 at 13:54
  • I have done it and get result: Product(Some(3),some name,Jsome desc,45.00,1) - this is insert statement – Developus May 27 '17 at 14:16
  • Well, something very strange: https://stackoverflow.com/questions/14453447/logging-options-for-slick – Pavel May 27 '17 at 14:25
  • Finally it works! I changed my addProduct function (look first post) and now everything looks fine. Thank you for your help. – Developus May 27 '17 at 14:31
  • 1
    Please update you question OR post new answer to close this thread. Thanks – Pavel May 27 '17 at 14:33
2

Finally it works. I changed my add function into:

 def add(product: Product): Unit = {
    try {
      Await.result(db.run(DBIO.seq(
        products.schema.create,
        products += (product),
        products.result.map(println))), Duration.Inf)
    } finally db.close
  }

and now schema is created and data is added into database.

Developus
  • 1,400
  • 2
  • 14
  • 50
  • 1
    just general advise to be careful with Await.result , but as long as it works, its good! More reading about Await.results: http://docs.scala-lang.org/overviews/core/futures.html – Pavel May 27 '17 at 14:37