Squeryl-シンプルさと優雅さ

こんにちは、ハブラ!



Scalaの軽量ORM-Squeryl 0.9.5の例を使用して、短いレビューを書くことにしました。



このフレームワークの主な利点から始めましょう。



1)SquerylはSQLクエリにDSLを提供します。 例えば



def songs = from(MusicDb.songs)(s => where(s.artistId === id) select(s)) def fixArtistName = update(songs)(s => where(s.title === "Prodigy") set( s.title := "The Prodigy", ) )
      
      







構文はC#LINQを連想させます。 クエリでわかるように、ラムダ式が使用され、コードの量が大幅に削減されます。



この例では、songsメソッドはIterableインターフェイスを実装するQuery [Song]オブジェクトを返します。これにより、通常のコレクションのように操作できます。



また、クエリをサブクエリとして使用できることにも注意してください。このため、テーブルではなくfromコンストラクトでクエリを指定するだけで十分です。



2)モデルの最も簡単な説明



 class User(var id:Long, var username:String) extends KeyedEntity[Long] object MySchema extends Schema{ val userTable = table[User] }
      
      







この例では、Long型のプライマリキーIDとString型のフィールドユーザー名を持つモデルを記述していますが、追加の構成は必要ありません。 モデルを記述した後、回路に登録する必要があります。



デフォルトでは、Squerylはテーブル名にクラス名を使用し、フィールド名にクラスプロパティ名を使用します。

テーブル名を明示的に示すには、次を使用できます。



  val userTable = table[User]("USER_TABLE")
      
      







また、列には@Column属性を使用できます



 class User(var id:Long, @Column("USER_NAME") var username:String) extends KeyedEntity[Long]
      
      







複合キーの場合、複合キーのフィールド数に応じて、タイプCompositeKey2 [K1、K2]、CompositeKey3 [K1、K2、K3]などが使用されます。



フィールドがデータベースに保存されないようにするには、 Transient注釈でマークします。



3)カスタム関数。



Squerylには、データベースを操作するために必要な最小限の機能が含まれています。このセットは簡単に補足できます。



たとえば、PostgreSQLのdate_trunc関数を実装します



 class DateTrunc(span: String, e: DateExpression[Timestamp], m: OutMapper[Timestamp]) extends FunctionNode[Timestamp]( "date_trunc", Some(m), Seq(new TokenExpressionNode("'" + span + "'"), e) ) with DateExpression[Timestamp] def dateTrunc(span: String, e: DateExpression[Timestamp])(implicit m: OutMapper[Timestamp]) = new DateTrunc(span, e, m)
      
      







公式ウェブサイトsqueryl.org/getting-started.htmlで詳細な説明を見つけることができます



さて、練習に近いもの



挑戦する


ORMの動作を示すために、Play Framework 2に小さなアプリケーションを作成します。このアプリケーションは、オブジェクトの受信、オブジェクトの保存/作成、クラス名とその識別子による削除のためのユニバーサルAPIを提供します



データベースとしてPostgreSQL 9.3を使用します。



統合


build.sbtに追加



  "org.squeryl" %% "squeryl" % "0.9.5-7", "org.postgresql" % "postgresql" % "9.3-1101-jdbc41"
      
      







conf / application.confに追加します



 db.default.driver = org.postgresql.Driver db.default.url = "postgres://postgres:password@localhost/database" db.default.logStatements = true evolutionplugin = disabled
      
      







appディレクトリにGlobal.scalaを作成します



 import org.squeryl.adapters.PostgreSqlAdapter import org.squeryl.{Session, SessionFactory} import play.api.db.DB import play.api.mvc.WithFilters import play.api.{Application, GlobalSettings} object Global extends GlobalSettings { override def onStart(app: Application) { SessionFactory.concreteFactory = Some(() => Session.create(DB.getConnection()(app), new PostgreSqlAdapter)) } }
      
      







したがって、アプリケーションの起動時に、デフォルトのセッションファクトリを初期化します。



モデル


Long型のidフィールドを含むモデルの基本特性を作成します-データベースでモデルが作成された時間、更新された時間-最後に変更された時間(おそらくholivarを呼び出しますが、フラグが削除されるブール型の削除されたフィールド)オブジェクトかどうか、および必要に応じて、このオブジェクトを復元できます。



また、オブジェクトをjsonに変換するための機能もすぐに実装します。このため、Gsonライブラリを使用して追加し、build.sbtに登録します。



"com.google.code.gson" % "gson" % "2.2.4"









もちろん、Play Frameworkには既にjsonを操作するためのメカニズムが組み込まれていますが、私の意見ではこれらには欠点があるため、Gsonと組み合わせます。



これを行うには、app / models / Entity.scalaを作成します



 package models import com.google.gson.Gson import org.joda.time.DateTime import org.squeryl.KeyedEntity import play.api.libs.json.JsValue trait EntityBase[K] extends KeyedEntity[K] { def table = findTablesFor(this).head def json(implicit gson: Gson): JsValue = play.api.libs.json.Json.parse(gson.toJson(this)) def isNew: Boolean def save(): this.type = transaction { if (isNew) table.insert(this) else table.update(this) this } } trait EntityC[K] extends EntityBase[K] { var created: TimeStamp = null override def save(): this.type = { if (isNew) created = DateTime.now() super.save() } } trait EntityCUD[K] extends EntityC[K] { var updated: TimeStamp = null var deleted = false override def save(): this.type = { updated = DateTime.now() super.save() } def delete(): this.type = { deleted = true save() } } class Entity extends EntityCUD[Long] { var id = 0L override def isNew = id == 0L }
      
      







このコードは、相互に継承されて新しい機能を追加するいくつかの特性を実装します。



主な概念:save()メソッドは、指定されたオブジェクトがデータベースに保存されているかどうかを確認し、これに応じて、対応するテーブルでcreateまたはupdateメソッドが呼び出されます。



Squerylは時間を保存するためにjava.sql.Timestamp型を使用しますが、私にとって(そして多くの人が同意するでしょう)使用するのは非常に不便です。 時間をかけて作業するために、joda.DateTimeを使用することを好みます。 幸いなことに、Scalaは暗黙的な型変換のための便利なメカニズムを提供します。



データスキームと便利なユーティリティのセットを作成します。便宜上、パッケージオブジェクトを作成します。そのために、次のコードでapp / models / package.scalaファイルを作成します。



 import java.sql.Timestamp import com.google.gson.Gson import org.joda.time.DateTime import org.squeryl.customtypes._ import org.squeryl.{Schema, Table} import play.api.libs.json.{JsObject, JsValue, Json} import scala.language.implicitConversions package object models extends Schema with CustomTypesMode { val logins = table[Login] def getTable[E <: Entity]()(implicit manifestT: Manifest[E]): Table[E] = tables.find(_.posoMetaData.clasz == manifestT.runtimeClass).get.asInstanceOf[Table[E]] def getTable(name: String): Table[_ <: Entity] = tables.find(_.posoMetaData.clasz.getSimpleName.toLowerCase == name) .get.asInstanceOf[Table[_ <: Entity]] def get[T <: Entity](id: Long)(implicit manifestT: Manifest[T]): Option[T] = getTable[T]().lookup(id).map(e => { if (e.deleted) None else Some(e) }).getOrElse(None) def get(table: String, id: Long): Option[Entity] = getTable(table).lookup(id).map(e => { if (e.deleted) None else Some(e) }).getOrElse(None) def getAll(table: String): Seq[Entity] = from(getTable(table))(e => select(e)).toSeq def save(table: String, json: String)(implicit gson: Gson) = gson.fromJson( json, getTable(table).posoMetaData.clasz ).save() def delete(table: String, id: Long) = get(table, id).map(_.delete()) class TimeStamp(t: Timestamp) extends TimestampField(t) implicit def jodaToTimeStamp(dateTime: DateTime): TimeStamp = new TimeStamp(new Timestamp(dateTime.getMillis)) implicit def timeStampToJoda(timeStamp: TimeStamp): DateTime = new DateTime(timeStamp.value.getTime) class Json(s: String) extends StringField(s) implicit def stringToJson(s: String): Json = new Json(s) implicit def jsonToString(json: Json): String = json.value implicit def jsValueToJson(jsValue: JsValue): Json = new Json(jsValue.toString()) implicit def jsonToJsObject(json: Json): JsObject = Json.parse(json.value).asInstanceOf[JsObject] class ForeignKey[E <: Entity](l: Long) extends LongField(l) { private var _entity = Option.empty[E] def entity(implicit manifestT: Manifest[E]): E = _entity.getOrElse({ val res = get[E](value).get _entity = Some(res) res }) def entity_=(value: E) { _entity = Some(value) } } implicit def entityToForeignKey[E <: Entity](entity: E): ForeignKey[E] = { val fk = new ForeignKey[E](entity.id) fk.entity = entity fk } implicit def foreignKeyToEntity[T <: Entity](fk: ForeignKey[T])(implicit manifestT: Manifest[T]): T = fk.entity implicit def longToForeignKey[T <: Entity](l: Long)(implicit manifestT: Manifest[T]) = new ForeignKey[T](l) }
      
      







データベースを操作するための基本的なメソッドを実装し、TimeStamp時間の独自のクラス、データベースにjsonを格納する独自のクラス、および必要なすべての暗黙的な変換を伴う外部キーの独自のクラスを作成します。 多くの人はコードをやり過ぎだと見なしますが、実際のほとんどのタスクでは、コードはまったく役に立たないとすぐに言います。Squerylの機能を実証しようとしました。



最後に、ログイン、パスワードフィールド、およびログイン上の外部キーを使用してログインモデルを記述し、テストデータを使用してデータベースに対応するテーブルを作成することを忘れないでください。



 package models class Login extends Entity { var login = "" var password = "" var parent: ForeignKey[Login] = null }
      
      







アクション


要求を満たすには、コードをinTransaction {}またはトランザクション{}に配置する必要があります。



inTransaction {}は、現在のトランザクションにリクエストを追加します。



トランザクション{}は、単一のトランザクションでコードを実行します。



1つのアクションが1つのトランザクションに対応し、各アクションでトランザクションブロックを書き込まないようにするために、ファイルapp / controller / BaseController.scalaにDbActionを作成します。



 package controllers import models._ import play.api.mvc._ import utils.Jsons import scala.concurrent.Future import scala.language.implicitConversions trait BaseController extends Controller { implicit val gson = new Gson object DbAction extends ActionBuilder[Request] { override def invokeBlock[A](request: Request[A], block: (Request[A]) => Future[Result]): Future[Result] = transaction { block(request) } } }
      
      







ここでは、モデルをjson /に変換するために使用されるgsonオブジェクトを指定しました



そして最後に、APIのコントローラー、app / controllers / Api.scalaを作成します



 package controllers import play.api.libs.json.Json import play.api.mvc.Action object Api extends BaseController { def get(cls: String, id: Long) = DbAction { Ok(models.get(cls, id).map(_.json).getOrElse(Json.obj())) } def save(cls: String) = DbAction{ request => Ok(models.save(cls, request.form.getOrElse("data", "{}")).json) } def delete(cls: String, id: Long) = DbAction { Ok(models.delete(cls, id).map(_.json).getOrElse(Json.obj())) } }
      
      







confにアクションを追加/ routesルート



 # Api GET /api/:cls/:id controllers.Api.get(cls:String,id:Long) POST /api/save/:cls controllers.Api.save(cls:String) POST /api/delete/:cls/:id controllers.Api.delete(cls:String,id:Long)
      
      







そして最後に、実行します:



画像



さらに、URLの任意のID、ログインの代わりに任意のクラスを登録すると、応答で必要なJsonを取得できます。 必要に応じて、モデルで、jsonメソッドをオーバーロードしてデータを追加/非表示にすることができます。 GsonはScalaコレクションをシリアル化しないことに注意してください。そのためには、Javaコレクションの変換を使用するか、Jsonと連携するためにPlay Frameworkの組み込みメカニズムを使用する必要があります。



まとめると


記述されたコードは、Squerylの幅広い機能を完全に実証していますが、小さなタスクの場合、このようなものをまったく実装する必要はありません。Squerylは、データベースを使用した本格的な作業をわずか5行で提供できることに注意してください。



私の意見の主な欠点は、移行メカニズムがないことです。Squerylでできることは、現在のDDLを発行することです。



私はSquerylと他のORMとの比較分析を行いません(少なくともこの記事では)が、個人的には非常に怠け者で、データベースに新しいエンティティを追加するときに余分なものを書きたくない人にとって、このORMは完全に適合します。



All Articles