ScalaのSQLのINSERTモードORMを高めるために使用されるマクロ

類似HibernateまたはJPA、の定義case class、例えばPerson、次にインスタンスcase class直接呼び出しをdataSource.save(obj)、またはdataSource.saveWithSchema("person1")(obj)データを挿入します。

使用例

次のようにデータソースを定義して、人のテーブルを作成し、テーブルの構造は次のようになります。


6393906-3f542f6543845cb6.png
person.schema

プログラムによって挿入されました:

import com.xiaomi.ad.common.db.DruidDataSourceInitializer._
import wangzx.scala_commons.sql._

object ScalaSqlSpec {
    case class Person(name: String, age: Int, firtsHobby: String, typeName: String)

    def main(args: Array[String]): Unit = {
        val p1 = Person("Walter White", 50, "cook", "White")
        val p2 = Person("Jesse Pinkman", 26, "party", "Pinkman")

        dataSource.save(p1)
        dataSource.saveWithSchema("person")(p2)
    }
}

次のように実行は、正常に挿入、ログは次のとおりです。

5-31 15:09:53 627 main INFO - {dataSource-1} inited
05-31 15:09:53 675 main DEBUG - SQL Preparing: INSERT INTO person (name,age,first_hobby,type_name) VALUES ( ?,?,?,? ) args: List(JdbcValue(Walter White), JdbcValue(50), JdbcValue(cook), JdbcValue(White))
05-31 15:09:53 696 main DEBUG - SQL result: 1
05-31 15:09:53 697 main DEBUG - SQL Preparing: INSERT INTO person (name,age,first_hobby,type_name) VALUES ( ?,?,?,? ) args: List(JdbcValue(Jesse Pinkman), JdbcValue(26), JdbcValue(party), JdbcValue(Pinkman))
05-31 15:09:53 717 main DEBUG - SQL result: 1

実装

1. RichDataSource saveWithSchemaを定義して保存

RichDataSource同等の暗黙的な変換モードによって導入プログラムに追加機能を提供するDataSourceクラスが拡張され、我々は一般的なDataSourceクラスによってRichDataSourceでメソッドを呼び出すことができます。

def save[T: OrmInsert](dto: T): Int = withConnection(_.save(dto))

def saveWithSchema[T: OrmInsert](schema: String)(dto: T): Int = withConnection(_.saveWithSchema(schema, dto))

2. RichConnection定義しsaveWithSchemaを保存

またwithConnectionは、あなたが呼び出すことができるConnetion、我々はRichConnectionの強化を使用し、メソッドRichConnectionを取得します

def save[T: OrmInsert](dto: T): Int = {
        val (sql, sqlWithArgs) = implicitly[OrmInsert[T]].from(dto, None)
        val prepared = conn.prepareStatement(sql, Statement.NO_GENERATED_KEYS)

        try {
            if (sqlWithArgs != null) setStatementArgs(prepared, sqlWithArgs)

            LOG.debug("SQL Preparing: {} args: {}", Seq(sql, sqlWithArgs): _*)

            val result = prepared.executeUpdate()

            LOG.debug("SQL result: {}", result)

            result
        }
        finally {
            prepared.close()
        }
    }

    def saveWithSchema[T: OrmInsert](schemaName: String, dto: T): Int = {
        val (sql, sqlWithArgs) = implicitly[OrmInsert[T]].from(dto, Some(schemaName))
        val prepared = conn.prepareStatement(sql, Statement.NO_GENERATED_KEYS)

        try {
            if (sqlWithArgs != null) setStatementArgs(prepared, sqlWithArgs)

            LOG.debug("SQL Preparing: {} args: {}", Seq(sql, sqlWithArgs): _*)

            val result = prepared.executeUpdate()

            LOG.debug("SQL result: {}", result)

            result
        }
        finally {
            prepared.close()
        }
    }

3.新しい方法はRichConnectionに焦点を当てて

保存してsaveWithSchema大同リトルイタリーは、我々はこの方法に焦点を当てたセーブ分析

キーコードは他のコードへと、大きな変化の前に類似した次のパラグラフであり、

val (sql, sqlWithArgs) = implicitly[OrmInsert[T]].from(dto, None)

我々は、オブジェクト内のT OrmInsert [T]、そこに導入コンテキストバインド前記事のにバインドコンテキスト結合特性の使用タイプT、である渡さ。

trait OrmInsert[C] {
        def from(c: C, schemaName: Option[String]): (String, Seq[JdbcValue[_]])

        def build(c: C): (String, List[Token], Seq[JdbcValue[_]])
}

主に、OrmInsert [T]を話すTに結合した後、例えば、その後、インサートが挿入されるように生成し、SQLパラメータのSeq [JdbcValue [_]、着信オブジェクトCを解析する方法で行うことができるから。

プログラム内の上記目的は、SQL p1とパラメータのリストを生成する必要があります以下の通りです:

INSERT INTO person (name,age,first_hobby,type_name) VALUES ( ?,?,?,? ) 

args: List(JdbcValue(Walter White), JdbcValue(50), JdbcValue(cook), JdbcValue(White))

することによりimplicitly[OrmInsert[T]]、我々は、形質OrmInsertのGa実装クラスを取得する上記のプログラム、の存在下での暗黙の値OrmInsert [T]を取得し、その後、クラスがどのようにそれを取得しますか?

4. OrmInsert [T]がコンパイルマクロでクラスによって実装される生成

最初の露出OrmInsert [T]は、暗黙的な値

implicit def materialize[C]: OrmInsert[C] = macro converterToMapMacro[C]

コンパイル呼び出しconverterToMapMacroマクロでコードによって生成されたコードは、この方法の工程を達成します。

def converterToMapMacro[C: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
            import c.universe._
            val tpe = weakTypeOf[C]

            val fields = tpe.decls.collectFirst {
                case m: MethodSymbol if m.isPrimaryConstructor => m
            }.get.paramLists.head

            val (names, jdbcValues) = fields.map { field =>
                val name = field.name.toTermName
                val decoded = name.decodedName.toString

                val value = q"JdbcValue.wrap(t.$name)"
                (q"Token($decoded)", value)
            }.unzip

            val schemaName = TermName(tpe.typeSymbol.name.toString).toString.toLowerCase()

            val tree =
                q"""
                     new AbstractInsert[$tpe] {
                        def build(t: $tpe) = ($schemaName,$names,$jdbcValues)
                     }
            """
            tree
}

上記のコードは、最終的に限り、我々はそれが動的にコードを生成理解し、コードがOrmInsert抽象クラスにAbstractInsert実装クラス、AbstractInsertを返します、ASTとして、このツリーをツリーを生成します。

val tpe = weakTypeOf[C]タイプタイプタイプC、各種情報の置換型Cのこのタイプを取得します。

TPEを取得し、すべてのフィールド名は、このような動作により、ケースクラス、およびフィールドタイプ、タイプCはすなわち、OrmInsert方法を構築するために渡されたインスタンスに従います取得def build(t: T)マクロコードでは、我々が使用する型T $tpeへ特定の場所で、我々はトンの現在のインスタンスのt.fieldNameすなわちモードフィールド値を介して取得することができ、その後、処理するために、この簡単で、私たちは「Qを使用して、上記のコードでは、値JdbcValueを変換する必要がありますので、 JdbcValue.wrap(t.decoded)「`フィールド名は、主にこぶとアンダースコアを変換する目的のために、トークンに包まれました。

次のように最後に実装クラスを生成しました:

new AbstractInsert[$tpe] {
       def build(t: $tpe) = ($schemaName,$names,$jdbcValues)
}
  • $schemaName:これは、データベーステーブル名、具体的場合には同定されていない、場合クラスの名前であります
  • $namesこれは主に、後続の挿入およびデータベースを使用するフィールド名のフィールドトークン(filedName)であります
  • $jdbcValues:ここでは、入ってくる例のT BUID方法ので、直接t.fieldNameの形態における各値を、その後の挿入のためのデータテーブルは、特定の値を準備します。

挿入をスプライスするためにSQLを使用した抽象クラスAbstractInsert

abstract class AbstractInsert[C] extends OrmInsert[C] {

    def from(c: C, schemaName: Option[String]): (String, Seq[JdbcValue[_]]) = {
        val (schema, tokenNames, args) = build(c)
        val useSchema = schemaName match {
            case Some(value) if value != null ⇒ value
            case None ⇒ schema
        }
        val sqlFields = tokenNames.map(_.underscoreName).mkString(",")
        val interrogation = tokenNames.indices.map(_ ⇒ "?").mkString(",")
        val sql = s"INSERT INTO $useSchema ($sqlFields) VALUES ( $interrogation )"

        (sql, args)
    }
}

()メソッドとインスタンスCデータベーステーブル名を渡してからschemaName、あなたが小文字の場合クラスのクラス名の使用を指定しない場合は、。
方法からマクロコンパイル方法で生成されたコールbuild()スキーマテーブルを取得するために、tokeNamesフィールドリスト、
特定のデータを挿入する配列[JdbcValue [_]。そして、ステッチSQLを起動し、スプライシングコードは比較的簡単ですが、彼らはここでは詳細に説明されていません。

ここでは、最終的にフィールドを挿入するSQLを、つなぎだろう?彼はその後、スプライシングSQL文字列を返すと値にアクセスすると述べました。

6. SQL決勝を実行

def save[T: OrmInsert](dto: T): Int = {
        val (sql, sqlWithArgs) = implicitly[OrmInsert[T]].from(dto, None)
        val prepared = conn.prepareStatement(sql, Statement.NO_GENERATED_KEYS)

        try {
            if (sqlWithArgs != null) setStatementArgs(prepared, sqlWithArgs)

            LOG.debug("SQL Preparing: {} args: {}", Seq(sql, sqlWithArgs): _*)

            val result = prepared.executeUpdate()

            LOG.debug("SQL result: {}", result)

            result
        }
        finally {
            prepared.close()
        }
    }

prepareStatementは、最終的にデータベースにデータを使用するために、全体の処理が終了します。

概要

このオブジェクト指向の挿入モードでのScalaの-SQLで実装、場合、最も重要なのは、我々は、オブジェクトのフィールドと各フィールドの外に値を取得する方法ですが、私たちは多くの分野は、このオブジェクトがあるだろうか事前に知っていません通常モードによれば、動作中にも反射モードによって行われ、これはエレガントではないことができます。

ん行うための使用マクロは、スプライシング後に、この情報を取得するために、我々は、コンパイルすべてのフィールドとフィールド値に対応する位置に挿入されるオブジェクトに直接取得する、非常にエレガントになりますSQLは、よりになり挿入シンプル。

することができます詳細をお知りになりたいソースコードを表示、このプロジェクトからのフォークを

ます。https://www.jianshu.com/p/86f741f51e71で再現

おすすめ

転載: blog.csdn.net/weixin_34101229/article/details/91095271