RailsとJPAでテーブル命名規約を共有する

両者を触ってみたことのある人なら解ると思いますが、この二つのテーブル命名規約は結構似ています。サロゲートキーを主キーとすることを推奨したり、外部キーの命名方法だったり、排他制御の為のバージョンカラム指定だったり。ただ、JPAよりもRailsActiveRecordの方が更に規約が厳しくて徹底しています。またテーブル名を複数形、モデルクラスを単数系にするという独自の規約もあるので、一見JPAの規約とは互換性が無いようにも思えます。しかし、RailsJPAも無理をしない形で、規約を共有することも十分可能です。同一DBにJPAActiveRecordの双方でアクセスするような要件にもし出会ったら、この両者の規約共有の検討が必要になるかもしれません。
というわけで、ちょっとした例を考えてみました。まずは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関連の記述がずいぶん解りやすくなります。
以上のような点に気をつければ、RailsJPAでテーブルの共有は十分可能だと思います。RailsJPAもテーブル定義に関してはほぼ同じ方向性だと思うので、どうせなら言語間を超えたテーブル命名規約の標準としてこの形が広まってほしいものです。