ActiveObjectsでCompositeパターンを実装できない理由


で、調子に乗っていろいろやっていると、Compositeパターンで嵌る。



いろいろ解決法を探していると。

Polymorphicの実験(3) ==失敗== @Polymorphicと@OneToManyを組み合わせた再帰的構造 - 何かしらの言語による記述を解析する日記

やはり、無理なのか?

ActiveObjectsでのCompositeパターンの実装はたぶんできません。
今回は、Compositeパターンを実装できない理由について調べてみました。

関連を持つエンティティには、インデックスが作られる

まず、他のエンティティへの関連を持つエンティティについて考えます。このようなエンティティをマイグレーションすると、ActiveObjectsはエンティティテーブル間に外部制約とインデックスを作成します。

外部制約は関連元のエンティティテーブルから、関連先のエンティティテーブルに対して作成されます。また、外部キーの値から関連元のエンティティテーブルを検索するインデックスが作成されます。

例えば、以下のエンティティComponentは他のエンティティCompositへの関連を持ちます。

interface Component extends Entity {
	Composite getParent();
	void setParent(Composite parent);
}
interface Composite extends Entity {
}

このエンティティをマイグレーションすると、以下のようなDDLが発行されます。

// compositeテーブルを作成する。
CREATE TABLE composite (
    id INTEGER AUTO_INCREMENT NOT NULL,
    PRIMARY KEY(id)
) ENGINE=InnoDB

// componentテーブルを作成する。
CREATE TABLE component (
    id INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(255),
    parentID INTEGER,
    // compositeテーブルへの外部制約
    CONSTRAINT fk_component_parentid FOREIGN KEY (parentID) REFERENCES composite(id),
    PRIMARY KEY(id)
) ENGINE=InnoDB

// 外部キーでcomponentテーブルを検索するためのインデックス
CREATE INDEX index_component_parentid ON component(parentID)

これにより、componentテーブルからcompositeテーブルへの外部制約が作成されます。また、componentテーブルのエンティティを外部キーparentIDで検索するインデックスが作成されます。

これらのテーブルの関連図は以下のようになります。

Polymophicを使った場合、関連エンティティへのインデックス作成がおかしい!!

さて、本題です。ComponentエンティティをPolymophicにし、CompositeエンティティにComponentを継承させます。

@Polymorphic
interface Component extends Entity {
	String getName();
	void setName(String name);

	Composite getParent();
	void setParent(Composite parent);
}

interface Composite extends Component {
}

このエンティティをマイグレーションすると、以下のようなDDLが発行されます。

// compositeテーブルを作成する。
CREATE TABLE composite (
    id INTEGER AUTO_INCREMENT NOT NULL,
    name VARCHAR(255),
    parentID INTEGER,
    CONSTRAINT fk_composite_parentid FOREIGN KEY (parentID) REFERENCES composite(id),
    PRIMARY KEY(id)
) ENGINE=InnoDB

// componentテーブルは作成されない。Polymorphicであるため!

// 外部キーでcompositeテーブルを検索するためのインデックスを作成する。
CREATE INDEX index_composite_parentid ON composite(parentID)

// 外部キーでcomponentテーブルを検索するためのインデックスを作成する????
CREATE INDEX index_component_parentid ON component(parentID)

// インデックス作成で、エラー発生
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'aotest.component' doesn't exist
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:532)
        at com.mysql.jdbc.Util.handleNewInstance(Util.java:353)
        at com.mysql.jdbc.Util.getInstance(Util.java:336)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1031)
        at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957)
        at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2938)
        at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1601)
        at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1710)
        at com.mysql.jdbc.Connection.execSQL(Connection.java:2430)
        at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1374)
        at com.mysql.jdbc.Statement.executeUpdate(Statement.java:1290)
        at net.java.ao.schema.SchemaGenerator.migrate(SchemaGenerator.java:77)
        at net.java.ao.EntityManager.migrate(EntityManager.java:192)
        at TestC.main(TestC.java:26)

CompositeエンティティはComponentエンティティの以下のメソッドを継承します。

Composite getParent();
void setParent(Composite parent);

そのため、Compositeエンティティは自分自身に対する関連を持ちます。したがって、compositeテーブルに外部キーによるインデックスが作成されます。ここまでは問題ありません。

しかし、ActiveObjectsはcomponentテーブルに対してもインデックスを作成しようとするのです。というのも、エンティティが他のエンティティを継承している場合、ActiveObjectsは親エンティティを調べます。そして、親エンティティの持つ関連に対しても、インデックスを作成しようとします。
今回の場合、Compositeエンティティはの親エンティティであるComponentエンティティを調べます。ComponentエンティティはCompositeエンティティに対して関連をもちます。したがって、componentテーブルにインデックスを作成しようします。

とはいえ、componentテーブルはPolymorphicであるため、componentテーブルは存在しません。存在しないテーブルに対するインデックス作成は、当然エラーになります。

これらを図解すると以下のようになります。

なんでActiveObjectsはこんなことに??

わかりません。ともかく現状はこういったことはできないようです。
ちなみに、ActiveObjectsのソースを書き換えればこの問題には対処できます。Polymorphicの実験(3) ==失敗== @Polymorphicと@OneToManyを組み合わせた再帰的構造を参照ください。

参考:ActiveObjectsの発行するSQLの確認

EntityManagerのインスタンスを作成後に以下のコードを実行すると、ActiveObjectsの発行したSQLが標準出力に出力されるようになります。

java.util.logging.Logger.getLogger("net.java.ao").setLevel(java.util.logging.Level.FINE);