Spray(10)REST API Project - DAO Layer
8. Prepare the DB first
8.1 H2 database
If we want to use this database in embedded mode, we will do as follow:
Add the h2*.jar to the class path
Use the JDBC driver class: org.h2.Driver
The database URL jdbc:h2:~/test opens the database test in my user home directory.
Based on H2 Console Application, we can access a SQL database using a browser interface. (We do not run under this mode)
9. How to work with DB
I use H2 database as my DAO layer example database.
Here are my application.conf configuration file:
test {
db.driver = org.h2.Driver
db.url = "jdbc:h2:mem:easysprayrestserver_test;DB_CLOSE_DELAY=-1"
db.user = sa
db.password = password
cp = ${cp}
cp.maxPoolSize=25
}
local {
db.driver = org.h2.Driver
db.url = "jdbc:h2:~/easysprayrestserver"
db.user = sa
db.password = password
cp = ${cp}
cp.maxPoolSize=25
}
cp {
minPoolSize=2
acquireIncrement=2
maxPoolSize=250
maxIdleTime=28800
testConnectionOnCheckout=true
preferredTestQuery="select 1"
}
It is very happy to know that the configuration properties can be treated like this>
cp = ${cp}
That is really helpful.
And from my understanding, I am coming from Java Springframework and always playing with J2EE application. So I prefer to have a DAO layer.
BaseDAO.scala class will be as follow:
package com.sillycat.easysprayrestserver.dao
import scala.slick.driver.ExtendedProfile
import scala.slick.driver.H2Driver
import scala.slick.driver.MySQLDriver
import scala.slick.session.Database
import scala.slick.session.Database.threadLocalSession
import scala.slick.session.Session
import com.sillycat.easysprayrestserver.util.DBConn
import com.sillycat.easysprayrestserver.util.TestDBConn
class BaseDAO(overridevalprofile: ExtendedProfile, dbConn: DBConn) extends ProductDAO with UserDAO with CartDAO with RCartProductDAO with Profile {
def db: Database = { dbConn.database }
def create: Unit = db withSession {
Users.create
Products.create
Carts.create
RCartProducts.create
}
def drop: Unit = db withSession {
Users.drop
Products.drop
Carts.drop
RCartProducts.drop
}
def doWithSession(f: Unit => Unit) = db withSession { f }
}
object BaseDAO {
import com.sillycat.easysprayrestserver.model.DBType._
def apply: BaseDAO = {
new BaseDAO(MySQLDriver, DBConn)
}
def apply(s: String): BaseDAO = s match {
case"test" => new BaseDAO(H2Driver, TestDBConn)
case _ => new BaseDAO(MySQLDriver, DBConn)
}
// Clients can import this rather than depending on slick directly
implicitdef threadLocalSession: Session = scala.slick.session.Database.threadLocalSession
}
The Profile file will be as follow, Profile.scala
package com.sillycat.easysprayrestserver.dao
import scala.slick.driver.ExtendedProfile
import com.sillycat.easysprayrestserver.util.JodaDateTimeMapper
import com.sillycat.easysprayrestserver.util.UserTypeMapper
trait Profile {
valprofile: ExtendedProfile
implicitvaljodaMapper = JodaDateTimeMapper
implicitvaluserTypeMapper = UserTypeMapper
}
Here is the most complex part, ModelDAO.scala
package com.sillycat.easysprayrestserver.dao
import scala.slick.util.Logging
import org.joda.time.DateTime
import com.sillycat.easysprayrestserver.model.Product
import com.sillycat.easysprayrestserver.model.Cart
import com.sillycat.easysprayrestserver.model.RCartProduct
import scala.slick.jdbc.meta.MTable
import com.sillycat.easysprayrestserver.model.User
import com.sillycat.easysprayrestserver.model.UserType
import spray.httpx.SprayJsonSupport
import spray.json.DefaultJsonProtocol
import spray.json.DeserializationException
import spray.json.JsNumber
import spray.json.JsObject
import spray.json.JsString
import spray.json.JsValue
import spray.json.RootJsonFormat
import org.joda.time.format.DateTimeFormat
import org.joda.time.DateTime
import spray.json.JsArray
import spray.json._
import DefaultJsonProtocol._
import org.joda.time.DateTime
import scala.slick.util.Logging
import scala.slick.jdbc.meta.MTable
import spray.json.{ RootJsonFormat, DefaultJsonProtocol }
import spray.httpx.SprayJsonSupport
import org.joda.time.format.DateTimeFormat
import scala.slick.ast._
import scala.slick.ast.Util._
trait UserDAO extends Logging { this: Profile =>
import profile.simple._
object Users extends Table[(Long, String, Int, String, DateTime, DateTime, String)]("USER") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc) // 1 This is the primary key column
def userName = column[String]("USER_NAME") // 2
def age = column[Int]("AGE") //3
def userType = column[String]("USER_TYPE") //4
def createDate = column[DateTime]("CREATE_DATE") //5
def expirationDate = column[DateTime]("EXPIRATION_DATE") // 6
def password = column[String]("PASSWORD") // 7
def * = id ~ userName ~ age ~ userType ~ createDate ~ expirationDate ~ password
def persist(implicit session: Session) = id.? ~ userName ~ age ~ userType ~ createDate ~ expirationDate ~ password
def insert(user: User)(implicit session: Session): Long = {
valid = persist.insert(user.id, user.userName, user.age, user.userType.toString(), user.createDate, user.expirationDate, user.password)
id
}
def auth(userName: String, password: String)(implicit session: Session): Option[User] = {
logger.debug("I am authing the userName=" + userName + " password=" + password)
(userName, password) match {
case ("admin", "admin") =>
Option(User(Some(1), "admin", 100, UserType.ADMIN, new DateTime(), new DateTime(), "admin"))
case ("customer", "customer") =>
Option(User(Some(2), "customer", 100, UserType.CUSTOMER, new DateTime(), new DateTime(), "customer"))
case ("manager", "manager") =>
Option(User(Some(3), "manager", 100, UserType.SELLER, new DateTime(), new DateTime(), "manager"))
case _ => None
}
}
def get(userId: Long)(implicit session: Session): Option[User] = {
logger.debug("Try to fetch User Object with userId = " + userId)
valquery = for { item <- Users if (item.id === userId) } yield (item)
logger.debug("Get User by id, SQL should be : " + query.selectStatement)
query.firstOption map {
case (user) => User(Option(user._1), user._2, user._3, UserType.withName(user._4), user._5, user._6, user._7)
}
}
def create(implicit session: Session) = {
if (!MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.create
ddl.createStatements.foreach(println)
}
}
def drop(implicit session: Session) = {
if (MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.drop
ddl.dropStatements.foreach(println)
}
}
}
}
trait ProductDAO extends Logging { this: Profile =>
import profile.simple._
object Products extends Table[Product]("PRODUCT") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc) // 1 This is the primary key column
def productName = column[String]("PRODUCT_NAME") // 2
def productDesn = column[String]("PRODUCT_DESN") //3
def createDate = column[DateTime]("CREATE_DATE") //4
def expirationDate = column[DateTime]("EXPIRATION_DATE") // 5
def productCode = column[String]("PRODUCT_CODE") //6
def * = id.? ~ productName ~ productDesn ~ createDate ~ expirationDate ~ productCode <> (Product.apply _, Product.unapply _)
def forInsert = productName ~ productDesn ~ createDate ~ expirationDate ~ productCode <>
({ t => Product(None, t._1, t._2, t._3, t._4, t._5) },
{ (s: Product) => Some(s.productName, s.productDesn, s.createDate, s.expirationDate, s.productCode) })
def insert(s: Product)(implicit session: Session): Long = {
Products.forInsert returning id insert s
}
def forProductCode(productCode: String)(implicit session: Session): Option[Product] = {
valquery = for {
item <- Products if item.productCode === productCode
} yielditem
query.firstOption
}
def all()(implicit session: Session): Seq[Product] = {
Query(Products).list
}
def create(implicit session: Session) = {
if (!MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.create
ddl.createStatements.foreach(println)
}
}
def drop(implicit session: Session) = {
if (MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.drop
ddl.dropStatements.foreach(println)
}
}
}
}
//Cart(id: Option[Long], cartName: String, cartType: CartType.Value, user: User, products: Seq[Product])
trait CartDAO extends Logging { this: Profile with RCartProductDAO =>
import profile.simple._
object Carts extends Table[(Long, String, String, Long)]("CART") {
def id = column[Long]("ID", O.PrimaryKey, O.AutoInc) // 1 This is the primary key column
def cartName = column[String]("CART_NAME") // 2
def cartType = column[String]("CART_TYPE") //3
def userId = column[Long]("USER_ID") //5
def * = id ~ cartName ~ cartType ~ userId
def persist(implicit session: Session) = id.? ~ cartName ~ cartType ~ userId.?
def insert(cart: Cart)(implicit session: Session): Long = {
valcartId = persist.insert(cart.id, cart.cartName, cart.cartType.toString(), cart.user.id)
RCartProducts.insertAll(cartId, cart.products)
cartId
}
def create(implicit session: Session) = {
if (!MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.create
ddl.createStatements.foreach(println)
}
}
def drop(implicit session: Session) = {
if (MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.drop
ddl.dropStatements.foreach(println)
}
}
}
}
trait RCartProductDAO extends Logging { this: Profile =>
import profile.simple._
object RCartProducts extends Table[RCartProduct]("R_CART_PRODUCT") {
def cartId = column[Long]("CART_ID", O.NotNull) // 1 This is the primary key column
def productId = column[Long]("PRODUCT_ID", O.NotNull) //2
def * = cartId ~ productId <> (RCartProduct.apply _, RCartProduct.unapply _)
def insertAll(cartId: Long, productIds: Seq[Product])(implicit session: Session): Unit = {
productIds.foreach(x => RCartProducts.insert(RCartProduct(cartId, x.id.get)))
}
def create(implicit session: Session) = {
if (!MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.create
//ddl.createStatements.foreach(println)
}
}
def drop(implicit session: Session) = {
if (MTable.getTables(this.tableName).firstOption.isDefined) {
valddl = this.ddl
ddl.drop
//ddl.dropStatements.foreach(println)
}
}
}
}
Mostly, the test class will be as follow:
class ModelDAOSpec extends FunSuite with ShouldMatchers with BeforeAndAfterAll {
implicitvaldao: BaseDAO = BaseDAO.apply("test")
overridedef beforeAll() { dao.create }
overridedef afterAll() { dao.drop }
test("Database tables are created and dropped") {
assert("x" === "x")
}
test("Persist Product") {
dao.db withSession {
valitem = new Product(None, "Iphone5", "Nice device", DateTime.now, DateTime.now, "IPHONE5")
info(item.toString)
valid = dao.Products.insert(item)
assert(id === 1)
dao.Products.insert(new Product(None, "IPhone4S", "Also good", DateTime.now, DateTime.now, "IPHONE4S"))
}
}
…snip…
I will run the test case as follow:
sbt>test-only com.sillycat.easysprayrestserver.dao.*
Next step, I will take a nap, I will try to begin the server part of my SuperPlan project.
10. How to Work with Actor
come soon…
11. How to do Validation
come soon...
Tips
References:
Spray 1 ~ 9
http://sillycat.iteye.com/blog/1766558
http://sillycat.iteye.com/blog/1766568
http://sillycat.iteye.com/blog/1857105
http://sillycat.iteye.com/blog/1858282
http://sillycat.iteye.com/blog/1858298
http://sillycat.iteye.com/blog/1859025
H2
http://www.h2database.com/html/main.html