参考
http://nobeans.hatenablog.com/entry/20091030/1256896937
http://stackoverflow.com/questions/2822623/how-to-set-up-an-insert-to-a-grails-created-file-with-next-sequence-number
GrailsでPostgreSQLを使う場合、デフォルトだとPostgreSQLのsequenceが利用されない。
以下のように各ドメインに自分のsequenceを定義することで利用できるようになる。
package hoge
class Test{
String word
static constraints = {
}
static mapping = {
id generator:'sequence', params: [sequence:'test_id_seq']
}
}
ただし、これだと実は落とし穴があって、GrailsがCreateDropモードの場合は、上記のsequenceも含めてテーブルを全部自動生成してくれるけど、 そのテーブルのidには、属性としてそのsequenceのnextvalが指定されない。
そのため、生のSQLでidを省略すると、 idにnullは許されないぜ! というエラーが当然発生する。
以下はその条件でGrailsによって自動生成されるテーブルの定義
-- こっちがテーブル
mydb=# \d test
Tabelle public.test
Spalte | Typ | Attribute
---------+------------------------+-----------
id | bigint | not null
version | bigint | not null
word | character varying(255) | not null
Indexe:
"test_pkey" PRIMARY KEY, btree (id)
-- こっちがsequence
mydb=# \d test_id_seq
Sequenz public.test_id_seq
Spalte | Typ | Wert
---------------+---------+---------------------------
sequence_name | name | test_id_seq
last_value | bigint | 4
start_value | bigint | 1
increment_by | bigint | 1
max_value | bigint | 9223372036854775807
min_value | bigint | 1
cache_value | bigint | 1
log_cnt | bigint | 32
is_cycled | boolean | f
is_called | boolean | t
id のAttributeにnextvalが指定されていないので、以下のようなSQLがエラーになる。
INSERT INTO test (version,word) VALUES(1, 'aaa')
Grails自体は、ドメインに指定されたsequenceの情報を知っているので、ちゃんと生成したsequenceを使ってくれる。
でも、PostgreSQL自体は、上のテーブル定義を見れば分かるとおり、testテーブルのidに対応するsequenceの情報がどこにも書かれていないので、INSERT時にidが省略された場合、自動でsequenceを利用することが出来ない。
本来は以下のようなテーブル定義になる必要がある。
こうすれば、idを省略すると、nextvalが指定されているので自動的にPostgreSQLがそのsequenceを利用してくれる。
Tabelle public.test2
Spalte | Typ | Attribute
--------+---------+--------------------------------------------------------
id | integer | not null Vorgabewert nextval('test2_id_seq'::regclass)
name | text |
Grailsの用意している機能を使ってデータベースを利用する場合は、sequenceを使おうが使うまいが特に問題はない。
問題になるのは、SQL(INSERT)を自分で書いて実行する場合。
対応策としては以下の2点がある
1.テーブルとsequenceは全て自分で生成する or 後からnextvalを追加する。
2.SQLを直接実行する際には、idをちゃんと指定する。
2番の場合は以下のような感じになる。
def query = "INSERT INTO test (id,version,word) VALUES(nextval('test_id_seq'),?,?)"
sql.executeInsert(query, [1,"test-data-a"])
ちなみにサービスの中でSQLを直接実行してもちゃんとロールバックできる。