jsonscalaplayframeworkanorm

Play! Framework/Anorm: Converting data to JSON and storing into DB


Working with the play framework for the first time and I'm struggling to save the data I have converted to JSON into my database that I established in the framework.

Here's what I'm trying:

Controller:

//Write converter to convert our model data to JSON
implicit val taskWrites: Writes[Task] = (
    (JsPath \ "id").write[Int] and
    (JsPath \ "name").write[String] and
    (JsPath \ "description").write[String] and
    (JsPath \ "group").write[String]
    )(unlift(Task.unapply))



//saving tasks in JSON format. Returning OK if success.
def saveTask = Action(BodyParsers.parse.json) { request =>
    val taskResult = request.body.validate[Task]
    taskResult.fold(
        errors => {
            BadRequest(Json.obj("status" -> "KO", "Message" -> JsError.toJson(errors)))
        },
        task => {
            Task.save(task)
            //ERROR HERE: Task.insertIntoDB(task)
            Ok(Json.obj("status" -> "Ok", "Message" -> ("Task '" +task.name+ "' saved")))
        }
    )
}

Model:

case class Task(id: Int, name: String, description: String, group: String)

var list: List[Task] = Nil

//define a parser that will transform a JDBC ResultSet into a Task value.
val task = {
    get[Int]("id") ~
    get[String]("name") ~
    get[String]("description") ~
    get[String]("group") map {
        case id~name~description~group => Task(id, name, description, group)
    }

}

def save(task: Task) = {
    list = list ::: List(task)
}

def insertIntoDB(task: Task){
    DB.withConnection { implicit c =>
        SQL("insert into task (id, name, description, groupname) values ({task.id, task.name, task.description, task.group})").on(
            'id -> task.id,
            'name -> task.name,
            'description -> task.description,
            'groupname -> task.group
            ).executeUpdate()
    }
}

I'm using the H2-database engine

evolutions/default/1.sql:

#Tasks Schema

# ---- !Ups
CREATE SEQUENCE task_id_seq;
CREATE TABLE task (
    id integer NOT NULL DEFAULT nextval('task_id_seq'),
    name varchar(255),
    description varchar(255),
    groupname varchar(255)
);

# ---- !Downs
DROP SEQUENCE task_id_seq;
DROP TABLE task;

But when I try and add a new task:

curl --include --request POST --header "content-type: application/json" --data '{"id":4, "name": "test5", "description": "testdesc1","group": "groupc"}' http://localhost:9000/tasks

I am receiving errors:

[JdbcSQLException: Syntax error in SQL statement "INSERT INTO TASK (ID, NAME, DESCRIPTION, GROUPNAME) VALUES ( [*]"; expected "), DEFAULT, NOT, EXISTS, INTERSECTS"; SQL statement: insert into task (id, name, description, groupname) values ( [42001-191]]

Am I even doing this correctly? Disregarding the error, I don't think so. How can I combine save and insertIntoDB?

Also, how can I test adding data in without having to curl every time?

Thanks for the help!

EDIT: Added schema + new error


Solution

  • The problem is that you are using the wrong name/keys when doing the insert:

    Your code:

    def insertIntoDB(task: Task) {
      DB.withConnection { implicit c =>
        SQL("insert into task (id, name, description, groupname) values ({task.id, task.name, task.description, task.group})").on(
          'id -> task.id,
          'name -> task.name,
          'description -> task.description,
          'groupname -> task.group
        ).executeUpdate()
      }
    }
    

    The problems are:

    1. The insert template is wrong. It is suppose to use {} for each variable
    2. The keys used in the template (task.id) does not match the ones used in on (id)

    You can rewrite to:

    def insertIntoDB(task: Task) {
      DB.withConnection { implicit c =>
        SQL("insert into task (id, name, description, groupname) values ({id}, {name}, {description}, {groupname})").on(
          'id -> task.id,
          'name -> task.name,
          'description -> task.description,
          'groupname -> task.group
        ).executeUpdate()
      }
    }
    

    Or can even abbreviate it to:

    def insertIntoDB(task: Task) {
      DB.withConnection { implicit c =>
        SQL("insert into task (id, name, description, groupname) values (${task.id}, ${task.name}, ${task.description}, ${task.group})").executeUpdate()
      }
    }
    

    I highly recommend that you read the Anorm docs here:

    https://www.playframework.com/documentation/2.5.x/ScalaAnorm