下書きが溜まってしまったのでとりあえず投稿。そのうち文章とか修正予定。
#dbm-gorm-diffが比較するもの
これはあくまで現在のデータベースの状態(databasechangelog含)とドメインの状態の差分です。
まだ適用されていないChangeSetがあったとしてもその内容は無視されます。
PostgreSQLのsequenceについて
とりあえず以下のドメインを想定
package dbmigrationtest
class Person {
String name
Integer age
static constraints = {
}
static mapping = {
id generator:'sequence', params: [sequence:'test_id_seq']
}
}
ただし、この状態だと、確かにPostgreSQLのtest_id_seqというテーブルは存在しているけど、テストテーブル自体にはまだその情報は追加されていません。
そのため、手動でINSERTした時には毎回このsequence名を指定しなければなりません。
dbmigrationtest=# \d
List of relations
Schema | Name | Type | Owner
--------+-----------------------+----------+-------
public | databasechangelog | table | koji
public | databasechangeloglock | table | koji
public | hibernate_sequence | sequence | koji
public | hoge | table | koji
public | person | table | koji
public | test_id_seq | sequence | koji
(6 rows)
dbmigrationtest=# \d person
Table "public.person"
Column | Type | Modifiers
---------+------------------------+-----------
id | bigint | not null
version | bigint | not null
age | integer | not null
name | character varying(255) | not null
Indexes:
"personPK" PRIMARY KEY, btree (id)
dbmigrationtest=#
で、以下のようなファイルを作成します。
databaseChangeLog = {
changeSet(author: "koji (generated)", id: "add default") {
grailsChange {
change {
sql.execute("ALTER TABLE person ALTER COLUMN id SET DEFAULT nextval('test_id_seq')")
}
}
}
}
この作成したファイルを、changeog.gooryの、databaseChagneLogの中の末尾にincludeとして追加します。
databaseChangeLog = {
/*
* 長いので省略
*/
// これを追加。changeSetクロージャの外側に記述すること。
// つまりdatabasechangeLogに突っ込むクロージャのトップレベルに定義する。
include file: 'addSequence.groovy'
}
これでdbm-updateを実行します。
grails dbm-update
でデータベースを確認すると。。。?
dbmigrationtest=# \d person
Table "public.person"
Column | Type | Modifiers
---------+------------------------+---------------------------------------------------
id | bigint | not null default nextval('test_id_seq'::regclass)
version | bigint | not null
age | integer | not null
name | character varying(255) | not null
Indexes:
"personPK" PRIMARY KEY, btree (id)
反映されました!
これを手作業で開発環境、テスト環境、本番環境それぞれで実行しようとしているといつか作業漏れなどが発生しますよね。
こういう作業もdbmigrationに任せてしまえば安心!
#データの登録
BootStrap.groovyで、Grails起動時/終了時にテーブルのレコードを更新する事ができますが、起動時/終了時に毎回実行されるので、データの存在チェックなどのロジックを一緒に記述する必要があります。煩雑になりますね。
そこでもdbmigrationの登場です。
dbmigrationを使えば当然一回実行された更新は今後二度と実行されることはありません。この機能を利用しない手はありませんね。またデータに関わる作業をdbmigrationで一括管理することができるのでよりスマートですね。
なお、dbmigrationでは以下の3通りの方法でデータを操作することができます。
- InsertStatementベース(Liquibase)
- SQLベース
- GORMベース
では、オリジナルのchangeSetを作成する。
import dbmigrationtest.Person
import liquibase.statement.core.InsertStatement
databaseChangeLog = {
/**
* liquibaseベース
*/
changeSet(author: "koji", id: "liquibase-1") {
grailsChange {
change {
def statements = []
3.times {
def insertStatement = new InsertStatement('public', 'person')
.addColumnValue('name', "'liquibase-${it}'")
.addColumnValue('age', 30)
.addColumnValue('version', 1 as BigInteger)
statements.add(insertStatement)
}
// ここでまとめて実際に実行
sqlStatements(statements)
confirm 'liquireベースで実行'
}
}
}
/**
* SQLベース
*/
changeSet(author: "koji", id: "sql-1") {
grailsChange {
change {
sql.execute("INSERT INTO person (name, age, version) values('sql-1', 30, 1)")
sql.execute("INSERT INTO person (name, age, version) values('sql-2', 30, 1)")
sql.execute("INSERT INTO person (name, age, version) values('sql-3', 30, 1)")
confirm 'sqlベースで実行'
}
}
}
/**
* GORMベース
*/
changeSet(author: "koji", id: "gorm-1") {
grailsChange {
change {
3.times {
new Person(name:'gorm-1', age:30, version: 1).save(flush:true)
new Person(name:'gorm-2', age:30, version: 1).save(flush:true)
new Person(name:'gorm-3', age:30, version: 1).save(flush:true)
}
confirm 'GORMベースで実行'
}
}
}
}
そしてそのファイルをchangelog.groovyからincludeします。
databaseChangeLog = {
/*
* 長いので省略
*/
// これを追加。changeSetクロージャの外側に記述すること。
// つまりdatabasechangeLogに突っ込むクロージャのトップレベルに定義する。
include file: 'add-records.groovy'
}
で、例のごとくdbm-upate
を実行します。
grails dbm-update
ではレコードを見てみます。
dbmigrationtest=# select * from person;
id | version | age | name
----+---------+-----+-------------
1 | 1 | 30 | liquibase-0
2 | 1 | 30 | liquibase-1
3 | 1 | 30 | liquibase-2
4 | 1 | 30 | sql-1
5 | 1 | 30 | sql-2
6 | 1 | 30 | sql-3
7 | 0 | 30 | gorm-1
8 | 0 | 30 | gorm-2
9 | 0 | 30 | gorm-3
10 | 0 | 30 | gorm-1
11 | 0 | 30 | gorm-2
12 | 0 | 30 | gorm-3
13 | 0 | 30 | gorm-1
14 | 0 | 30 | gorm-2
15 | 0 | 30 | gorm-3
(15 rows)
ちゃんとINSERTされました!
なんかGORM経由でいっぱい登録されているぞ、と思ったのですが、単純に自分のソースの記述ミスです。
これでdbmigrationを使ってデータベースにレコードを登録する方法がわかりました。
BootStrap.groovyと違って2回目以降は実行されないので重複してデータが登録されることはありません。
なお、liquibase使うメリットは正直全くないと思います...
今回は登録だけですが、GORMとSQLが使えるということは、メンテナンスにも大きな威力を発揮しそうですね!
#カラムの順番の入れ替え
GORM(というかHibrernate?)はカラムの追加の順番を制御できません。
さらに、PostgreSQLはカラムの追加は必ずテーブルの末尾になります。
動作自体に特に問題はありませんが、自分の意図した順番でカラムを並べ替えたいと思うのが人情。
この記事をここまで読んでくださった方ならなんとなく想像されている方もいらっしゃるかもしれませんが、dbmigrationはその管理すべき変更を全てただのGroovyファイルとして保存しています。
また、そのGroovyファイルの中で直接SQL文を記述することも可能です。
カラムの順番という情報自体はGORMの範疇にはない情報です。なので、dbm−*系のコマンドでは管理できない情報ということになります。
そこで、手動でChangeSetを作成して、その中でSQLを直接実行するようにすることでこの問題を解決することができます。
dbmigrationtest=# \d person
Table "public.person"
Column | Type | Modifiers
---------+------------------------+-----------
id | bigint | not null
version | bigint | not null
age | integer | not null
name | character varying(255) | not null
Indexes:
"personPK" PRIMARY KEY, btree (id)
dbmigrationtest=#
このテーブルのカラムの並びを変えたい、(具体的にはid, name, age, versionの順番にしたいとする。)
ちなみにPostgreSQLにはカラムを並び替える命令は存在しないので、テーブルを再作成する必要があります。
ということで、それをdbmigration経由でやってみます。
手動で以下のchangeSet用ファイルを作成しました。
databaseChangeLog = {
changeSet(author:"koji", id:"hogehoge-1") {
grailsChange {
change {
// データのバックアップ
sql.execute("CREATE TABLE person_table_backup AS SELECT * FROM person")
// 対象テーブルの削除
sql.execute("DROP TABLE person CASCADE")
}
}
}
changeSet(author:"koji", id:"hogehoge-2") {
// 対象テーブルの再作成
// dbmigrationでちゃんとテーブルを作成していれば、その時の定義が残っているはずなので、
// それを探し出してきてカラムの順番を並び替えるだけでOK
createTable(tableName: "person") {
column(name: "id", type: "int8") {
constraints(nullable: "false", primaryKey: "true", primaryKeyName: "personPK")
}
column(name: "name", type: "varchar(255)") {
constraints(nullable: "false")
}
column(name: "age", type: "int4") {
constraints(nullable: "false")
}
column(name: "version", type: "int8") {
constraints(nullable: "false")
}
}
}
changeSet(author:"koji", id:"hogehoge-3") {
grailsChange {
change {
// バックアップデータのリストア
sql.execute("""
INSERT INTO person (
id,
name,
age,
version
)
(SELECT id, name, age, version FROM person_table_backup);
""")
// バックアップテーブルの削除
sql.execute("DROP TABLE person_table_backup CASCADE")
confirm '複雑な作業'
}
}
}
}
なんかcreateTableを書く場合はchangeSetの中にcreateTable自信を一つしか描けないらしい(sqlとか別に実行しようとするとエラーになる)
まぁそいうなんだということでそこは放置。
これも当然changeset.groovyに追加します。
databaseChangeLog = {
/*
* 長いので省略
*/
// これを追加。changeSetクロージャの外側に記述すること。
// つまりdatabasechangeLogに突っ込むクロージャのトップレベルに定義する。
include file: 'reorder-columns.groovy'
}
で、例のごとくdbm-upate
を実行します。
grails dbm-update
実行結果を確認すると。。。?
bmigrationtest=# \d person
Table "public.person"
Column | Type | Modifiers
---------+------------------------+-----------
id | bigint | not null
name | character varying(255) | not null
age | integer | not null
version | bigint | not null
Indexes:
"personPK" PRIMARY KEY, btree (id)
dbmigrationtest=# select * from person;
id | name | age | version
----+-------------+-----+---------
1 | liquibase-0 | 30 | 1
2 | liquibase-1 | 30 | 1
3 | liquibase-2 | 30 | 1
4 | sql-1 | 30 | 1
5 | sql-2 | 30 | 1
6 | sql-3 | 30 | 1
7 | gorm-1 | 30 | 0
8 | gorm-2 | 30 | 0
9 | gorm-3 | 30 | 0
10 | gorm-1 | 30 | 0
11 | gorm-2 | 30 | 0
12 | gorm-3 | 30 | 0
13 | gorm-1 | 30 | 0
14 | gorm-2 | 30 | 0
15 | gorm-3 | 30 | 0
(15 rows)
ちゃんと並べ替えられました!
こうやって最終的にdbmigration経由でちゃんとカラムを並べ替えられることはできましたが、まぁやはり最初にテーブルを生成する際に、生成されたchangeSetでカラムの並びをちゃんと入れ替えておいたほうが無難ですね。。。