因此, 依樣畫葫蘆做了一個 domain class:
class Employee {
String id
String code // 員工編號
String name // 員工姓名
static constraints = {
id maxSize: 3
code maxSize: 3, nullable: false, blank: false, unique: true
name maxSize: 20
}
static mapping = {
id generator: 'assigned', name: 'code'
}
String getId() {
this.id ?: getCode()
}
void setCode(code) {
this.code = code?.toUpperCase() // 大寫
}
}
在典型的 CRUD controller 中如果有提供 code 的欄位變更, 因設計了 code 來代替 id 欄位, 而 id 負責查詢而 code 負責輸入的情形下, 需要特別處理 save() 與 update() 兩個 methods :
- save() 中...
- unique validation 需要自行處理, 而非讓它發生 org.springframework.dao.DataIntegrityViolationException
- update() 中...
- 須先行以 params.code 查詢是否輸了已經存在的資料了
- 變更 code 欄位值之前, 先刪掉"舊"的資料 (它不像下 SQL 那般直接 update key value 就好, 參考資訊: Hibernate, alter identifier/primary key )
@Transactional
def save(Employee employeeInstance) {
// ... (略) ...
try {
employeeInstance.save flush:true
} catch (DataIntegrityViolationException dive) {
employeeInstance.errors.rejectValue('code', 'default.not.unique.message' ,['code', message(code: 'employee.label'), employeeInstance.code] as Object[], message(code: 'default.not.unique.message'))
employeeInstance.code = params?.id // 還原
respond employeeInstance.errors, view:'create'
return
}
request.withFormat {
// ... (略) ...
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
def beforeUpdate(employeeInstance) {
if (Employee.findByCode(params?.code?.toUpperCase())) {
employeeInstance.errors.rejectValue('code', 'default.not.unique.message' ,['code', message(code: 'employee.label'), employeeInstance.code] as Object[], message(code: 'default.not.unique.message'))
employeeInstance.code = params?.id // 還原
throw new ValidationException(null, employeeInstance.errors)
}
}
@Transactional
def update(Employee employeeInstance) {
// ... (略) ...
if (params?.id != params?.code?.toUpperCase()) {
try {
this.&beforeUpdate(employeeInstance)
} catch(e) {
employeeInstance.discard()
} finally {
if (employeeInstance.hasErrors()) {
respond employeeInstance.errors, view:'edit'
return
}
}
// 先刪後增
employeeInstance.delete()
employeeInstance.discard()
employeeInstance.id = null
}
employeeInstance.save flush:true // 功能變為 insert 而不是 update 了
request.withFormat {
// ... (略) ...
}
要注意的是, 這是 v2.3.x 的作法: save() 與 update() methods 都冠上了 @Transactional, 而早期 transaction 處理則是交由 service 來做。所以, 為避免先查詢是否已存在的資料時, 發生
org.hibernate.HibernateException: identifier of an instance of ... was altered from ... to ...
因此將它獨立為另一 method: beforeUpdate(), 並宣告為 transaction "NOT_SUPPORTED"。完成。
ps. 花了不少時間!