【Grails 4.0】Grails - 使用MySQL來存取資料啦

因為最近把換成M1版的Mac mini了,什麼都沒有裝,所以先來記錄一下,Java類的APP安裝過程,也來記錄一下SQL的相關使用過程,讀取資料是身為後臺最重要的一環嘛。話說SDKMAN 5.10.0IntelliJ 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版本

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

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

安裝跟新增資料庫

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

新增資料庫

新增專案

  • 然後Run一下看看能不能正常啟動。這裡我是使用IDE啟動的,當然要使用Grails CLI去啟動也是可以的。
grails      # 使用Grails CLI
run-app     # 啟動Web APP

加上MySQL JDBC Driver

dependencies {
    runtime 'mysql:mysql-connector-java:8.0.23'
}

連接資料庫

com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/demo

  • 當然,也可以使用像Sequel Pro的第三方APP來登入

錯誤處理

mysql -uroot -p                             # 登入MySQL CLI
SHOW VARIABLES LIKE 'validate_password%';   -- 顯示出跟Password有關的變數
SET GLOBAL validate_password.policy=LOW;    -- 把密碼的要求等級 => LOW
SHOW VARIABLES LIKE 'validate_password%';   -- 再確認一下

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;                             -- 顯示資料庫列表

新增資料表

相關設定檔

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

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>');
    }
}
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這個這設定檔的緣故。

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

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)
    }
}

範例程式碼下載

後記

  • 這篇寫得有點亂,不過至少把關鍵字都記下來了,以後方便查詢。