web-dev-qa-db-ja.com

完全なORM(JPA / Hibernate)をより軽量なソリューションに置き換える:ロード/保存の推奨パターンは?

私は新しいJava Webアプリケーションを開発しており、データを永続化する新しい方法(私にとっては新しい!)を模索しています。 、この種の完全なORMは非常に複雑になる可能性があると思うし、さらに、私はそれらと一緒に作業するのが好きではないので、SQLに近い新しいソリューションを探しています。

現在調査中のソリューション:

しかし、Hibernateと比較して、これらのソリューションで心配しているユースケースが2つあります。これらのユースケースに推奨されるパターンは何か知りたいです。


使用例1-エンティティを取得し、それに関連付けられているいくつかの子および孫エンティティにアクセスします。

  • Personエンティティがあるとします。
    • このPersonには、関連付けられたAddressエンティティがあります。
      • このAddressには、関連付けられたCityエンティティがあります。
        • このCityエンティティには、nameプロパティがあります。

個人のエンティティから始まる都市の名前にアクセスするためのフルパスは次のとおりです。

_person.address.city.name
_

ここで、このメソッドを使用してPersonServiceからPersonエンティティをロードするとします。

_public Person findPersonById(long id)
{
    // ...
}
_

Hibernateを使用すると、Personに関連付けられたエンティティをオンデマンドで遅延ロードできるため、_person.address.city.name_にアクセスして、このプロパティにアクセスできることを確認できます(すべてのエンティティそのチェーンではnullを使用できません)。

しかし、私が調査している3つのソリューションのいずれかを使用すると、より複雑になります。これらのソリューションでは、このユースケースを処理するための推奨パターンは何ですか?事前に、3つの可能なパターンが表示されます。

  1. 使用されるSQLクエリによって、必要なすべての関連する子および孫エンティティを積極的にロードできます。

    しかし、このソリューションで見られる問題は、Personエンティティからotherエンティティ/プロパティパスにアクセスする必要がある他のコードがあるかもしれないということです。たとえば、おそらくいくつかのコードは_person.job.salary.currency_にアクセスする必要があります。既に持っているfindPersonById()メソッドを再利用したい場合、SQLクエリはより多くの情報をロードする必要があります!関連する_address->city_エンティティだけでなく、関連する_job->salary_エンティティも。

    では、10個人エンティティから始まる他の情報にアクセスする必要がある他の場所がある場合はどうでしょうか?潜在的に必要な情報を常にall積極的にロードする必要がありますか?または、個人エンティティをロードするための12の異なるサービスメソッドがありますか? :

    _findPersonById_simple(long id)
    
    findPersonById_withAdressCity(long id)
    
    findPersonById_withJob(long id)
    
    findPersonById_withAdressCityAndJob(long id)
    
    ...
    _

    しかし、その後Personエンティティを使用するたびに、何がロードされ、何がロードされなかったかを知る必要があります...それは非常に面倒ですよね?

  2. PersonエンティティのgetAddress() getterメソッドで、アドレスが既にロードされているかどうかを確認し、ロードされていない場合は遅延ロードしますか?これは実際のアプリケーションで頻繁に使用されるパターンですか?

  3. ロードされたモデルから必要なエンティティ/プロパティにアクセスできることを確認するために使用できる他のパターンはありますか?


ユースケース2-エンティティを保存し、関連するエンティティおよび変更されたエンティティも保存されるようにします。

このPersonのメソッドを使用してPersonServiceエンティティを保存できるようにしたい:

_public void savePerson(Person person)
{
    // ...
}
_

Personエンティティがあり、_person.address.city.name_を別のものに変更した場合、Cityを保存するときにPersonエンティティの変更が保持されるようにするにはどうすればよいですか? Hibernateを使用すると、関連するエンティティへの保存操作を簡単にcascadeできます。私が調査しているソリューションはどうですか?

  1. ある種のdirtyフラグを使用して、人物を保存するときにどの関連エンティティも保存する必要があるかを知る必要がありますか?

  2. このユースケースに対処するのに役立つ他の既知のパターンはありますか?


Update:JOOQフォーラムでこの質問について 議論 があります。

37
electrotype

この種の問題は、実際のORMを使用しない場合によく見られ、特効薬はありません。 iBatis(myBatis)を使用した(あまり大きくない)Webアプリで機能したシンプルな設計アプローチは、永続化のために2つのレイヤーを使用することです。

  • ダムの低レベルレイヤー:各テーブルには、Javaクラス(POJOまたはDTO)があり、テーブル列に直接マップするフィールドがありますPERSONテーブルを指す_ADDRESS_ID_フィールドを持つADRESSテーブルがあるとします;そして、PersonDbがありますaddressId(整数)フィールドのみのクラス; personDb.getAdress()メソッドはなく、単なるpersonDb.getAdressId()のみです。これらのJavaクラスは非常に愚かです(永続性や関連クラスについては知りません)対応するPersonDaoクラスは、このオブジェクトをロード/永続化する方法を知っています。このレイヤーは、次のようなツールで簡単に作成および管理できますiBatis + iBator(またはMyBatis + MYBatisGenerator )。

  • リッチドメインオブジェクトを含む上位層:これらは通常、グラフ上記のPOJOの。これらのクラスには、それぞれのDAOを呼び出すことにより、グラフをロード/保存するためのインテリジェンスもあります(おそらく、いくつかのダーティフラグを使用して、おそらく遅延的に)。ただし、重要なことは、これらのリッチドメインオブジェクトはPOJOオブジェクト(またはDBテーブル)に1対1ではなく、domainユースケース。各グラフの「サイズ」は決定され(無期限に成長することはありません)、特定のクラスのように外部から使用されます。したがって、複数のユースケースまたはサービスメソッドで使用されるリッチなPersonクラス(関連オブジェクトの不確定なグラフを含む)が1つあるわけではありません。代わりに、いくつかの豊富なクラスPersonWithAddresesPersonWithAllData...があり、それぞれが独自の永続ロジックで特定の十分に制限されたグラフをラップします。これは非効率的または不器用に思えるかもしれませんが、状況によってはそうかもしれませんが、オブジェクトの完全なグラフを保存する必要があるユースケースが実際に制限されることがよくあります。

  • さらに、表形式のレポート(表示する列の列を返す特定のSELECT)の場合は、上記を使用せずに、ストレートおよびダムPOJO(おそらくマップ)を使用します

私の関連する答えを参照してください こちら

28
leonbloy

あなたの多くの質問への答えは簡単です。 3つの選択肢があります。

  1. 前述の3つのSQL中心のツールのいずれかを使用します( MyBatisjOOQDbUtils )。つまり、OOドメインモデルとオブジェクトリレーショナルマッピング(つまり、エンティティと遅延読み込み)の観点から考える必要はありません。SQLはリレーショナルデータに関するものであり、RBDMSはいくつかの結合の結果を「熱心にフェッチ」します。通常、時期尚早のキャッシングはあまり必要ありません。また、時々データ要素をキャッシュする必要がある場合は、 EhCache のようなものを使用できます。 =

  2. これらのSQL中心のツールを使用せず、Hibernate/JPAに固執します。 Hibernateが好きではないと言っても、「Hibernateを考えている」からです。 Hibernateは、オブジェクトグラフをデータベースに永続化するのに非常に優れています。これらのツールはいずれも、Hibernateのように動作することを強制できません。なぜなら、それらの使命は別のものだからです。彼らの使命は、SQLを操作することです。

  3. まったく異なる方法で、リレーショナルデータモデルを使用しないことを選択します。他のデータモデル(たとえばグラフ)が適している場合があります。あなたが実際にその選択肢を持たないかもしれないので、私はこれを3番目のオプションとして入れています、そして、私は代替モデルであまり個人的な経験を持っていません。

ご質問は、jOOQに関するものではありません。それでも、jOOQを使用すると、 ModelMapper などの外部ツールを使用して、フラットなクエリ結果(結合されたテーブルソースから生成)のオブジェクトグラフへのマッピングを外部化できます。 ModelMapper User Group には、こうした統合に関する興味深い進行中のスレッドがあります。

(免責事項:私はjOOQの背後にある会社で働いています)

28
Lukas Eder

永続化アプローチ

シンプル/ベーシックから洗練/リッチまでのソリューションの範囲は次のとおりです。

  • SQL/JDBC-オブジェクト内のSQLのハードコード
  • SQLベースのフレームワーク(例:jOOQ、MyBatis)-アクティブレコードパターン(個別の一般オブジェクトは行データを表し、SQLを処理します)
  • ORM-Framework(Hibernate、EclipseLink、DataNucleusなど)-データマッパーパターン(エンティティごとのオブジェクト)と作業単位パターン(永続コンテキスト/エンティティマネージャー)

最初の2つのレベルのいずれかを実装しようとします。つまり、フォーカスをオブジェクトモデルからSQLに移します。しかし、あなたの質問は、SQLにマッピングされているオブジェクトモデル(つまり、ORMの動作)を含むユースケースを求めています。最初の2つのレベルのいずれかの機能に対して、3番目のレベルの機能を追加したい場合。

この動作をActive Record内に実装してみることができます。ただし、これには、各アクティブレコードインスタンス(実際のエンティティ、他のエンティティとの関係、遅延読み込み設定、カスケード更新設定)に添付する豊富なメタデータが必要になります。これにより、非表示のマップされたエンティティオブジェクトになります。また、jOOQとMyBatisは、ユースケース1と2に対してこれを行いません。

リクエストを達成する方法は?

フレームワークまたは生のSQL/JDBCの上にある小さなカスタムレイヤーとして、狭いORM動作をオブジェクトに直接実装します。

ユースケース1:各エンティティオブジェクト関係のメタデータを保存します。(i)関係を遅延ロードする必要があるか(クラスレベル)、(ii)遅延ロードが発生したか(オブジェクトレベル)。次に、ゲッターメソッドでこれらのフラグを使用して、遅延読み込みを実行して実際に実行するかどうかを決定します。

ユースケース2:ユースケース1と同様-自分で実行します。各エンティティ内にダーティフラグを保存します。各エンティティオブジェクトの関係に対して、保存をカスケードする必要があるかどうかを示すフラグを保存します。次に、エンティティが保存されたら、再帰的に各「カスケードの保存」関係にアクセスします。発見された汚れたエンティティを書き込みます。

パターン

長所

  • SQLフレームワークの呼び出しは簡単です。

短所

  • オブジェクトはより複雑になります。オープンソース製品内のユースケース1と2のコードを見てください。些細なことではありません
  • オブジェクトモデルのサポートの欠如。ドメインでJavaでオブジェクトモデルを使用している場合、データ操作のサポートは少なくなります。
  • スコープクリープとアンチパターンのリスク:上記の欠落している機能は氷山の一角です。最終的には、ビジネスロジックで車輪とインフラストラクチャの膨張をやり直すことがあります。
  • 非標準ソリューションの教育とメンテナンス。 JPA、JDBC、およびSQLは標準です。他のフレームワークやカスタムソリューションはそうではありません。

価値がある???

このソリューションは、データ処理の要件がかなり単純で、エンティティの数が少ないデータモデルの場合にうまく機能します。

  • もしそうなら、素晴らしい!上記を行います。
  • そうでない場合、このソリューションは不十分な適合であり、努力の誤った節約を表します-つまり、ORMを使用するよりも時間がかかり、より複雑になります。その場合は、JPAをもう一度見てください。思ったより簡単かもしれませんし、CRUDのORMと複雑なクエリの生のSQLをサポートしています:-)。
11
Glen Best

過去10年間、私はJDBC、EJBエンティティBean、Hibernate、GORM、そして最後にJPAをこの順序で使用していました。現在のプロジェクトでは、パフォーマンスに重点を置いているため、プレーンJDBCの使用に戻りました。したがって、私は欲しかった

  • SQLステートメントのgenerationに対するフルコントロール:DBパフォーマンスチューナーにステートメントを渡し、最適化されたバージョンをプログラムに戻すことができるようにするため
  • データベースに送信されるSQLステートメントのnumberのフルコントロール
  • ストアドプロシージャ(トリガー)、ストアドファンクション(SQLクエリでの複雑な計算用)
  • 利用可能なすべてのSQL機能を制限なしで使用できるようにする(CTEを使用した再帰クエリ、ウィンドウ集計関数など)

データモデルはデータディクショナリで定義されます。モデル駆動型のアプローチを使用すると、ジェネレーターはヘルパークラス、DDLスクリプトなどを作成します。データベースに対するほとんどの操作は読み取り専用です。少数のユースケースのみが記述されています。

質問1:子の取得

システムはユースケースに基づいて構築されており、特定のユースケース/リクエストのすべてのデータを取得するための1つの専用SQLステートメントがあります。 SQLの一部のステートメントは20kbを超え、結合し、Java/Scalaで記述されたストアド関数を使用して計算し、並べ替え、ページ分割などを行い、結果がデータ転送オブジェクトに直接マッピングされ、ビューにフィードされます。 (アプリケーション層でそれ以上の処理はありません)。結果として、データ転送オブジェクトもユースケース固有です。指定されたユースケースのデータのみが含まれます(それ以上、それ以下)。

結果セットは既に「完全に結合」されているため、レイジー/イーガーフェッチなどの必要はありません。データ転送オブジェクトは完成です。キャッシュは必要ありません(データベースキャッシュは問題ありません)。例外:結果セットが大きい場合(約50 000行)、データ転送オブジェクトがキャッシュ値として使用されます。

質問2:保存

コントローラーがGUIからの変更をマージして戻すと、データを保持する特定のオブジェクトが再びあります。基本的には、階層内の状態(新規、削除、変更など)を持つ行です。データを階層に保存するのは手動の反復です。新規または変更されたデータは、SQL insertまたはupdateコマンドを生成するヘルパークラスを使用して保持されます。削除されたアイテムに関しては、これはカスケード削除(PostgreSql)に最適化されています。複数の行を削除する場合、これは単一のdelete ... where id in ...ステートメントにも最適化されます。

繰り返しますが、これはユースケース固有であるため、一般的なアプローチを扱っていません。より多くのコード行が必要ですが、これらは最適化を含む行です。

これまでの経験

  • HibernateまたはJPAを習得する努力を過小評価しないでください。キャッシュの構成、クラスターでのキャッシュの無効化、熱心な/遅延フェッチ、およびチューニングに費やされる時間も考慮する必要があります。別のHibernateメジャーバージョンへの移行は、単なる再コンパイルではありません。
  • ORMなしでアプリケーションを構築する努力を過大評価しないでください。
  • SQLを直接使用する方が簡単です-SQLにcloseである(HQL、JPQLなど)ことはnotと同じですDBパフォーマンスチューナー
  • SQL Serverは、特にScalaで記述されたストアド関数と組み合わせた場合、長く複雑なクエリを実行するときに非常に高速です
  • ユースケース固有のSQLステートメントを使用しても、コードサイズは小さくなります。「完全に結合された」結果セットにより、アプリケーション層で多くの行が節約されます。

関連情報:

更新:インターフェイス

論理データモデルにPersonエンティティがある場合、JPAエンティティと同様に、Personエンティティ(CRUD操作用)を永続化するクラスがあります。

ただし、ユースケース/クエリ/ビジネスロジックの観点から見ると、単一のPersonエンティティは存在しないということです:各サービスメソッドは、Personown概念を定義し、exactlyそのユースケースに必要な値。これ以上でもそれ以下でもありません。 Scalaを使用しているため、多くの小さなクラスの定義と使用は非常に効率的です(セッター/ゲッターボイラープレートコードは不要です)。

例:

class GlobalPersonUtils {
  public X doSomeProcessingOnAPerson(
    Person person, PersonAddress personAddress, PersonJob personJob, 
    Set<Person> personFriends, ...)
}

に置き換えられます

class Person {
  List addresses = ...
  public X doSomeProcessingOnAPerson(...)
}

class Dto {
  List persons = ...
  public X doSomeProcessingOnAllPersons()
  public List getPersons()
}

ユースケース固有のPersonsAdressesなどの使用:この場合、Personはすべての関連データをすでに集約しています。より多くのクラスが必要ですが、JPAエンティティを渡す必要はありません。

この処理は読み取り専用であり、結果はビューによって使用されることに注意してください。例:個人のリストから個別のCityインスタンスを取得します。

データが変更された場合、これは別のユースケースです。人の都市が変更された場合、これは別のサービスメソッドによって処理され、その人はデータベースから再度取得されます。

7
Beryllium

警告:これは、プロジェクト作成者によるもう一つの恥知らずなプラグです。

JSimpleDB をチェックして、シンプルさとパワーの基準を満たしているかどうかを確認してください。これは、SQLおよびORMを介したJava永続性に対処しようとする10年以上のフラストレーションから生まれた新しいプロジェクトです。

これは、キー/値ストアとして機能できるデータベース、たとえば FoundationDB (またはすべてが詰まっているSQLデータベース)の上で動作します。単一のキー/値テーブル)。

2
Archie

問題の中心的な問題は、リレーショナルモデル自体にあるように思えます。これまで説明してきたことから、グラフデータベースは問題領域を非常にきれいにマッピングします。別の方法として、ドキュメントストアは問題にアプローチする別の方法です。これは、インピーダンスがまだ存在している間、ドキュメントは一般的にセットよりも推論が簡単だからです。もちろん、どのアプローチにも注意すべき独自の癖があります。

1
CurtainDog

シンプルで軽量なライブラリが必要で、SQLを使用するため、 fjorm をご覧になることをお勧めします。これにより、手間をかけずにPOJOとCRUD操作を使用できます。

免責事項:私はプロジェクトの著者です。

0
Mladen Adamovic