(快速參考)

4 設定

版本 6.2.0

4 設定

在一個採用「慣例優於組態」的架構中,我們現在探討這個主題,這似乎有點奇怪。使用 Grails 的預設設定,你實際上可以在完全不進行任何組態的情況下開發應用程式,就像快速入門中展示的那樣,但重要的是在需要時學習在哪裡以及如何覆寫慣例。使用者指南的後續章節將提到你可以使用哪些組態設定,但不會提到如何設定它們。假設你至少已讀過本章的第一節!

4.1 基本組態

Grails 中的組態通常分為 2 個領域:建置組態和執行時期組態。

建置組態通常透過 Gradle 和 build.gradle 檔案完成。執行時期組態預設在 grails-app/conf/application.yml 檔案中以 YAML 指定。

如果你偏好使用 Grails 2.0 風格的 Groovy 組態,那麼可以使用 Groovy 的 ConfigSlurper 語法來指定組態。有兩個 Groovy 組態檔案可用:grails-app/conf/application.groovygrails-app/conf/runtime.groovy

  1. 對不依賴應用程式類別的組態,請使用 application.groovy

  2. 對依賴應用程式類別的組態,請使用 runtime.groovy

這個區分是有必要的,因為在 application.groovy 中定義的組態值可供 Grails CLI 使用,而 Grails CLI 需要在應用程式編譯之前載入 application.groovy。當 CLI 執行這些命令時,application.groovy 中對應用程式類別的參照會導致例外發生

Error occurred running Grails CLI:
startup failed:script14738267015581837265078.groovy: 13: unable to resolve class com.foo.Bar

對於 Groovy 組態,下列變數可供組態指令碼使用

變數 說明

userHome

正在執行 Grails 應用程式的帳戶的家目錄位置。

grailsHome

你安裝 Grails 的目錄位置。如果設定了 GRAILS_HOME 環境變數,則會使用它。

appName

應用程式名稱,如 build.gradle 中所示。

appVersion

應用程式版本,如 build.gradle 中所示。

例如

my.tmp.dir = "${userHome}/.grails/tmp"

使用 GrailsApplication 存取組態

如果你要讀取執行時期組態設定,也就是在 application.yml 中定義的設定,請使用 grailsApplication 物件,它在控制器和標籤庫中可用做變數

class MyController {
    def hello() {
        def recipient = grailsApplication.config.getProperty('foo.bar.hello')

        render "Hello ${recipient}"
    }
}

grailsApplication 物件的 config 屬性是 Config 介面的實例,並提供許多有用的方法來讀取應用程式的組態。

特別是,getProperty 方法(如上所示)對於有效率地擷取組態屬性很有用,同時指定屬性類型(預設類型為字串)和/或提供預設的備用值。

class MyController {

    def hello(Recipient recipient) {
        //Retrieve Integer property 'foo.bar.max.hellos', otherwise use value of 5
        def max = grailsApplication.config.getProperty('foo.bar.max.hellos', Integer, 5)

        //Retrieve property 'foo.bar.greeting' without specifying type (default is String), otherwise use value "Hello"
        def greeting = grailsApplication.config.getProperty('foo.bar.greeting', "Hello")

        def message = (recipient.receivedHelloCount >= max) ?
          "Sorry, you've been greeted the max number of times" :  "${greeting}, ${recipient}"
        }

        render message
    }
}

請注意,Config 實例是基於 Spring 的 PropertySource 概念合併的組態,並從環境、系統屬性和本機應用程式組態中讀取組態,將它們合併成一個單一物件。

GrailsApplication 可以輕鬆注入到服務和其他 Grails 製品中

import grails.core.*

class MyService {
    GrailsApplication grailsApplication

    String greeting() {
        def recipient = grailsApplication.config.getProperty('foo.bar.hello')
        return "Hello ${recipient}"
    }
}

GrailsConfigurationAware 介面

在執行階段動態存取組態會對應用程式的效能產生輕微的影響。另一種方法是實作 GrailsConfigurationAware 介面,它提供一個 `setConfiguration` 方法,在類別初始化時接受應用程式組態作為參數。然後,你可以將相關的組態屬性指定給類別的執行個體屬性,以供稍後使用。

`Config` 執行個體具有與注入的 `GrailsApplication` config 物件相同的屬性和用法。以下是前一個範例中的服務類別,它使用 `GrailsConfigurationAware`,而不是注入 `GrailsApplication`

import grails.core.support.GrailsConfigurationAware

class MyService implements GrailsConfigurationAware {

    String recipient

    String greeting() {
        return "Hello ${recipient}"
    }

    void setConfiguration(Config config) {
        recipient = config.getProperty('foo.bar.hello')
    }

}

Spring 值註解

你可以使用 Spring 的 Value 註解來注入組態值

import org.springframework.beans.factory.annotation.*

class MyController {
    @Value('${foo.bar.hello}')
    String recipient

    def hello() {
        render "Hello ${recipient}"
    }
}
在 Groovy 程式碼中,你必須在 `Value` 註解的值周圍使用單引號,否則它會被解釋為 GString,而不是 Spring 表達式。

如你所見,在存取組態設定時,你使用與定義它們時相同的點號表示法。

4.1.1 YML 格式 Config 的選項

`application.yml` 檔案是在 Grails 3.0 中引入的,而 YAML 現在是組態檔案的首選格式。

使用系統屬性/命令列參數

假設你正在使用 `JDBC_CONNECTION_STRING` 命令列參數,並且你想要在 yml 檔案中存取相同的內容,那麼可以按照下列方式進行

production:
    dataSource:
        url: '${JDBC_CONNECTION_STRING}'

同樣地,可以存取系統參數。

如果你使用 `./gradlew bootRun` 來啟動應用程式,則需要在 `build.gradle` 中加入這一段,以修改 `bootRun` 目標

bootRun {
    systemProperties = System.properties
}

對於測試,需要將 `test` 任務變更如下

test {
    systemProperties = System.properties
}

外部組態

Grails 預設會從 `./config` 或目前目錄讀取 `application.(properties|yml)`。由於 Grails 是 SpringBoot,因此組態選項也可用,有關文件,請參閱:https://spring-docs.dev.org.tw/spring-boot/docs/2.7.16/reference/html/features.html#features.external-config.files

4.1.2 內建選項

Grails 有一組核心設定值得了解。它們的預設值適用於大多數專案,但了解它們的作用很重要,因為你可能稍後需要其中一個或多個。

執行階段設定

在執行階段,即 `grails-app/conf/application.yml`,還有更多核心設定

  • grails.enable.native2ascii - 如果不需要 Grails i18n 屬性檔案的 native2ascii 轉換,請將此設定為 false(預設值:true)。

  • grails.views.default.codec - 設定 GSP 的預設編碼機制 - 可以是 'none'、'html' 或 'base64'(預設值:'none')。若要降低 XSS 攻擊風險,請將此設定為 'html'。

  • grails.views.gsp.encoding - 用於 GSP 原始檔的檔案編碼(預設值:'utf-8')。

  • grails.mime.file.extensions - 是否使用檔案副檔名在 內容協商 中指定 MIME 類型(預設值:true)。

  • grails.mime.types - 支援的 MIME 類型清單,用於 內容協商

  • grails.serverURL - 指定絕對連結伺服器 URL 部分的字串,包括伺服器名稱,例如 grails.serverURL="http://my.yourportal.com"。請參閱 createLink。重導也會使用此設定。

  • grails.views.gsp.sitemesh.preprocess - 決定是否執行 SiteMesh 前置處理。停用此設定會降低網頁呈現速度,但如果您需要 SiteMesh 來剖析從 GSP 檢視產生的 HTML,停用此設定是正確的選擇。如果您不了解這個進階屬性,不用擔心:請將它設定為 true。

  • grails.reload.excludesgrails.reload.includes - 設定這些指令會決定專案特定原始檔的重新載入行為。每個指令都會取得一個字串清單,其中包含專案原始檔的類別名稱,這些類別名稱應從重新載入行為中排除或包含,具體取決於使用 bootRun 任務在開發環境中執行應用程式時。如果設定了 grails.reload.includes 指令,則只會重新載入清單中的類別。

4.1.3 記錄

從 Grails 3.0 開始,記錄由 Logback 記錄架構 處理,並且可以使用 grails-app/conf/logback.xml 檔案進行設定。

從 Grails 5.1.2 開始,已移除對 Groovy 設定 (grails-app/conf/logback.groovy) 的支援 (由 logback 1.2.9 移除)。您可以透過將 logback-groovy-config 函式庫新增到專案中,來新增 Groovy 設定。

如需設定記錄的詳細資訊,請參閱 Logback 文件 中的相關主題。

4.1.3.1 記錄器名稱

Grails 人工製品 (控制器、服務 …​) 會自動注入 log 屬性。

在 Grails 3.3.0 之前,Grails 人工製品的記錄器名稱遵循慣例 grails.app.<type>.<className>,其中 type 是人工製品的類型,例如 controllersservices,而 className 是人工製品的完整限定名稱。

Grails 3.3.x 簡化了記錄器名稱。以下範例說明了變更

BookController.groovy 位於 grails-app/controllers/com/company,未註解 @Slf4j

記錄器名稱 (Grails 3.3.x 或更高版本)

記錄器名稱 (Grails 3.2.x 或更低版本)

com.company.BookController

grails.app.controllers.com.company.BookController

位於 grails-app/controllers/com/companyBookController.groovy 使用 @Slf4j 加上註解

記錄器名稱 (Grails 3.3.x 或更高版本)

記錄器名稱 (Grails 3.2.x 或更低版本)

com.company.BookController

com.company.BookController

位於 grails-app/services/com/companyBookService.groovy 未使用 @Slf4j 加上註解

記錄器名稱 (Grails 3.3.x 或更高版本)

記錄器名稱 (Grails 3.2.x 或更低版本)

com.company.BookService

grails.app.services.com.company.BookService

位於 grails-app/services/com/companyBookService.groovy 使用 @Slf4j 加上註解

記錄器名稱 (Grails 3.3.x 或更高版本)

記錄器名稱 (Grails 3.2.x 或更低版本)

com.company.BookService

com.company.BookService

位於 src/main/groovy/com/companyBookDetail.groovy 使用 @Slf4j 加上註解

記錄器名稱 (Grails 3.3.x 或更高版本)

記錄器名稱 (Grails 3.2.x 或更低版本)

com.company.BookDetail

com.company.BookDetail

4.1.3.2 從堆疊追蹤記錄中遮罩要求參數

當 Grails 記錄堆疊追蹤時,記錄訊息可能會包含目前要求的所有要求參數的名稱和值。若要遮罩安全要求參數的值,請在 grails.exceptionresolver.params.exclude 設定屬性中指定參數名稱

grails-app/conf/application.yml
grails:
    exceptionresolver:
        params:
            exclude:
                - password
                - creditCard

要求參數記錄可能會透過將 grails.exceptionresolver.logRequestParameters 設定屬性設為 false 而完全關閉。當應用程式在 DEVELOPMENT 模式下執行時,預設值為 true,而其他所有環境則為 false。

grails-app/conf/application.yml
grails:
    exceptionresolver:
        logRequestParameters: false

4.1.3.3 外部設定檔

如果您設定設定屬性 logging.config,您可以指示 Logback 使用外部設定檔。

grails-app/conf/application.yml
logging:
    config: /Users/me/config/logback.groovy

或者,您可以使用系統屬性提供設定檔位置

$ ./gradlew -Dlogging.config=/Users/me/config/logback.groovy bootRun

或者,您可以使用環境變數

$ export LOGGING_CONFIG=/Users/me/config/logback.groovy
$ ./gradlew bootRun

4.1.4 GORM

Grails 提供下列 GORM 設定選項

  • grails.gorm.failOnError - 如果設為 true,會導致網域類別上的 save() 方法在儲存期間 驗證 失敗時擲出 grails.validation.ValidationException。此選項也可以指定一個表示套件名稱的字串清單。如果值是字串清單,則 failOnError 行為只會套用至這些套件(包含子套件)中的網域類別。請參閱 save 方法文件以取得更多資訊。

例如,要為所有網域類別啟用 failOnError

grails:
    gorm:
        failOnError: true

並為網域類別依套件啟用 failOnError

grails:
    gorm:
        failOnError:
            - com.companyname.somepackage
            - com.companyname.someotherpackage

4.1.5 設定 HTTP 代理

要設定 Grails 以使用 HTTP 代理,有兩個步驟。首先,如果您想要使用它來建立應用程式等,則需要設定 grails CLI 以了解代理。這可以使用 GRAILS_OPTS 環境變數來完成,例如在 Unix 系統上

export GRAILS_OPTS="-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=3128 -Dhttp.proxyUser=test -Dhttp.proxyPassword=test"
預設設定檔存放庫是透過 HTTPS 解析,因此使用 https.proxyPorthttps.proxyUser,但是使用者名稱和密碼使用 http.proxyUserhttp.proxyPassword 指定

對於 Windows 系統,可以在 我的電腦/進階/環境變數 下設定環境變數。

有了這個設定,grails 指令就可以透過代理連線和驗證。

其次,由於 Grails 使用 Gradle 作為建置系統,因此您需要設定 Gradle 以透過代理驗證。有關如何這樣做的說明,請參閱 Gradle 使用者指南中的主題

4.2 應用程式類別

每個新的 Grails 應用程式都在 grails-app/init 目錄中有一個 Application 類別。

Application 類別是 GrailsAutoConfiguration 類別的子類別,並有一個 static void main 方法,表示它可以作為常規應用程式執行。

4.2.1 執行應用程式類別

有幾種方法可以執行 Application 類別,如果您使用的是 IDE,則可以簡單地右鍵按一下類別並直接從 IDE 執行它,這將啟動您的 Grails 應用程式。

這對除錯也很有用,因為你可以直接從 IDE 除錯,而無需在命令列使用 ./gradlew bootRun --debug-jvm 指令時連接遠端除錯器。

你也可以將你的應用程式封裝成可執行 WAR 檔案,例如

$ ./gradlew bootWar
$ java -jar build/libs/myapp-0.1.war

如果你計畫使用無容器方式部署你的應用程式,這會很有用。

4.2.2 自訂應用程式類別

有幾種方式可以自訂 Application 類別。

自訂掃描

預設情況下,Grails 會掃描所有已知的來源目錄以尋找控制器、網域類別等,不過如果你希望掃描其他 JAR 檔案中的套件,你可以覆寫 Application 類別的 packageNames() 方法

class Application extends GrailsAutoConfiguration {
    @Override
    Collection<String> packageNames() {
        super.packageNames() + ['my.additional.package']
    }

    ...
}

註冊其他 Bean

Application 類別也可以用作 Spring bean 定義的來源,只需定義一個使用 Bean 註解的方法,回傳的物件就會變成 Spring bean。方法的名稱用作 bean 名稱

class Application extends GrailsAutoConfiguration {
    @Bean
    MyType myBean() {
        return new MyType()
    }

    ...
}

4.2.3 應用程式生命週期

Application 類別也實作了 GrailsApplicationLifeCycle 介面,所有外掛程式都實作了這個介面。

這表示 Application 類別可以用來執行與外掛程式相同的功能。你可以覆寫 一般外掛程式掛鉤,例如 doWithSpringdoWithApplicationContext 等,方法是覆寫適當的方法

class Application extends GrailsAutoConfiguration {
    @Override
    Closure doWithSpring() {
        {->
            mySpringBean(MyType)
        }
    }

    ...
}

4.3 環境

每個環境設定

Grails 支援每個環境設定的概念。grails-app/conf 目錄中的 application.ymlapplication.groovy 檔案可以使用 YAML 或 ConfigSlurper 提供的語法來使用每個環境設定。以下是一個 Grails 提供的預設 application.yml 定義範例

environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: update
            url: jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
            properties:
               jmxEnabled: true
               initialSize: 5
        ...

可以在 application.groovy 中使用 Groovy 語法表達上述內容,如下所示

dataSource {
    pooled = false
    driverClassName = "org.h2.Driver"
    username = "sa"
    password = ""
}
environments {
    development {
        dataSource {
            dbCreate = "create-drop"
            url = "jdbc:h2:mem:devDb"
        }
    }
    test {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:mem:testDb"
        }
    }
    production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:prodDb"
            properties {
               jmxEnabled = true
               initialSize = 5
            }
        }
    }
}

請注意,共用設定是在頂層提供,然後 environments 區塊會為 DataSourcedbCreateurl 屬性指定每個環境設定。

為不同環境封裝和執行

Grails 的 命令列 內建了在特定環境中執行任何指令的能力。格式為

grails <<environment>> <<command name>>

此外,Grails 已知有 3 個預設環境:devprodtest,分別代表 開發生產測試。例如,若要為 test 環境建立 WAR,您會執行

grails test war

若要鎖定其他環境,您可以將 grails.env 變數傳遞至任何指令

./gradlew bootRun -Dgrails.env=UAT

程式化環境偵測

在您的程式碼中,例如在 Gant 腳本或開機類別中,您可以使用 Environment 類別偵測環境

import grails.util.Environment

...

switch (Environment.current) {
    case Environment.DEVELOPMENT:
        configureForDevelopment()
        break
    case Environment.PRODUCTION:
        configureForProduction()
        break
}

依環境開機

通常會希望在應用程式於每個環境啟動時執行程式碼。為此,您可以使用 grails-app/init/BootStrap.groovy 檔案對每個環境執行提供支援

def init = { ServletContext ctx ->
    environments {
        production {
            ctx.setAttribute("env", "prod")
        }
        development {
            ctx.setAttribute("env", "dev")
        }
    }
    ctx.setAttribute("foo", "bar")
}

一般每個環境執行

前一個 BootStrap 範例在內部使用 grails.util.Environment 類別執行。您也可以自行使用此類別執行您自己的特定環境邏輯

Environment.executeForCurrentEnvironment {
    production {
        // do something in production
    }
    development {
        // do something only in development
    }
}

4.4 資料來源

由於 Grails 建立在 Java 技術上,設定資料來源需要具備一些 JDBC (Java 資料庫連線技術) 知識。

如果您使用 H2 以外的資料庫,則需要 JDBC 驅動程式。例如,對於 MySQL,您需要 Connector/J

驅動程式通常以 JAR 檔案形式提供。如果 Maven 儲存庫中有 JAR 檔案,最好使用相依性解析來解析 JAR 檔案,例如您可以像這樣新增 MySQL 驅動程式的相依性

dependencies {
    runtimeOnly 'mysql:mysql-connector-java:5.1.29'
}

解析 JAR 檔案後,您需要熟悉 Grails 管理其資料庫組態的方式。組態可以保留在 grails-app/conf/application.groovygrails-app/conf/application.yml 中。這些檔案包含資料來源定義,其中包括下列設定

  • driverClassName - JDBC 驅動程式的類別名稱

  • username - 用於建立 JDBC 連線的使用者名稱

  • password - 用於建立 JDBC 連線的密碼

  • url - 資料庫的 JDBC URL

  • dbCreate - 是否從網域模型自動產生資料庫 - 選項為 'create-drop'、'create'、'update'、'validate' 或 'none'

  • pooled - 是否使用連線池 (預設為 true)

  • logSql - 啟用 SQL 記錄至標準輸出

  • formatSql - 格式化已記錄的 SQL

  • dialect - 表示用於與資料庫通訊的 Hibernate 方言的字串或類別。請參閱 org.hibernate.dialect 套件以取得可用的方言。

  • readOnly - 如果為 true,會將 DataSource 設為唯讀,這會導致連線池在每個 Connection 上呼叫 setReadOnly(true)

  • transactional - 如果為 false,會將 DataSource 的 transactionManager bean 留在鏈結的 BE1PC transaction manager 實作之外。這只適用於其他資料來源。

  • persistenceInterceptor - 預設資料來源會自動連接到持續性攔截器,其他資料來源不會自動連接,除非將此設定為 true

  • properties - 要設定在 DataSource bean 上的額外屬性。請參閱 Tomcat Pool 文件。還有一個 Javadoc 格式的 屬性文件

  • jmxExport - 如果為 false,會停用所有 DataSources 的 JMX MBeans 註冊。預設會為屬性中 jmxEnabled = true 的 DataSources 加入 JMX MBeans。

  • type - 如果您希望在有多個可用時強制 Grails 使用它,則為連線池類別。

application.groovy 中 MySQL 的典型組態可能是像這樣

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://127.0.0.1:3306/my_database"
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    username = "username"
    password = "password"
    type = "com.zaxxer.hikari.HikariDataSource"
    properties {
       jmxEnabled = true
       initialSize = 5
       maxActive = 50
       minIdle = 5
       maxIdle = 25
       maxWait = 10000
       maxAge = 10 * 60000
       timeBetweenEvictionRunsMillis = 5000
       minEvictableIdleTimeMillis = 60000
       validationQuery = "SELECT 1"
       validationQueryTimeout = 3
       validationInterval = 15000
       testOnBorrow = true
       testWhileIdle = true
       testOnReturn = false
       jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
       defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED
    }
}
在組態 DataSource 時,請勿在任何組態設定之前包含 type 或 def 關鍵字,因為 Groovy 會將這些視為局部變數定義,而且不會處理它們。例如,下列無效
dataSource {
    boolean pooled = true // type declaration results in ignored local variable
    ...
}

使用額外屬性進行進階組態的範例

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://127.0.0.1:3306/my_database"
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    username = "username"
    password = "password"
    type = "com.zaxxer.hikari.HikariDataSource"
    properties {
       // Documentation for Tomcat JDBC Pool
       // https://tomcat.dev.org.tw/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
       // https://tomcat.dev.org.tw/tomcat-7.0-doc/api/org/apache/tomcat/jdbc/pool/PoolConfiguration.html
       jmxEnabled = true
       initialSize = 5
       maxActive = 50
       minIdle = 5
       maxIdle = 25
       maxWait = 10000
       maxAge = 10 * 60000
       timeBetweenEvictionRunsMillis = 5000
       minEvictableIdleTimeMillis = 60000
       validationQuery = "SELECT 1"
       validationQueryTimeout = 3
       validationInterval = 15000
       testOnBorrow = true
       testWhileIdle = true
       testOnReturn = false
       ignoreExceptionOnPreLoad = true
       // https://tomcat.dev.org.tw/tomcat-7.0-doc/jdbc-pool.html#JDBC_interceptors
       jdbcInterceptors = "ConnectionState;StatementCache(max=200)"
       defaultTransactionIsolation = java.sql.Connection.TRANSACTION_READ_COMMITTED // safe default
       // controls for leaked connections
       abandonWhenPercentageFull = 100 // settings are active only when pool is full
       removeAbandonedTimeout = 120
       removeAbandoned = true
       // use JMX console to change this setting at runtime
       logAbandoned = false // causes stacktrace recording overhead, use only for debugging
       // JDBC driver properties
       // Mysql as example
       dbProperties {
           // Mysql specific driver properties
           // https://mysqldev.dev.org.tw/doc/connector-j/en/connector-j-reference-configuration-properties.html
           // let Tomcat JDBC Pool handle reconnecting
           autoReconnect=false
           // truncation behaviour
           jdbcCompliantTruncation=false
           // mysql 0-date conversion
           zeroDateTimeBehavior='convertToNull'
           // Tomcat JDBC Pool's StatementCache is used instead, so disable mysql driver's cache
           cachePrepStmts=false
           cacheCallableStmts=false
           // Tomcat JDBC Pool's StatementFinalizer keeps track
           dontTrackOpenResources=true
           // performance optimization: reduce number of SQLExceptions thrown in mysql driver code
           holdResultsOpenOverStatementClose=true
           // enable MySQL query cache - using server prep stmts will disable query caching
           useServerPrepStmts=false
           // metadata caching
           cacheServerConfiguration=true
           cacheResultSetMetadata=true
           metadataCacheSize=100
           // timeouts for TCP/IP
           connectTimeout=15000
           socketTimeout=120000
           // timer tuning (disable)
           maintainTimeStats=false
           enableQueryTimeouts=false
           // misc tuning
           noDatetimeStringSync=true
       }
    }
}

更多關於 dbCreate

Hibernate 可以自動建立您的網域模型所需的資料庫表格。您可以透過 dbCreate 屬性控制它何時以及如何執行此動作,它可以採用下列值

  • create - 捨棄現有的架構,並在啟動時建立架構,首先捨棄現有的表格、索引等。

  • create-drop - 與 create 相同,但在應用程式正常關閉時也會捨棄表格。

  • 更新 - 建立遺失的表格和索引,並更新目前的架構,而不會刪除任何表格或資料。請注意,這無法適當地處理許多架構變更,例如欄位重新命名(您會保留包含現有資料的舊欄位)。

  • 驗證 - 不會對您的資料庫進行任何變更。將設定檔與現有的資料庫架構進行比較,並報告警告。

  • 任何其他值 - 不執行任何動作

建議在您的架構相對穩定時,或在您的應用程式和資料庫部署到生產環境時,將 dbCreate 設定設為「none」。然後透過適當的遷移來管理資料庫變更,可以使用 SQL 腳本或遷移工具,例如 FlywayLiquibase資料庫遷移 外掛程式使用 Liquibase。

4.4.1 資料來源和環境

前一個範例設定假設您想要對所有環境使用相同的設定:生產、測試、開發等。

不過,Grails 的 DataSource 定義是「環境感知」的,因此您可以執行

dataSource {
    pooled = true
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    // other common settings here
}

environments {
    production {
        dataSource {
            url = "jdbc:mysql://liveip.com/liveDb"
            // other environment-specific settings here
        }
    }
}

4.4.2 自動資料庫遷移

DataSource 定義的 dbCreate 屬性很重要,因為它決定了 Grails 在執行階段應如何根據 GORM 類別自動產生資料庫表格。選項說明在 DataSource 區段中

  • 建立

  • 建立並刪除

  • 更新

  • 驗證

  • 無值

開發 模式中,dbCreate 預設設為「建立並刪除」,但在開發的某個時間點(絕對是在您進入生產環境時),您需要停止每次啟動伺服器時刪除並重新建立資料庫。

您可能會想切換到 更新,以便保留現有資料,並只在您的程式碼變更時更新架構,但 Hibernate 的更新支援非常保守。它不會進行任何可能導致資料遺失的變更,也不會偵測重新命名的欄位或表格,因此您會保留舊的欄位或表格,同時也會擁有新的欄位或表格。

Grails 透過外掛程式支援使用 Liquibase 或 Flyway 進行遷移。

4.4.3 具有交易感知功能的 DataSource 代理程式

實際的 dataSource bean 會封裝在具有交易感知功能的代理程式中,因此您將獲得目前交易或 Hibernate Session 使用的連線(如果有一個交易或 Hibernate Session 處於活動狀態)。

如果不是這樣,從 dataSource 擷取連線將會是一個新的連線,而且您將無法看到尚未提交的變更(假設您有明智的交易隔離設定,例如 READ_COMMITTED 或更佳)。

4.4.4 資料庫主控台

H2 資料庫主控台是 H2 的一項便利功能,它提供了一個基於 Web 的介面,供您使用任何有 JDBC 驅動程式的資料庫,而且對於檢視您正在開發的資料庫非常有用。當針對記憶體中資料庫執行時,它特別有用。

您可以透過在瀏覽器中導覽至 https://127.0.0.1:8080/h2-console 來存取主控台。請參閱 Spring Boot H2 主控台文件 以取得有關可用選項的更多資訊。

H2 主控台在預設情況下會停用(除非您正在使用 Spring Boot 的開發人員工具),而且必須透過將 spring.h2.console.enabled 屬性設定為 true 值才能啟用。
H2 主控台僅供在開發期間使用,因此應注意確保在生產環境中未將 spring.h2.console.enabled 設定為 true

4.4.5 多個資料來源

在預設情況下,所有網域類別會共用一個 DataSource 和一個資料庫,但您可以選擇將網域類別分割成兩個或更多個資料來源。

設定其他資料來源

grails-app/conf/application.yml 中的預設 DataSource 設定看起來像這樣

dataSource:
    pooled: true
    jmxExport: true
    driverClassName: org.h2.Driver
    username: sa
    password:

environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: update
            url: jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
            properties:
               jmxEnabled: true
               initialSize: 5

這會設定一個單一 DataSource,其 Spring bean 名稱為 dataSource。若要設定其他資料來源,請新增一個 dataSources 區塊(在頂層、環境區塊中,或兩者都新增,就像標準 DataSource 定義一樣),並使用自訂名稱。例如,這個設定會新增一個第二個 DataSource,在開發環境中使用 MySQL,在生產環境中使用 Oracle

dataSource:
    pooled: true
    jmxExport: true
    driverClassName: org.h2.Driver
    username: sa
    password:

dataSources:
    lookup:
        dialect: org.hibernate.dialect.MySQLInnoDBDialect
        driverClassName: com.mysql.jdbc.Driver
        username: lookup
        password: secret
        url: jdbc:mysql://127.0.0.1/lookup
        dbCreate: update

environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    production:
        dataSource:
            dbCreate: update
            url: jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
            properties:
                jmxEnabled: true
                initialSize: 5
                ...
        dataSources:
            lookup:
                dialect: org.hibernate.dialect.Oracle10gDialect
                driverClassName: oracle.jdbc.driver.OracleDriver
                username: lookup
                password: secret
                url: jdbc:oracle:thin:@localhost:1521:lookup
                dbCreate: update

只要 Hibernate 支援,您就可以使用相同或不同的資料庫。

如果您需要在 Grails 工件中注入 lookup 資料來源,您可以這樣做

DataSource dataSource_lookup
在定義多個資料來源時,其中一個資料來源必須命名為「dataSource」。這是必需的,因為 Grails 會透過判斷哪一個資料來源命名為「dataSource」來判斷哪一個資料來源是預設資料來源。

設定網域類別

如果網域類別沒有 DataSource 設定,它會預設為標準 'dataSource'。在 mapping 區塊中設定 datasource 屬性,以設定非預設 DataSource。例如,如果您想要讓 ZipCode 網域使用 'lookup' DataSource,請這樣設定

class ZipCode {

   String code

   static mapping = {
      datasource 'lookup'
   }
}

一個網域類別也可以使用兩個或更多個資料來源。使用 datasources 屬性,並提供一個名稱清單,以設定多個資料來源,例如

class ZipCode {

   String code

   static mapping = {
      datasources(['lookup', 'auditing'])
   }
}

如果一個網域類別使用預設 DataSource 和一個或多個其他 DataSource,請使用特殊名稱 'DEFAULT' 來表示預設 DataSource

class ZipCode {

   String code

   static mapping = {
      datasources(['lookup', 'DEFAULT'])
   }
}

如果網域類別使用所有已設定的資料來源,請使用特殊值 'ALL'

class ZipCode {

   String code

   static mapping = {
      datasource 'ALL'
   }
}

命名空間和 GORM 方法

如果網域類別使用多個 DataSource,則可以使用每個 DataSource 名稱隱含的命名空間,針對特定 DataSource 進行 GORM 呼叫。例如,考慮使用兩個資料來源的這個類別

class ZipCode {

   String code

   static mapping = {
      datasources(['lookup', 'auditing'])
   }
}

當不使用明確命名空間時,第一個指定的 DataSource 是預設值,因此在這種情況下,我們預設為 'lookup'。但您可以使用 DataSource 名稱呼叫 'auditing' DataSource 上的 GORM 方法,例如

def zipCode = ZipCode.auditing.get(42)
...
zipCode.auditing.save()

如您所見,您在靜態情況和執行個體情況下,將 DataSource 新增至方法呼叫中。

Hibernate 對應網域類別

您也可以將註解的 Java 類別分割為不同的資料來源。使用預設資料來源的類別會註冊在 grails-app/conf/hibernate.cfg.xml 中。若要指定註解類別使用非預設資料來源,請為該資料來源建立一個 hibernate.cfg.xml 檔案,並將檔案名稱加上資料來源名稱作為前綴。

例如,如果 Book 類別在預設資料來源中,您會在 grails-app/conf/hibernate.cfg.xml 中註冊它

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          '-//Hibernate/Hibernate Configuration DTD 3.0//EN'
          'http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd'>
<hibernate-configuration>
   <session-factory>
      <mapping class='org.example.Book'/>
   </session-factory>
</hibernate-configuration>

如果 Library 類別在「ds2」資料來源中,您會在 grails-app/conf/ds2_hibernate.cfg.xml 中註冊它

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
          '-//Hibernate/Hibernate Configuration DTD 3.0//EN'
          'http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd'>
<hibernate-configuration>
   <session-factory>
      <mapping class='org.example.Library'/>
   </session-factory>
</hibernate-configuration>

使用 hbm.xml 檔案對應的類別的程序相同 - 只要將它們列在適當的 hibernate.cfg.xml 檔案中即可。

服務

與網域類別類似,服務預設使用預設 DataSourcePlatformTransactionManager。若要設定服務使用不同的 DataSource,請使用靜態 datasource 屬性,例如

class DataService {

   static datasource = 'lookup'

   void someMethod(...) {
      ...
   }
}

交易服務只能使用單一 DataSource,因此請務必只針對 DataSource 與服務相同的網域類別進行變更。

請注意,服務中指定的資料來源與網域類別使用的資料來源無關;這是由網域類別本身中宣告的資料來源決定的。它用於宣告要使用哪個交易管理員。

如果您在 dataSource1 中有一個 Foo 領域類別,在 dataSource2 中有一個 Bar 領域類別,如果 WahooService 使用 dataSource1,一個用於儲存新的 Foo 和新的 Bar 的服務方法只會對 Foo 進行交易,因為它們共用相同的資料來源。交易不會影響 Bar 執行個體。如果您想要兩者都進行交易,您需要使用兩個服務和 XA 資料來源進行兩階段提交,例如使用 Atomikos 外掛程式。

跨多個資料來源的交易

Grails 預設不會嘗試處理跨多個資料來源的交易。

您可以啟用 Grails 使用盡力而為 1PC 模式來處理跨多個資料來源的交易。為此,您必須在 application.yml 中將 grails.transaction.chainedTransactionManagerPostProcessor.enabled 設定設為 true

grails:
  transaction:
    chainedTransactionManagerPostProcessor:
      enabled: true

盡力而為 1PC 模式 非常普遍,但在某些情況下可能會失敗,開發人員必須了解這些情況。

這是一個非 XA 模式,涉及同步單階段提交多個資源。由於未曾使用 2PC,因此它永遠不會像 XA 交易一樣安全,但如果參與者了解折衷方案,通常就足夠好了。

基本概念是盡可能在交易中延後提交所有資源,以便唯一可能出錯的是基礎架構故障(而不是業務處理錯誤)。依賴於盡力而為 1PC 的系統認為基礎架構故障足夠罕見,因此他們可以承擔風險以換取更高的處理量。如果業務處理服務也設計為冪等的,那麼實際上很少會出錯。

BE1PC 實作已新增至 Grails 2.3.6。在此變更之前,其他資料來源不會參與在 Grails 中發起的交易。其他資料來源中的交易基本上處於自動提交模式。在某些情況下,這可能是預期的行為。原因之一可能是效能:在每個新交易開始時,BE1PC 交易管理員會為每個資料來源建立一個新交易。透過在其他資料來源的相關組態區塊中設定 transactional = false,可以讓其他資料來源不屬於 BE1PC 交易管理員。具有 readOnly = true 的資料來源也會被排除在連鎖交易管理員之外(自 2.3.7 起)。

預設情況下,BE1PC 實作會將所有實作 Spring PlatformTransactionManager 介面的 Bean 新增至連鎖 BE1PC 交易管理員。例如,Grails 應用程式內容中可能的 JMSTransactionManager Bean 會新增至 Grails BE1PC 交易管理員的交易管理員鏈。

您可以使用此組態選項將交易管理員 Bean 排除在 BE1PC 實作之外

grails:
  transaction:
    chainedTransactionManagerPostProcessor:
      enabled: true
      blacklistPattern: '.*'

排除比對是針對交易管理員 bean 的名稱進行。具有 transactional = falsereadOnly = true 的資料庫交易管理員將會略過,而且在這種情況下不需要使用此組態選項。

XA 和兩階段提交

如果最佳嘗試 1PC 模式不適合用於處理跨多個交易資源(不只資料庫)的交易,則有幾個選項可供使用,以將 XA/2PC 支援新增到 Grails 應用程式。

Spring 交易文件 包含有關整合不同應用程式伺服器的 JTA/XA 交易管理員的資訊。在這種情況下,您可以在 resources.groovyresources.xml 檔案中手動設定名稱為 transactionManager 的 bean。

4.5 版本控制

在執行階段偵測版本

您可以使用 GrailsApplication 類別,透過 Grails 對應用程式元資料的支援來偵測應用程式版本。例如,在 控制器 中有一個隱含的 grailsApplication 變數,可以使用

def version = grailsApplication.metadata.getApplicationVersion()

您可以使用下列方式擷取正在執行的 Grails 版本

def grailsVersion = grailsApplication.metadata.getGrailsVersion()

GrailsUtil 類別

import grails.util.GrailsUtil
...
def grailsVersion = GrailsUtil.grailsVersion

4.6 相依性解析

相依性解析是由 Gradle 建置工具 處理,所有相依性都定義在 build.gradle 檔案中。請參閱 Gradle 使用者指南 以取得更多資訊。