# 配置

在一个遵循“约定优于配置”框架中,谈论配置的话题,这似乎有些奇怪。使用 Grails 的默认设置,您确实可以在不进行任何配置的情况下开发应用程序。但学习在必要时如何覆盖这些约定也非常重要。下文将介绍哪些配置您可以使用。

# 基本配置

Grails 中的配置通常分为两个方面:构建配置运行时配置

构建配置通常通过 Gradle 和build.gradle文件完成。而运行时配置默认使用grails-app/conf/application.yml文件来配置。

如果你喜欢 Grails 2.0 风格的 Groovy 配置(Groovy 的 ConfigSlurper 语法),你还可以使用grails-app/conf/application.groovygrails-app/conf/runtime.groovy配置文件:

  1. 使用application.groovy进行不依赖于应用程序类的配置
  2. 使用runtime.groovy进行依赖于应用程序类的配置

TIP

之所以要拆分为两个文件,是因为定义在application.groovy中的配置是可以在应用编译前被 Grails CLI 访问的,如果application.groovy引用了应用中的类,执行 Grails CLI 命令是就会引起以下的错误:

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

对于 Groovy 配置,配置脚本中还以使用以下变量:

变量 描述
userHome 用户的 Home 目录位置
grailsHome 安装 Grails 的目录位置,若设置了GRAILS_HOME环境变量,则就是它。
appName 定义在build.gradle文件里的应用名
appVersion 定义在build.gradle文件里的应用版本

举个例子:

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

# 使用 GrailsApplication 访问配置

如果要读取运行时配置(即application.yml|application.groovy|runtime.groovy中的配置),可使用grailsApplication对象,该对象在 Controller 和 TabLib 中都可以直接访问

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

        render "Hello ${recipient}"
    }
}

对象grailsApplicationconfig属性是接口Config的一个实例,它提供了一系列用户访问应用配置的方法。

特别地,getProperty方法(如上所示)可以指定属性的类型(默认类型为 String)和回退值,这可以让配置检索的更加高效。

class MyController {

    def hello(Recipient recipient) {
        // 获取 Integer 配置 'foo.bar.max.hellos', 没有则默认为 5
        def max = grailsApplication.config.getProperty('foo.bar.max.hellos', Integer, 5)

        // 获取 'foo.bar.greeting' 配置,没有指定类型(默认为 String), 没有则默认为 "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还可以轻松的注入到 Service 和其它 Grails 组件中:

import grails.core.*

class MyService {
    GrailsApplication grailsApplication

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

# GrailsConfigurationAware 接口

虽然在运行时动态访问配置对应用程序的性能有影响很小,但 Grails 还支持通过实现GrailsConfigurationAware接口的方式来获取配置,该接口有一个setConfiguration方法,它会在类初始化的使用传入一个Config实例进来,在该方法内,你可以将相关配置属性赋值给类上的实例属性,以供后续使用。例如:

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 Value 注解

你还可以使用 Spring 的 Value 注解来注入一个配置的值:

class MyController {

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

    def hello() {
        render "Hello ${recipient}"
    }
}

TIP

在 Groovy 代码中,Value注解里的字符串必须使用单引号,否则它将被框架认为是 GString 而不是 Spring 的表达式。

# YML 的配置选项

文件application.yml是在 Grails 3.0 中引进的,现在它已是配置文件的首选格式。

# 使用系统属性/命令行参数

假设你在命令行中使用了参数JDBC_CONNECTION_STRING,那你可以在 yml 文件里通过下面的方式方法它:

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

同样,这样的方式也可以访问系统参数。

但是如果你使用grails run-app的方式启动应用,那需要修改build.gradle文件里的bootRun任务:

bootRun {
    systemProperties = System.properties
}

为了可以测试,还需要更改test任务:

test {
    systemProperties = System.properties
}

# 外部配置

默认情况下,Grails 也从./config或当前目录中读取application.(properties | yml)。这是因为 Grails 也是一个 Spring Boot 应用。相关文档,请参阅: Externalized Configuration

# 内置选项

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 - 是否在 Content Negotiation 中使用文件扩展名指定 mime 类型(默认值:true)。
  • grails.mime.types - Content Negotiation 支持的 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 - 这两个配置将用来确定项目指定源文件的重加载行为。每个配置都是一个字符串列表,这些字符串是项目源文件的类名,在使用run-app命令运行开发中的应用程序时,这些字符串应排除在重加载行为之外,或相应地包含在其中。如果配置了grails.reload.includes指令,则只会重新加载该列表中的类。

# 日志

自 Grails 3.0 起,就使用了 Logback 日志框架,它的配置文件是:grails-app/conf/logback.groovy

TIP

如果你喜欢 XML,也可以用logback.xml文件代替logback.groovy文件。

配置日志的更多信息,请参阅 Logback 文档

# Logger Names

Grails 的组件(Controller,Service 等)会被自动注入一个log属性。

在 Grails 3.3.0 之前,Grails 组件的 logger 名称遵循grails.app.<type><className>的约定,其中type是组件的类型,例如controllersservicesclassName是组件的类全名。

Grails 3.3.x 简化了 logger 的名称。例如位于grails-app/controllers/com/company位置的BookController.groovy

当没有加 @Slf4j 注解时,它的 logger name 如下:

Logger Name (Grails 3.3.x 或更高) Logger Name (Grails 3.2.x 或更低)
com.company.BookController grails.app.controllers.com.company.BookController

当加了 @Slf4j 注解后,它的 logger name 如下:

Logger Name (Grails 3.3.x 或更高) Logger Name (Grails 3.2.x 或更低)
com.company.BookController com.company.BookController

在 Service、Domain 里亦是如此,便不再赘述。

# 在 Stacktrace 日志中屏蔽请求参数

当 Grails 记录 stacktrace 时,日志消息可能包含当前请求的所有请求参数的名称和值。要屏蔽安全请求参数的值,请在grails.exceptionresolver.params.exclude config属性中指定参数名称:

grails:
    exceptionresolver:
        params:
            exclude:
                - password
                - creditCard

你也可以通过设置grails.exceptionresolver.logRequestParametersfalse来关闭请求参数的打印,它在开发环境默认是true,其它环境默认是false:

grails:
    exceptionresolver:
        logRequestParameters: false

# 外部配置文件

如果你配置了logging.config,则可以让Logback使用一个外部的配置文件:

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

# GORM

Grails 提供了以下一些 GORM 配置项:

  • grails.gorm.failOnError - 若设置为true,在使用save()方法时,若 校验 失败,则会抛出一个grails.validation.ValidationException异常。这个配置项还可以设置一个包名列表。如果值是字符串列表,则 failOnError 行为将仅会作用在这些包(包括子包)中的域类上。

例如,在所有域类上启用 failOnError:

grails:
    gorm:
        failOnError: true

再如,按包名来启用 failOnError:

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

# 配置 HTTP 代理

开项目开发过程中,要让 Grails 使用 HTTP 代理,有以下两个步骤。

  • 第一步:配置GRAILS_OPTS环境变量

例如在类 Unix 系统上:

$ export GRAILS_OPTS="-Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=3128 -Dhttp.proxyUser=test -Dhttp.proxyPassword=test"

在 Windows 系统上,右键 计算机 -> 属性 -> 高级系统设置 -> 环境变量做同样的配置即可。

通过以上配置,grails命令就可以通过代理进行连接和身份验证。

  • 第二步,由于 Grails 使用 Gradle 作为构建系统,因此需要配置 Gradle 使用代理进行身份验证。具体如何配置,请参阅 Gradle 指南的相关内容

# Application 类

每个 Grails 应用的grails-app/init目录下都有一个Application类。

这个Application类继承自 GrailsAutoConfiguration,并且它有一个static void main方法,这意味这它可以作为常规 Java 应用来运行。

# 运行 Application 类

如果您使用的是 IDE,那么您只需右键该类并直接从 IDE 运行它,IDE 将启动 Grails 应用程序。

这种方式也很便于调试,因为您可以直接从 IDE 进行调试,而无需在命令行里使用run app--debug jvm来连接远程调试器了。

你还可以把你的应用打成一个可运行的 WAR 包,例如:

> grails package
> java -jar build/libs/myapp-0.1.war

如果您打算使用无容器方式部署应用的话,这将非常有用。

# 定制 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()
    }

    ...
}

# 生命周期

和所有插件一样,类Application也实现了 GrailsApplicationLifeCycle 接口。

这意味着Application类可以做插件能做的事情,你可以重写一些生命周期钩子函数,例如doWithSpringdoWithApplicationContext等:

class Application extends GrailsAutoConfiguration {

    @Override
    Closure doWithSpring() {
        {->
            mySpringBean(MyType)
        }
    }

    ...
}

# 环境

# 每个环境的配置

Grails 支持为每个环境单独配置。位于grails-app/conf目录下的application.ymlapplication.groovy文件可以使用 YAML 或 ConfigSlurper 语法对每个环境进行配置。

例如在application.yml里 Grails 提供的默认配置:

dataSource:
    pooled: false
    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
        ...

application.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
            }
        }
    }
}

# 不同环境的打包和运行

Grails 的命令行内置了在特定环境的上下文中执行任何命令的功能,它的格式为:

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

另外,Grails 中还预设了 3 个环境变量:devprodtest,分别用于developmentproductiontest。例如,打一个test环境的 WAR 包,你可以执行这个命令:

> grails test war

若要指向其它环境,你可以使用grails.env参数,这适用于任何命令,例如:

> grails -Dgrails.env=UAT run-app

# 编程式环境检测

在代码里,例如 BootStrap 类中,你可以使用Environment类来检测当前的环境:

import grails.util.Environment

...

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

# Bootstrap 中的环境

当应用刚启动时,我们经常需要为每个环境执行一些特定的代码。若要如此,您应该使用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
    }
}

# 数据源(DataSource)

因为 Grails 构建在 Java 技术栈之上,因此数据源依赖于 JDBC 技术。如果你用的数据库不是 H2,那你还需要一个 JDBC 驱动。例如 MySQL 的 JDBC 驱动是 Connector/J

驱动通常是一个 JAR 包,如果它存在于 Maven 仓库里,最好使用依赖的方式引用它,例如你可以像这样添加一个 MySQL 驱动:

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

驱动安装好后,那接下来你就要熟悉 Grails 对这些数据库是如何配置的。这些仍然需要在文件grails-app/conf/application.groovygrails-app/conf/application.yml里进行,可用的配置项有以下这些:

  • driverClassName - 驱动的类名;
  • username - 用于 JDBC 连接的用户名;
  • password - 用于 JDBC 连接的密码;
  • url - 数据库 JDBC 的 URL;
  • dbCreate - 是否根据域类信息自动生成数据库的策略,可以是:create-dropcreateupdatevalidate
  • pooled - 是否使用连接池(默认为true
  • logSql - 是否在控制台输出 SQL 日志
  • formatSql - 日志输出的 SQL 是否格式化
  • dialect - Hibernate 数据库方言,可以是一个字符串或类,请参考 org.hibernate.dialect 查看可用的方言;
  • readOnly - 数据源是否是只读。若为true,则连接池中的每一个Connection都会调用setReadOnly(true)方法;
  • transactional - 若为false,则 BE1PC 事务管理器实现中将不包含数据源的事务管理器(transactionManager),这仅适用于附加的数据源。
  • persistenceInterceptor - 默认的数据源会自动的使用持久化烂机器,其它的数据源不会自动,除非该配置项设置为true
  • properties - 数据源额外的配置属性。请查看 Tomcat Pool 的文档;
  • jmxExport - 是否为所有数据源注册 JMX MBean,若为false,则都不注册,默认为true
  • type - 连接池的类,若有多个连接池可用,该配置项可以强制 Grails 使用其中的某一个。

例如在application.groovy文件里,一个典型的 MySQL 配置,可能像这样:

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://localhost: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
    }
}

WARNING

任何一个配置项的前面都不需要添加类型声明或者def关键字!如果这么做了,Groovy 将把它当做一个本地变量,而不是配置了,例如下面这个例子就是错误的:

dataSource {
    boolean pooled = true // 类型声明将导致该配置项项无效!
    ...
}

使用额外属性高级配置的例子:

dataSource {
    pooled = true
    dbCreate = "update"
    url = "jdbc:mysql://localhost: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
       // http://tomcat.apache.org/tomcat-7.0-doc/jdbc-pool.html#Common_Attributes
       // https://tomcat.apache.org/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
       // http://tomcat.apache.org/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
           // http://dev.mysql.com/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
       }
    }
}

# 数据源和环境

上面的配置示例都假设你想要在所有环境都使用一样的配置。实际上,Grails 的数据库是可以感知环境的,因此你的配置可以像这样:

dataSource {
    pooled = true
    driverClassName = "com.mysql.jdbc.Driver"
    dialect = org.hibernate.dialect.MySQL5InnoDBDialect
    // 其它通用配置都放在这
}

environments {
    production {
        dataSource {
            url = "jdbc:mysql://liveip.com/liveDb"
            // 其它生产环境的特殊配置都放在这
        }
    }
}

# 数据源自动迁移

Hibernate 可以根据 域模型自动创建所需的数据库表。您可以通过dbCreate 属性控制何时以及如何执行此过程,该属性有以下取值:

  • create - 在应用启动时先删除数据库中的已有内容(表、索引等),然后再自动创建;
  • create-drop - 和 create 大致相同,区别是在应用停止的时候就会删除数据库;
  • update - 创建缺失的表和索引,在不删除任何表和数据的情况下更新当前数据库,需要注意的是:它无法正确处理类似列重命名的操作,它将新增一个列,并且留下包含现有数据的旧列;
  • validate - 不更改现有数据库。将当前的配置与现有数据库架构进行比较,并给出差别警告;
  • 其它值 - 什么都不做。

在开发模式下,dbCreate默认被设置为create-drop,但是当开发进行到某一阶段后,你可能并不想在每次重启应用时都重新创建数据库。

这时,可以把dbCreate设置为update,这样应用重启时你就可以保留已有的数据,但是由于 Hibernate 的 update 操作比较保守,它不会做任何可能导致数据丢失的更改,也不会检测重命名的数据列,因此你的数据库中可能会留有旧的数据表和列。

当您的应用和数据库都相对稳定并且在生产环境中部署时,我们建议将dbCreate设置设置为 "none",然后通过合适的迁移操作来管理数据库的升级,例如使用 SQL 脚本或类似 FlywayLiquibase 的迁移工具。

Grails 有 Liquibase 和 Flyway 的插件:

# 感知事务的代理

事实上,dataSource 被包裹在一个可感知事务的代理中,因此你每次从数据源中取得的连接,都是当前事务或 Hibernate 的Session持有的接连(同一个)。

若不是这样,则每次从数据源取得的连接都是一个新的连接,并且您将不能查询到上段代码中尚未提交的更改(假设您有一个合理的事务隔离设置,例如READ_COMMITTED或更高)。

# 数据库控制台

H2 database console 是 H2 的一个很便捷的功能,它可以为任何有 JDBC 驱动程序的数据库提供了一个基于 Web 的交互界面,这对正在开发的数据库非常有用,特别是内存数据库。

你可以再浏览器中访问 http://localhost:8080/h2-console 来打开这个控制台,关于它的更多配置可以参考 Spring Boot H2 Console 的文档。

# 多数据源

默认情况下,所有域类(domain class)都共享一个DataSource和一个数据库,但是你可以选择将域类划分到两个或多个数据源里去。

# 配置额外的数据源

默认的DataSourcegrails-app/conf/application.yml里是类似这样配置的:

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。若要再配置额外的数据源,可以添加一个dataSources块并自定义一个数据源名称。例如,以下的配置,添加了第二个数据源(lookup),它在开发环境使用 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://localhost/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

如果你想在 Grails 组件中注入这个lookup数据源,你可以这样做:

DataSource dataSource_lookup

TIP

当定义多个数据源时,其中必须有一个名字是"dataSource",因为 Grails 需要根据这个名字来判断哪一个是默认的数据源。

# 配置域类

如果一个域类没有配置数据源,那它会默认使用dataSource数据源。你可以在mapping块中设置datasource属性来配置一个非默认的数据源。例如,让域类ZipCode使用lookup数据源,可以这样配置:

class ZipCode {

   String code

   static mapping = {
      datasource 'lookup'
   }
}

一个域类也可以被指定两个或多个数据源,这时需要使用datasources属性,例如:

class ZipCode {

   String code

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

如果一个域类既使用默认数据源,又使用其它的数据源,可以使用'DEFAULT'来指向默认的数据源:

class ZipCode {

   String code

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

如果这个域类指向所有的数据源,可以使用'ALL'来表达:

class ZipCode {

   String code

   static mapping = {
      datasource 'ALL'
   }
}

# 命名空间与 GORM 方法调用

如果域类使用多个数据源,则可以使用数据源名称作为命名空间对特定数据源进行 GORM 方法调用。例如,这个使用两个数据源的域类:

class ZipCode {

   String code

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

当没有指定命名空间时,上述代码中指定的第一个数据源(lookup)将默认被使用,但你可以在 GORM 方法前显式使用 'auditing' 来指明使用第二个数据源,例如:

def zipCode = ZipCode.auditing.get(42)  // 在域类上指定数据源名称
...
zipCode.auditing.save()                 // 在实例上指定数据源名称

如你所见,在域类上和域类实例上都将可以在方法调用前指定数据源名称。

# Services

与域类一样,默认情况下,服务使用默认的DataSourcePlatformTransactionManager。要将 Servuces 配置为使用其他数据源,可以使用静态的datasource属性,例如:

class DataService {

   static datasource = 'lookup'

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

事务性的 Service 只能使用单个数据源,因此请确保在事务中仅对与 Service 有相同数据源的域类进行更改。

注意,服务中指定的数据源与域类使用的数据源是没有关系的。服务中声明的数据源用于指定使用哪一个事务管理器。

如果有一个使用dataSource1的域类Foo,使用dataSource2的域类Bar,并且WahooService使用dataSource1,那么当一个WahooService里的方法保存一个Foo实例和一个Bar实例时,则事务仅对使用相同数据源的Foo有效,而对Bar实例没有影响。如果你想让两者都具有事务,则需要使用两个 Service 和 XA 数据源进行 two-phase Commit,例如使用 Atomikos 插件。

# 跨数据源的事务

默认情况下,Grails 不会处理跨多个数据源的事务。

您可以在application.yml里设置grails.transaction.chainedTransactionManagerPostProcessor.enabledtrue让 Grails 启用 Best Effort 1PC 模式处理跨多个数据源的事务:

grails:
  transaction:
    chainedTransactionManagerPostProcessor:
      enabled: true

Best Effort 1PC 模式的使用相当普遍,但是开发者必要要注意到它在某些情况下可能会失败。

这是一个非 XA 模式,并且多个资源是单阶段同步提交的。因为没有使用 2PC,所以它并没有 XA 事务那样安全,但通常也是足够的如果要求不是那么高的话。

文档完善中...

# XA 与 Two-phase Commit

文档完善中...

# 版本

文档完善中...

# 项目依赖

文档完善中...