休止状態の継承:戦略の選択

継承は、OOPの主要な原則の1つです。 同時に、かなりの数のエンタープライズアプリケーションがリレーショナルデータベースに基づいています。



オブジェクト指向モデルとリレーショナルモデルの主な矛盾は、オブジェクトモデルが2種類のリレーション(「is a」-「is」および「has a」-「has」)をサポートし、SQLベースのモデルがリレーションのみをサポートすることです。 「持っている」。



つまり、 SQLは型の継承を理解せず、サポートしません



したがって、エンティティとデータベーススキーマを構築する段階で、開発者の主なタスクの1つは、継承階層を表すための最適な戦略を選択することです。



次の4つの戦略があります。



1)クラスごとに1つのテーブルを使用し、デフォルトのポリモーフィック動作を使用します。



2)特定のクラスごとに1つのテーブル。SQLスキーマからポリモーフィズムと継承関係を完全に除外します(実行時のポリモーフィックな動作には、UNIONクエリが使用されます)



3)クラスの階層全体の単一のテーブル。 SQLスキーマの非正規化によってのみ可能です。 文字列を区別することにより、スーパークラスとサブクラスを定義することができます。



4)サブクラスごとに1つのテーブル。リレーション「is a」は「has a」として表されます。 -JOINを使用した外部キ​​ー通信。



選択した戦略によって影響を受ける主な要因は3つあります。



1)パフォーマンス(「hibernate_show_sql」を使用して、データベースに対するすべてのクエリを表示および評価します)



2)スキームの正規化とデータの整合性の保証(すべての戦略がNOT NULL制約への準拠を保証するわけではありません)



3)回路を進化させる能力



カットの下で、これらの戦略のそれぞれが詳細に検討され、利点と欠点を示し、特定のケースでの戦略の選択に関する推奨事項が示されます。



作者からのいくつかの言葉と例の操作手順
この記事は、「Java Persistance with Hibernate」という本からの抜粋です。 著者-HibernateプロジェクトGa​​vin King(Gavin King)の創設者であり、Hibernate開発チームChristian Bauer(Christian Bauer)のメンバー。

2017年の夏に、ロシア語で翻訳および公開されました。



素材のプレゼンテーションを簡素化するとともに、例を使って作業を試みました。 開始するのに1時間混乱する必要がある例に強い嫌悪感を覚えたので、この記事ではできる限り便利に作業できるようにしました。

-IDEにコピーできるすべてのJavaコード。 ある戦略から別の戦略に移行する際のJavaコードへのすべての変更はネタバレで示されるため、新しい戦略に切り替えると、古いクラスコードを簡単に削除して、新しいクラスコードをコピーできます。 MainクラスとHibernateUtilクラスは変更されずに残り、すべての例を検討するときに機能します。



-各戦略のネタバレには、すべてのデータベーステーブルを作成するためのスクリプトもあります。 したがって、次の戦略を検討した後、すべてのテーブルを削除できます。次のセクションでは、新しいテーブルを作成するための実際のスクリプトを見つけます。



Java 1.7、Hibernate5およびPostgreSQL9を使用して記述されたコード



読書をお楽しみください!



戦略1



クラスごとに1つのテーブル



状況:



eBayの栄光を覆すことにし、この目的のためにオンラインオークションアプリケーションを作成しています。 各ユーザーは賭けをすることができ、彼のレートが最大の場合はオンラインで支払いを行います。



実際には、支払いプロセスをデータモデルと見なします。

ユーザーは、銀行カードを使用する方法と銀行口座の詳細を使用する方法の2つの方法で支払いを行うことができます。



クラス図を以下に示します。



画像



サンプルを実行するJavaコード
pom.xml:



<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.hiber.jd2050</groupId> <artifactId>hiberLearn</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.7</java.version> </properties> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <target>${java.version}</target> </configuration> </plugin> </plugins> </build> <dependencies> <!-- PostgreSQL --> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.0-801.jdbc4</version> </dependency> <!-- Hibernate-JPA-2.1-API --> <dependency> <groupId>org.hibernate.javax.persistence</groupId> <artifactId>hibernate-jpa-2.1-api</artifactId> <version>1.0.0.Final</version> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> <version>1.1</version> </dependency> <!-- Hibernate-core --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.0.5.Final</version> </dependency> </dependencies> </project>
      
      





hibernate.cfg.xml



 <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="connection.url">jdbc:postgresql://localhost:5432/dobrynin_db</property> <!-- BD Mane --> <property name="connection.driver_class">org.postgresql.Driver</property> <!-- DB Driver --> <property name="connection.username">postgres</property> <!-- DB User --> <property name="connection.password">filyaSl9999</property> <!-- DB Password --> <property name="dialect">org.hibernate.dialect.PostgreSQL9Dialect</property> <!-- DB Dialect --> <property name="hbm2ddl.auto">create-drop</property> <!-- create / create-drop / update --> <property name="show_sql">true</property> <!-- Show SQL in console --> <property name="format_sql">true</property> <!-- Show SQL formatted --> <property name="hibernate.current_session_context_class">thread</property> <mapping class="CreditCard"/> <mapping class="BankAccount"/> <mapping class="BillingDetails"/> </session-factory> </hibernate-configuration>
      
      





 import javax.persistence.*; @MappedSuperclass public abstract class BillingDetails { private String owner; public BillingDetails() { } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "owner='" + owner + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @Table(name = "CREDIT_CARD") public class CreditCard extends BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @Table(name = "BANK_ACCOUNT") public class BankAccount extends BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } }
      
      





 import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { public static SessionFactory getSessionFactory() { return new Configuration().configure().buildSessionFactory(); } }
      
      





main()メソッドを持つメインクラス:



 import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import java.util.*; public class Main { public static void main(String[] args) throws Exception { CreditCard creditCard = new CreditCard(); creditCard.setCardNumber(44411111); creditCard.setExpMonth("Jan"); creditCard.setExpYear("2017"); creditCard.setOwner("Bill Gates"); BankAccount bankAccount = new BankAccount(); bankAccount.setAccount(111222333); bankAccount.setBankName("Goldman Sachs"); bankAccount.setSwift("GOLDUS33"); bankAccount.setOwner("Donald Trump"); SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); Session session; Transaction transaction = null; try { session = sessionFactory.getCurrentSession(); transaction = session.beginTransaction(); session.persist(creditCard); session.persist(bankAccount); transaction.commit(); } catch (Exception e) { transaction.rollback(); throw e; } Session session1; Transaction transaction1 = null; try { session1 = sessionFactory.getCurrentSession(); transaction1 = session1.beginTransaction(); List billingDetails = session1.createQuery("select bd from BillingDetails bd").list(); for (int i = 0; i < billingDetails.size(); i++) { System.out.println(billingDetails.get(i)); } } catch (Exception e) { transaction1.rollback(); throw e; } } }
      
      







BankAccountクラスとCreditCardクラスは、BillingDetailsの共通の抽象祖先を継承します。 図からわかるように、同様の機能にもかかわらず、それらの状態は大きく異なります。カードについては、番号と有効性が重要であり、銀行口座については詳細フィールドが重要です。



親クラスには、すべての子孫に共通の所有者に関する情報のみが格納されます。

さらに、たとえば、Idフィールドを生成の種類と共に取り出すことができます(この場合は、なしで行いました)。



最初の戦略のデータベースのスキーマは次のようになります。

画像



テーブル作成のクエリ:



CREDIT_CARD
 create table credit_card ( id serial not null constraint bank_account_pkey primary key, cc_owner varchar(20) not null, card_number integer not null, exp_month varchar(9) not null, exp_year varchar(4) not null ) ;
      
      







BANK_ACCOUNT
 create table bank_account ( id serial not null primary key, owner varchar(20), account integer not null, bank_name varchar(20) not null, swift varchar(20) not null ) ;
      
      







この場合の多態性は暗黙的です。 Entityアノテーションで反映できる各子クラス。



重要! スーパークラスのプロパティはデフォルトでは無視されます。 それらを特定のサブクラスのテーブルに保存するには、@ MappedSuperClassアノテーションを使用する必要があります。



サブクラスの表示は珍しいことではありません。 注意が必要なのは、@ AttributeOverrideアノテーションだけです。これはおそらくなじみのないものです。

祖先テーブルと子孫テーブルの名前が一致しない場合、サブクラステーブルの列の名前を変更するために使用されます(この場合、BillingDetailsの「所有者」はCREDIT_CARDテーブルのCC_OWNERにマップされます)。



この戦略を使用する場合の主な問題は、ポリモーフィックな関連付けを最大限に使用することができないことです。通常、それらは外部キーアクセスとしてデータベースに提示され、BILLING_DETAILSテーブルはありません。 また、アプリケーション内の各BillingDetailsオブジェクトは特定のUserオブジェクトに関連付けられるため、子孫テーブルのそれぞれにはUSERSテーブルを参照する外部キーが必要です。



さらに、ポリモーフィッククエリも問題になります。



リクエストを満たすようにしましょう



 SELECT bd FROM BillingDetails bd
      
      





これを行うには(以降)、main()メソッドを実行します。



この場合、次のように実行されます。



 Hibernate: select bankaccoun0_.id as id1_1_, bankaccoun0_.owner as owner2_1_, bankaccoun0_.account as account3_1_, bankaccoun0_.bank_name as bank_nam4_1_, bankaccoun0_.swift as swift5_1_ from BANK_ACCOUNT bankaccoun0_ Hibernate: select creditcard0_.id as id1_2_, creditcard0_.owner as owner2_2_, creditcard0_.card_number as card_num3_2_, creditcard0_.exp_month as exp_mont4_2_, creditcard0_.exp_year as exp_year5_2_ from CREDIT_CARD creditcard0_
      
      





つまり、特定のサブクラスごとに、Hibernateは個別のSELECTクエリを使用します。



この戦略を使用する際の別の重要な問題は、リファクタリングの複雑さです。 スーパークラスのフィールドの名前を変更すると、多くのテーブルの名前を変更する必要があり、手動で名前を変更する必要があります(ほとんどのIDEツールは@AttributeOverrideを考慮しません)。 スキームにテーブルが2つではなく50ある場合、これには多くの時間がかかります。



このアプローチは、クラス階層の最上部でのみ使用できます。



a)ポリモーフィズムは必要ありません(Hibernateの特定のサブクラスのサンプリングは1回のリクエストで実行されます->パフォーマンスが高くなります)



b)スーパークラスでの変更は予見されません。



リクエストがBillingDetailsの親クラスを参照するアプリケーションの場合、この戦略は機能しません。



戦略2



結合(UNION)を持つクラスごとに1つのテーブル



抽象クラスは再びBillingDetailsになります。

データベーススキーマも変更されません。



唯一のことは、CREDIT_CARDテーブルのCC_OWNERフィールドの名前をOWNERに変更する必要があることです。この戦略は@AttributeOverrideをサポートしていないためです。 ドキュメントから:

「このアプローチの制限は、プロパティがスーパークラスにマップされる場合、列名はすべてのサブクラステーブルで同じでなければならないことです。」



示されたTABLE_PER_CLASS戦略を持つスーパークラスの上に示されたアノテーション@Inheritanceも新しくなります。



重要! この戦略の枠組みの中で、スーパークラスに識別子が存在することは必須の要件です (最初の例では、識別子なしで行いました)。



重要! JPA標準によれば、TABLE_PER_CLASS戦略はオプションであるため、他の実装はサポートされない場合があります。



変更されたJavaコード
 import javax.persistence.*; @Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @Table(name = "CREDIT_CARD") public class CreditCard extends BillingDetails { @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @Table(name = "BANK_ACCOUNT") public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } }
      
      







SQLスキーマは継承について何も知りません。 テーブル間に関係はありません。



この戦略の主な利点は、前の例のポリモーフィッククエリを実行することで確認できます。



 SELECT bd FROM BillingDetails bd
      
      





今回は異なる方法で実行されます。



 Hibernate: select billingdet0_.id as id1_1_, billingdet0_.owner as owner2_1_, billingdet0_.card_number as card_num1_2_, billingdet0_.exp_month as exp_mont2_2_, billingdet0_.exp_year as exp_year3_2_, billingdet0_.account as account1_0_, billingdet0_.bank_name as bank_nam2_0_, billingdet0_.swift as swift3_0_, billingdet0_.clazz_ as clazz_ from ( select id, owner, card_number, exp_month, exp_year, null::int4 as account, null::varchar as bank_name, null::varchar as swift, 1 as clazz_ from CREDIT_CARD union all select id, owner, null::int4 as card_number, null::varchar as exp_month, null::varchar as exp_year, account, bank_name, swift, 2 as clazz_ from BANK_ACCOUNT ) billingdet0_
      
      





この場合、HibernateはFROMを使用して、すべてのサブクラステーブルからBillingDetailsのすべてのインスタンスを取得します。 テーブルはUNIONを使用して結合され、リテラル(1と2)が中間結果に追加されます。 Hibernateはリテラルを使用して、正しいクラスをインスタンス化します。



テーブルを結合するには同じ列構造が必要であるため、存在しない列の代わりにNULLが挿入されました(たとえば、credit_cardの "null :: varchar as bank_name"-クレジットカードテーブルに銀行名がありません)。



最初の戦略に対するもう1つの重要な利点は、ポリモーフィックな関連付けを使用できることです。 これで、UserクラスとBillingDetailsクラスの間の関連付けを簡単にマップできます。



戦略3



クラス階層全体の単一のテーブル



クラス階層は、1つのテーブルで完全に選択できます。 階層内の各クラスのすべてのフィールドの列が含まれます。 各レコードについて、特定のサブクラスは、 セレクターの追加列の値によって決定されます。



ダイアグラムは次のようになります。

画像



作成のリクエスト
 create table billing_details ( id serial not null constraint billing_details_pkey primary key, bd_type varchar(2), owner varchar(20), card_number integer, exp_month varchar(9), exp_year varchar(4), account integer, bank_name varchar(20), swift varchar(20) ) ; create unique index billing_details_card_number_uindex on billing_details (card_number) ;
      
      







Javaクラスの構造:



画像



単一のテーブルマッピングを作成するには、継承戦略SINGLE_TABLEを使用する必要があります。

ルートクラスはテーブルBILLING_DETAILSにマップされます。 タイプを区別するために、セレクター列が使用されます。 これはエンティティフィールドではなく、Hibernate専用に作成されました。 その値は文字列-「CC」または「BA」になります。

重要! スーパークラスでセレクター列を明示的に指定しない場合、デフォルト名のDTYPEとタイプVARCHARが取得されます。



各階層クラスは、@ DiscriminatorValueアノテーションを使用してセレクター値を示すことができます。

セレクターの明示的な名前を無視しないでください。デフォルトでは、Hibernateは完全修飾クラス名またはエンティティ名を使用します(XML-HibernateファイルまたはJPA /アノテーションxmlファイルが使用されるかどうかによって異なります)。



変更されたJavaコード
 import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "BD_TYPE") public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @DiscriminatorValue("BA") public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @DiscriminatorValue("CC") public class CreditCard extends BillingDetails { @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } }
      
      







検証のために、mainメソッドでおなじみのリクエストを使用します



 SELECT bd FROM BillingDetails bd
      
      





単一のテーブルの場合、このクエリは次のように実行されます。



 Hibernate: select billingdet0_.id as id2_0_, billingdet0_.owner as owner3_0_, billingdet0_.card_number as card_num4_0_, billingdet0_.exp_month as exp_mont5_0_, billingdet0_.exp_year as exp_year6_0_, billingdet0_.account as account7_0_, billingdet0_.bank_name as bank_nam8_0_, billingdet0_.swift as swift9_0_, billingdet0_.BD_TYPE as BD_TYPE1_0_ from BILLING_DETAILS billingdet0_
      
      





要求が特定のサブクラスに対して実行される場合、「where BD_TYPE =“ CC”」という行が単に追加されます。



単一のテーブルへのマッピングは次のようになります。

画像



スキームが継承され、セレクター列を追加することが不可能な場合、@ DiscriminatorFormulaアノテーションが役立ちます。これは親クラスに追加する必要があります。 式CASE ... WHENをそれに渡す必要があります。



 import org.hibernate.annotations.DiscriminatorFormula; import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorFormula("CASE WHEN CARD_NUMBER IS NOT NULL THEN 'CC' ELSE 'BA' END") public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; //................. }
      
      





この戦略の主な利点はパフォーマンスです。 クエリ(多態性および非多態性の両方)は非常に高速で、手動で簡単に記述できます。 接続とユニオンを使用する必要はありません。 回路の進化も非常に簡単です。



ただし、この戦略を取り巻く問題は多くの場合、その利点を上回ります。



主なものはデータの整合性です。 サブクラスで宣言されているプロパティの列には、NULLが含まれている場合があります。 その結果、単純なソフトウェアエラーにより、データベースに番号や有効期限のないクレジットカードが作成される可能性があります。



別の問題は、正規化、具体的には3番目の正規形の違反です。 この観点から、生産性の向上の利点はすでに疑わしいように見えます。 結局のところ、少なくとも護衛の利便性を犠牲にする必要があります。長期的には、非正規化されたスキームはうまく機能しません。



戦略4



結合を使用するクラスごとに1つのテーブル(JOIN)



クラスの概要は変更されません。



画像



しかし、データベーススキーマにはいくつかの変更があります。



画像



BILLING_DETAILSの作成リクエスト
 create table billing_details ( id integer not null constraint billing_details_pkey primary key, owner varchar(20) not null ) ;
      
      







CREDIT_CARDの場合
 create table credit_card ( id integer not null constraint credit_card_pkey primary key constraint credit_card_billing_details_id_fk references billing_details, card_number integer not null, exp_month varchar(255) not null, exp_year varchar(255) not null ) ; create unique index credit_card_card_number_uindex on credit_card (card_number) ;
      
      







BANK_ACCOUNTの場合
 create table bank_account ( id integer not null constraint bank_account_pkey primary key constraint bank_account_billing_details_id_fk references billing_details, account integer not null, bank_name varchar(255) not null, swift varchar(255) not null ) ; create unique index bank_account_account_uindex on bank_account (account) ;
      
      







Javaコードでは、JOINED戦略を使用してこのようなマッピングを作成する必要があります。



変更されたJavaコード
 import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.JOINED) public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @Table(name = "CREDIT_CARD") public class CreditCard extends BillingDetails { @Column(name = "card_number") private int cardNumber; @Column(name = "exp_month") private String expMonth; @Column (name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @Table(name = "BANK_ACCOUNT") public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } }
      
      







現在、たとえばCreditCardのインスタンスを保存するとき、Hibernateは2つのレコードを挿入します。 BillingDetailsスーパークラスのフィールドで宣言されたプロパティはBILLING_DETAILSテーブルに分類され、CreaditCardサブクラスのフィールドの値はCREDIT_CARDテーブルに書き込まれます。 これらのエントリは、共通の主キーによって結合されます。



したがって、回路は正常になりました。 回路の進化と整合性制約の決定も簡単です。

外部キーを使用すると、特定のサブクラスとのポリモーフィックな関連付けを表すことができます。



リクエストを完了することにより



 SELECT bd FROM BillingDetails bd
      
      





、次の図が表示されます。



 Hibernate: select billingdet0_.id as id1_1_, billingdet0_.owner as owner2_1_, billingdet0_1_.card_number as card_num1_2_, billingdet0_1_.exp_month as exp_mont2_2_, billingdet0_1_.exp_year as exp_year3_2_, billingdet0_2_.account as account1_0_, billingdet0_2_.bank_name as bank_nam2_0_, billingdet0_2_.swift as swift3_0_, case when billingdet0_1_.id is not null then 1 when billingdet0_2_.id is not null then 2 when billingdet0_.id is not null then 0 end as clazz_ from BILLING_DETAILS billingdet0_ left outer join CREDIT_CARD billingdet0_1_ on billingdet0_.id=billingdet0_1_.id left outer join BANK_ACCOUNT billingdet0_2_ on billingdet0_.id=billingdet0_2_.id
      
      





BILLING_DETAILS



画像



CREDIT_CARD



画像



BANK_ACCOUNT



画像



CASE ... WHEN句により、Hibernateは各レコードに特定のサブクラスを定義できます。 リテラルを使用して、サブクラスCREDIR_CARDおよびBANK_ACCOUNTのテーブルの行の有無をチェックします。



このような戦略を手動で実装することは非常に困難です。 任意のクエリに基づいてレポートを実装することでさえ、はるかに困難です。

クエリは複数のテーブルの結合または多くの順次読み取り操作を必要とするため、特定のプロジェクトではパフォーマンスが許容できない場合もあります。



継承マッピング戦略の混合



戦略TABLE_PER_CLASS、SINGLE_TABLE、およびJOINEDを使用する場合、重大な不便は、それらを切り替えることができないという事実です。 選択した戦略を最後まで順守する必要があります(またはスキームを完全に変更します)。

ただし、特定のサブクラスの表示戦略を切り替えることができるトリックがあります。



たとえば、クラス階層を単一のテーブル(戦略3)にマッピングすることで、個別のサブクラス(戦略4)の個別のテーブルと外部キーを持つ戦略を選択できます。



画像



画像



BILLING_DETAILSを作成するスクリプト
 create table billing_details ( id integer not null constraint billing_details_pkey primary key, owner varchar(20), account integer, bank_name varchar(20), swift varchar(20) ) ;
      
      







CREDIT_CARDの場合
 create table credit_card ( card_number integer not null, exp_month varchar(255) not null, exp_year varchar(255) not null, id integer not null constraint credit_card_pkey primary key constraint fksf645frtr6h3i4d179ff4ke9h references billing_details ) ;
      
      







これで、CreditCardサブクラスを別のテーブルにマッピングできます。

これを行うには、InheritanceType.SINGLE_TABLE戦略をBillingDetailsスーパークラスに適用する必要があります。@ SecondaryTableアノテーションは、CreditCardクラスの操作に役立ちます。



変更されたJavaコード
 import javax.persistence.*; @Entity @Table(name = "BILLING_DETAILS") @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name = "BD_TYPE") public abstract class BillingDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private int id; private String owner; public BillingDetails() { } public int getId() { return id; } public String getOwner() { return owner; } public void setOwner(String owner) { this.owner = owner; } @Override public String toString() { return "BillingDetails{" + "id=" + id + ", owner='" + owner + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity public class BankAccount extends BillingDetails { private int account; @Column(name = "bank_name") private String bankName; private String swift; public BankAccount() { } public int getAccount() { return account; } public void setAccount(int account) { this.account = account; } public String getBankName() { return bankName; } public void setBankName(String bankName) { this.bankName = bankName; } public String getSwift() { return swift; } public void setSwift(String swift) { this.swift = swift; } @Override public String toString() { return "BankAccount{" + "account=" + account + ", bankName='" + bankName + '\'' + ", swift='" + swift + '\'' + '}'; } }
      
      





 import javax.persistence.*; @Entity @DiscriminatorValue("CC") @SecondaryTable(name = "CREDIT_CARD", pkJoinColumns = @PrimaryKeyJoinColumn(name = "ID")) public class CreditCard extends BillingDetails { @Column(table = "CREDIT_CARD",name = "card_number") private int cardNumber; @Column(table = "CREDIT_CARD",name = "exp_month") private String expMonth; @Column (table = "CREDIT_CARD",name = "exp_year") private String expYear; public CreditCard() { } public int getCardNumber() { return cardNumber; } public String getExpMonth() { return expMonth; } public String getExpYear() { return expYear; } public void setCardNumber(int cardNumber) { this.cardNumber = cardNumber; } public void setExpMonth(String expMonth) { this.expMonth = expMonth; } public void setExpYear(String expYear) { this.expYear = expYear; } @Override public String toString() { return "CreditCard{" + "cardNumber=" + cardNumber + ", expMonth='" + expMonth + '\'' + ", expYear='" + expYear + '\'' + '}'; } }
      
      







アノテーション@SecondaryTableおよび@Columnを使用して、メインテーブルとその列を再定義し、データの取得元をHibernateに示します。



SINGLE_TABLE戦略を選択すると、サブクラス列にNULLが含まれる場合があります。この手法を使用すると、特定のサブクラス(この場合はCreditCard)のデータ整合性を保証できます。

ポリモーフィッククエリを実行することにより、Hibernateは外部結合を実行してBillingDetailsおよびそのすべてのサブクラスのインスタンスを取得します。



試してみましょう:



 SELECT bd FROM BillingDetails bd
      
      





結果:



 Hibernate: select billingdet0_.id as id2_0_, billingdet0_.owner as owner3_0_, billingdet0_.account as account4_0_, billingdet0_.bank_name as bank_nam5_0_, billingdet0_.swift as swift6_0_, billingdet0_1_.card_number as card_num1_1_, billingdet0_1_.exp_month as exp_mont2_1_, billingdet0_1_.exp_year as exp_year3_1_, billingdet0_.BD_TYPE as BD_TYPE1_0_ from BILLING_DETAILS billingdet0_ left outer join CREDIT_CARD billingdet0_1_ on billingdet0_.id=billingdet0_1_.ID
      
      





画像



画像



この手法は、階層の他のクラスに適用できますが、広範な階層では、この場合の外部接続が問題になるため、あまりうまく機能しません。このような階層の場合、外部結合ではなく2番目のSQLクエリをすぐに実行する戦略が最適です。



戦略の選択



上記の戦略と手法にはそれぞれ長所と短所があります。特定の戦略を選択するための一般的な推奨事項は次のようになります



。-多態的なクエリと関連付けが不要な場合、戦略番号2(UNIONに基づくTABLE_PER_CLASS)。「BillingDetails bdからbdを選択する」ことがめったにない(またはまったく実行しない)場合、BillingDetailsを参照するクラスがない場合は、このオプションの方が適しています(最適化されたポリモーフィッククエリと関連付けを追加する機能が残るため)。



-戦略番号3(SINGLE_TABLE)を使用する必要があります。



a)単純なタスクのみ。正規化とNOT NULL制約が重要な状況では、戦略#4(参加)を推奨します。継承を完全に放棄して委任に置き換える 価値があるかどうかを考えることは理にかなっています

。b)ポリモーフィックなクエリと関連付け、および実行時の特定のクラスの動的定義が必要な場合。ただし、サブクラスで宣言される新しいフィールドは比較的少なく、スーパークラスとの主な違いは動作です。

さて、これに加えて、DBAとの真剣な会話があります。



-戦略#4(JOINED)は、多態的なクエリと関連付けが必要であるが、サブクラスが比較的多くの新しいフィールドを宣言する場合に適しています。



ここで言及する価値があります:JOINEDとTABLE_PER_CLASSの間の決定は、継承階層の幅と深さが接続のコスト(結果としてパフォーマンス)を受け入れられないようにする可能性があるため、実際のデータに対するクエリ実行の計画を評価する必要があります。



また、継承アノテーションをインターフェイスに適用できないことも考慮に入れる必要があります。



ご清聴ありがとうございました!



All Articles