SlideShare ist ein Scribd-Unternehmen logo
1 von 84
Downloaden Sie, um offline zu lesen
Scala-ActiveRecord
Type-safe Active Record model for Scala
@teppei_tosa_en
Who am I
鉄平 土佐
TEPPEI TOSA
iron peace place name
The first official conference in Japan.
http://scalaconf.jp/en/
Typesafe members came to
Japan and gave speeches.
Talked about the case example of building
our original BRMS “BIWARD” in Scala.
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/
Scala-ActiveRecord
Type-safe Active Record model for Scala
• https://github.com/aselab/scala-activerecord
• Latest version : 0.2.2
• Licence : MIT
Features
• Squeryl wrapper
• type-safe (most part)
• Rails ActiveRecord-like operability
• Auto transaction control
• validations
• Associations
• Testing support
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
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.
Other libraries example
1. Anorm
2. Slick ( ScalaQuery )
3. Squeryl
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()
! ! }
! }
! ...
}
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 _)
}
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
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.
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.
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))
}
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)
}
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]
}
Getting Started
Define the dependency
in SBT project definition
Add the following settings in build.sbt or project/Build.scala.
libraryDependencies ++= Seq(
"com.github.aselab" %% "scala-activerecord" % "0.2.2",
"org.slf4j" % "slf4j-nop" % "1.7.2", // optional
"com.h2database" % "h2" % "1.3.170" // optional
)
resolvers += Resolver.sonatypeRepo("releases")
Using Scala ActiveRecord
Play2.1 Plugin
Add the following settings in project/Build.scala
val appDependencies = Seq(
"com.github.aselab" %% "scala-activerecord" % "0.2.2",
"com.github.aselab" %% "scala-activerecord-play2" % "0.2.2",
jdbc,
"com.h2database" % "h2" % "1.3.170"
)
val main = play.Project(appName, appVersion, appDependencies)
.settings(
resolvers ++= Seq(
Resolver.sonatypeRepo("releases")
)
)
Add the following settings in conf/play.plugins
9999:com.github.aselab.activerecord.ActiveRecordPlugin
Database Support
H2 database
MySQL
PostgrSQL
Derby
Oracle
Defining Schema
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]
}
CRUD
Create
val person = Person("person1", 25)
person.save // return true
val person = Preson("person1", 25).create
// return Person("person1", 25)
Read
Person.find(1)
// Some(Person("person1"))
Person.toList
// List(person("person1”), ...)
Person.findBy("name", "john")
// Some(Person("John"))
Person.where(_.name === "john").headOption
// Some(Person("john"))
Update
Person.find(1).foreach { p =>
! p.name = "Ichiro"
! p.age = 37
! p.save
}
Person.forceUpdate( _.id === 1)(
! _.name := "ichiro", _.age := 37
)
Delete
Person.where(_.name === "john").foreach(_.delete)
Person.find(1) match {
! case Some(person) => person.delete
! case _ =>
}
Person.delete(1)
Query Interface
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
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
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
Ordering
Client.orderBy(_.name)
Client.orderBy(_.name asc)
Client.orderBy(_.name asc, _.age desc)
Limit
Client.limit(10)
Offset
Client.page(2, 5)
Existence
Client.exists(_.name like “john%”)
// true or false
Specify selected fields
Client.select(_.name).toList
// List[String]
Client.select(c => (c.name, c.age)).toList
// List[(String, Int)]
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
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.
Validations
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]
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"
Callbacks
Available hooks
•beforeValidation
•beforeCreate
•afterCreate
•beforeUpdate
•afterUpdate
•beforeSave
•afterSave
•beforeDelete
•afterDelete
Example
case class User(login:String) extends ActiveRecord {
! @Transient
! var password:String = _
! var hashedPassword:String = _
! override def beforeSave() {
! ! hashedPassword = SomeLibrary.encrypt(password)
! }
}
val user = User("john")
user.password = "raw_password"
user.save
// stored encrypted password
Relationship
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
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")
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
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
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)
Many-to-Many
(hasManyThrough)
groups
id
name
memberships
id
user_id
group_id
isAdmin
users
id
name
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)
}
Conditions Options
case class Group(name:String) extends ActiveRecord {
! lazy val adminUsers =
! ! hasMany[User](conditions = Map("isAdmin" -> true))
}
group.adminUsers << user
// user.isAdmin == true
ForeignKey option
case class Comment(name:String) extends ActiveRecord {
! val authorId:Long
! lazy val author
= belongsTo[User](foreignKey = "authorId")
}
Join Tables
Scala
Client.joins[Order](
! (client, order) => client.id === order.clientId
).where(
! (client, order) => client.age.~ < 20 and order.price.~ > 1000
).select(
! (client, order) => (client.name, client.age, order.price)
).toList
generated SQL
Select clients.name, clients.age, order.price
From clients inner join orders on (clients.id = orders.client_id)
Where ((clients.age < 20) and (groups.price > 1000))
Eager loading associations
The solution for N+1 problem.
Scala
Order.includes(_.client).limit(10).map {
! order => order.client.name
}.mkString("n")
generated SQL
Select orders.price, orders.id
From orders limit 10 offset 0
Select clients.name, clients.age, clients.id
From clients inner join orders on (clients.id = orders.client_id)
Where (orders.id in (1,2,3,4,5,6,7,8,9,10))
Logging and Debugging
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>
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
Testing
Setting for test
build.sbt or project/Build.scala
libraryDependencies ++= Seq(
"com.github.aselab" %% "scala-activerecord" % "0.2.2",
"com.github.aselab" %% "scala-activerecord-specs" % "0.2.2" % "test",
"org.specs2" %% "specs2" % "1.12.3" % "test"
)
resolvers += Resolver.sonatypeRepo("releases")
application.conf
test {
schema = "models.Tables"
driver = "org.h2.Driver"
jdbcurl = "jdbc:h2:mem:test"
}
Test Example
import com.github.aselab.activerecord._
object SomeModelSpecs extends ActiveRecordSpecification {
override val config
= Map("schema" -> "com.example.models.Tables")
override def beforeAll = {
super.beforeAll
SomeModel("test1").create
}
override def afterAll = {
super.afterAll
}
"sample" should {
// Some specifications code
}
}
Performance
ActiveRecord Overhead
• How Sql statements are generated.
• The time to create active-record object
from the results of query.
ORM Race
•Anorm
•Slick
•Squerl
•Scala-ActiveRecord
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
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
The Race Results
39.8ms
116.8ms
177.2ms
258.8ms
Squeryl
Anorm
Scala-ActiveRecord
Slick
Future
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.
Support Serialization
Form Model XML
JSON
MessagePack
Validation
View
Bind
View
Helper
Support Web framework
• CRUD controller
• Form Helper for Play and Scalatra
• Code generator as SBT plugin
Secret
DEMO withYATTER
(Yet Another twiTTER )
YATTER’s tables
Follows
id
userid
follows_users
id
user_id
follow_id
Users
id
name
Tweets
id
userId
textManyToMany
OneToMany
OneToOne
(But not supported yet)
https://github.com/ironpeace/yatter
#scalajp
Mt.FUJI
Tokyo Station
Japanese Castle
Sushi
Okonomiyaki
@teppei_tosa_en
https://github.com/aselab/scala-activerecord
https://github.com/ironpeace/yatter
Thank you

Weitere ähnliche Inhalte

Was ist angesagt?

Java EE 6 CDI Integrates with Spring & JSF
Java EE 6 CDI Integrates with Spring & JSFJava EE 6 CDI Integrates with Spring & JSF
Java EE 6 CDI Integrates with Spring & JSF
Jiayun Zhou
 
Testing your javascript code with jasmine
Testing your javascript code with jasmineTesting your javascript code with jasmine
Testing your javascript code with jasmine
Rubyc Slides
 
Easy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMix
Easy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMixEasy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMix
Easy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMix
elliando dias
 

Was ist angesagt? (20)

Solid And Sustainable Development in Scala
Solid And Sustainable Development in ScalaSolid And Sustainable Development in Scala
Solid And Sustainable Development in Scala
 
How to Design a Great API (using flask) [ploneconf2017]
How to Design a Great API (using flask) [ploneconf2017]How to Design a Great API (using flask) [ploneconf2017]
How to Design a Great API (using flask) [ploneconf2017]
 
Requery overview
Requery overviewRequery overview
Requery overview
 
Load Data Fast!
Load Data Fast!Load Data Fast!
Load Data Fast!
 
Demystifying Oak Search
Demystifying Oak SearchDemystifying Oak Search
Demystifying Oak Search
 
Spring data requery
Spring data requerySpring data requery
Spring data requery
 
Alternatives of JPA/Hibernate
Alternatives of JPA/HibernateAlternatives of JPA/Hibernate
Alternatives of JPA/Hibernate
 
Scala @ TechMeetup Edinburgh
Scala @ TechMeetup EdinburghScala @ TechMeetup Edinburgh
Scala @ TechMeetup Edinburgh
 
DOSUG Taking Apache Camel For A Ride
DOSUG Taking Apache Camel For A RideDOSUG Taking Apache Camel For A Ride
DOSUG Taking Apache Camel For A Ride
 
Java EE 6 CDI Integrates with Spring & JSF
Java EE 6 CDI Integrates with Spring & JSFJava EE 6 CDI Integrates with Spring & JSF
Java EE 6 CDI Integrates with Spring & JSF
 
Building a Unified Data Pipline in Spark / Apache Sparkを用いたBig Dataパイプラインの統一
Building a Unified Data Pipline in Spark / Apache Sparkを用いたBig Dataパイプラインの統一Building a Unified Data Pipline in Spark / Apache Sparkを用いたBig Dataパイプラインの統一
Building a Unified Data Pipline in Spark / Apache Sparkを用いたBig Dataパイプラインの統一
 
Administering and Monitoring SolrCloud Clusters
Administering and Monitoring SolrCloud ClustersAdministering and Monitoring SolrCloud Clusters
Administering and Monitoring SolrCloud Clusters
 
55 New Features in Java 7
55 New Features in Java 755 New Features in Java 7
55 New Features in Java 7
 
Testing your javascript code with jasmine
Testing your javascript code with jasmineTesting your javascript code with jasmine
Testing your javascript code with jasmine
 
First glance at Akka 2.0
First glance at Akka 2.0First glance at Akka 2.0
First glance at Akka 2.0
 
ScalaDays 2014 - Reactive Scala 3D Game Engine
ScalaDays 2014 - Reactive Scala 3D Game Engine ScalaDays 2014 - Reactive Scala 3D Game Engine
ScalaDays 2014 - Reactive Scala 3D Game Engine
 
Advanced akka features
Advanced akka featuresAdvanced akka features
Advanced akka features
 
Easy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMix
Easy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMixEasy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMix
Easy Enterprise Integration Patterns with Apache Camel, ActiveMQ and ServiceMix
 
Node.js vs Play Framework (with Japanese subtitles)
Node.js vs Play Framework (with Japanese subtitles)Node.js vs Play Framework (with Japanese subtitles)
Node.js vs Play Framework (with Japanese subtitles)
 
Annotation processing and code gen
Annotation processing and code genAnnotation processing and code gen
Annotation processing and code gen
 

Ähnlich wie Scala active record

Rafael Bagmanov «Scala in a wild enterprise»
Rafael Bagmanov «Scala in a wild enterprise»Rafael Bagmanov «Scala in a wild enterprise»
Rafael Bagmanov «Scala in a wild enterprise»
e-Legion
 

Ähnlich wie Scala active record (20)

Scala Frustrations
Scala FrustrationsScala Frustrations
Scala Frustrations
 
Solid and Sustainable Development in Scala
Solid and Sustainable Development in ScalaSolid and Sustainable Development in Scala
Solid and Sustainable Development in Scala
 
Lobos Introduction
Lobos IntroductionLobos Introduction
Lobos Introduction
 
Spring Day | Spring and Scala | Eberhard Wolff
Spring Day | Spring and Scala | Eberhard WolffSpring Day | Spring and Scala | Eberhard Wolff
Spring Day | Spring and Scala | Eberhard Wolff
 
Scala and Spring
Scala and SpringScala and Spring
Scala and Spring
 
Spark Sql for Training
Spark Sql for TrainingSpark Sql for Training
Spark Sql for Training
 
Rails on Oracle 2011
Rails on Oracle 2011Rails on Oracle 2011
Rails on Oracle 2011
 
Full Stack Scala
Full Stack ScalaFull Stack Scala
Full Stack Scala
 
Project kepler compile time metaprogramming for scala
Project kepler compile time metaprogramming for scalaProject kepler compile time metaprogramming for scala
Project kepler compile time metaprogramming for scala
 
Scala in a wild enterprise
Scala in a wild enterpriseScala in a wild enterprise
Scala in a wild enterprise
 
Jstl Guide
Jstl GuideJstl Guide
Jstl Guide
 
TDC 2012 - Patterns e Anti-Patterns em Ruby
TDC 2012 - Patterns e Anti-Patterns em RubyTDC 2012 - Patterns e Anti-Patterns em Ruby
TDC 2012 - Patterns e Anti-Patterns em Ruby
 
Alberto Paro - Hands on Scala.js
Alberto Paro - Hands on Scala.jsAlberto Paro - Hands on Scala.js
Alberto Paro - Hands on Scala.js
 
Scala Italy 2015 - Hands On ScalaJS
Scala Italy 2015 - Hands On ScalaJSScala Italy 2015 - Hands On ScalaJS
Scala Italy 2015 - Hands On ScalaJS
 
Using the Tooling API to Generate Apex SOAP Web Service Clients
Using the Tooling API to Generate Apex SOAP Web Service ClientsUsing the Tooling API to Generate Apex SOAP Web Service Clients
Using the Tooling API to Generate Apex SOAP Web Service Clients
 
Wider than rails
Wider than railsWider than rails
Wider than rails
 
Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLon
 
Rafael Bagmanov «Scala in a wild enterprise»
Rafael Bagmanov «Scala in a wild enterprise»Rafael Bagmanov «Scala in a wild enterprise»
Rafael Bagmanov «Scala in a wild enterprise»
 
Real-time streaming and data pipelines with Apache Kafka
Real-time streaming and data pipelines with Apache KafkaReal-time streaming and data pipelines with Apache Kafka
Real-time streaming and data pipelines with Apache Kafka
 
Rails 3 (beta) Roundup
Rails 3 (beta) RoundupRails 3 (beta) Roundup
Rails 3 (beta) Roundup
 

Mehr von 鉄平 土佐

Asakusa Framework 勉強会 2014 夏
Asakusa Framework 勉強会 2014 夏Asakusa Framework 勉強会 2014 夏
Asakusa Framework 勉強会 2014 夏
鉄平 土佐
 
Asakusa Framework はじめの一歩 ( ver 0.6.2 )
Asakusa Framework はじめの一歩 ( ver 0.6.2 )Asakusa Framework はじめの一歩 ( ver 0.6.2 )
Asakusa Framework はじめの一歩 ( ver 0.6.2 )
鉄平 土佐
 
Asakusa fw勉強会2014冬
Asakusa fw勉強会2014冬Asakusa fw勉強会2014冬
Asakusa fw勉強会2014冬
鉄平 土佐
 
Scala稟議の通し方(公開版)
Scala稟議の通し方(公開版)Scala稟議の通し方(公開版)
Scala稟議の通し方(公開版)
鉄平 土佐
 

Mehr von 鉄平 土佐 (20)

GraphX によるグラフ分析処理の実例と入門
GraphX によるグラフ分析処理の実例と入門GraphX によるグラフ分析処理の実例と入門
GraphX によるグラフ分析処理の実例と入門
 
Reactテストに役立つ実装の工夫
Reactテストに役立つ実装の工夫Reactテストに役立つ実装の工夫
Reactテストに役立つ実装の工夫
 
GraphX Advent Calendar Day17
GraphX Advent Calendar Day17GraphX Advent Calendar Day17
GraphX Advent Calendar Day17
 
GraphX Advent Calendar Day15
GraphX Advent Calendar Day15GraphX Advent Calendar Day15
GraphX Advent Calendar Day15
 
GraphX Advent Calendar Day 14
GraphX Advent Calendar Day 14GraphX Advent Calendar Day 14
GraphX Advent Calendar Day 14
 
GraphX Advent Calendar Day 13
GraphX Advent Calendar Day 13GraphX Advent Calendar Day 13
GraphX Advent Calendar Day 13
 
GraphX Advent Calendar Day12 : Pregel概要
GraphX Advent Calendar Day12 : Pregel概要GraphX Advent Calendar Day12 : Pregel概要
GraphX Advent Calendar Day12 : Pregel概要
 
Asakusa fwはじめの一歩 0.7.0
Asakusa fwはじめの一歩 0.7.0Asakusa fwはじめの一歩 0.7.0
Asakusa fwはじめの一歩 0.7.0
 
Spark GraphXについて @Spark Meetup 2014/9/8
Spark GraphXについて @Spark Meetup 2014/9/8Spark GraphXについて @Spark Meetup 2014/9/8
Spark GraphXについて @Spark Meetup 2014/9/8
 
「Asakusa0.7の新機能で、テストデータをどうドキュメントするのか的な実用的なアレ」 by @okachimachiorz1
「Asakusa0.7の新機能で、テストデータをどうドキュメントするのか的な実用的なアレ」 by @okachimachiorz1「Asakusa0.7の新機能で、テストデータをどうドキュメントするのか的な実用的なアレ」 by @okachimachiorz1
「Asakusa0.7の新機能で、テストデータをどうドキュメントするのか的な実用的なアレ」 by @okachimachiorz1
 
GraphXはScalaエンジニアにとってのブルーオーシャン @ Scala Matsuri 2014
GraphXはScalaエンジニアにとってのブルーオーシャン @ Scala Matsuri 2014GraphXはScalaエンジニアにとってのブルーオーシャン @ Scala Matsuri 2014
GraphXはScalaエンジニアにとってのブルーオーシャン @ Scala Matsuri 2014
 
GraphX is the blue ocean for scala engineers @ Scala Matsuri 2014
GraphX is the blue ocean for scala engineers @ Scala Matsuri 2014GraphX is the blue ocean for scala engineers @ Scala Matsuri 2014
GraphX is the blue ocean for scala engineers @ Scala Matsuri 2014
 
Asakusa fw演算子チートシートについて
Asakusa fw演算子チートシートについてAsakusa fw演算子チートシートについて
Asakusa fw演算子チートシートについて
 
Asakusa fw勉強会2014真夏
Asakusa fw勉強会2014真夏Asakusa fw勉強会2014真夏
Asakusa fw勉強会2014真夏
 
Asakusa Framework 勉強会 2014 夏
Asakusa Framework 勉強会 2014 夏Asakusa Framework 勉強会 2014 夏
Asakusa Framework 勉強会 2014 夏
 
Asakusa Framework はじめの一歩 ( ver 0.6.2 )
Asakusa Framework はじめの一歩 ( ver 0.6.2 )Asakusa Framework はじめの一歩 ( ver 0.6.2 )
Asakusa Framework はじめの一歩 ( ver 0.6.2 )
 
Asakusa fwはじめの一歩・改
Asakusa fwはじめの一歩・改Asakusa fwはじめの一歩・改
Asakusa fwはじめの一歩・改
 
Asakusa fw勉強会2014冬
Asakusa fw勉強会2014冬Asakusa fw勉強会2014冬
Asakusa fw勉強会2014冬
 
Scala稟議の通し方(公開版)
Scala稟議の通し方(公開版)Scala稟議の通し方(公開版)
Scala稟議の通し方(公開版)
 
はてブちう
はてブちうはてブちう
はてブちう
 

Kürzlich hochgeladen

Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Victor Rentea
 

Kürzlich hochgeladen (20)

Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
Emergent Methods: Multi-lingual narrative tracking in the news - real-time ex...
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdfRising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
Rising Above_ Dubai Floods and the Fortitude of Dubai International Airport.pdf
 
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
Apidays New York 2024 - APIs in 2030: The Risk of Technological Sleepwalk by ...
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
Apidays New York 2024 - Accelerating FinTech Innovation by Vasa Krishnan, Fin...
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024Finding Java's Hidden Performance Traps @ DevoxxUK 2024
Finding Java's Hidden Performance Traps @ DevoxxUK 2024
 
ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot ModelMcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
Mcleodganj Call Girls 🥰 8617370543 Service Offer VIP Hot Model
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 

Scala active record

  • 1. Scala-ActiveRecord Type-safe Active Record model for Scala @teppei_tosa_en
  • 2. Who am I 鉄平 土佐 TEPPEI TOSA iron peace place name
  • 3. The first official conference in Japan. http://scalaconf.jp/en/
  • 4. Typesafe members came to Japan and gave speeches.
  • 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/
  • 7. Scala-ActiveRecord Type-safe Active Record model for Scala • https://github.com/aselab/scala-activerecord • Latest version : 0.2.2 • Licence : MIT
  • 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.
  • 11. Other libraries example 1. Anorm 2. Slick ( ScalaQuery ) 3. Squeryl
  • 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] }
  • 21. Define the dependency in SBT project definition Add the following settings in build.sbt or project/Build.scala. libraryDependencies ++= Seq( "com.github.aselab" %% "scala-activerecord" % "0.2.2", "org.slf4j" % "slf4j-nop" % "1.7.2", // optional "com.h2database" % "h2" % "1.3.170" // optional ) resolvers += Resolver.sonatypeRepo("releases")
  • 22. Using Scala ActiveRecord Play2.1 Plugin Add the following settings in project/Build.scala val appDependencies = Seq( "com.github.aselab" %% "scala-activerecord" % "0.2.2", "com.github.aselab" %% "scala-activerecord-play2" % "0.2.2", jdbc, "com.h2database" % "h2" % "1.3.170" ) val main = play.Project(appName, appVersion, appDependencies) .settings( resolvers ++= Seq( Resolver.sonatypeRepo("releases") ) ) Add the following settings in conf/play.plugins 9999:com.github.aselab.activerecord.ActiveRecordPlugin
  • 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] }
  • 26. CRUD
  • 27. Create val person = Person("person1", 25) person.save // return true val person = Preson("person1", 25).create // return Person("person1", 25)
  • 28. Read Person.find(1) // Some(Person("person1")) Person.toList // List(person("person1”), ...) Person.findBy("name", "john") // Some(Person("John")) Person.where(_.name === "john").headOption // Some(Person("john"))
  • 29. Update Person.find(1).foreach { p => ! p.name = "Ichiro" ! p.age = 37 ! p.save } Person.forceUpdate( _.id === 1)( ! _.name := "ichiro", _.age := 37 )
  • 30. Delete Person.where(_.name === "john").foreach(_.delete) Person.find(1) match { ! case Some(person) => person.delete ! case _ => } Person.delete(1)
  • 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
  • 37. Specify selected fields Client.select(_.name).toList // List[String] Client.select(c => (c.name, c.age)).toList // List[(String, Int)]
  • 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"
  • 45. Example case class User(login:String) extends ActiveRecord { ! @Transient ! var password:String = _ ! var hashedPassword:String = _ ! override def beforeSave() { ! ! hashedPassword = SomeLibrary.encrypt(password) ! } } val user = User("john") user.password = "raw_password" user.save // stored encrypted password
  • 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") }
  • 56. Join Tables Scala Client.joins[Order]( ! (client, order) => client.id === order.clientId ).where( ! (client, order) => client.age.~ < 20 and order.price.~ > 1000 ).select( ! (client, order) => (client.name, client.age, order.price) ).toList generated SQL Select clients.name, clients.age, order.price From clients inner join orders on (clients.id = orders.client_id) Where ((clients.age < 20) and (groups.price > 1000))
  • 57. Eager loading associations The solution for N+1 problem. Scala Order.includes(_.client).limit(10).map { ! order => order.client.name }.mkString("n") generated SQL Select orders.price, orders.id From orders limit 10 offset 0 Select clients.name, clients.age, clients.id From clients inner join orders on (clients.id = orders.client_id) Where (orders.id in (1,2,3,4,5,6,7,8,9,10))
  • 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
  • 62. Setting for test build.sbt or project/Build.scala libraryDependencies ++= Seq( "com.github.aselab" %% "scala-activerecord" % "0.2.2", "com.github.aselab" %% "scala-activerecord-specs" % "0.2.2" % "test", "org.specs2" %% "specs2" % "1.12.3" % "test" ) resolvers += Resolver.sonatypeRepo("releases") application.conf test { schema = "models.Tables" driver = "org.h2.Driver" jdbcurl = "jdbc:h2:mem:test" }
  • 63. Test Example import com.github.aselab.activerecord._ object SomeModelSpecs extends ActiveRecordSpecification { override val config = Map("schema" -> "com.example.models.Tables") override def beforeAll = { super.beforeAll SomeModel("test1").create } override def afterAll = { super.afterAll } "sample" should { // Some specifications code } }
  • 65. ActiveRecord Overhead • How Sql statements are generated. • The time to create active-record object from the results of query.
  • 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.
  • 72. Support Serialization Form Model XML JSON MessagePack Validation View Bind View Helper
  • 73. Support Web framework • CRUD controller • Form Helper for Play and Scalatra • Code generator as SBT plugin
  • 76.
  • 82. Sushi