【Grails 4.0】Grails - 使用Apache Groovy編程語言的開源Web應用程序框架
Grails是一種根基於Groovy, Spring framework與Hibernate的 Web Framework,也就是說,它把所有要開發網站所需要用到的功能都結合起來了,照官方的說法是 - 『A powerful Groovy-based web application framework for the JVM built on top of Spring Boot』,因為公司需要用到,所以在這裡記錄一下。身為單身狗的我,祝大家情人節快樂,請上帝賜給我一個可愛的正妹吧。
安裝
安裝SDKMAN!
curl -s "https://get.sdkman.io" | bash
sdk version
安裝JDK
- 因為Grails用的Groovy語言是Java平台的語言,所以要安裝JDK - Java Development Kit
sdk list java # 查看支援的版本
sdk install java # 安裝最穩定的版本
sdk install java 11.0.10.hs-adpt # 安裝特定版本
java --version # 查看Java的版本
安裝Grails
- 接下來安裝Grails
sdk list grails # 查看支援的版本
sdk install grails # 安裝最穩定的版本
sdk install grails 4.0.7 # 安裝特定版本
grails --version # 查看Grails的版本
查看當前安裝的版本
- 當然也可以查看當前安裝的版本
sdk current java
sdk current grails
切換當前版本
- 當然,這一類的工具最重要的就是要有『切換版本』的功能
sdk list java
sdk install java 8.0.282.hs-adpt
sdk default java 8.0.282.hs-adpt
使用Grails
建立空白資料夾
- 首先先建立一個空白的資料夾,然後進入Grails CLI,建立Web-APP
cd ~/Desktop
md grails_demo # 建立空白資料夾
cd grails_demo
grails # 啟動Grails CLI
create-app # 建立Web-APP
啟動Web-APP
- Grails CLI內,啟動Web-APP,完成後,就可以在這裡看到Grails的啟動畫面了
run-app # 啟動Web-APP
run-app -port=8787 # 建立Web-APP (特定Port)
關閉Web-APP
- 有啟動當然就有關閉
stop-app
exit
萬一把Termail關掉怎麼辦?
lsof -i:8080 # 查看8080Port是哪些程式在使用的
kill <PID> # 關閉該程式 with <PID>
Grails的第一課
Grails檔案長相
- 當然除了Grails最新版的官方說明書說明之外,看圖說故事是最快的
- grails-app/conf/spring/application.yml,就類似iOS的info.plist設定檔,設定一些該APP的環境參數
- grails-app/init/grails_demo/Application.groovy,就類似iOS的main.m,程式的進入點,沒事是不會動到它的
- grails-app/init/grails_demo/BootStrap.groovy,就類似iOS的ViewDidLoad,程式的啟動點,一些整體初始化的工作都可以在這裡執行
BootStrap
- Grails啟動設定
package grails_demo
class BootStrap {
def init = { servletContext ->
this.initSetting()
}
def destroy = {}
private def initSetting() {
def message = "==> The Message is ${this.message()}" // 可以用def當成回傳型態 => Object (Grails)
println(message)
}
private String message() {
String message = 'Grails is Good !!!' // 也可以直接用String當成回傳型態 => String (Java)
return message
}
}
建立Controller
- 建立一個名叫『first』的『Controller』,位置在『grails-app/controllers/grails_demo/FirstController.groovy』,這個跟iOS的ViewController很像,可以用Code寫HTML,網址就是http://localhost:8080/first/index
create-controller first
package grails_demo
class FirstController {
def index() {
render('<h1>Grails初體驗</h1>')
}
}
- 當然也可以使用HTML來處理畫面,在這裡叫做*.gsp - Groovy Server Pages,func名稱就是gsp名稱,也是網址名稱http://localhost:8080/first/other
package grails_demo
class FirstController {
/// index()就是指grails-app/views/first/index.gsp => http://localhost:8080/first/index
def index() {
// render('<h1>Grails初體驗</h1>')
}
/// other()就是指grails-app/views/first/other.gsp => http://localhost:8080/first/other
def other() {}
}
建立Domain
Create-Domain-Class
- 使用指令來建立Domain-Class,基本上會建立在grails-app/domain/grails_demo/之下
create-domain-class myBook
run-app
連接Database Console
- 這裡使用H2 Database,開啟Console頁面,然後JDBC URL改成jdbc:h2:mem:devDb,連進去之後就可以發現,剛剛建立的Class居然已經建立在DB內了,真的是太神奇了
jdbc:h2:mem:devDb
設定資料屬性
- 這裡就來新增一些欄位,跟相關屬性,記得要重開Web-APP才可以生效
- 不得不說,這一類的ORM真的太好用了,在這裡叫做GORM - Grails Object Relational Mapping,不用去學SQL語法,就可以簡單入門,雖然不能取代SQL,但在基本的應用上已經可以解決大部分的問題了
package grails_demo
class MyBook {
static constraints = {
title unique: true
releaseDate nullable: true
}
String title
Date releaseDate
}
新增資料
- GORM的第一步就先新增一筆資料吧,順便也使用一下簡單的GSP Tags
- 利用[http://localhost:8080/first/index?title=重新認識 Vue.js](http://localhost:8080/first/index?title=重新認識 Vue.js)就可以新增一本書籍的資料到資料庫上
package grails_demo
class BootStrap {
def init = { servletContext ->
this.initSetting()
}
def destroy = {}
private def initSetting() {
TimeZone.setDefault(TimeZone.getTimeZone('UTC'))
}
}
package grails_demo
import grails.artefact.Controller
import java.time.*
class FirstController {
/// index()就是指grails-app/views/first/index.gsp => http://localhost:8080/first/index
def index() {
def title = this.queryString('title', this)
def result = this.appendBook(title, new Date())
return [result: result]
}
/// 新增書籍
private def appendBook(String title, Date releaseDate) {
def book = new MyBook()
book.title = title
book.releaseDate = releaseDate
return book.save()
}
/// 取得網址上的queryString
private def queryString(String key, Controller self) {
def queryString = self.getParams()
return queryString[key]
}
}
<!doctype html>
<html>
<head>
<title>Grails初體驗</title>
</head>
<body>
<g:if test="${result}">
<h1>新增成功 - ${result.title}</h1>
</g:if>
<g:else>
<h1>新增失敗</h1>
</g:else>
</body>
安裝好用的IDE
為什麼不用VSCode?
- 寫後臺不不外乎就是CRUD - 新增 / 修改 / 刪除 / 查詢,因為Grails會自動產生一些相關的方法,所以在點點點的時候,希望能自帶方法出來,所以要安裝IDE。
Spring Tool Suite
- Spring Tool Suite,是基於Eclipse改良而成的IDE,免錢,但是因為沒用過,所以放棄
IntelliJ IDEA
- IntelliJ IDEA,這家的商品因為生產Android Studio的關係,變的很熱門,不過它的產品真的是intelli,之所以叫做Intelli『J』,就是因為是以JVM為主的產品,而且還推出了Kotlin程式語言,也成為了Android開發的官方主力語言。缺點嘛…要錢,而且很貴,就先試用試用吧。
安裝IntelliJ-IDEA
- 安裝IntelliJ-IDEA,其實它有Community - 免錢跟Ultimate - 要錢,這裡當然是安裝Ultimate版的來試用30天看看,使用Homebrew來裝比較快,趕快打開來試試,用起來有沒有比較厲害啊?XD
java --version
brew install --cask intellij-idea
安裝plugin
- IntelliJ-IDEA的套件管理工具是叫gradle,就按圖施工,保證成功,當然重開專案也是會更新的,設定檔在src/build.gradle
- 這裡先安裝Excel-Import plugin,一個用來讀取Excel的工具,當然官方說明也寫的很仔細
- 另外安裝MySQL的JDBC驅動 - MySQL Connector/J
dependencies {
compile 'org.grails.plugins:excel-import:3.0.2'
runtime 'mysql:mysql-connector-java:8.0.23'
}
切換專案環境JDK
- 不得不說,IDE最重要的功能之一,就是切換SDK版本,因為有時候要換到舊版本才能跑
資料庫的操作
常用小工具
- 基本的新增功能,因為GORM的關係,只要使用save()就可以把資料存到資料庫了
- 這裡新增一個ApiController,用來放置API使用
- 另外個人習慣寫一些小工具,就放在utils資料夾中的package中
package idv.william
import grails.artefact.Controller
import java.text.SimpleDateFormat
// MARK: - 小工具
final class Utility {
// MARK: - 網頁相關
/// 取得網址上的queryString
def queryString(String key, Controller self) {
def queryString = self.getParams()
return queryString[key]
}
/// ["2021-01-01 01:23:45" => 1577836800](https://timestamp.online/)
def dateStringToTimestamp(String dateString, Enumeration.DateFormat pattern) {
if (dateString == null || pattern == null) { return null }
def format = new SimpleDateFormat(pattern.toString())
def timestamp = format.parse(dateString).getTime() / 1000
return timestamp
}
/// [2021-01-01 01:23:45 => 1577836800](https://timestamp.online/)
def dateToTimestamp(Date date, Enumeration.DateFormat pattern) {
if (date == null || pattern == null) { return null }
def dateString = this.dateToString(date, pattern)
def timestamp = this.dateStringToTimestamp(dateString, pattern)
return timestamp
}
/// "2020-01-01" => 2021-01-01 01:23:45
def stringToDate(String dateString, Enumeration.DateFormat pattern) {
if (dateString == null || pattern == null) { return null }
def format = new SimpleDateFormat(pattern.toString())
def date = format.parse(dateString)
return date
}
/// 2021-01-01 01:23:45 => "2021-01-01"
def dateToString(Date date, Enumeration.DateFormat pattern) {
if (date == null || pattern == null) { return null }
def format = new SimpleDateFormat(pattern.toString())
def dateString = format.format(date)
return dateString
}
}
package idv.william
/// MARK: - 常用列舉
final class Enumeration {
/// [日期格式](http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns)
enum DateFormat {
Short("yyyy-MM-dd"),
Middle("yyyy-MM-dd HH:mm"),
Long("yyyy-MM-dd HH:mm:ss"),
Full("yyyy-MM-dd HH:mm:ss ZZZ")
private final String value
/// 利用文字尋找Enum
static find(String value) { values().find { it.value == value } }
/// 初始化
DateFormat(String value) { this.value = value }
/// override toString() => 取得內容文字的值
String toString() { return value }
}
}
新增書籍API
- 寫完後,可以試試[API](http://localhost:8080/api/appendBook?title=重新認識Vue.js:008天絕對看不完的Vue.js 3指南&releaseDate=2021-02-09),是不是簡單又好用啊?終於可以轉生成為C/P值極高的高端打字工了。
package grails_demo
import grails.converters.JSON
import idv.william.Enumeration
import idv.william.Utility
final class ApiController {
private def util = new Utility()
def index() {}
// MARK: - API
/// 新增書籍
/// => http://localhost:8080/api/appendBook?title=重新認識Vue.js:008天絕對看不完的Vue.js 3指南&releaseDate=2021-02-09
/// => http://localhost:8080/api/appendBook?title=就算忙盲茫 我決定給自己一點時間&releaseDate=2021-03-02
/// => http://localhost:8080/api/appendBook?title=天橋上的魔術師&releaseDate=2011-11-30
def appendBook() {
def title = util.queryString('title', this)
def releaseDate = util.queryString('releaseDate', this)
def json = this.appendBookResult(title, releaseDate)
render(json)
}
// MARK: - Function
/// 新增書籍
private def appendBookResult(String title, String releaseDate) {
if (title == null) {
def json = [error: '沒打書名'] as JSON
return json
}
if (releaseDate == null) {
def json = [error: '沒打發行日期'] as JSON
return json
}
def myBook = new MyBook()
myBook.title = title
myBook.releaseDate = util.stringToDate(releaseDate, Enumeration.DateFormat.Short)
def result = myBook.save() // 將資料儲存到資料庫中
def json = [result: result] as JSON // 將Map => JSON
return json
}
}
搜尋書籍API
- 在這裡,我們以id做為搜尋的依據,就是findBy系的function,是不是很方便啊?
final class ApiController {
/// 搜尋書籍 => http://localhost:8080/api/searchBook?id=3
def searchBook() {
def id = util.queryString('id', this) as Long
def book = MyBook.findById(id)
render(book.title)
}
}
修改書籍API
- 在這裡,還是以id做為修改的依據 - 唯一值,加上要修改的title內容,簡單來說,就是『搜尋 => 設定數值 => 記錄』,不過在這裡要注意的是,要加上@Transactional這個Tag,不然的話,實際上在資料庫是沒有改的。
package grails_demo
import grails.gorm.transactions.Transactional
final class ApiController {
/// 修改書籍 => http://localhost:8080/api/editBook?id=3&title=天橋上的魔術師ABC
@Transactional
def editBook() {
def id = util.queryString('id', this) as Long
def title = util.queryString('title', this) as String
def myBook = MyBook.findById(id)
myBook.title = title
def result = myBook.save()
def json = [result: result] as JSON
render(json)
}
}
刪除書籍API
- 最後,還是以id做為刪除的依據,就是『搜尋 => 刪除』,不過在這裡要注意的是,還是要加上@Transactional這個Tag,不然的話在資料庫還是刪不掉的。
package grails_demo
import grails.gorm.transactions.Transactional
final class ApiController {
/// 刪除書籍 => http://localhost:8080/api/deleteBook?id=3
@Transactional
def deleteBook() {
def id = util.queryString('id', this) as Long
def title = util.queryString('title', this) as String
def myBook = MyBook.findById(id)
myBook.delete()
render("刪除完成")
}
}
範例程式碼下載
- 如果有遇到grails不能執行的情況,請將build資料夾刪除,再執行即可
後記
- 學這個能在台灣找工作嗎?我上1111人力銀行搜尋了一下,發現…很可怕,居然完全沒有相關的工作,應該是它寫的搜尋法太差了吧?還是說這個工作太穩了?XD