EclipseLinkを使用してJPA2に テンポラルテーブル を実装する方法を知りたいです。時間的とは、有効期間を定義するテーブルを意味します。
私が直面している問題の1つは、参照テーブルの性質上、主キーに有効期間が含まれているため、参照テーブルが参照テーブル(一時テーブル)への外部キー制約を持つことができなくなったことです。
私が見つけた唯一のものは、これを処理する DAO Fusion と呼ばれるフレームワークです。
これは、データモデルとそのクラスの架空の例です。それは、時間的側面を扱う必要のない単純なモデルとして始まります。
最初のシナリオ:非時間モデル
データモデル:
チーム:
@Entity
public class Team implements Serializable {
private Long id;
private String name;
private Integer wins = 0;
private Integer losses = 0;
private Integer draws = 0;
private List<Player> players = new ArrayList<Player>();
public Team() {
}
public Team(String name) {
this.name = name;
}
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQTEAMID")
@SequenceGenerator(name="SEQTEAMID", sequenceName="SEQTEAMID", allocationSize=1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(unique=true, nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getWins() {
return wins;
}
public void setWins(Integer wins) {
this.wins = wins;
}
public Integer getLosses() {
return losses;
}
public void setLosses(Integer losses) {
this.losses = losses;
}
public Integer getDraws() {
return draws;
}
public void setDraws(Integer draws) {
this.draws = draws;
}
@OneToMany(mappedBy="team", cascade=CascadeType.ALL)
public List<Player> getPlayers() {
return players;
}
public void setPlayers(List<Player> players) {
this.players = players;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Team other = (Team) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
プレーヤー:
@Entity
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"team_id","number"})})
public class Player implements Serializable {
private Long id;
private Team team;
private Integer number;
private String name;
public Player() {
}
public Player(Team team, Integer number) {
this.team = team;
this.number = number;
}
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQPLAYERID")
@SequenceGenerator(name="SEQPLAYERID", sequenceName="SEQPLAYERID", allocationSize=1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@ManyToOne
@JoinColumn(nullable=false)
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
@Column(nullable=false)
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
@Column(unique=true, nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((number == null) ? 0 : number.hashCode());
result = prime * result + ((team == null) ? 0 : team.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Player other = (Player) obj;
if (number == null) {
if (other.number != null)
return false;
} else if (!number.equals(other.number))
return false;
if (team == null) {
if (other.team != null)
return false;
} else if (!team.equals(other.team))
return false;
return true;
}
}
テストクラス:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/META-INF/application-context-root.xml"})
@Transactional
public class TestingDao {
@PersistenceContext
private EntityManager entityManager;
private Team team;
@Before
public void setUp() {
team = new Team();
team.setName("The Goods");
team.setLosses(0);
team.setWins(0);
team.setDraws(0);
Player player = new Player();
player.setTeam(team);
player.setNumber(1);
player.setName("Alfredo");
team.getPlayers().add(player);
player = new Player();
player.setTeam(team);
player.setNumber(2);
player.setName("Jorge");
team.getPlayers().add(player);
entityManager.persist(team);
entityManager.flush();
}
@Test
public void testPersistence() {
String strQuery = "select t from Team t where t.name = :name";
TypedQuery<Team> query = entityManager.createQuery(strQuery, Team.class);
query.setParameter("name", team.getName());
Team persistedTeam = query.getSingleResult();
assertEquals(2, persistedTeam.getPlayers().size());
//Change the player number
Player p = null;
for (Player player : persistedTeam.getPlayers()) {
if (player.getName().equals("Alfredo")) {
p = player;
break;
}
}
p.setNumber(10);
}
}
ここで、特定の時点でのチームとプレーヤーの履歴を保持するように求められます。そのため、追跡するテーブルごとに期間を追加する必要があります。それでは、これらの時間列を追加しましょう。 Player
だけから始めます。
2番目のシナリオ:時間モデル
データモデル:
ご覧のとおり、主キーを削除して、日付(期間)を含む別のキーを定義する必要がありました。また、テーブル内で繰り返すことができるようになったため、一意の制約を削除する必要がありました。これで、テーブルに現在のエントリと履歴を含めることができます。
チームを一時的にする必要がある場合、状況はかなり醜くなります。この場合、Player
テーブルがTeam
に必要な外部キー制約を削除する必要があります。問題は、JavaおよびJPAでそれをどのようにモデル化するかです。
IDは代理キーであることに注意してください。ただし、代理キーには日付を含める必要があります。含まない場合、同じ「version」を複数保存することはできません。エンティティ(タイムライン中)。
私はこのトピックに非常に興味があります。私はこれらのパターンを使用するアプリケーションの開発に数年間取り組んでいます。このアイデアは、ドイツの卒業論文から生まれました。
「DAOFusion」フレームワークを知りませんでした。興味深い情報とリンクを提供してくれて、この情報を提供してくれてありがとう。特に パターンページ と アスペクトページ は素晴らしいです!
あなたの質問へ:いいえ、私は他のサイト、例またはフレームワークを指摘することはできません。 DAO Fusionフレームワークを使用するか、この機能を自分で実装する必要があります。本当に必要な機能の種類を区別する必要があります。 「DAOFusion」フレームワークの観点から言えば、「有効な時間的」と「記録的な時間的」の両方が必要ですか?変更がデータベースに適用されたときの時間的状態(通常は問題の監査に使用)、変更が実際に発生したときの有効な時間的状態、または実際の生活で有効なときの有効な時間的状態(アプリケーションで使用)を記録します。ほとんどの場合、1つの次元で十分であり、2番目の次元は必要ありません。
とにかく、一時的な機能はデータベースに影響を与えます。あなたが述べたように: "現在、主キーには有効期間が含まれています"。では、エンティティのIDをどのようにモデル化しますか? 代理キー の使用を好みます。その場合、これは次のことを意味します。
テーブルの主キーはオブジェクトIDです。各エンティティには、オブジェクトIDで識別される1つ以上(1-n)のエントリがテーブルにあります。テーブル間のリンクは、エンティティIDに基づいています。時間エントリはデータ量を乗算するため、標準の関係は機能しません。標準の1-n関係は、x * 1-y * n関係になる場合があります。
これをどのように解決しますか?標準的なアプローチはマッピングテーブルを導入することですが、これは自然なアプローチではありません。 1つのテーブルを編集するためだけに(たとえば、住居の変更が発生した場合)、すべてのプログラマーにとって奇妙なマッピングテーブルを更新/挿入する必要があります。
もう1つのアプローチは、マッピングテーブルを使用しないことです。この場合、参照整合性と外部キーを使用できません。各テーブルは分離されて動作します。あるテーブルから別のテーブルへのリンクは、JPA機能ではなく手動で実装する必要があります。
データベースオブジェクトを初期化する機能は、(DAO Fusionフレームワークのように)オブジェクト内にある必要があります。私はそれをサービスに入れません。それをDAOに渡すか、アクティブレコードパターンを使用するかはあなた次第です。
私の答えは「すぐに使える」フレームワークを提供していないことを認識しています。あなたは非常に複雑な領域にいます。私の経験リソースからこの使用シナリオまで、見つけるのは非常に困難です。ご質問ありがとうございます!しかしとにかく、私はあなたのデザインであなたを助けたことを願っています。
この回答には、リファレンスブック「SQLでの時間指向データベースアプリケーションの開発」があります。 https://stackoverflow.com/a/800516/734687 を参照してください。
更新:例
上記の仮定(2テーブル、1-n)で実装された 例 を拡張するには:
データベース内のすべてのエントリを表示するクエリ(すべての有効性情報とレコード-別名テクニカル-情報が含まれています):
SELECT * FROM Person p, Residence r
WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON // JOIN
レコードを非表示にするクエリ(別名テクニカル情報)。これは、エンティティのすべての有効な変更を示しています。
SELECT * FROM Person p, Residence r
WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND
p.recordTo=[infinity] and r.recordTo=[infinity] // only current technical state
実際の値を表示するクエリ。
SELECT * FROM Person p, Residence r
WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND
p.recordTo=[infinity] and r.recordTo=[infinity] AND
p.validFrom <= [now] AND p.validTo > [now] AND // only current valid state person
r.validFrom <= [now] AND r.validTo > [now] // only current valid state residence
ご覧のとおり、ROW_IDは使用していません。 [now]をタイムスタンプに置き換えて、過去にさかのぼります。
更新を反映するように更新します
次のデータモデルをお勧めします。
「PlaysInTeam」テーブルを導入します。
チームのプレーヤーを一覧表示するときは、関係が有効であり、[ValdFrom、ValidTo)に含まれている必要がある日付を照会する必要があります。
チームを一時的にするために、私には2つのアプローチがあります。
アプローチ1:シーズンの有効性をモデル化する「シーズン」テーブルを導入する
チームテーブルを分割します。チームに属し、時間に関連しないフィールド(名前、住所など)と、シーズンに関連するフィールド(勝ち、負け、..)があります。その場合、TeamとTeamInSeasonを使用します。 PlaysInTeamは、TeamではなくTeamInSeasonにリンクできます(考慮する必要があります-Teamを指すようにします)
TeamInSeason
アプローチ2:季節を明示的にモデル化しないでください。チームテーブルを分割します。チームに属し、時間に関連しないフィールド(名前、住所、...)と時間に関連するフィールド(勝ち、負け、..)があります。その場合、TeamとTeamIntervalを使用します。 TeamIntervalには、間隔の「from」フィールドと「to」フィールドがあります。 PlaysInTeamは、TeamではなくTeamIntervalにリンクできます(Teamにリンクさせます)
TeamInterval
どちらのアプローチでも、時間に関連するフィールドがないために個別のチームテーブルが必要ない場合は、分割しないでください。
意味は正確にはわかりませんが、EclipseLinkは履歴を完全にサポートしています。 @DescriptorCustomizerを使用して、ClassDescriptorで HistoryPolicy を有効にできます。
DAO Fusion では、両方のタイムライン(有効性とレコード間隔)でエンティティを追跡することは、そのエンティティをBitemporalWrapper
でラップすることによって実現されます。
両耳側性参照ドキュメント は、通常のOrder
エンティティがBitemporalOrder
エンティティによってラップされている例を示しています。 BitemporalOrder
は、有効性とレコード間隔の列、および各テーブル行のOrder
への外部キー参照(@ManyToOne
経由)を備えた個別のデータベーステーブルにマップされます。
ドキュメントには、各両耳側性半盲(例:BitemporalOrder
)が両耳側性半盲内の1つのアイテムを表すことも示されています。したがって、両耳側性半盲のコレクションを含む高レベルのエンティティが必要です。 @OneToMany Collection<BitemporalOrder> orders
を含むCustomer
エンティティ。
したがって、「論理的な子」エンティティ(たとえば、Order
またはPlayer
)を両耳側性追跡する必要がある場合、およびその「論理的な親」エンティティ(たとえば、Customer
またはTeam
)もバイテンポラルに追跡するには、両方にバイテンポラルラッパーを提供する必要があります。 BitemporalPlayer
とBitemporalTeam
があります。 BitemporalTeam
は@OneToMany Collection<BitemporalPlayer> players
を宣言できます。ただし、前述のように、@OneToMany Collection<BitemporalTeam> teams
を含めるには上位レベルのエンティティが必要です。たとえば、Game
コレクションを含むBitemporalTeam
エンティティを作成できます。
ただし、レコード間隔が不要で、有効期間だけが必要な場合(たとえば、両耳側ではなく、エンティティの単時間追跡)、最善の策は、独自のカスタム実装をロールすることです。
テーブル名とスキーマ全体が静的であると想定しているため、JPAでは実行できないようです。
最良のオプションは、JDBCを介してそれを行うことです(たとえば、DAOパターンを使用して)
パフォーマンスが問題である場合、数千万のレコードについて話しているのでない限り、クラスを動的に作成してコンパイルしてからロードする方が良いとは思えません。
別のオプションは、ビューを使用することです(JPAを使用する必要がある場合)、何らかの方法でテーブルを抽象化する(@Entity(name = "myView"をマップする)場合は、CREATE OR REPLACE VIEW usernameView AS SELECT * FROM prefix_sessionId
たとえば、次のように1つのビューを記述できます。
if (EVENT_TYPE = 'crear_tabla' AND ObjectType = 'tabla ' && ObjectName starts with 'userName')
then CREATE OR REPLACE VIEW userNameView AS SELECT * FROM ObjectName //the generated table.