読者です 読者をやめる 読者になる 読者になる

GORMでの継承とデータベーススキーマ

GORMでの継承によってデータベース上でどのようにスキーマ定義するかというお話です。

Grailsのデフォルトの継承戦略はtable-per-hierarchyになっています。例えば次のようなドメインがあったとします。

class Parent {
    String name
}
class Sub1 extends Parent {
    String sub1Value
}
class Sub2 extends Parent {
    String sub2Value
}

というクラス構成でschema-exportすると以下のようになります。

    create table parent (
        id bigint generated by default as identity,
        version bigint not null,
        name varchar(255) not null,
        class varchar(255) not null,
        sub2value varchar(255),
        sub1value varchar(255),
        primary key (id)
    );

データベース上ではすべて同一のテーブルにプロパティを突っ込む形になって、同一テーブルに突っ込んでる手前サブクラスのプロパティにNotNullを設定できなくなるのがデメリットです。またclassというカラムがデータベース上に作成されて、この情報を元にどちらのドメインの情報かを区別しています。

こんどはtablePerHierarchyをfalseにしてみます。

class Parent {

    String name

    static mapping = {
        tablePerHierarchy false
    }
    
}

こんどは以下のようなスキーマになります。

    create table parent (
        id bigint generated by default as identity,
        version bigint not null,
        name varchar(255) not null,
        primary key (id)
    );

    create table sub1 (
        id bigint not null,
        sub1value varchar(255) not null,
        primary key (id)
    );

    create table sub2 (
        id bigint not null,
        sub2value varchar(255) not null,
        primary key (id)
    );

今度はドメインクラスに1対1で対応付く形でテーブルが定義されます。先ほどとの違いは、NotNullが設定できるが、検索時にJOINするためパフォーマンスに難点があるといったとこでしょうか。

最後にもう一つParentをドメインディレクトリから普通のsrcディレクトリに移動し、abstractとする方法です。

abstract class Parent {

    String name

}

この場合は以下のようになります。

    create table sub1 (
        id bigint generated by default as identity,
        version bigint not null,
        name varchar(255) not null,
        sub1value varchar(255) not null,
        primary key (id)
    );

    create table sub2 (
        id bigint generated by default as identity,
        version bigint not null,
        name varchar(255) not null,
        sub2value varchar(255) not null,
        primary key (id)
    );

完全に別テーブになって、親テーブルを元に検索出来ない(Parent.findBy...)、DRYじゃないけど、互いに疎である必要があるは必然的にこれになります。