Advertisement
mitrakov

Slick: DAO

Feb 6th, 2018
422
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scala 9.20 KB | None | 0 0
  1. // 1. Let's create a new test table via Play Evolutions (https://pastebin.com/5qhdsZp2)
  2. // 2.sql
  3. # students schema, course table
  4.  
  5. # --- !Ups
  6.  
  7. CREATE TABLE `course` (
  8.     `course_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'primary key',
  9.     `name` VARCHAR(64) NOT NULL COMMENT 'course name',
  10.     `duration` TINYINT(2) NOT NULL COMMENT 'duration, in semesters',
  11.     `group` ENUM('A','B','C','D') NOT NULL COMMENT 'course group',
  12.     `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
  13.     `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'update time',
  14.     PRIMARY KEY (`course_id`),
  15.     INDEX `IDX_course_name` (`name` ASC)
  16. ) COMMENT 'course table' COLLATE 'utf8_general_ci' ENGINE InnoDB;
  17.  
  18. # --- !Downs
  19.  
  20. DROP TABLE IF EXISTS `course`;
  21.  
  22.  
  23. '
  24. // CourseDao.scala
  25. package dao
  26.  
  27. import java.sql.Timestamp
  28. import java.time.LocalDateTime
  29. import javax.inject.Inject
  30.  
  31. import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
  32. import play.db.NamedDatabase
  33. import slick.jdbc.JdbcProfile
  34. import slick.lifted.ProvenShape
  35.  
  36. import scala.concurrent.Future
  37.  
  38. case class Course(courseId: Option[Long] = None, name: String, duration: Int, group: Int, createdAt: Timestamp, updatedAt: Timestamp)
  39.  
  40. /**
  41.  * Course DAO
  42.  * @param dbConfigProvider DB Config Provider
  43.  */
  44. class CourseDao @Inject() (
  45.     @NamedDatabase("students") val dbConfigProvider: DatabaseConfigProvider
  46.   ) extends HasDatabaseConfigProvider[JdbcProfile] {
  47.  
  48.   import profile.api._  // slick.driver.MySQLDriver.api._ for old versions
  49.  
  50.   private class CourseTable(tag: Tag) extends Table[Course](tag, "course") {
  51.     def courseId: Rep[Option[Long]] = column[Option[Long]]("course_id", O.PrimaryKey, O.AutoInc)
  52.     def name: Rep[String] = column[String]("name")
  53.     def duration: Rep[Int] = column[Int]("duration")
  54.     def group: Rep[Int] = column[Int]("group")
  55.     def createdAt: Rep[Timestamp] = column[Timestamp]("created_at")
  56.     def updatedAt: Rep[Timestamp] = column[Timestamp]("updated_at")
  57.  
  58.     override def * : ProvenShape[Course] =
  59.       (courseId, name, duration, group, createdAt, updatedAt) <> (Course.tupled, Course.unapply)
  60.     //(courseId, name, duration, group, createdAt, updatedAt).mapTo[Course] also possible
  61.   }
  62.  
  63.   private lazy val table = TableQuery[CourseTable]
  64.  
  65.   def addCourse(name: String, duration: Int, group: Int) = {
  66.     val now = Timestamp.valueOf(LocalDateTime.now)
  67.     val query = table += Course(None, name, duration, group, now, now)
  68.     println(query.statements.mkString)
  69.     db.run(query)
  70.   }
  71. }
  72.  
  73.  
  74. // TestController.scala:
  75. package controllers
  76.  
  77. import javax.inject.Inject
  78. import scala.concurrent.{ExecutionContext, Future}
  79. import play.api.mvc._
  80. import play.api.libs.json.{JsValue, Json, Reads}
  81.  
  82. import dao.{CourseDao}
  83.  
  84. class TestController @Inject()(
  85.     courseDao: CourseDao, cc: ControllerComponents)(implicit ec: ExecutionContext) extends AbstractController(cc) {
  86.  
  87.   case class CourseRequest(duration: Int, group: Int)
  88.   implicit val courseReads: Reads[CourseRequest] = Json.reads[CourseRequest]
  89.  
  90.   // add to routes file: POST    /courses/add/:name    controllers.TestController.addNewCourse(name)
  91.   def addNewCourse(name: String): Action[JsValue] = Action(parse.json).async { request =>
  92.     request.body.asOpt[CourseRequest] match {
  93.       case Some(cReq) => courseDao.addCourse(name, cReq.duration, cReq.group) map {res => Ok(s"Result: $res")}
  94.       case None => Future.successful(BadRequest("Cannot parse json"))
  95.     }
  96.   }
  97. }
  98.  
  99.  
  100.  
  101. // 2. Check it out! Use curl, Postman or another tool you wish
  102. http://localhost:9000/courses/add/math
  103. Cannot parse json
  104.  
  105.  
  106.  
  107. // 2.2. Oh, man! We need to specify json data:
  108. http://localhost:9000/courses/add/math with JSON: {"duration": 3, "group": 1}
  109. insert into `course` (`name`,`duration`,`group`,`created_at`,`updated_at`)  values (?,?,?,?,?)
  110. Result: 1 // 1 is how many rows affected
  111. DB:
  112. "1" "math"  "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  113.  
  114.  
  115.  
  116. // 3. Let's add "returning" feature, so that we can return new ID generated by MySQL
  117. def addCourse(name: String, duration: Int, group: Int): Future[Option[Long]] = {
  118.   val now = Timestamp.valueOf(LocalDateTime.now)
  119.   val query = (table returning table.map(_.courseId)) += Course(None, name, duration, group, now, now)
  120.   println(query.statements.mkString)
  121.   db.run(query)
  122. }
  123.  
  124. http://localhost:9000/courses/add/history with JSON: {"duration": 3, "group": 1}
  125. insert into `course` (`name`,`duration`,`group`,`created_at`,`updated_at`)  values (?,?,?,?,?)
  126. Result: Some(2)
  127. DB:
  128. "1" "math"      "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  129. "2" "history"   "3" "A" "2018-02-06 23:08:40"   "2018-02-06 23:08:40"
  130.  
  131.  
  132.  
  133. // 4. Next feature: we can generate timestamps (and other default values) on the DB side; just
  134. // specify "O.AutoInc" to skip an argument in INSERT code;
  135. // if you wanna force AutoInc parameters, use "forceInsert" instead of "+="
  136. case class Course(
  137.   courseId: Option[Long] = None,
  138.   name: String,
  139.   duration: Int,
  140.   group: Int,
  141.   createdAt: Option[Timestamp] = None,
  142.   updatedAt: Option[Timestamp] = None
  143. )
  144.  
  145. def createdAt: Rep[Option[Timestamp]] = column[Option[Timestamp]]("created_at", O.AutoInc)
  146. def updatedAt: Rep[Option[Timestamp]] = column[Option[Timestamp]]("updated_at", O.AutoInc)
  147.  
  148. def addCourse(name: String, duration: Int, group: Int): Future[Option[Long]] = {
  149.   val query = (table returning table.map(_.courseId)) += Course(None, name, duration, group)
  150.   println(query.statements.mkString)
  151.   db.run(query)
  152. }
  153.  
  154. http://localhost:9000/courses/add/geography with JSON: {"duration": 1, "group": 2}
  155. insert into `course` (`name`,`duration`,`group`)  values (?,?,?)
  156. Result: Some(3)
  157. DB:
  158. "1" "math"      "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  159. "2" "history"   "3" "A" "2018-02-06 23:08:40"   "2018-02-06 23:08:40"
  160. "3" "geography" "1" "B" "2018-02-06 23:24:57"   "2018-02-06 23:24:57"
  161.  
  162.  
  163.  
  164. // 5. Next feature: we can use String for Enums instead of Int:
  165. case class Course(
  166.   courseId: Option[Long] = None,
  167.   name: String,
  168.   duration: Int,
  169.   group: String,
  170.   createdAt: Option[Timestamp] = None,
  171.   updatedAt: Option[Timestamp] = None
  172. )
  173.  
  174. def group: Rep[String] = column[String]("group")
  175.  
  176. def addCourse(name: String, duration: Int, group: String): Future[Option[Long]] = {
  177.  
  178. // in controller:
  179. case class CourseRequest(duration: Int, group: String)
  180.  
  181. http://localhost:9000/courses/add/biology with JSON: {"duration": 1, "group": 2}
  182. Cannot parse json
  183.  
  184.  
  185.  
  186. // 5.1. Ooops! Specify "C" group instead of 2 in json data:
  187. http://localhost:9000/courses/add/biology with JSON: {"duration": 1, "group": "C"}
  188. insert into `course` (`name`,`duration`,`group`)  values (?,?,?)
  189. Result: Some(4)
  190. DB:
  191. "1" "math"      "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  192. "2" "history"   "3" "A" "2018-02-06 23:08:40"   "2018-02-06 23:08:40"
  193. "3" "geography" "1" "B" "2018-02-06 23:24:57"   "2018-02-06 23:24:57"
  194. "4" "biology"   "1" "C" "2018-02-06 23:31:26"   "2018-02-06 23:31:26"
  195.  
  196.  
  197.  
  198. // 6. Next feature: you can use "id.?" to get rid of Option[Long] for your id.
  199. // it allows to return Future[Long] instead of Future[Option[Long]]
  200. def courseId: Rep[Long] = column[Long]("course_id", O.PrimaryKey, O.AutoInc)
  201.  
  202. override def * : ProvenShape[Course] =
  203.   (courseId.?, name, duration, group, createdAt, updatedAt) <> (Course.tupled, Course.unapply)
  204.  
  205. def addCourse(name: String, duration: Int, group: String): Future[Long]
  206.  
  207. http://localhost:9000/courses/add/russian with JSON: {"duration": 2, "group": "C"}
  208. insert into `course` (`name`,`duration`,`group`)  values (?,?,?)
  209. Result: 5
  210. DB:
  211. "1" "math"      "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  212. "2" "history"   "3" "A" "2018-02-06 23:08:40"   "2018-02-06 23:08:40"
  213. "3" "geography" "1" "B" "2018-02-06 23:24:57"   "2018-02-06 23:24:57"
  214. "4" "biology"   "1" "C" "2018-02-06 23:31:26"   "2018-02-06 23:31:26"
  215. "5" "russian"   "2" "C" "2018-02-06 23:34:10"   "2018-02-06 23:34:10"
  216.  
  217.  
  218.  
  219. // 7. Let's create incorrect group (e.g. "E"):
  220. http://localhost:9000/courses/add/physics with JSON: {"duration": 1, "group": "E"}
  221. insert into `course` (`name`,`duration`,`group`)  values (?,?,?)
  222. [SQLException: Data truncated for column 'group' at row 1]
  223. DB:
  224. "1" "math"      "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  225. "2" "history"   "3" "A" "2018-02-06 23:08:40"   "2018-02-06 23:08:40"
  226. "3" "geography" "1" "B" "2018-02-06 23:24:57"   "2018-02-06 23:24:57"
  227. "4" "biology"   "1" "C" "2018-02-06 23:31:26"   "2018-02-06 23:31:26"
  228. "5" "russian"   "2" "C" "2018-02-06 23:34:10"   "2018-02-06 23:34:10"
  229.  
  230.  
  231.  
  232. // 7.1. So we can check it directly in App and not pass it to DB:
  233. case class Course(
  234.   courseId: Option[Long] = None,
  235.   name: String,
  236.   duration: Int,
  237.   group: String,
  238.   createdAt: Option[Timestamp] = None,
  239.   updatedAt: Option[Timestamp] = None
  240. ) {require(Set("A", "B", "C", "D") contains group)}
  241.  
  242. http://localhost:9000/courses/add/physics with JSON: {"duration": 1, "group": "E"}
  243. [IllegalArgumentException: requirement failed]
  244. In D:\Workspace\play-scala-starter-example\app\dao\CourseDao.scala:20
  245. DB:
  246. "1" "math"      "3" "A" "2018-02-06 23:07:25"   "2018-02-06 23:07:25"
  247. "2" "history"   "3" "A" "2018-02-06 23:08:40"   "2018-02-06 23:08:40"
  248. "3" "geography" "1" "B" "2018-02-06 23:24:57"   "2018-02-06 23:24:57"
  249. "4" "biology"   "1" "C" "2018-02-06 23:31:26"   "2018-02-06 23:31:26"
  250. "5" "russian"   "2" "C" "2018-02-06 23:34:10"   "2018-02-06 23:34:10"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement