- Published on
ใช้ Liquibase กับ Spring Boot
ก่อนจะเริ่มต้น อย่างน้อยควรจะมีพื้นฐานและเคยใช้ Sprig Boot , SQL มาบ้างไม่มากก็น้อย
สำหรับคนที่ขี้เกียจอ่านทั้งหมด 😂
- Liquibase คืออะไร ?
- ทำไมต้องใช้ Liquibase ?
- ถ้าไม่ใช้ Liquibase ต้องทำไรบ้าง ?
- เพิ่ม Maven Dependency
- เพิ่ม config ใน application.properties
- Structure
- db-changelog-master.xml
- การทำงานของ Liquibase
- Syntax
- changeSet
- Sub tags
- Entities
Liquibase คืออะไร ?
เป็นเครื่องมือ Open Source ที่ช่วยจัดการ Database Schema โดยจะยึดตามการเปลี่ยนแปลงของโครงสร้างต่างๆของตาราง (Table,Column,View,Sequence) หรือแม้แต่การย้ายฐานข้อมูล เปรียบเสมือน Tool ที่คอยตรวจสอบและดูแลทุกอย่างใน Database ให้เป็นปัจจุบันเสมอในทุก Environment อยู่ในรูปแบบของ XML, YAML หรือ JSON โดย Syntax ไม่ได้ยึดตาม Database Platform ที่ใช้ สามารถ Custom เพิ่มเติมได้ด้วยการเขียน SQL Script รองรับทั้ง MySQL, PostgreSQL, Oracle, SQL Server และอื่นๆ
ทำไมต้องใช้ Liquibase ?
Database Schemas จะเป็นปัจจุบันและเหมือนกันทุก Environment โดยไม่ต้องมาคอยดูแลและจัดการ Script ทุกครั้งด้วยตัวเอง (ลด Human Error) รวมถึงการย้าย Server , Environment หรือเปลี่ยน Database ก็สามารถทำได้ง่ายขึ้น
ถ้าไม่ใช้ Liquibase ต้องทำไรบ้าง ?
- หลังจาก Run script SQL บน Dev แล้วต้องมีการจัดเก็บ Script ส่วนนี้ไว้ทุกครั้ง โดยต้องมีที่จัดเก็บให้เป็นระเบียบ และมีโอกาสที่ Developer จะลืมหรือตกหล่นไปบาง Script ด้วย
- จากข้อ 1. ที่พูดถึงการจัดเก็บ Script บางครั้ง ถ้า Script นั้น Developer ไม่ได้เป็นคนเขียน แต่เป็น BA , SA หรือตำแหน่งอื่นๆ Script นั้นอาจจะไม่ได้ถูกจัดเก็บไว้ใน Project ซึ่งที่ถูกต้องควรจะอยู่ใน Project นั้นๆ
- หลังจาก Deploy แล้วต้องนำ Script ที่เตรียมไว้มา Run อีกรอบบน Environment นั้นๆ ขั้นตอนนี้มีโอกาสที่จะนำ Script มา Run ไม่ครบ หรือ Run ผิด Sequence อาจจะทำให้เกิดปัญหา Deploy ไม่ผ่าน
ลองคิดเล่นๆดูนะครับ ถ้าการ Deploy ในรอบนั้น มีอยู่ประมาณ 10 Script และคนที่ Deploy ไม่ใช่ Developer เจ้าของ Script มีหน้าที่แค่เอาทุกอย่างที่เตรียมไว้ไป Deploy ตาม Check List เมื่อเกิดปัญหาขึ้นมา คงจะเสียเวลาและวุ่นวายน่าดู โดยเฉพาะการ Deploy ที่ Production บางทีอาจจะมีเวลาที่จำกัด
ถ้าเราใช้ Liquibase ปัญหาหรือความเสี่ยงต่างๆที่กล่าวมานี้ จะไม่เกิดขึ้น เพราะถ้าเกิดปัญหา จะรู้และไม่ผ่านตั้งแต่ตอน Dev แล้ว ลดปัญหาที่อาจเกิดจาก Human error นั่นเอง
เพิ่ม Maven Dependency
เพิ่ม spring-boot-starter-jdbc
, liquibase-core
ที่ไฟล์ pom.xml และ Reimport maven
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
...
</dependencies>
เพิ่ม config ใน application.properties
#เป็นการกำหนด Path file ของ Db changelog ตั้งต้น
#ถ้าเราไม่กำหนด Spring boot จะมองหา Path file 'db/changelog/db.changelog-master.yaml' เป็น Path ตั้งต้น
spring.liquibase.change-log = classpath:/db/db-changelog-master.xml
#ตั้งค่า Level logs ได้ ผมใช้เป็น INFO
logging.level.liquibase = INFO
Structure
└── src
└── main
└── resources
└── db
└── changelogs
│ ├── table
│ │ └── xxx01.xml
│ │ └── xxx02.xml
│ └── view
│ └── v_xxx01.xml
│ └── v_xxx02.xml
└── sql
│ └── init_master_xxx01.sql
│ └── init_master_xxx02.sql
└── db-changelog-master.xml
โครงสร้างในการสร้างโฟลเดอร์และไฟล์ต่างๆของ Liquibase จะใช้เป็นประมาณนี้ โดยจะแยกออกเป็นกลุ่มหลักๆ คือ
- changelogs/table - จะเป็น Script ทั้งหมดที่เกี่ยวข้องกับตารางต่างๆ โดยจะแยกออกตามตาราง
- changelogs/view - จะเป็น Script ทั้งหมดที่เกี่ยวข้องกับ View โดยจะแยกออกตาม View
- sql - จัดเก็บ Script ในกรณีมีความซับซ้อนสูง ไม่สามารถเขียนเป็น Syntax ของ Liquibase ได้ หรือ สำหรับ Master data ต่างๆ
ถ้ามีอย่างอื่นนอกเหนือจาก table , view ก็สร้างเพิ่มใน changelogs
db-changelog-master.xml
สร้างไฟล์ใน Path src/main/resources/db/
และทุกครั้งที่ Run project จะวิ่งมาอ่านไฟล์นี้เพื่อเช็คว่ามีอะไร Change บ้าง และถ้ามีการสร้างไฟล์ .xml
ใหม่ต้องมาเพิ่มในนี้ด้วย
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<!-- Table -->
<include file="/db/changelogs/table/xxx01.xml"/>
<include file="/db/changelogs/table/xxx02.xml"/>
<!-- View -->
<include file="/db/changelogs/view/v_xxx01.xml"/>
<include file="/db/changelogs/view/v_xxx02.xml"/>
</databaseChangeLog>
การทำงานของ Liquibase
ถ้า Run project ครั้งแรกหลังจากเพิ่ม Maven dependency และ Config เรียบร้อยแล้ว Spring boot จะสร้างตารางที่ชื่อว่า
- databasechangelog - เก็บ Logs ของการ Run script ทุกครั้ง โดยใช้ id เป็น Primary key
Column | Data type | Description |
---|---|---|
ID | VARCHAR(255) | id ของ changeSet |
AUTHOR | VARCHAR(255) | author ของ changeSet |
FILENAME | VARCHAR(255) | Path file ของ changelog |
DATEEXECUTED | DATETIME | Date/time ที่ changeSet นั้นๆ Execute |
ORDEREXECUTED | INT | ลำดับของการ Executed |
EXECTYPE | VARCHAR(10) | EXECUTED, FAILED, SKIPPED, RERAN และ MARK_RAN |
DESCRIPTION | VARCHAR(255) | คำอธิบายสั้นๆ ว่า changeSet นี้ทำอะไร |
ตัวอย่างข้อมูลในตาราง
หลักๆแล้ว Column ทีเราสนใจจะมีประมาณนี้ แต่ Column ทั้งหมดมีมากกว่านี้
- databasechangeloglock - ถ้าเกิดมี Developer 2 คน Run script นั้นๆ ไปที่ Database เดียวกันพร้อมกัน Liquibase จะถูก Lock เพื่อป้องกัน Transaction conflict สามารถ Unlock ได้ด้วยการ Run script
UPDATE DATABASECHANGELOGLOCK SET LOCKED=false
Column | Data type | Description |
---|---|---|
ID | INT | ปัจจุบันมีแค่ id = 1 |
LOCKED | BOOLEAN | true = Locked , false = Unlocked |
LOCKGRANTED | DATETIME | วันที่และเวลาที่ Unlocked |
LOCKEDBY | VARCHAR(255) | ใครเป็นคนทำให้ Locked |
Spring boot จะรู้ได้อย่างไร ?
Spring boot จะรู้ว่ามี Script เพิ่มมาใหม่ก็ต่อเมื่อ id นั้นไม่มีใน Database โดยจะมาเช็คไฟล์ .xml
และ Compare กับ Database ทุกครั้ง
Syntax
Tags และ Attibutes ทั้งหมดที่มีให้ใช้ โดยจะให้เห็นโครงสร้างทั้งหมดด้วยว่า Tag ไหนต้องอยู่ตรงไหน แต่ล่ะ Tag มี Attibute อะไรบ้าง
ซึ่งผมจะไม่อธิบายและยกตัวอย่างทั้งหมด จะเลือกเฉพาะอันที่ผมเคยใช้เท่านั้น สามารถอ่านเพิ่มเติมได้ที่นี่ Liquibase Documents
<changeSet id="action-entitie-datetime" author="author_name" >
<!-- Sub Tags -->
<comment>
<!-- Description of this changeSet -->
</comment>
<preConditions >
<changeSetExecuted id="action-entitie-datetime" author="author_name" changeLogFile="changelog.xml"/>
<columnExists tableName="my_table" columnName="my_column"/>
<tableExists tableName="my_table"/>
<viewExists viewName="my_view"/>
<foreignKeyConstraintExists foreignKeyName="my_foreignKey"/>
<indexExists indexName="my_index" tableName="my_table" columnNames="my_table"/>
<sequenceExists sequenceName="my_sequence" />
<primaryKeyExists primaryKeyName="my_primaryKey" tableName="my_table"/>
<sqlCheck expectedResult="my_expectedResult">
SELECT COUNT(1) FROM my_table WHERE user = 'liquibase'
</sqlCheck>
</preConditions>
<validCheckSum>
<!-- Get data from column 'md5sum' -->
</validCheckSum>
<rollback>
<!-- Sql Script or Change Type -->
</rollback>
<!-- Any Refactoring Tags -->
<!-- Entities -->
<!-- Table -->
<createTable></createTable>
<dropTable></dropTable>
<renameTable></renameTable>
<!-- Column -->
<addColumn></addColumn>
<dropColumn></dropColumn>
<renameColumn></renameColumn>
<modifyDataType></modifyDataType>
<setColumnRemarks></setColumnRemarks>
<addAutoIncrement></addAutoIncrement>
<!-- Index -->
<createIndex></createIndex>
<dropIndex></dropIndex>
<!-- View -->
<createView></createView>
<dropView></dropView>
<renameView></renameView>
<!-- Procedure -->
<createProcedure></createProcedure>
<dropProcedure></dropProcedure>
<!-- Sequence -->
<createSequence></createSequence>
<dropSequence></dropSequence>
<renameSequence></renameSequence>
<alterSequence ></alterSequence>
<!-- Constraints -->
<!-- Default value -->
<addDefaultValue></addDefaultValue>
<dropDefaultValue></dropDefaultValue>
<!-- Foreign key -->
<addForeignKeyConstraint></addForeignKeyConstraint>
<dropForeignKeyConstraint></dropForeignKeyConstraint>
<dropAllForeignKeyConstraints></dropAllForeignKeyConstraints>
<!-- Not null -->
<addNotNullConstraint></addNotNullConstraint>
<dropNotNullConstraint></dropNotNullConstraint>
</changeSet>
changeSet
เพื่อแบ่งกลุ่มการทำงานของ Script
- id - แต่ล่ะ changeSet ห้ามมี id ที่ซ้ำกัน เพราะจะใช้เป็น Primary key ใน Database ถ้าซ้ำจะเกิด Error และควรตั้งชื่อ id ให้สื่อความหมายด้วย เช่น สร้างตาราง จะตั้งเป็น
create-table-my_table-MMDDYYYhhmm
เพื่อจะได้ไม่ซ้ำกัน - author - ชื่อคนที่เขียน Script นี้ เพื่อ Track กลับมาได้ ถ้าเกิดมีปัญหา
Sub tags
comment
ระบุรายละเอียดของ changeSet
ว่าทำอะไร ทำไมต้องทำแบบนี้ เป็นต้น และสามารถเอารายละเอียดไปทำเป็นเอกสารประกอบได้ถ้าต้องการ
preConditions
เป็นการเช็คเงื่อนไขต่างๆตามที่เรากำหนด ถ้าไม่ผ่าน จะให้ทำอะไรต่อ ซึ่งมีให้เลือกใช้หลายแบบ อ่านเพิ่มเติม
onFail - เป็นการบอกว่าจะให้ทำอะไรต่อ ถ้าการเช็คเงื่อนไขเกิด Fail ขึ้นมา ซึ่งมีทั้งหมด 4 ทางเลือก
- HALT - สามารถเอาไว้นอก
changeSet
ได้ โดยเอาไว้แรกสุดของ changelog ถ้าเงื่อนไขไม่ผ่าน จะไม่ทำchangeSet
ที่เหลือทั้งหมดใน changelog นั้น เช่น
<preConditions onFail="HALT"> ... </preConditions> <changeSet id="1" author="liquibase"> ... </changeSet> <changeSet id="2" author="liquibase"> ... </changeSet>
ถ้าเงื่อนไขใน
preConditions
ไม่ผ่าน จะไม่ทำchangeSet
ที่ id = 1 และ 2 ต่อ- CONTINUE - เป็นการข้าม
changeSet
นั้นๆ และจะกลับมา execute อีกครั้ง เมื่อ changeSet มีการเปลี่ยนแปลง - MARK_RAN - เป็นการข้าม
changeSet
นั้นๆ แต่จะบันทึกลง databasechangelog ว่าได้ทำการ execute ไปแล้ว - WARN - ส่งคำเตือน และดำเนินการ
changeSet
ต่อและบันทึก changelog ตามปกติ สามารถเอาไว้นอกchangeSet
ได้ เหมือนกับHALT
แต่จะไม่หยุดการทำงาน
- HALT - สามารถเอาไว้นอก
ส่วนตัวแล้วจะผมใช้แค่ MARK_RAN
preConditions มีอะไรให้ใช้บ้าง ?
- changeSetExecuted
เป็นการตรวจสอบว่า changeSet
ที่ระบุได้ทำการ execute ไปแล้วหรือยัง
<!-- ตรวจสอบว่าต้องยังไม่ execute ถ้าพบว่า execute ไปแล้ว จะ Fail -->
<preConditions onFail...>
<changeSetExecuted id="action-entitie-datetime" author="author_name" changeLogFile="changelog.xml" />
</preConditions>
- columnExists
เป็นการตรวจสอบ Column ตามที่ระบุ โดยต้องบอกด้วยว่าที่ Table ไหน ถ้าเช็คว่ามี ส่วนมากจะใช้กับ <dropColumn/>
หรือการ Change ต่างๆ แต่ถ้าตรวจสอบว่าไม่มี จะใช้กับ <addColumn/>
เพราะจะเพิ่ม Column ได้ต้องไม่มี Column ตามชื่อที่ระบุ
<!-- ตรวจสอบว่าต้องมี ถ้าพบว่าไม่มี จะ Fail -->
<preConditions onFail...>
<not>
<columnExists tableName="my_table" columnName="my_column" />
</not>
</preConditions>
<!-- ตรวจสอบว่าต้องไม่มี ถ้าพบว่ามี จะ Fail-->
<preConditions onFail...>
<columnExists tableName="my_table" columnName="my_column" />
</preConditions>
- tableExists
เป็นการตรวจสอบ Table ตามที่ระบุ ถ้าเช็คว่ามี ส่วนมากจะใช้กับ <dropTable/>
หรือการ Change ต่างๆ แต่ถ้าตรวจสอบว่าไม่มี จะใช้กับ <createTable/>
<!-- ตรวจสอบว่าต้องมี ถ้าพบว่าไม่มี จะ Fail -->
<preConditions onFail...>
<not>
<tableExists tableName="my_new_table"/>
</not>
</preConditions>
<!-- ตรวจสอบว่าต้องไม่มี ถ้าพบว่ามี จะ Fail-->
<preConditions onFail...>
<tableExists tableName="my_new_table"/>
</preConditions>
ผมขอข้าม Change type ที่เป็นกลุ่มของ Exists ไปนะครับ เนื่องจาก Concept จะเหมือนกันหมด ตามที่ได้ยกตัวอย่างของ Column และ Table
- sqlCheck
เป็นการตรวจสอบ Return value ที่มาจากการเขียน Sql script ซึ่งต้องเป็นการ Return เพียง 1 row หรือ 1 value เท่านั้น
<preConditions onFail...>
<sqlCheck expectedResult="1">
SELECT COUNT(1) FROM my_table WHERE user = 'liquibase'
</sqlCheck>
</preConditions>
validCheckSum
Tag นี้ เพื่อ Execute ซ้ำอีกครั้ง โดยที่ไม่ Error เพราะปกติแล้ว เราจะไม่สามารถกลับไปแก้ไข changeSet
ที่เคย Execute ไปแล้วได้ จะมี Error ต้องสร้าง changeSet
เป็น id ใหม่เท่านั้น แต่เราไม่ได้ต้องการสร้าง changeSet
ใหม่ จะทำการแก้ไขที่ changeSet
เดิม และเพิ่ม <validCheckSum/>
โดยเอาค่า Sum ใหม่จาก Error
Validation Failed:
1 change sets check sum
com/example/changelog.xml::1::author_name was:
8:1b99d310bf88ee47d9d7daf1f33f2275 but is now: 8:af6710859b7acfa6538fed7079fe9525
<changeSet id="action-entitie-datetime" author="author_name">
<validCheckSum>8:af6710859b7acfa6538fed7079fe9525</validCheckSum>
(new logic here)
</changeSet>
rollback
สามารถระบุได้ทั้ง SQL และ Change Type หรือ อ้างอิง changeSet
อื่นๆได้ ถ้าใน changeSet
มีการเรียกใช้ rollback
เมื่อเกิด Error จะยกเลิกการทำงานภายใน changeSet
นั้นๆทั้งหมด และคืนค่าทุกอย่างกลับไปเป็นแบบเดิม มีทั้งหมด 4 แบบ
- Change Type
<changeSet id="action-entitie-datetime" author="author_name">
<createTable tableName="my_table">
<rollback>
<dropTable tableName="my_table"/>
</rollback>
</changeSet>
ถ้า createTable
สำเร็จ แต่ dropTable
ไม่สำเร็จ จะคืนค่าทุกอย่าง เท่ากับว่า คำสั่ง createTable
จะไม่มีผล ตารางจะไม่ถูกสร้าง แต่ถ้าไม่มี rollback
ครอบไว้ ตารางจะถูกสร้าง และอีกตารางก็จะไม่ถูก drop
- อ้างอิง
changeSet
อื่น
<changeSet id="action-entitie-datetime-1" author="author_name-1">
<dropTable tableName="my_table"/>
<rollback changeSetId="action-entitie-datetime-2" changeSetAuthor="author_name-2"/>
</changeSet>
ถ้า changeSet
ที่เราอ้างอิง เกิดข้อผิดพลาด จะกลับมา rollback
ที่ changeSet
นี้
- SQL
<changeSet id="action-entitie-datetime" author="author_name">
<createTable tableName="my_table"/>
<rollback >
<sql>
drop table my_table;
</sql>
</rollback>
</changeSet>
สามารถเขียนเป็น SQL ได้ ถ้าแบบ Change Type ไม่สามารถทำได้
ข้อควรระวัง ถ้าเขียนแบบ SQL ต้องอิงตาม Syntax ของ Database แต่ล่ะประเภท
Entities
หลักๆที่ผมใช้มี แบ่งได้ตามนี้ครับ
Table
- createTable
เงื่อนไข preConditions
คือ ต้องไม่มีตาราง employee
<changeSet id="create_table_employee_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="employee"/>
</not>
</preConditions>
<createTable tableName="employee">
<column name="id" type="int">
<constraints primaryKey="true"/>
</column>
<column name="employee_no" type="varchar(10)" remarks="รหัสพนักงาน">
<constraints nullable="false"/>
</column>
</createTable>
</changeSet>
- createTable (SQL)
<changeSet id="action-entitie-datetime" author="author_name">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="member"/>
</not>
</preConditions>
<sql>
CREATE TABLE public."member" (
id int8 NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_by varchar(255) NULL,
created_date timestamp NULL,
updated_by varchar(255) NULL,
updated_date timestamp NULL,
"name" varchar(255) NULL,
status varchar(255) NULL,
CONSTRAINT member_pkey PRIMARY KEY (id)
);
comment on column public."member".name is 'ชื่อสมาชิก';
</sql>
</changeSet>
- dropTable
เงื่อนไข preConditions
คือ ต้องมีตาราง users
<changeSet id="drop_table_users_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<tableExists tableName="users" />
</preConditions>
<dropTable tableName="users"/>
</changeSet>
Column
- addColumn
เงื่อนไข preConditions
คือ ต้องมีตาราง member และ ต้องไม่มี column active
<changeSet id="add_column_active_table_member_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<tableExists tableName="member" />
<not>
<columnExists tableName="member" columnName="active"/>
</not>
</preConditions>
<addColumn tableName="member">
<column name="active" type="varchar(1)" remarks="Flag Y : ใช้งาน , N : ไม่ใช้งาน" defaultValue="Y"/>
</addColumn>
</changeSet>
- dropColumn
เงื่อนไข preConditions
คือ ต้องมีตาราง users และ ต้องมี column version
<changeSet id="drop_column_version_table_users_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<tableExists tableName="users" />
<columnExists tableName="users" columnName="version"/>
</preConditions>
<dropColumn tableName="users" columnName="version"/>
</changeSet>
- SQL
<changeSet id="alter_column_phone_number_table_users_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<tableExists tableName="users" />
<columnExists tableName="users" columnName="phone_number"/>
</preConditions>
<sql>
ALTER TABLE users ALTER COLUMN phone_number DROP NOT NULL;
</sql>
</changeSet>
<changeSet id="alter_column_fine_time_table_groups_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<tableExists tableName="groups" />
<columnExists tableName="groups" columnName="fine_time"/>
</preConditions>
<sql>
ALTER TABLE public."groups" DROP COLUMN fine_time;
ALTER TABLE public."groups" ADD fine_time int4 NULL;
</sql>
</changeSet>
View
ถ้า view มีการแก้ไขอะไรก็ตาม ผมใช้วิธีเปลี่ยน id changeSet
ไปเรื่อยๆ v1 , v2 , v3 ... และเปลี่ยน DDMMYYYY ไม่ต้องสร้าง changeSet
ใหม่ ต้องใช้คู่กับการ drop view
- createView
preConditions
คือ ต้องมีตารางที่เกี่ยวข้องทั้งหมดที่ใช้ใน view v_name_02
<changeSet id="create_view_v_name_02_v1_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<tableExists tableName="groups" />
<tableExists tableName="member" />
<viewExists viewName="v_name_01" />
</preConditions>
<sqlFile path="/db/sql/v_name_02.sql" encoding="UTF-8" />
</changeSet>
/db/sql/v_name_02.sql
create view v_name_02
as select
...
from
v_name_01 v01
left join "groups" g on ...
left join "member" m on ...
- dropView
ทกุ changeSet
การ drop view ผมจะรวมไว้ใน /db/changelogs/view/drop.xml
<changeSet id="drop_view_v_name_02_v1_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<viewExists viewName="v_name_02"/>
</preConditions>
<sql>
DROP VIEW v_name_02;
</sql>
</changeSet>
และลำดับการอ่าน .xml
ใน db/db-changelog-master.xml
ผมทำแบบนี้ drop ก่อนเสมอแล้วค่อย create
<include file="/db/changelogs/view/drop.xml"/>
<include file="/db/changelogs/view/v_name_02.xml"/>
เวลามีการอัพเดต view เราแค่มาเปลี่ยน version และวันที่ใน id changeSet
ทำให้ Spring Boot เห็นว่าเป็น id ใหม่ และมา Run .xml นั้นๆ
Sequence
- dropSequence
<changeSet id="drop_table_users_DDMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<sequenceExists sequenceName="seq_users_id" />
</preConditions>
<dropSequence sequenceName="seq_users_id"/>
</changeSet>
- SQL
<changeSet id="create_sequence_hibernate_sequence_DMMYYYY" author="author_name">
<preConditions onFail="MARK_RAN">
<not>
<sequenceExists sequenceName="hibernate_sequence"/>
</not>
</preConditions>
<sql>
CREATE SEQUENCE hibernate_sequence;
</sql>
</changeSet>
Data
สำหรับ master data ที่จำเป็นต้องใช้ในการ set up project เช่น parameter ต่างๆ
- Insert
อันนี้จะเป็น member ตั้งต้น ที่ต้องมี อาจจะเป็น admin ของระบบ เป็นต้น และใน preConditions
ผมจะเขียน sql เช็คด้วยว่าต้องไม่มี member นี้
<changeSet author="author_name" id="insert_member_DDMMYYYY">
<preConditions onFail="MARK_RAN">
<and>
<tableExists tableName="member" />
</and>
<and>
<sqlCheck expectedResult="0">
select count(id) from member where name = 'admin';
</sqlCheck>
</and>
</preConditions>
<sql>
INSERT INTO public."member"
(id, created_by, created_date, updated_by, updated_date, name)
VALUES(default,'system',now(),'system',null,'admin');
</sql>
</changeSet>
ซึ่งการใช้งานส่วนใหญ่ของผมก็จะประมาณนี้ จะมากน้อยขึ้นอยู่กับความซับซ้อน และการออกแบบ Database ด้วย ยังมี Function อีกมากมายที่ผมยังไม่ได้ใช้เหมือนกัน ที่ผมได้นำเรื่อง Liquibase มาเขียน Blog เพราะผมได้นำไปช้ใน Project ที่เป็นงานจริงๆ และมันช่วยแก้ปัญหา Human error ได้มากเลยทีเดียว ทุกครั้งที่ Deploy ผมไม่ต้องมานั่งกังวลว่าจะลืม Run script อะไรไปหรือเปล่า มีอะไรตกหล่นไปหรือไม่(ถ้าตอน dev ไม่ได้มีใครไปทำอะไร manual ที่ db อ่ะนะ) ผมหวังว่า Blog นี้จะเป็นประโยชน์ไม่มากก็น้อยแก่ผู้อ่านทุกท่านครับ 😅