ひがやすを技術ブログ

電通国際情報サービスのプログラマ

ERモデルとドメインモデル

エンティティのマッピングで、S2JDBCJPAと違っているところは、外部キーのプロパティを必要とすることです。
これは、S2JDBCJPAの根本的なモデリングに対する考えの違いからきています。
S2JDBCは、ERモデルをエンティティに忠実に反映させるという考えです。だから、テーブルのカラムは、エンティティと一対一に対応させます。
それに対しJPAは、ドメインモデルとERモデルを別途作成し、それをO/R Mappingしていきます。重要なのは、ドメインモデルなので、ドメインモデル上必要のない、外部キーに対するプロパティはいらないのです。
どっちがいいのかは、好みの問題ですが、個人的には、ドメインモデルとERモデルという、似てそうで、分析手法が違う2つのモデルを作成するより、ERモデル1つの設計で済ませるほうが楽なので好みです。
データの入れ物にRDBMSを使っているんだから、RDBMSで扱いやすいモデルのほうが確実で楽なんじゃないでしょうか。
ドメインモデル派の主張の1つにドメインモデルは、継承・多態などのオブジェクト指向のテクニックを使ってメンテナンス性拡張性の高い設計ができるというのがあります。
全従業員のボーナスを計算するというユースケースを例にして検証してみましょう。従業員の仕事のタイプには、SALESMANとPRESIDENTがいて、ボーナスは、SALESMANは給料の2倍。PRESIDENTは給料の10倍もらえるとします。まずは、JPAドメインモデルのほうから。


@Entity
@Inheritance
@DiscriminatorColumn(name = "JOB_TYPE")
public abstract class Employee {
private int salary;
...
public abstract Integer calcBonus();
}

@Entity
public class Salesman extends Employee {
public int calcBonus() {
return salary * 2;
}
}

@Entity
public class President extends Employee {
public int calcBonus() {
return salary * 10;
}
}
全従業員のボーナスを求めるロジックはこちら。

List<Employee> results = (List<Employee>)
entityManager.createQuery(
"select e from Employee")
.getResultList();
int total = 0;
for (Employee e : List<Employee>) {
total += e.calcBonus();
}
S2JDBC版は、Strategyパターンを使います。

public interface JobStrategy {
int calcBonus(int salary);
}

public class Salesman implements JobStrategy {
public int calcBonus(int salary) {
return salary * 2;
}
}

public class President implements JobStrategy {
public int calcBonus(int salary) {
return salary * 10;
}
}

public enum JobType {
SALESMAN {
@Override
public JobStrategy createStrategy() {
return new Salesman();
}
},
PRESIDENT {
@Override
public JobStrategy createStrategy() {
return new President();
}
};
public abstract JobStrategy createStrategy();
}

@Entity
public class Employee {
public int salary;
public JobType jobType;
...
}
全従業員のボーナスを求めるロジックはこちら。

List<Employee> results =
jdbcManager.from(Employee.class)
.getResultList();
int total = 0;
for (Employee e : List<Employee>) {
total += e.jobType.createStrategy().calcBonus(e.salary);
}
継承ではなくて、委譲を使っているところがポイント。継承のほうが、ソースコードは少なくなりますが、委譲のほうが親クラスと密に結びつかない分、柔軟でメンテナンス性が良くなります。この辺は、一般的な「継承より委譲」とよばれるイディオムです。
例えば、毎年の有給休暇が勤続10未満の若い社員は、10日支給され、10年以上の社員は20日支給されるとします。このとき、毎年の有給休暇を計算するというユースケースが従業員に対して必要だとしましょう。
継承を利用する場合は、Employeeを継承するクラスをセールスマンの若い人とシニア、プレシデントの若い人とシニア用に用意する必要があります。仕事のタイプが増えたり、勤続年数による区分がさらに増えると掛け算でクラス数が増えていきます。Javaでは、多重継承が認められていないので、仕事のタイプによるクラスか振る舞いと勤続年数によるクラスのどちらかを継承し、他方は処理を委譲することになります。だったら、最初から委譲を利用したほうがシンプルでしょう。

S2JDBC & Hibernateベンチマーク

S2JDBCHibernate、素のJDBCS2Daoとのベンチマーク結果をこれから徐々に公開していきます。
このベンチマークのプロジェクトは、S2Container.javaリポジトリにコミットされているので、誰でも自由に見ることができます。
第一弾は、S2JDBCHibernateで1万件の従業員を検索するシンプルなやつです。関連は、すべてLazyです。従業員は、良くある部署や住所との関連を持ちます。データはデータベースにキャッシュされるようにテストのsetUp()で全件検索するSQLを実行しています。データベースはオラク○です。ナノ秒精度で3回測定しました。()の中が平均です。

  • S2JDBC(1.357176629)
    • 1.355739341
    • 1.357618839
    • 1.358171707
  • Hibernate(2.173966161)
    • 2.174851771
    • 2.179259106
    • 2.167787608

Hibernateが1.6倍くらい時間がかかっているのがわかります。
従業員は、関連の外部キーを持っているため、Hibernateは、関連のプロキシ用に余分なSQLを発行することがありません。つまり、S2JDBCHibernateも"select * from employee"を発行しているだけです。この違いは、永続コンテキストを管理するコストと見ることができるでしょう。
永続コンテキストは、デフォルトでトランザクション中は、キャッシュされるます。今回の時間の違いは、キャッシュにヒットしなかったときのパフォーマンスの違いと捕らえると良いのではないかと思います。