RailsとJPAでテーブル命名規約を共有する
両者を触ってみたことのある人なら解ると思いますが、この二つのテーブル命名規約は結構似ています。サロゲートキーを主キーとすることを推奨したり、外部キーの命名方法だったり、排他制御の為のバージョンカラム指定だったり。ただ、JPAよりもRailsのActiveRecordの方が更に規約が厳しくて徹底しています。またテーブル名を複数形、モデルクラスを単数系にするという独自の規約もあるので、一見JPAの規約とは互換性が無いようにも思えます。しかし、RailsもJPAも無理をしない形で、規約を共有することも十分可能です。同一DBにJPAとActiveRecordの双方でアクセスするような要件にもし出会ったら、この両者の規約共有の検討が必要になるかもしれません。
というわけで、ちょっとした例を考えてみました。まずはMySQLで、categoriesとcontentsという二つのテーブルを作成します。Railsのマイグレーションで作ってもいいんですけど、今回Railsは普通にModelを作るだけですので、あまり関係ありません。というか正直言ってrailsのDBマイグレーションはテーブル作成するには不便だと思う・・・
CREATE TABLE categories ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 'ID' ,name VARCHAR(255) NOT NULL COMMENT '名前' ,lock_version INT UNSIGNED NOT NULL COMMENT 'バージョン' ,created_at DATETIME NOT NULL COMMENT '作成日時' ,updated_at DATETIME NOT NULL COMMENT '更新日時' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='カテゴリー'; CREATE TABLE contents ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT 'ID' ,category_id INT UNSIGNED NOT NULL COMMENT 'カテゴリーID' ,name VARCHAR(50) NOT NULL COMMENT '名前' ,lock_version INT UNSIGNED NOT NULL COMMENT 'バージョン' ,created_at DATETIME NOT NULL COMMENT '作成日時' ,updated_at DATETIME NOT NULL COMMENT '更新日時' ,FOREIGN KEY (category_id) REFERENCES categories(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='コンテンツ';
Rails上ではCategory、Contentという2つのモデルに自動的にマッピングされます。主キーは規約により「id」という名前のサロゲートキー、バージョンカラムもRailsの規約により「lock_version」という名前で統一。「created_at」が登録日時、「updated_at」が更新日時、これもRailsの規約です。外部キーは、関連先テーブルの単数系+"_id"という名前になります。
Railsの命名規約によって作成されたこのテーブルを、次はJPAのEntityでマッピングしてみます。
package sample.entity; import java.io.Serializable; import java.sql.Timestamp; import java.util.Set; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Version; @Entity public class Categories implements Serializable { /** * */ private static final long serialVersionUID = -5495066888589478365L; @Id @GeneratedValue private Integer id; private String name; @OneToMany(mappedBy = "category") private Set<Contents> contents; @Version private Integer lock_version; private Timestamp created_at; private Timestamp updated_at; (setter、getter省略) }
package sample.entity; import java.io.Serializable; import java.sql.Timestamp; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.Version; @Entity public class Contents implements Serializable { /** * */ private static final long serialVersionUID = -8869777206458598835L; @Id @GeneratedValue private Integer id; private String name; @ManyToOne(fetch = FetchType.LAZY) private Categories category; @Version private Integer lock_version; private Timestamp created_at; private Timestamp updated_at; (setter、getter省略) }
クラス名、フィールド名はテーブル定義の名前をそのまま使用します。サロゲートキーなので@GeneratedValueアノテーションをidフィールドに追加します。lock_versionフィールドには@Versionカラムを追加。残念ながらcreated_at、updated_atにはJPAは対応してくれないので、ここは個別に値をセットしなければなりません。
ポイントはContentsクラスのcategoryフィールドです。これは多対1の関連定義を示すフィールドですが、このフィールド名を、Railsでも使用しているテーブルの単数名で定義します。そうすると、JPAの場合外部キー名のデフォルトは「関連先Entityを定義したフィールド名+ "_" + 関連先Entityの主キー名」となるので、結果としてRailsの命名規約と一致することになります。一対多のときのフィールド名は、Railsの規約とは関係ありませんが、これをテーブル名と同じ複数名にしておけば、単数名はN対1の関連、複数名はN対Nの関連だと一目で分かるようになります。
この命名方法はテーブル名をそのまま使っている為、Java標準のクラス名、フィールド名から少々外れています。通常は「created_at」ではなく「createdAt」と書くのがJava的な手法です。・・・ですがしかし、敢えてテーブル名に合わせることによって、JPQLでその恩恵を受けることが出来ます。試しに簡単なQueryを書いてみると・・・
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0"> <named-query name="Categories.getCategories"> <query><![CDATA[ SELECT c FROM Categories c LEFT OUTER JOIN FETCH c.contents WHERE c.created_at >= :date ORDER BY c.updated_at DESC ]]> </query> </named-query> </entity-mappings>
・・・みたいなかんじで、JPQLがほとんどSQLの名前そのままになります。クラス名、フィールド名にテーブル定義の名前をそのまま使ってるから当然なのですが、これなら、ほぼSQLを書くときの感覚でJPQLを書いていくことが出来ます。HibernateのQriteria等を使うときもこの感覚は同じです。下手にJava用の名前で再定義するよりは、テーブル名を正としてあげることによってQuery関連の記述がずいぶん解りやすくなります。
以上のような点に気をつければ、RailsとJPAでテーブルの共有は十分可能だと思います。RailsもJPAもテーブル定義に関してはほぼ同じ方向性だと思うので、どうせなら言語間を超えたテーブル命名規約の標準としてこの形が広まってほしいものです。