16


12

ここを含め、オブジェクトリレーショナルマッピングについては十分に議論されています。 私はいくつかのアプローチと落とし穴と妥協点についての経験があります。 真の解決には、オブジェクト指向またはリレーショナルモデル自体への変更が必要になるようです。

関数型言語を使用している場合は、同じ問題が発生しますか? 私には、これら2つのパラダイムは、オブジェクト指向とRDBMSよりもうまく合うはずです。 RDBMSのセットで考えるという考えは、機能的アプローチが約束しているように思われる自動並列化と噛み合うようです。

誰かが興味深い意見や洞察をお持ちですか? 業界の現状はどうですか?

6 Answer


9


ORMの目的は何ですか?

ORMを使用する主な目的は、ネットワーク化されたモデル(オブジェクトの向き、グラフなど)とリレーショナルモデルとの間の橋渡しをすることです。 そして2つのモデルの主な違いは驚くほど簡単です。 それは、親が子供を指す(ネットワークモデル)か、子供が親を指す(リレーショナルモデル)かです。

この単純さを念頭に置いて、私はhttps://blog.jooq.org/2015/08/26/there-is-no-such-thing-as-object-relational-impedance-mismatch/[thereがそのようなことではないと思います2つのモデル間の「インピーダンス不整合」として。 人々が通常遭遇する問題は純粋に実装特有のものであり、クライアントとサーバー間のより良いデータ転送プロトコルがあれば解決可能であるべきです。

SQLは、ORMに関する問題にどのように対処できますか?

特に、https://en.wikipedia.org/wiki/The_Third_Manifesto[third manifesto]は、ネストされたコレクションを可能にすることで、SQL言語とリレーショナル代数の欠点を解決しようとしています。 :

  • Oracle(おそらく最も洗練された実装)

  • PostgreSQL(ある程度)

  • Informix

  • SQL Server、MySQLなど (XMLまたはJSONを介した「エミュレーション」を通じて)

私の意見では、すべてのデータベースがSQL標準の `MULTISET()`演算子(例えば、 Oracleはそうしています。マッピングのためにORMを使うことはおそらくないでしょう(おそらくオブジェクトグラフの永続化のために)。 このクエリ:

SELECT actor_id, first_name, last_name,
  MULTISET (
    SELECT film_id, title
    FROM film AS f
    JOIN film_actor AS fa USING (film_id)
    WHERE fa.actor_id = a.actor_id
  ) AS films
FROM actor AS a

すべての俳優とその映画を、正規化されていない結合結果ではなく、ネストされたコレクションとして生成します(各映画に対して俳優が繰り返される場合)。

クライアント側の機能的パラダイム

クライアント側の関数型プログラミング言語がデータベースの対話に適しているかどうかという問題は、実に直交的です。 ORMはオブジェクトグラフの永続化に役立ちます。したがって、クライアントサイドモデルがグラフで、それをグラフにしたい場合は、関数型プログラミング言語を使用してそのグラフを操作するかどうかにかかわらず、ORMが必要になります。

ただし、オブジェクト指向は関数型プログラミング言語ではあまり慣用されていないため、すべてのデータ項目をオブジェクトにまとめることはほとんどありません。 SQLを書いている人にとっては、任意の_tuples_を射影するのはとても自然です。 SQLは構造型付けを取り入れています。 各SQLクエリは、以前に名前を割り当てる必要なしに、独自の行タイプを定義します。 特に型推論が洗練されている場合、これは関数型プログラマと非常によく似ています。その場合、SQLの結果を以前に定義されたオブジェクト/クラスにマッピングすることは考えられません。

// Higher order, SQL query producing function:
public static ResultQuery> actors(Function p) {
    return ctx.select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
              .from(ACTOR)
              .where(p.apply(ACTOR)));
}

このアプローチは、SQL言語が何らかのORMによって抽象化されている場合、またはSQLの本来の「文字列ベース」の性質が使用されている場合よりもはるかに優れたSQLステートメントの構成性をもたらします。 上記の機能は今使用することができます。 このような:

// Get only actors whose first name starts with "A"
for (Record rec : actors(a -> a.FIRST_NAME.like("A%")))
    System.out.println(rec);

SQL上のFRMの抽象化

FRMの中には、通常次のような理由でSQL言語を抽象化しようとするものがあります。

  • 彼らはSQLが十分に構成可能でないと主張している(jOOQはこれを反証し、それは 正しくするのは非常に難しいです)。

  • 彼らは、APIユーザーは「ネイティブ」コレクションAPIに慣れていると主張しています。 そう、例えば JOINはflatMap()に翻訳され、WHEREはfilter()に翻訳されます。

あなたの質問に答えるために

FRMはORMより「簡単」ではありません、それは別の問題を解決します。 実際、FRMはまったく問題を解決しません。それは、SQLは宣言型プログラミング言語そのものであり(関数型プログラミングとそれほど変わらない)、他の関数型クライアントプログラミング言語に非常に適しているからです。 つまり、FRMは、SQL、外部DSL、およびクライアント言語の間のギャップを埋めるものです。

(私はhttps://www.jooq.org [jOOQ]の背後にある会社で働いているので、この答えは偏っています)


7


リレーショナルデータベースを拡張する上での難しい問題は、トランザクションの拡張、データ型の不一致、クエリの自動変換、 N 1 Select thatなどです。リレーショナルシステムを去ることの根本的な問題であり - そして私の意見では - 受信プログラミングパラダイムを変更しても変わらない。


4


それはあなたのニーズによります

  1. データ構造に注目したい場合は、JPA / HibernateのようなORMを使用してください。

  2. もしあなたが治療法に焦点を当てたいのなら、FRMライブラリーを見てください。QueryDSLまたはJooq

  3. SQL要求を特定のデータベースに調整する必要がある場合は、JDBCおよびネイティブSQL要求を使用してください。

さまざまな "リレーショナルマッピング"テクノロジの強みは移植性です。アプリケーションをほとんどのACIDデータベースで実行できるようにします。 それ以外の場合は、手動でSQL要求を書くときにさまざまなSQL方言間の違いに対処します。

もちろん、あなたは自分自身をSQL92標準に制限することができ(そしてそれからいくつかの関数型プログラミングをする)、あるいはあなたはORMフレームワークで関数型プログラミングのいくつかの概念を再利用することができます。

ORMの強みは、ボトルネックとなる可能性があるセッションオブジェクトを基に構築されています。

  1. 基礎となるデータベーストランザクションが実行されている限り、オブジェクトのライフサイクルを管理します。

  2. それはあなたのJavaオブジェクトとあなたのデータベース行の間の一対一のマッピングを維持します(そしてオブジェクトの重複を避けるために内部キャッシュを使います)。

  3. 関連付けの更新と削除する孤立オブジェクトを自動的に検出します。

  4. 楽観的ロックまたは悲観的ロックで同時に発生する問題を処理します。

それにもかかわらず、その長所はまた短所です。

  1. セッションはオブジェクトを比較できなければならないので、equals / hashCodeメソッドを実装する必要があります。 しかし、オブジェクトの同一性は、データベースIDではなく「ビジネスキー」に基づいている必要があります(新しい一時オブジェクトにはデータベースIDがありません!)。 ただし、具体化された概念の中にはビジネスの平等性を持たないものがあります(たとえば操作)。 一般的な回避策はデータベース管理者を混乱させる傾向があるGUIDに依存しています。

  2. セッションは関係の変化をスパイする必要がありますが、そのマッピング規則はビジネス・アルゴリズムには不適切なコレクションの使用を推進します。 HashMapを使いたいと思うかもしれませんが、ORMではキーを別の「リッチドメインオブジェクト」にする必要があります。 それから、キーとして機能するリッチドメインオブジェクトにオブジェクト等価性を実装する必要があります。 しかし、あなたはできません。このオブジェクトは、ビジネスの世界に対応するものがないからです。 それであなたはあなたが反復しなければならない単純なリストに頼ります(そしてパフォーマンスの問題はそこから生じます)。

  3. ORM APIは実際の使用には不適切な場合があります。 たとえば、実際のWebアプリケーションは、データをフェッチするときにいくつかの "WHERE"句を追加してセッションの分離を強制しようとします。 それでは、 "Session.get(id)"で十分ではないので、もっと複雑なDSL(HSQL、Criteria API)を使うか、ネイティブSQLに戻る必要があります。

  4. データベースオブジェクトは、他のフレームワーク専用の他のオブジェクトと競合します(OXMフレームワーク=オブジェクト/ XMLマッピングなど)。 たとえば、RESTサービスがビジネスオブジェクトのシリアル化にjacksonライブラリを使用しているとします。 しかし、このジャクソンはまさにHibernate Oneに対応しています。 それから、両方をマージすると、APIとデータベースの間の強力なカップリングが表示されるか、翻訳を実装する必要があり、ORMから保存したすべてのコードがそこで失われます。

一方、FRMは「オブジェクトリレーショナルマッピング」(ORM)とネイティブJDBCクエリ(JDBCを使用)の間のトレードオフです。

FRMとORMの違いを説明する最良の方法は、DDDアプローチを採用することです。

  • Object Relational Mappingは、データベーストランザクション中に状態が変更可能なJavaクラスである「Rich Domain Object」の使用を可能にします。

  • 関数型リレーショナルマッピングは不変の「貧弱なドメインオブジェクト」に依存しています(そのため、内容を変更するたびに新しいオブジェクトを複製する必要があります)。

これはORMセッションに課された制約を解放し、ほとんどの場合SQL上のDSLに依存します(移植性は重要ではありません)。一方、トランザクションの詳細、並行性の問題を調べなければなりません。

リストpeople = queryFactory.selectFrom(person).where(person.firstName.eq( "John")、person.lastName.eq( "Doe")).fetch();


3


機能からリレーショナルへのマッピングは、OOからRDBMSに比べて作成と使用が簡単であるべきだと思います。 データベースを照会するだけであれば、それです。 私は本当に(まだ)あなたが良い方法で副作用なしでデータベースの更新を行うことができる方法を見ません。

私が見ている主な問題はパフォーマンスです。 今日のRDMSは、機能的なクエリで使用するようには設計されておらず、おそらくごく少数のケースではうまく動作しません。


3


Samが述べたように、DBが更新されるべきなら、OOの世界と同じ並行性の問題に直面しなければならないと思います。 プログラムの機能的な性質は、RDBMSのデータの状態、トランザクションなどのために、オブジェクトの性質よりも少し問題が多いかもしれません。

しかし読むためには、関数型言語はいくつかの問題領域でもっと自然になるかもしれません(DBに関係なくそうであるように)

機能的RDBMSマッピングは、OO RDMBSマッピングと大きな違いはないはずである。 しかし、まったく新しいDBスキーマを使用してプログラムを開発する場合や、従来のDBスキーマに対して何かを実行する場合などは、どのような種類のデータを使用するかによって大きく左右されると思います。

たとえば、関連付けのための遅延フェッチなどは、遅延評価に関連するいくつかの概念を使用して、おそらく非常にうまく実装できます。 (たとえ彼らがOOでもかなりうまくやることができるとしても)

編集:いくつかのグーグルで私は HaskellDB(Haskell用のSQLライブラリ)を見つけた - それは試す価値があるだろうか?


3


私は機能的リレーショナルマッピング、つまりそれ自体を行っていませんが、RDBMSへのアクセスを高速化するために機能的プログラミング技法を使いました。

データセットから始めて、複雑な計算をして結果を保存するのはごく一般的です。たとえば、結果は元のデータセットに追加の値を追加したものです。 必須のアプローチでは、最初のデータセットに余分なNULL列を格納し、計算を実行してから、計算された値でレコードを更新します。

合理的に思えます。 しかし、それに関する問題はそれが非常に遅くなる可能性があるということです。 更新クエリ自体以外に別のSQL文が計算に必要な場合、またはアプリケーションコードで実行する必要がある場合でも、計算後に結果を正しい行に格納するために変更しているレコードを(再)検索する必要があります。 。

結果用の新しいテーブルを作成するだけでこれを回避できます。 こうすれば、更新の代わりにいつでも挿入できます。 キーを複製して別のテーブルを作成することになりますが、NULLを格納する列にスペースを無駄にする必要はなくなりました。所有しているものだけを格納します。 それからあなたはあなたの最終的な選択であなたの結果に参加する。

私(ab)はこのようにRDBMSを使い、ほとんどこのようなSQL文を書いてしまいました…​

select ...として表temp_foo_1を作成します。 select ...として表temp_foo_2を作成します。
...
temp_foo_n内部結合temp_foo_1からselect *としてテーブルfoo_resultsを作成します。 内部結合temp_foo_2 ...;

これが本質的にしていることは、不変バインディングの束を作ることです。 ただし、いいことに、セット全体を一度に操作できるということです。 Matlabのように、行列を扱うことができる言語を思い出させます。

これにより、並列処理もはるかに容易になると思います。

特別な利点は、この方法で作成されたテーブルのカラムのタイプは、選択されたカラムから推論されるので指定する必要がないということです。