5. Talked about the case example of building
our original BRMS “BIWARD” in Scala.
6. Japanese engineers talked about
Scala tips or their libraries.
• “Stackable-controller” by @gakuzzzz
https://github.com/t2v/stackable-controller
• “How we write and use Scala libraries not
to cry” by @tototoshi
http://tototoshi.github.io/slides/how-we-write-and-use-scala-libraries-
scalaconfjp2013/#1
For example,
http://scalaconf.jp/en/
8. Features
• Squeryl wrapper
• type-safe (most part)
• Rails ActiveRecord-like operability
• Auto transaction control
• validations
• Associations
• Testing support
9. Most of the other
ORM Libraries
Wrap SQL
val selectCountries = SQL(“Select * from Countries”)
Have to define mappings from the results of SQL to
the models.
val countries = selectCountries().map(row =>
row[String](“code”) -> row[String](“name”)
).toList
10. The motivation of
Scala-ActiveRecord
• Want to define the model mapping more
easily.
• Want not to define the find methods in
each model classes.
• Want not to write SQLs.
12. 1.Anorm
• Anorm doesn’t have the Model layer.
• Have to define similar methods in each
Class.
case class Person(id:Pk[Long], name:String)
object Person {
! def create(person:Person):Unit = {
! ! DB.withConnection { implicit connection =>
! ! ! SQL("insert int person(name) values ({name}")
! ! ! ! .on('name -> person.name)
! ! ! ! .executeUpdate()
! ! }
! }
! ...
}
13. 2. Slick ( ScalaQuery )
• Query Interface is good.
• Definding tables syntax is redundant.
• Have to define the mapping between table
and model in each table.
case class Member(id:Int, name:String, email:Option[String])
object Members extends Table[Member]("MEMBERS") {
! def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)
! def name = column[String]("Name")
! def email = column[Option[String]]("EMAIL")
! def * = id.? ~ name ~ email <> (Member, Member.unapply _)
}
14. 3. Squeryl
• The best one in these libraries.
• Scala-ActiveRecord wraps this with some
improvement.
1. Optimize the generated SQLs
2. Automate the transaction control
3. Use CoC approach to build relationship
15. When queries are combined
Squeryl generates sub-query SQL.
Squeryl
val query = from(table)(t => where(t.id.~ > 20) select(t))
from(query)(t => where(t.name like "%test%) select(t))
Scala
select * from
! (Select * from table where table.id > 20) q1
where q1.name like "test"
SQL
It negatively affect performance.
16. When queries are combined
Squeryl generates sub-query SQL.
Scala-ActiveRecord
val query = Table.where(_.id.~ > 20)
query.where(_.name like "%test%").toList
Scala
select * from table
where table.id > 20 and table.name like "test"
SQL
It can generate more simple SQL statement.
17. Automate
the transaction control
Squeryl
Scala-ActiveRecord
• Call “inTransaction” automatically at accessing
Iterable#iterator.
• When the save or delete method is called,
“inTransaction” is executed by default.
• Off course, you can call “inTransaction” expressly.
inTransaction {
books.insert(new Author(1, "Michel","Folco"))! !
val a = from(authors)
(a=> where(a.lastName === "Folco") select(a))
}
18. Use CoC approach to
build relationship.
Squeryl
object Schema extends Schema{
! val foo = table[Foo]
! val bar = table[Bar]
! val fooToBar = oneToManyRelation(Foo, Bar).via(
! ! (f,b) => f.barId === b.id
! )
}
class Foo(var barId:Long) extends SomeEntity {
! lazy val bar:ManyToOne[Bar] = schema.fooToBar.right(this)
}
class Bar(var bar:String) extends SomeEntity {
! lazy val foos:OneToMany[Foo] = schema.fooToBar.left(this)
}
19. Use CoC approach to
build relationship.
Scala-ActiveRecord
object Table extends ActiveRecordTabels {
! val foo = table[Foo]
! val bar = table[Bar]
}
class Foo(var barId:Long) extends ActiveRecord {
! lazy val bar = belongsTo[Bar]
}
class Bar(var bar:String) extends ActiveRecord {
! lazy val foos = hasMany[Foo]
}
25. Model implementation
case class Person(var name:String, var age:Int)
! extends ActiveRecord
object Person
! extends ActiveRecordCompanion[Person]
Schema definition
object Tables extends ActiveRecordTable {
! val people = table[Person]
}
32. Find single object
val client = Client.find(10)
// Some(Client) or None
val John = Client.findBy("name", "john")
// Some(Client("john")) or None
val john = Client.findBy(("name", "john"), ("age",25))
// Some(Client("john",25)) or None
33. Get the search result as List
Scala
Clients.where(c =>
! c.name === "john" and c.age.~ > 25
).toList
Clients
! .where(_.name == "john")
! .where(_.age.~ > 25)
! .toList
generated SQL
select clients.name, clients.age, clients.id
from clients
where clients.name = "john" and clients.age > 25
34. Using iterable methods
val client = Client.head
// First Client or RecordNotFoundException
val client = Client.lastOption
// Some(Last Client) or None
val (adults, children) = Client.partition(_.age >= 20)
// Parts of clients
38. Combine Queries
Scala
Clients.where(_.name like "john%")
! .orderBy(_.age desc)
! .where(_.age.~ < 25)
! .page(2, 5)
! .toList
generated SQL
select clients.name, clients.age, clients.id
from clients
where ((clients.name like "john%") and (clients.age < 25))
order by clients.age desc
limit 5 offset 2
39. Cache Control
val orders = Order.where(_.age.~ > 20)
//execute this SQL query and cheche the query
orders.toList
//don't execute the SQL query
orders.toList
When the query is implicitly converted, the query is cached.
41. Annotation-based
Validation
case class User(
! @Required name:String,
! @Length(max=20) profile:String,
! @Range(min=0, max=150) age:Int
) extends ActiveRecord
Object User extends ActiveRecordCompanion[User]
42. Example
val user = user("", "Profile", 25).create
user.isValid // false
user.hasErrors // true
user.errors.messages // Seq("Name is required")
user.hasError("name") // true
User("", "profile", 15).saveEither match {
case Right(user) => println(user.name)
case Left(errors) => println(errors.message)
}
// "Name is required"
47. One-to-Many
case class User(name:String) extends ActiveRecord {
! val groupId:Option[Long] = None
! lazy val group = belongsTo[Group]
}
case class Group(name:String) extends ActiveRecord {
! lazy val users = hasMany[User]
}
groups
id
name
users
id
group_id
name
48. One-to-Many
val user1 = User("user1").create
val group1 = Group("group1").create
group1.users << user1
group1.users.toList
// List(User("user1"))
user1.group.getOrElse(Group("group2"))
// Group("group1")
49. Generated SQL sample
Scala
group1.users.where(_.name like "user%")
! .orderBy(_.id desc)
! .limit(5)
! .toList
generated SQL
Select users.name, users.id
From users
Where ((users.group_id = 1) And (users.name like "user%"))
Order by users.id Desc
limit 5 offset 0
50. Many-to-Many (HABTM)
case class User(name:String) extends ActiveRecord {
! lazy val groups = hasAndBelongsToMany[Group]
}
case class Group(name:String) extends ActiveRecord {
! lazy val users = hasAndBelongsToMany[user]
}
groups
id
name
groups_users
left_id
right_id
users
id
name
51. val user1 = User("user1").create
val group1 = Group("group1").create
val group2 = Group("group2").create
user1.groups := List(group1, group2)
user1.groups.toList
// List(Group("group1"), Group("group2"))
group1.users.toList
// List(User("user1"))
Many-to-Many (HABTM)
53. Many-to-Many
(hasManyThrough)
case class Membership(
! userId:Long, projectid:Long, isAdmin:Boolean = false
) extends ActiveRecord {
! lazy val user = belongsTo[User]
! lazy val group = belongsTo[Group]
}
case class User(name:String) extends ActiveRecord {
! lazy val memberships = hasMany[Membership]
! lazy val groups = hasManyThrough[Group, Membership](memberships)
}
case class Group(name:String) extends ActiveRecord {
! lazy val memberships = hasmany[Membership]
! lazy val users = hasManyThrough[User, Membership](memberships)
}
54. Conditions Options
case class Group(name:String) extends ActiveRecord {
! lazy val adminUsers =
! ! hasMany[User](conditions = Map("isAdmin" -> true))
}
group.adminUsers << user
// user.isAdmin == true
55. ForeignKey option
case class Comment(name:String) extends ActiveRecord {
! val authorId:Long
! lazy val author
= belongsTo[User](foreignKey = "authorId")
}
59. See the generated SQLs
Use the toSql method
println(User.where(_.name like "john%").orderBy(_.age desc).toSql)
Set logging level with “debug” in logback.xml
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%highlight(%-5level) %cyan(%logger{15}) - %msg %n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
60. In SBT console
build.sbt or project/Build.scala
initialCommands in console := """
import com.github.aselab.activerecord._
import com.github.aselab.activerecord.dsl._
import models._
SomeTables.initialize(Map("schema" -> "models.SomeTables"))
"""
In the console
> console
scala> User.forceInsertAll{ (1 to 10000).map{i => User("name" + i)} }
scala> User.where(_.name === "name10").toList
67. Race Condition
• Create the “User” table which has only 3 columns
• Insert 1000 records into the “User” table
• Select all from the table with same statements
• Time their trip to the end of creation objects
• Exclude the time to creating DB connection
• Use Play framework 2.1.1
• Use H2-database
• Run in Global.onStart
• Run 5 times
• Compare with the average times
68. The Racers
Anorm Squeryl
Slick Scala-ActiveRecord
SQL("SELECT * FROM USER")
.as(User.simple *)
Query(Users).list
from(AppDB.user)
(s => select(s))
.toList
User.all.toList
71. Validation at compiling
(with “Macro”)
• The findBy method and conditions of
association will be type-safe.
• Validate whether foreign-key is specified
with existing key or not.