【Grails 4.0】Grails - 使用MySQL來存取資料啦
因為最近把換成M1版的Mac mini了,什麼都沒有裝,所以先來記錄一下,Java類的APP安裝過程,也來記錄一下SQL的相關使用過程,讀取資料是身為後臺最重要的一環嘛。話說SDKMAN 5.10.0跟IntelliJ IDEA 2020.3.1也開始支援M1了,真的是可喜可賀啊…
作業環境
項目 | 版本 |
---|---|
CPU | Apple M1 |
macOS | Big Sur 11.2.3 |
SDKMAN | 5.11 ARM64 |
Homebrew | 3.1.3 ARM64 |
Java | 8.0.282-zulu ARM64 |
IntelliJ IDEA | 2021.1 ARM64 |
MySQL | 8.0.23 x86_64 |
Grails | 4.0.10 |
環境安裝
查看macOS的CPU版本
- 續上集, 使用SDKMAN安裝環境,首先可以使用arch這個命令來查看跟設定,當然,因為目前的macOS 11有Rosetta2 編譯器的關係,才能做切換的動作,記要把SDKMAN的支援Rosetta2的功能關掉,以獲得最好的原生支援,當然使用指令也可以,在APP上去做關閉也是可以的。
arch -x86_64 zsh # 執行i386的環境 => zsh (Rosetta2)
arch -ARM64 zsh # 執行ARM64的環境 => zsh
sudo code .sdkman/etc/config # 設定SDKMan的設定檔 => VSCode
sdkman_rosetta2_compatible=false # 把支援Rosetta2的功能關掉
安裝Java for M1
- 在這裡就來安裝OpenJDK for ARM64的版本吧
sdk list java # 列出可以安裝的java列表
sdk install java 8.0.282-zulu # 安裝支援ARM64的版本
安裝Grails
sdk install grails 4.0.10
安裝IntelliJ IDEA for M1
- 這個IDE - IntelliJ IDEA就看個人要不要安裝了,當然要使用用其它的IDE也是可以的,這裡是使用Homebrew安裝。
brew install --cask intellij-idea
安裝跟新增資料庫
- 其實Grails支援性還滿大的,像MySQL / GraphQL / MongoDB / Neo4j,拜GORM所賜,免寫SQL嘛ㄟ通,這裡選擇MySQL Community Server 8.0.23的版本進行安裝,然後使用MySQL CLI做新增資料庫的動作。
brew install mysql
mysql.server stop # 關閉MySQL服務
mysql.server start # 開啟MySQL服務
mysql_secure_installation # 設定MySQL的root安全密碼 (12345678)
mysql -uroot -p # 登入MySQL CLI
show databases; -- 顯示資料庫列表
create database demo; -- 建立空白資料庫
show databases;
exit -- 離開MySQL CLI
新增資料庫
新增專案
- 首先,新增加一個Grails的專案,當然要先把OpenJDK 1.8 + Grails SDK 4.0.10的路徑寫入正確的值才行。
- 然後Run一下看看能不能正常啟動。這裡我是使用IDE啟動的,當然要使用Grails CLI去啟動也是可以的。
grails # 使用Grails CLI
run-app # 啟動Web APP
加上MySQL JDBC Driver
dependencies {
runtime 'mysql:mysql-connector-java:8.0.23'
}
連接資料庫
- 在這裡,我們可以使用內建的IDE來讀取資料庫
- 比較要注意的是,新版的MySQL Driver的claasname,由com.mysql.jdbc.Driver => com.mysql.cj.jdbc.Driver
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/demo
- 當然,也可以使用像Sequel Pro的第三方APP來登入
錯誤處理
- 不過呢,有時候會發生像MySQL ERROR 1819 (HY000): Your password does not satisfy the current policy requirements的警告,明明帳號密碼都對,但就是登不進去,真的是…,這是因為對密碼的安全要求預設是MEDIUM,所以啊,這種12345678的這種超簡單密碼,自然就無法登入了…XD,沒關係,我們把要求等級改低一點就可以了。
mysql -uroot -p # 登入MySQL CLI
SHOW VARIABLES LIKE 'validate_password%'; -- 顯示出跟Password有關的變數
SET GLOBAL validate_password.policy=LOW; -- 把密碼的要求等級 => LOW
SHOW VARIABLES LIKE 'validate_password%'; -- 再確認一下
- 或者是會碰到像MySQL said: Authentication plugin ‘caching_sha2_password’ cannot be loaded的這種問題,主要是因為MySQL 8之後,身份驗證的PlugIn進步成了caching_sha2_password,而造對有些只對mysql_native_password的有反應的APP,無法登入,沒關係,我們把它改回來就好了。
use mysql; -- 使用mysql資料庫
SELECT Host, User, plugin from user; -- 顯示有關PlugIn的值
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '12345678'; -- PlugIn => mysql_native_password
FLUSH PRIVILEGES; -- 重新生效
建立資料庫
- 在這裡會利用CLI去建立一個叫做『eBook』的資料庫。
mysql -uroot -p # 登入MySQL CLI
CREATE DATABASE eBook; -- 建立<eBook>資料庫
SHOW DATABASES; -- 顯示資料庫列表
新增資料表
相關設定檔
- 先設定application.yml檔,上面填入資料庫的相關資訊。
- 然後連連看h2-console是否正常。
environments:
development:
server:
url: http://localhost:8080/
contextPath: /
port: 8080
dataSource:
dbCreate: create-drop
url: jdbc:mysql://localhost:3306/eBook?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: 12345678
新增資料表
- 在這裡,我們在domain的資料夾下,新增一個叫『idv.william.Book』的Domain Class,然後重Run一次專案,一個叫Book的Table就長出來了,當然使用CLI也是可以的。
grails create-domain-class idv.william.Book
設定Domain欄位
- 設定欄位,我們都知道ISBN是唯一值,是書的身份證字號,所以要在Constraints這裡做設定,這裡就不細說了,只要將其設定後,重新啟動,欄位就自動跑出來了。
package grailssql.idv.william
class Book {
static constraints = {
isbn13(size: 13, nullable: false, unique: true) // (13碼 / 不得為空值 / 是唯一值)
title(nullable: false)
releaseDate(nullable: false)
}
Long isbn13
String title
Date releaseDate
}
USE eBook; # 使用該資料表
DESCRIBE book; # 顯示該資料表結構
CRUD
GORM
- 這個新增一個BookController,使用最基本的Object Relational Mapping (GORM)來處理資料,這裡就只做個簡單的示意,可以參考上一篇文的說明。
class BookController {
/**
* 新增書籍 => http://localhost:8080/book/addBook
* @return
*/
def addBook() {
def book = new Book()
book.isbn13 = 9789863317494
book.title = '圖解星座神話故事'
book.releaseDate = new Date('2015/05/28')
def result = book.save(flash: true)
if (result == null) { render('<h1>Fail</h1>'); return }
render('<h1>Success</h1>');
}
}
- 我們使用GET帶參數的方式,使它更可以靈活的使用。
class BookController {
def index() {}
/**
* 新增書籍 => http://localhost:8080/book/addBook?isbn13=9789572666203&title=鬼滅之刃_23完&releaseDate=2021/04/16
* @return
*/
def addBook() {
def params = this.getParams()
def isbn13 = params['isbn13'] as Long
def title = params['title'] as String
def releaseDate = params['releaseDate'] as String
def book = new Book()
book.isbn13 = isbn13
book.title = title
book.releaseDate = new Date(releaseDate)
def result = book.save(flash: true)
if (result == null) { render('<h1>Fail</h1>'); return }
render('<h1>Success</h1>');
}
}
UrlMappings
- 大家有沒有想過,為什麼網址這樣Key就可以對應到正確的function呢?主要是因為UrlMappings這個這設定檔的緣故。
- 可是照這樣寫,只能使用GET來傳參數,如果要使用POST / DELETE…等現在比較流行的Restful API的方式呢?其實只要修改UrlMappings就可以做得到了。
- 這裡我們使用API測試神器 - Postman來測試 => /eBook/new。
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/eBook/"(controller: 'book', action: 'save', method: 'GET')
"/eBook/$id?"(controller: 'book', action: 'update', method: 'POST')
"/eBook/$id?"(controller: 'book', action: 'update', method: 'PUT')
"/eBook/$id?"(controller: 'book', action: 'delete', method: 'DELETE')
"/eBook/new/"(controller: 'book', action: 'addBook', method: 'POST')
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
import grails.converters.JSON
import grailssql.idv.william.Book
class BookController {
def index() { render("預設值") }
def addBook() {
def json = this.request.getJSON()
def isbn13 = json['isbn13'] as Long
def title = json['title'] as String
def releaseDate = json['releaseDate'] as String
def book = new Book()
book.isbn13 = isbn13
book.title = title
book.releaseDate = new Date(releaseDate)
def result = book.save(flash: true)
if (result == null) { render([result: false] as JSON); return }
render([result: true] as JSON);
}
def save() { render "新增" }
def update(Long id) { render "修改 ${id}" }
def delete(Long id) { render "刪除 ${id}" }
def show(Long id) { render "查詢 ${id}" }
}
{
"isbn13": "9789578038097",
"releaseDate": "2011/12/26",
"title": "誰搬走了我的乳酪?"
}
createCriteria
- 搜尋在資料庫中有著很重要的地位,這裡介紹一下criteria語法。然後呢,一定要是domain才能使用它。
- 加入三筆測試資料後,在執行測試中可以發現,會有兩組答案出現,但要怎麼方便的使用它呢?這裡介紹一下GSON - Grails Views。
- 因為正常前後端是會分離的,比如說前端用Vue.js,後端用Grails,現在很少混在一起寫,所以使用GSP - Groovy Server Pages的機會就少了,整合網頁的功能就不需要了,安裝一下GSON - 專職打API簡單又輕巧。
dependencies {
compile "org.grails.plugins:views-json:2.0.4"
compile "org.grails.plugins:views-json-templates:2.0.4"
}
class UrlMappings {
"/eBook/criteria/"(controller: 'book', action: 'criteria', method: 'POST')
}
[
{
"isbn13": "9789578038097",
"releaseDate": "2011/12/26",
"title": "誰搬走了我的乳酪?"
},
{
"isbn13": "9789578038098",
"releaseDate": "2011/12/26",
"title": "誰搬走了我的乳酪2?"
},
{
"isbn13": "9789572666203",
"releaseDate": "2021/04/16",
"title": "鬼滅之刃_23完"
}
]
- 加上跟function同名的GSON檔
import grailssql.idv.william.Book
model {
ArrayList<Book> books
}
json {
bookList(books) { Book book ->
title book.title
isbn book.isbn13
releaseDate book.releaseDate
}
}
import grailssql.idv.william.Book
class BookController {
def criteria() {
def books = Book.createCriteria().list {
like("title", "誰搬走了%")
and {
between("isbn13", 9789578038090, 9789578038099)
}
maxResults(10)
order("isbn13", "desc")
}
respond([books: books])
}
}
HQL
- 這裡來介紹一下HQL語法,主要有四個方法 - find() / findAll() / executeQuery() / executeUpdate(),在這裡以find()來做個簡單的[執行測試]((http://localhost:8080/eBook/hql)
- 為什麼會有HQL - HibernateSession SQL這個東西呢?因為啊,每個資料庫多多少少都有自己的語法,如果要做到一寫百用,就一定要轉譯才行。
import grails.converters.JSON
import grailssql.idv.william.Book
class BookController {
def hql() {
def hqlQueryString = 'from Book';
def books = Book.findAll(hqlQueryString)
respond([books: books])
}
}
import grailssql.idv.william.Book
model {
ArrayList<Book> books
}
json {
result(books) { Book book ->
title book.title
isbn book.isbn13
releaseDate book.releaseDate
}
}
- 有時候測試資料太多時,一重開就不見了,這時候就可以在application.yml的dbCreate去做設定,讓它不要重新建立資料庫。
dbCreate: update
SQL
- 如果要求效能的時候,使用原生的SQL就是一個好方法,這裡也來做個簡單的[執行測試]](http://localhost:8080/eBook/sql)
import grails.converters.JSON
import grailssql.idv.william.Book
import groovy.sql.Sql
class BookController {
def sql() {
def connection = Sql.newInstance(
"jdbc:mysql://localhost/eBook",
"root",
"12345678",
"com.mysql.cj.jdbc.Driver"
)
def queryString = 'select * from book'
ArrayList<String> result = []
connection.eachRow(queryString) {resultSet ->
result.push(resultSet[2])
}
render([result: result] as JSON)
}
}
讀取設定檔
- 一般很常用到設定檔去做值的變換,但如果寫在Code裡面太難找了,這裡來跟大家說明如何讀取application.yml設定檔的值。
class BookController {
def index() {
// 讀取application.yml設定檔
def applicationYml = this.grailsApplication.config
def server = applicationYml.environments.development.server as HashMap<String, String>
render("${server}")
}
}
class BookController {
def index() {
// 讀取文字檔 (grails-app/conf/resources/demo.txt)
def resourceClassPath = 'classPath:resources/demo.txt'
def resource = this.grailsApplication.mainContext.getResource(resourceClassPath)
def fileString = IOUtils.toString(resource.inputStream)
println(fileString)
}
}
範例程式碼下載
後記
- 這篇寫得有點亂,不過至少把關鍵字都記下來了,以後方便查詢。