EJB 3.0(Proposed Final Draft)入門記 Java Persistence API Chapter 9 その2

Chapter 9 その2です。今日は次の4つのアノテーションを見てみます。

  • UniqueConstraint
  • Column
  • JoinColumn
  • JoinColumns
9.1.4 UniqueConstraint Annotation

このアノテーションは生成されるDDLにUnique制約をつけたいときに使うようです。ElementTypeが空なんですね。

@Target({}) @Retention(RUNTIME)
public @interface UniqueConstraint {
  String[] columnNames();
}

こんな感じで使うらしいです。コピペ。

@Table(
  name="EMPLOYEE",
  uniqueConstraints={@UniqueConstraint(columnNames={"EMP_ID", "EMP_NAME"})}
)
public class Employee { ... }
9.1.5 Column Annotation

Columnアノテーションでは永続化されるフィールドやプロパティにマップされるカラムを指定します。

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface Column {
  String name() default "";
  boolean unique() default false;
  boolean nullable() default true;
  boolean insertable() default true;
  boolean updatable() default true;
  String columnDefinition() default "";
  String table() default "";
  int length() default 255;
  int precision() default 0; // decimal precision
  int scale() default 0; // decimal scale
}

insertableやupdatableは生成されるSQLにこのカラムを含めるかどうかの指定。
tableはこのカラムを含むテーブルを指すそうです。tableに何も指定しない場合はPrimary Tableが選択されたとみなされます。そうか、ここでSecondaryTableに含まれるカラムであることを示せのかぁ。昨日示したCustomerクラスの例は正しくないかもです。
あとの要素はだいたい想像がつくかな。

9.1.6 JoinColumn Annotation

このアノテーションはエンティティの関連を結合するためのカラムを指定します。

@Target({TYPE, METHOD, FIELD}) @Retention(RUNTIME)
public @interface JoinColumn {
  String name() default "";
  String referencedColumnName() default "";
  boolean unique() default false;
  boolean nullable() default true;
  boolean insertable() default true;
  boolean updatable() default true;
  String columnDefinition() default "";
  String table() default "";
}

nameは外部キーとなるカラムの名前。単一のカラムで結合する場合でnameが指定されないとき、デフォルトの値は<参照先エンティティのプロパティもしくはフィールドの名称>_<参照先のプライマリキー名>となるそうです。文章で説明するとわかりにくいので下のEmployeeクラスの例を使います。Employeeクラスのaddressフィールドに注釈された@JoinColumnのname要素に「ADDR_ID」を指定してますが、もしこれがなければ「ADDRESS_ID」がデフォルトになるということです。アンダーバーより前方の「ADDRESS」がEmployeeエンティティのプロパティで後方の「ID」がAddressエンティティのプライマリキーです。
これってChapter 2のとこで学習したデフォルトのマッピングと同じルールですね。結合テーブルを使うときの名称のデフォルトもChapter 2と同じよう。
referencedColumnNameには参照先のカラムの名称を指定します。この要素が指定されない場合、外部キーは参照先のプライマリキーを参照するそうです。プライマリキー以外も参照できるけどそういうマッピングを使うアプリケーションはポータブルではないとのことです。
他の要素はColumnアノテーションと同じ。
要素に何も指定しないのならば、たぶん@JoinColumnを書く必要なしですね。

@Columnと@JoinColumnを使ってみます。仕様書に載っているのを適当に変更しました。お題はEmployeeとAddressのManyToOneです。エンティティとDDLはこんな感じになると思います。(Public Review版で動かしてからProposed Final Draft版に文法を変更してみました。ここに書いたコードがそのまま動いたわけではないです。DDLはhbm2ddlでHSQLDBにテーブルを自動生成してからcheckpointでHSQLDBに出力させました)。

@Entity
public class Employee {

	@Id 
	@GeneratedValue
	private int id;
	
	@Column(name="EMP_NO", nullable=false)
	private String name;

	@ManyToOne(cascade=PERSIST)
	@JoinColumn(name="ADDR_ID", referencedColumnName="id")
	private Address address;

	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}	
}

@Entity
public class Address {

	@Id
	@GeneratedValue
	private int id;
		
	private String city;

	public int getId() {
		return id;
	}

	public String getCity() {
		return city;
	}

	public void setCity(String city) {
		this.city = city;
	}
}
CREATE TABLE ADDRESS(
  ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1)  NOT NULL PRIMARY KEY,
  CITY VARCHAR(255)
)
CREATE TABLE EMPLOYEE(
  ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1)  NOT NULL PRIMARY KEY,
  EMP_NO VARCHAR(255) NOT NULL,
  ADDR_ID INTEGER,
  CONSTRAINT FK4AFD4ACEAB975AA1 FOREIGN KEY(ADDR_ID) REFERENCES ADDRESS(ID)
)

上のコード、実はいままで使い方をわかってなかったstaticインポート使ってみました。アノテーションで使うEnumのあたりがすっきりしますね(cascade=PERSISTとか)。いままで自分の作るサンプルとJPAの仕様書でコードが違うのは気づいてたんですけど、まだFinalじゃないからなのかなぁと勝手に思っていました。そうじゃなくてドキュメントに載っているコードはすべてstaticインポートつかってるんですね。

9.1.7 JoinColumns Annotation

JoinColumnアノテーションの複数形。このアノテーションで複合外部キーがサポートされるとのことです。あんまり使いたくないですが。

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface JoinColumns {
  JoinColumn[] value();
}

このアノテーションを使う場合、JoinColumnのnameとreferencedColumnNameの要素は指定必須だそうです。