私は新しいJava Webアプリケーションを開発しており、データを永続化する新しい方法(私にとっては新しい!)を模索しています。 、この種の完全なORMは非常に複雑になる可能性があると思うし、さらに、私はそれらと一緒に作業するのが好きではないので、SQLに近い新しいソリューションを探しています。
現在調査中のソリューション:
しかし、Hibernateと比較して、これらのソリューションで心配しているユースケースが2つあります。これらのユースケースに推奨されるパターンは何か知りたいです。
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つの可能なパターンが表示されます。
使用される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
エンティティを使用するたびに、何がロードされ、何がロードされなかったかを知る必要があります...それは非常に面倒ですよね?
Person
エンティティのgetAddress()
getterメソッドで、アドレスが既にロードされているかどうかを確認し、ロードされていない場合は遅延ロードしますか?これは実際のアプリケーションで頻繁に使用されるパターンですか?
ロードされたモデルから必要なエンティティ/プロパティにアクセスできることを確認するために使用できる他のパターンはありますか?
このPerson
のメソッドを使用してPersonService
エンティティを保存できるようにしたい:
_public void savePerson(Person person)
{
// ...
}
_
Person
エンティティがあり、_person.address.city.name
_を別のものに変更した場合、City
を保存するときにPerson
エンティティの変更が保持されるようにするにはどうすればよいですか? Hibernateを使用すると、関連するエンティティへの保存操作を簡単にcascadeできます。私が調査しているソリューションはどうですか?
ある種のdirtyフラグを使用して、人物を保存するときにどの関連エンティティも保存する必要があるかを知る必要がありますか?
このユースケースに対処するのに役立つ他の既知のパターンはありますか?
Update:JOOQフォーラムでこの質問について 議論 があります。
この種の問題は、実際の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つあるわけではありません。代わりに、いくつかの豊富なクラスPersonWithAddreses
、PersonWithAllData
...があり、それぞれが独自の永続ロジックで特定の十分に制限されたグラフをラップします。これは非効率的または不器用に思えるかもしれませんが、状況によってはそうかもしれませんが、オブジェクトの完全なグラフを保存する必要があるユースケースが実際に制限されることがよくあります。
さらに、表形式のレポート(表示する列の列を返す特定のSELECT)の場合は、上記を使用せずに、ストレートおよびダムPOJO(おそらくマップ)を使用します
私の関連する答えを参照してください こちら
あなたの多くの質問への答えは簡単です。 3つの選択肢があります。
前述の3つのSQL中心のツールのいずれかを使用します( MyBatis 、 jOOQ 、 DbUtils )。つまり、OOドメインモデルとオブジェクトリレーショナルマッピング(つまり、エンティティと遅延読み込み)の観点から考える必要はありません。SQLはリレーショナルデータに関するものであり、RBDMSはいくつかの結合の結果を「熱心にフェッチ」します。通常、時期尚早のキャッシングはあまり必要ありません。また、時々データ要素をキャッシュする必要がある場合は、 EhCache のようなものを使用できます。 =
これらのSQL中心のツールを使用せず、Hibernate/JPAに固執します。 Hibernateが好きではないと言っても、「Hibernateを考えている」からです。 Hibernateは、オブジェクトグラフをデータベースに永続化するのに非常に優れています。これらのツールはいずれも、Hibernateのように動作することを強制できません。なぜなら、それらの使命は別のものだからです。彼らの使命は、SQLを操作することです。
まったく異なる方法で、リレーショナルデータモデルを使用しないことを選択します。他のデータモデル(たとえばグラフ)が適している場合があります。あなたが実際にその選択肢を持たないかもしれないので、私はこれを3番目のオプションとして入れています、そして、私は代替モデルであまり個人的な経験を持っていません。
ご質問は、jOOQに関するものではありません。それでも、jOOQを使用すると、 ModelMapper などの外部ツールを使用して、フラットなクエリ結果(結合されたテーブルソースから生成)のオブジェクトグラフへのマッピングを外部化できます。 ModelMapper User Group には、こうした統合に関する興味深い進行中のスレッドがあります。
(免責事項:私はjOOQの背後にある会社で働いています)
永続化アプローチ
シンプル/ベーシックから洗練/リッチまでのソリューションの範囲は次のとおりです。
最初の2つのレベルのいずれかを実装しようとします。つまり、フォーカスをオブジェクトモデルからSQLに移します。しかし、あなたの質問は、SQLにマッピングされているオブジェクトモデル(つまり、ORMの動作)を含むユースケースを求めています。最初の2つのレベルのいずれかの機能に対して、3番目のレベルの機能を追加したい場合。
この動作をActive Record内に実装してみることができます。ただし、これには、各アクティブレコードインスタンス(実際のエンティティ、他のエンティティとの関係、遅延読み込み設定、カスケード更新設定)に添付する豊富なメタデータが必要になります。これにより、非表示のマップされたエンティティオブジェクトになります。また、jOOQとMyBatisは、ユースケース1と2に対してこれを行いません。
リクエストを達成する方法は?
フレームワークまたは生のSQL/JDBCの上にある小さなカスタムレイヤーとして、狭いORM動作をオブジェクトに直接実装します。
ユースケース1:各エンティティオブジェクト関係のメタデータを保存します。(i)関係を遅延ロードする必要があるか(クラスレベル)、(ii)遅延ロードが発生したか(オブジェクトレベル)。次に、ゲッターメソッドでこれらのフラグを使用して、遅延読み込みを実行して実際に実行するかどうかを決定します。
ユースケース2:ユースケース1と同様-自分で実行します。各エンティティ内にダーティフラグを保存します。各エンティティオブジェクトの関係に対して、保存をカスケードする必要があるかどうかを示すフラグを保存します。次に、エンティティが保存されたら、再帰的に各「カスケードの保存」関係にアクセスします。発見された汚れたエンティティを書き込みます。
パターン
長所
短所
価値がある???
このソリューションは、データ処理の要件がかなり単純で、エンティティの数が少ないデータモデルの場合にうまく機能します。
過去10年間、私はJDBC、EJBエンティティBean、Hibernate、GORM、そして最後にJPAをこの順序で使用していました。現在のプロジェクトでは、パフォーマンスに重点を置いているため、プレーンJDBCの使用に戻りました。したがって、私は欲しかった
データモデルはデータディクショナリで定義されます。モデル駆動型のアプローチを使用すると、ジェネレーターはヘルパークラス、DDLスクリプトなどを作成します。データベースに対するほとんどの操作は読み取り専用です。少数のユースケースのみが記述されています。
質問1:子の取得
システムはユースケースに基づいて構築されており、特定のユースケース/リクエストのすべてのデータを取得するための1つの専用SQLステートメントがあります。 SQLの一部のステートメントは20kbを超え、結合し、Java/Scalaで記述されたストアド関数を使用して計算し、並べ替え、ページ分割などを行い、結果がデータ転送オブジェクトに直接マッピングされ、ビューにフィードされます。 (アプリケーション層でそれ以上の処理はありません)。結果として、データ転送オブジェクトもユースケース固有です。指定されたユースケースのデータのみが含まれます(それ以上、それ以下)。
結果セットは既に「完全に結合」されているため、レイジー/イーガーフェッチなどの必要はありません。データ転送オブジェクトは完成です。キャッシュは必要ありません(データベースキャッシュは問題ありません)。例外:結果セットが大きい場合(約50 000行)、データ転送オブジェクトがキャッシュ値として使用されます。
質問2:保存
コントローラーがGUIからの変更をマージして戻すと、データを保持する特定のオブジェクトが再びあります。基本的には、階層内の状態(新規、削除、変更など)を持つ行です。データを階層に保存するのは手動の反復です。新規または変更されたデータは、SQL insert
またはupdate
コマンドを生成するヘルパークラスを使用して保持されます。削除されたアイテムに関しては、これはカスケード削除(PostgreSql)に最適化されています。複数の行を削除する場合、これは単一のdelete ... where id in ...
ステートメントにも最適化されます。
繰り返しますが、これはユースケース固有であるため、一般的なアプローチを扱っていません。より多くのコード行が必要ですが、これらは最適化を含む行です。
これまでの経験
関連情報:
更新:インターフェイス
論理データモデルにPerson
エンティティがある場合、JPAエンティティと同様に、Person
エンティティ(CRUD操作用)を永続化するクラスがあります。
ただし、ユースケース/クエリ/ビジネスロジックの観点から見ると、単一のPerson
エンティティは存在しないということです:各サービスメソッドは、Person
のown概念を定義し、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()
}
ユースケース固有のPersons
、Adresses
などの使用:この場合、Person
はすべての関連データをすでに集約しています。より多くのクラスが必要ですが、JPAエンティティを渡す必要はありません。
この処理は読み取り専用であり、結果はビューによって使用されることに注意してください。例:個人のリストから個別のCity
インスタンスを取得します。
データが変更された場合、これは別のユースケースです。人の都市が変更された場合、これは別のサービスメソッドによって処理され、その人はデータベースから再度取得されます。
警告:これは、プロジェクト作成者によるもう一つの恥知らずなプラグです。
JSimpleDB をチェックして、シンプルさとパワーの基準を満たしているかどうかを確認してください。これは、SQLおよびORMを介したJava永続性に対処しようとする10年以上のフラストレーションから生まれた新しいプロジェクトです。
これは、キー/値ストアとして機能できるデータベース、たとえば FoundationDB (またはすべてが詰まっているSQLデータベース)の上で動作します。単一のキー/値テーブル)。
問題の中心的な問題は、リレーショナルモデル自体にあるように思えます。これまで説明してきたことから、グラフデータベースは問題領域を非常にきれいにマッピングします。別の方法として、ドキュメントストアは問題にアプローチする別の方法です。これは、インピーダンスがまだ存在している間、ドキュメントは一般的にセットよりも推論が簡単だからです。もちろん、どのアプローチにも注意すべき独自の癖があります。
シンプルで軽量なライブラリが必要で、SQLを使用するため、 fjorm をご覧になることをお勧めします。これにより、手間をかけずにPOJOとCRUD操作を使用できます。
免責事項:私はプロジェクトの著者です。