因此, 依樣畫葫蘆做了一個 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. 花了不少時間!
沒有留言:
張貼留言