ドメインで文字列のキーをIDにする その2

ドメインで文字列のキーをIDにする
http://d.hatena.ne.jp/kkz_tech/20130606/1370609690

上の続きで、レガシーDBを使ってGrailsドメインクラスを作る場合のお話です。
※前提条件としては、テーブル定義は変更できないもの、としています。

いくらレガシーといえど、テーブルの親子関係くらいはありますよね。
前回の例だとユーザーを作りましたから、今度はさらにユーザーを親とする子テーブル、ユーザーのログイン履歴に相当するものを作ってみました。

何個か引っかかるポイントはあったのですが、まずは親のインスタンスをどう持たせるか、です。適当に指定したら親テーブルとの外部キーであるユーザーIDが2個出来てしまう(;´∀`)

ログイン履歴はタイムスタンプ型のログイン日付とユーザーIDの複合主キーなわけですが、これはstatic mappingに以下のような指定をする事で思った通りのレコードになりました。

class LoginHistory {
	LoginUser loginUser
	Date loginDate
	static mapping = {
		id generator: 'assigned', composite: ['loginUser', 'loginDate']
		loginUser column: 'user_id'
	}

ただ、このままではrun-app時にエラーが出てしまう。どうやら、ドメインクラスを比較する際にComparatorを使っているようなのですが、id列を持たせないようにしてしまった場合、そのためのメソッドが無いとダメみたいです。hashCode()とequals()を実装してあげると以下のような感じです。
コメントで教えて頂いたのですが、ドメインクラス自体にアノテーションで@EqualsAndHashCodeを指定するやり方もある模様。今度試したらまたエントリ作りますね。


hashCode、equalsともに「何を持って一意とするか(=主キー相当のプロパティは何か)」をきちんと意識する必要があります。って当たり前か。

class LoginHistory  implements Serializable {

	boolean equals(other) {
		if (!(other instanceof LoginHistory)) {
			return false
		}
		return (other.loginUser.userId == loginUser.userId && other.loginDate == loginDate)
	}

	int hashCode() {
		def builder = new HashCodeBuilder()
		builder.append loginUser.userId
		builder.append loginDate
		builder.toHashCode()
	}
}

とりあえず上記を踏まえて出来上がったのは、以下のような感じです。
レコードの作成日、更新日のカラムは既に持っていましたが、名前がGORMのデフォルトと違うので、自分で指定しています。

import java.util.Date;
import org.apache.commons.lang.builder.HashCodeBuilder;

class LoginHistory  implements Serializable {
	LoginUser loginUser
	Date loginDate
	Date dateCreated
	Date lastUpdated
	
	String toString() {
		return loginUser.userId + toString(loginDate)
	}
	
	boolean equals(other) {
		if (!(other instanceof LoginHistory)) {
			return false
		}
		return (other.loginUser.userId == loginUser.userId && other.loginDate == loginDate)
	}

	int hashCode() {
		def builder = new HashCodeBuilder()
		builder.append loginUser.userId
		builder.append loginDate
		builder.toHashCode()
	}
	
    static constraints = {
		loginDate blank:false, nullable:false
		ip blank:true, nullable:true, maxSize:19
    }
	
	static mapping = {
		table 'login_history'
		version false
		id generator: 'assigned', composite: ['loginUser', 'loginDate']
		loginUser column: 'user_id'
		dateCreated column: 'insert_date'
		lastUpdated column: 'update_date'
	}
}

実際のテーブルがどう出来ているかと言うとログイン履歴はこんな感じ。

 \d login_history
            テーブル "public.login_history"
   カラム    |             型              |  修飾語
                                                                                                          • -
user_id | character varying(64) | not null login_date | timestamp without time zone | not null insert_date | timestamp without time zone | not null update_date | timestamp without time zone | not null インデックス: "login_history_pkey" PRIMARY KEY, btree (user_id, login_date) 外部キー制約: "fkf4700b1e89c762ba" FOREIGN KEY (user_id) REFERENCES login_user(user_id)

親テーブルのユーザーマスタはこんな感じ。

# \d login_user
                 テーブル "public.login_user"
        カラム        |             型              |  修飾語
                                                                                                                            • -
user_id | character varying(64) | not null insert_date | timestamp without time zone | not null update_date | timestamp without time zone | not null name | character varying(64) | not null password | bytea | not null インデックス: "login_user_pkey" PRIMARY KEY, btree (user_id) 参照元: TABLE "login_history" CONSTRAINT "fkf4700b1e89c762ba" FOREIGN KEY (user_id) REFERENCES login_user(user_id)