[GO] Grails getting started

What is Grails

Grails is a full-stack framework developed to solve many of the challenges of web development through core technologies and related plugins.

While many web frameworks in the Java world are complex and do not embrace the DRY principle, Grails builds on the dynamic framework concept that led to modern thinking about web applications like Rails and Django. , Is built on existing Java technologies like Spring and Hibernate.

Install Grails

Reference http://docs.grails.org/latest/guide/single.html#requirements

In the article, I used the following version.

* How to install Grails for nix (Mac OS, Linux, BSD OS) users

How to install Grails for Windows users

Create a Grails application

When you create a Grails application, use the grails create-app command to create all the files you need.

$ cd ${Application save directory}
$ grails create-app grails-sample-app
# grails-sample-Change app to the application name as appropriate.
# "grails-sample-app"A directory is created,
#The package name of the application is"grails.sample.app"Will be.

You can also use a mode that allows you to execute each Grails command interactively. Interactive mode is available when you run the grails command with no options, and you can complete the command with the Tab key in interactive mode.

Run a Grails application

Run the run-app command to run the application.

Run a Grails application(without grails interactive mode)


$ cd ${create-app save destination directory}
$ grails run-app

Run a Grails application(When using grails interactive mode)


$ cd ${create-app save destination directory}
$ grails #Launch grails in interactive mode
grails> run-app

Application screen

After running the application with run-app, access http: // localhost: 8080 / and you will see the Grails intro screen as shown below. (The screen shows the HelloController created in addition to the file created by create-app)

image.png

Directory structure

.gradle/
.idea/              ...Configuration file for IntelliJ IDE
build/              ...Build file(create-Does not exist immediately after running the app)
gradle/
grails-app/         ...Application source code
  assets/           ...Asset resource storage(Files processed by the asset pipeline)
  conf/             ...Runtime settings
  controllers/      ...controller(MVC model C)
    ${app name}/       ...app name(If hyphens are included, the directory is separated for each hyphen)
      UrlMappings.groovy ...URL mapping
  domain/           ...Domain class(MVC model M)
  i18n/             ... internationalization(i18n)Configuration support
  init/             ...Processing at application startup
  services/         ...Service layer
  taglib/           ...Tag library(Definition of custom tags that can be used in View)
  utils/            ...Grails-specific utility
  views/            ... Groovy Server Page(GSP) or JSON Views (MVC model V)
src/
  integration-test/ ...Integration test
  main/             ...Static files that do not go through asset pipeline processing
    groovy/         ...Domain class that you don't want to associate with the DB table
    webapp/         ...Static file(Included in WAR, not in JAR)
    resources/public ...Static file(/static/x/y/Access with z)
  test/             ...Unit test
    groovy/
      ${app name}/
        ${***Spec}.groovy
.gitignore          ...VCS unmanaged file settings for Git
build.gradle        ...Build settings
gradle.properties   ...Gradle settings
gradlew             ...Gradle startup script(UN*For X)
gradlew.bat         ...Gradle startup script(For windows)
grails-wrapper.jar
grailsw             ...Grails launch script(UN*For X)
grailsw.bat         ...Grails launch script(For windows)
README.md           ... README

Detailed information

Show Hello World

Let's display "Hello World!" As a character string.

In Grails, when you create a controller and write an action in that class, it's mapped to a unique URL that you can access in your browser.

The mapping rule will be / <appname> / <controller> / <action>, where <controller> will be the controller class minus Class.

For example, to create a HelloController and have the ʻindex` action display" HelloWorld ", do the following:

Run a Grails application


grails> create-controller hello #HelloController Class is created

Edit the HelloController class created in grails-app / controllers / grails / sample / app / HelloController.groovy to the following content.

grails-app/controllers/grails/sample/app/HelloController.groovy


package grails.sample.app

class HelloController {
  def index() {
    render "Hello World!"
  }
}

When you run the application and access http: // localhost: 8080 / hello / index or http: // localhost: 8080 / hello /, the string "Hello World!" Is displayed. (The index action can be omitted on the URL)

Create CRUD functionality for your domain

The domain is M for MVC in Grails. You can create individual controllers and views, as in the previous HelloWorld display example, but you can use the generate-all option to create controllers and views with CRUD functionality for your domain (plus for testing). You can create a class).

Create Employee domain class and controller and view for CRUD, test


#Create an Employee domain class
$ grails create-domain-class employee
| Created grails-app/domain/grails/sample/app/Employee.groovy
| Created src/test/groovy/grails/sample/app/EmployeeSpec.groovy
#Create a controller, view, and test class to CRUD the Employee domain class
$ grails generate-all grails.sample.app.Employee
| Rendered template Controller.groovy to destination grails-app\controllers\grails\sample\app\EmployeeController.groovy
| Rendered template Service.groovy to destination grails-app\services\grails\sample\app\EmployeeService.groovy
| Rendered template Spec.groovy to destination src\test\groovy\grails\sample\app\EmployeeControllerSpec.groovy
| Rendered template ServiceSpec.groovy to destination src\integration-test\groovy\grails\sample\app\EmployeeServiceSpec.groovy
| Scaffolding completed for grails-app\domain\grails\sample\app\Employee.groovy
| Rendered template create.gsp to destination grails-app\views\employee\create.gsp
| Rendered template edit.gsp to destination grails-app\views\employee\edit.gsp
| Rendered template index.gsp to destination grails-app\views\employee\index.gsp
| Rendered template show.gsp to destination grails-app\views\employee\show.gsp
| Views generated for grails-app\domain\grails\sample\app\Employee.groovy

Now you have the ability to CRUD the Employee domain class.

The domain class doesn't have any attributes yet, so edit the file grails-app / domain / grils / sample / app / Employee.groovy and add the appropriate attributes.

grails-app/domain/grails/sample/app/Employee.groovy


package grails.sample.app

class Employee {

  String name

  static constraints = {
  }
}

When I run run-app again to display the application's TOP screen in the browser, grails.sample.app.EmployeeController is added to Available Controllers :.

The character string of grails.sample.app.EmployeeController is a link, and when you click it, you will be taken to the Employee list screen (http: // localhost: 8080 / employee / index).

Employee list screen
image.png

As you can see with some operation, the following routes have been added.

root function
GET /employee/index Display Employee list screen
GET /employee/create Display Employee new creation screen
GET /employee/show/:id Employee details screen(:id is the ID of the domain class)Show
GET /employee/edit/:id Employee edit screen(:id is the ID of the domain class)Show
POST /employee/save Employee new creation
PUT /employee/update/:id Employee update(:id is the ID of the domain class)
DELETE /employee/delete/:id Employee deleted(:id is the ID of the domain class)

The DB used by the application can be changed for each environment variable such as development, test, and production. Grails is set to use H2 Database by default in all environments.

You will also notice that the DB is initialized when you finish all CRUD operations, exit run-app and run the application again. This is because H2 is an in-memory DB, and the development environment DB is configured to DROP the DB at application termination and create it at startup.

These settings are set in grails-app / conf / application.yml. (See The Grails Framework> The DataSource for details on the settings.)

This time, I will set not to destroy the database created in the development environment. Rewrite grails-app / conf / application.yml with the following content.

grails-app/conf/application.yml


environments:
  development:
    dataSource:
      # create-Change from drop to update
      dbCreate: update
      # mem:from devDb./Change to devDb
      url: jdbc:h2:./devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

The values that can be set with dbCreate are as follows.

dbCreate settings Description
create Existing schema at startup(table,index,other)Drop and recreate
create-drop Same as create, but delete table when the application is closed
update Create or update a table or index that does not exist. Do not drop existing tables or data.(Note that old columns and data will remain because column name changes cannot be handled properly.)
validate Output a warning by comparing the existing schema and settings without making any changes to the DB
any other value do nothing

Manipulate domain classes in source code

Create a domain

def p = new Person(name: "Fred", age: 40, lastVisit: new Date())
p.save()

Read domain

def p = Person.get(1)
println('name: ' + p.name)

Update domain

def p = Person.get(1)
p.name = 'Bob'
p.save()

Delete domain

def p = Person.get(1)
p.delete()

Create an application that manages the Employee model

Let's extend the Employee domain class a little more and make it more practical.

grails-app/domain/grails/sample/app/Employee.groovy


package grails.sample.app

class Employee {

  String name
  String department
  String gender
  Date birth
  Date joinedDate
  Long payment
  String note

  static constraints = {
    name blank: false, unique: true
    department blank: false
    gender blank: false, inList: ['male', 'female', 'other']
    birth blank: false
    joinedDate blank: false
    payment min: new Long(0), blank: false
    note blank: true
  }

  /**
  *Length of service
  */
  public int serviceYears() {
    def today = new Date()
    return Math.max(0, today.year - this.joinedDate.year)
  }
}

For the time being, except for note, I set nullable: false (since it is default, it is not specified in constraints) and blank: false. If you set blank: true like note, you need to disable the process of converting to null when the data is blank.

The setting can be disabled by setting ʻapplication.groovyas follows. If the file does not exist, create it undergrails-app / conf /`.

grails-app/conf/application.groovy


// the default value for this property is true
grails.databinding.convertEmptyStringsToNull = false

Now, change the list screen and edit screen to display Employee as follows.

--List screen --Display ID, name, department, gender --You can sort by each item --Employee Display new button --You can press the new button at any time --Display Employee edit button --It can be pressed only when any Employee is selected. --Detail screen --In addition to the items displayed on the list screen, the birth, serviceYears () execution result, payment, and note are displayed.

List screen view(grails-app/views/employee/index.gsp)


<!DOCTYPE html>
<html>
  <head>
    <meta name="layout" content="main" />
    <g:set var="entityName" value="${message(code: 'employee.label', default: 'Employee')}" />
    <title><g:message code="default.list.label" args="[entityName]" /></title>
    <script type="text/javascript">
      /*Set the edit button status based on the radio button status*/
      function setEditButtonStatusByRadioButton() {
        var edit_button_id = "edit_button";
        var radios = document.getElementsByName('id');
        var checkedNum = 0;
        radios.forEach(e => e.checked && checkedNum++);
        if (checkedNum > 0) {
          document.getElementById(edit_button_id).disabled = false;
        } else {
          document.getElementById(edit_button_id).disabled = true;
        }
      }
    </script>
  </head>
  <body>
    <a href="#list-employee" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
    <div class="nav" role="navigation">
      <ul>
        <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
        <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
      </ul>
    </div>
    <div id="list-employee" class="content scaffold-list" role="main">
      <h1><g:message code="default.list.label" args="[entityName]" /></h1>
      <g:if test="${flash.message}">
        <div class="message" role="status">${flash.message}</div>
      </g:if>

      <g:form action="edit">
        <table class="table table-striped">
          <thead>
            <tr>
              <th>Edit</th>
              <g:each in="${['id', 'name', 'department', 'gender']}" var="p">
                <g:sortableColumn property="${p}" title="${p}" />
              </g:each>
            </tr>
          </thead>
          <tbody>
          <g:each in="${employeeList}" var="employee">
            <tr>
              <td><input name="id" type="radio" value="${employee.id}" onclick="setEditButtonStatusByRadioButton()" /></td>
              <g:each in="${['id', 'name', 'department', 'gender']}" var="p">
                <g:if test="${p=='id'}">
                  <td><g:link method="GET" resource="${employee}">${employee.properties[p]}</g:link></td>
                </g:if>
                <g:else>
                  <td>${employee.properties[p]}</td>
                </g:else>
              </g:each>
            </tr>
          </g:each>
          </tbody>
        </table>

        <button disabled="false" id="edit_button"><g:message code="default.edit.label" args="[entityName]" /></button>
      </g:form>

      <div class="pagination">
        <g:paginate total="${employeeCount ?: 0}" />
      </div>
    </div>
  </body>
</html>

Detail screen view(grails-app/views/employee/show.gsp)


<!DOCTYPE html>
<html>
  <head>
    <meta name="layout" content="main" />
    <g:set var="entityName" value="${message(code: 'employee.label', default: 'Employee')}" />
    <title><g:message code="default.show.label" args="[entityName]" /></title>
  </head>
  <body>
    <a href="#show-employee" class="skip" tabindex="-1"><g:message code="default.link.skip.label" default="Skip to content&hellip;"/></a>
    <div class="nav" role="navigation">
      <ul>
        <li><a class="home" href="${createLink(uri: '/')}"><g:message code="default.home.label"/></a></li>
        <li><g:link class="list" action="index"><g:message code="default.list.label" args="[entityName]" /></g:link></li>
        <li><g:link class="create" action="create"><g:message code="default.new.label" args="[entityName]" /></g:link></li>
      </ul>
    </div>
    <div id="show-employee" class="content scaffold-show" role="main">
      <h1><g:message code="default.show.label" args="[entityName]" /></h1>
      <g:if test="${flash.message}">
      <div class="message" role="status">${flash.message}</div>
      </g:if>

      <ol class="property-list employee">
        <li class="fieldcontain">
          <g:each in="${['id', 'name', 'department', 'gender', 'birth', 'serviceYears', 'payment', 'note']}" var="p">
            <span id="name-label" class="property-label">${p}</span>
            <g:if test="${p=='serviceYears'}">
              <div class="property-value" aria-labelledby="name-label">${employee.serviceYears()}</div>
            </g:if>
            <g:else>
              <div class="property-value" aria-labelledby="name-label">${employee.properties[p]}</div>
            </g:else>
          </g:each>
        </li>
      </ol>
      
      <g:form resource="${this.employee}" method="DELETE">
        <fieldset class="buttons">
          <g:link class="edit" action="edit" resource="${this.employee}"><g:message code="default.button.edit.label" default="Edit" /></g:link>
          <input class="delete" type="submit" value="${message(code: 'default.button.delete.label', default: 'Delete')}" onclick="return confirm('${message(code: 'default.button.delete.confirm.message', default: 'Are you sure?')}');" />
        </fieldset>
      </g:form>
    </div>
  </body>
</html>

That's a little more practical.

The view is described in Groovy Server Pages (GSP), and <g: link>, <g: form>, etc. are GSP tags. (See Groovy Server Pages (GSP) for more information)

Introduce Twitter Bootstrap

By default, Grails 3.3.5 introduced Bootstrap 3.3.6.

There seems to be twitter-bootstrap-grails-plugin of Grails plugin, but the current version is 3.3.5 on September 18, '18. It looks like this, and it doesn't seem to be well maintained because the last commit was two years ago, so I'll try installing Bootstrap v4 manually.

Twitter Bootstrap v4 installation procedure

  1. Remove the installed Bootstrap (js, css), JQuery
    • grails-app/assets/javascripts/bootstrap.js
    • grails-app/assets/stylesheets/bootstrap.css
    • grails-app/assets/javascripts/jquery-2.2.0.min.js
  2. Download Bootstrap v4
    • https://getbootstrap.com/docs/4.1/getting-started/download/ -Press the Download button from "Compiled CSS and JS"
  3. Download JQuery v3
    • https://jquery.com/download/ -Save the linked file of "Download the compressed, production jQuery 3.x.y"
  4. Copy Bootstrap v4, JQuery v3 to your Grails application folder
    • grails-app/assets/javascripts/bootstrap.bundle.js
    • grails-app/assets/javascripts/jquery-3.3.1.min.js
    • grails-app/assets/stylesheets/bootstrap.css --grails-app / assets / stylesheets / bootstrap.css.map (Add as you like as it is for CSS debugging)
  5. Edit grails-app / assets / javascripts / application.js --Change from // = require jquery-2.2.0.min to // = require jquery-3.3.1.min --Change from // = require bootstrap to // = require bootstrap.bundle

Rerun the application and you're done. (The navbar collapses, but I'm not using it, so ignore it)

Confirmation after introduction

Let's edit the index.gsp view we just wrote.

grails-app/views/layouts/main.gsp


<head>
  : <snip>
  <asset:stylesheet src="application.css"/>
  <asset:javascript src="application.js"/> <!--Move the line written in the body into the head-->
  : <snip>
</head>
  : <snip>

grails-app/views/employee/index.gsp


  : <snip>
<button class="btn btn-secondary" disabled="false" id="edit_button"><g:message code="default.edit.label" args="[entityName]" /></button>
  : <snip>

It's okay if the button has the Bootstrap 4 style applied.

Add a filter function to the list screen using DataTable

Advance preparation

Let's add a filter function to the Employee list screen to make it a little more practical.

I will use DataTables to add the search function to the table displayed on the list screen this time.

In addition, there was grails-datatables etc. as Grails plugin, but install the latest version of DataTables like bootstrap and jquery. I will decide.

From the DataTables Download Page (https://datatables.net/download/), select the item below to download the file.

Copy the following files from the downloaded file to grails-app / assets / (javascripts | stylesheets) / according to the type.

Finally, edit grails-app / assets / application. (Js | css). Since the require destination files described in application. (js | css) are read in order from the top, please be careful about the description order for packages with dependencies.

grails-app/assets/javascripts/application.js


  : <snip>
//= require jquery-3.3.1.min
//= require bootstrap.bundle
//= require jquery.dataTables
//= require dataTables.bootstrap4
//= require_tree .
//= require_self
  : <snip>

grails-app/assets/stylesheets/application.css


/*
  : <snip>
*= require bootstrap
*= require dataTables.bootstrap4
*= require grails
*= require main
*= require mobile
*= require_self
  : <snip>
*/

Now you're ready to use DataTables.

Apply DataTables to tables on the list screen

Apply DataTables to the table on the list screen.

To apply DataTables, apply the dataTable () method to the DOM that points to the table you want to apply in jQuery.

grails-app/views/employee/index.gsp


<!DOCTYPE html>
<html>
  <head>
    : <snip>
    <script type="text/javascript">
      /*Set the edit button status based on the radio button status*/
      function setEditButtonStatusByRadioButton() {
        var edit_button_id = "edit_button";
        var radios = document.getElementsByName('id');
        var checkedNum = 0;
        radios.forEach(e => e.checked && checkedNum++);
        if (checkedNum > 0) {
          document.getElementById(edit_button_id).disabled = false;
        } else {
          document.getElementById(edit_button_id).disabled = true;
        }
      }

      $(document).ready(function() {
        $('#employeeindex').dataTable();
      } );
    </script>
  </head>
  <body>
    : <snip>
        <table id="employeeindex" class="display table table-striped">
          : <snip>
        </table>
    : <snip>
  </body>
</html>

Run the application and you'll see that DataTables has been applied.

image.png

Here you can see that the pagination features of DataTables and Grails have become redundant.

Which feature to enable depends on the design of the application, but this time I will leave Grails' pagination feature to reduce the amount of data processing.

To disable the DataTables pagination feature, add the paging: true parameter to the dataTable () method as described on the Official Page (https://datatables.net/reference/option/paging). I will pass it.

#Apply DataTables to employeeindex DOM without pagination


$(document).ready(function() {
  $('#employeeindex').dataTable({
    "paging": false
  });
} );

Finally, fix the Navbar at the top of the screen. Also, modify the style accordingly and add Font Awesome on the CDN to use ICON.

grails-app/assets/stylesheets/main.css


/* NAVIGATION MENU */

.nav, nav {
    zoom: 1;
}

.nav ul {
    overflow: hidden;
    padding-left: 0;
    zoom: 1;
}

.nav li {
    display: block;
    float: left;
    list-style-type: none;
    margin-right: 0.5em;
    padding: 0;
}

.nav a, nav a {
    color: #666666;
    display: block;
    padding: 0.25em 0.7em;
    text-decoration: none;
    -moz-border-radius: 0.3em;
    -webkit-border-radius: 0.3em;
    border-radius: 0.3em;
}

.nav a:active, .nav a:visited, nav a:active, nav a:visited {
    color: #666666;
}

.nav a:focus, .nav a:hover, nav a:focus, nav a:hover {
    background-color: #999999;
    color: #ffffff;
    outline: none;
    text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}

.no-borderradius .nav a:focus, .no-borderradius nav a:focus, .no-borderradius .nav a:hover, .no-borderradius nav a:hover {
    background-color: transparent;
    color: #444444;
    text-decoration: underline;
}

.nav a.home, .nav a.list, .nav a.create, nav a.home, nav a.list, nav a.create {
    background-position: 0.7em center;
    background-repeat: no-repeat;
    text-indent: 25px;
}

.nav a.home, nav a.home {
    background-image: url(../images/skin/house.png);
}

.nav a.list, nav a.list {
    background-image: url(../images/skin/database_table.png);
}

.nav a.create, nav a.create {
    background-image: url(../images/skin/database_add.png);
}

.nav li.dropdown ul.dropdown-menu, nav li.dropdown ul.dropdown-menu {
    background-color: #424649;
}

grails-app/assets/stylesheets/mobile.css


@media screen and (max-width: 480px) {
    .nav, nav {
        padding: 0.5em;
    }

    .nav li, nav li {
        margin: 0 0.5em 0 0;
        padding: 0.25em;
    }
  : <snip>
}

grails-app/views/layouts/main.gsp


<!doctype html>
<html lang="en" class="no-js">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
  <title>
    <g:layoutTitle default="Grails"/>
  </title>
  <meta name="viewport" content="width=device-width, initial-scale=1"/>
  <asset:link rel="icon" href="favicon.ico" type="image/x-ico" />

  <asset:stylesheet src="application.css"/>
  <asset:javascript src="application.js"/>
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css" integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">

  <g:layoutHead/>
</head>
<body>

  <nav class="navbar-expand-lg pr-3 navbar navbar-default">
    <a class="navbar-brand" href="/#">
      <asset:image src="grails.svg" alt="Grails Logo"/>
    </a>
    <button class="navbar-toggler mx-2" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="fas fa-bars" style="font-size: 3rem;"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav ml-auto">
        <g:pageProperty name="page.nav" />
      </ul>
    </div>
  </nav>

  <g:layoutBody/>

  <div class="footer" role="contentinfo"></div>

  <div id="spinner" class="spinner" style="display:none;">
      <g:message code="spinner.alt" default="Loading&hellip;"/>
  </div>

</body>
</html>
Large Display Size Small Display Size
image.png image.png

Database migration

Until now, the database was updated by setting dbCreate: update with reference to the model file when the application was started, but due to the limited conditions that can be changed and the old columns remain, multiple people When developing an application, different people may have different database structures.

So I decided to create a migration file and create a database from it.

Use the database-migration plugin to do database migration in Grails. (The database-migratation plugin uses the Liquibase library.)

Migrations are processed using one or more change log files written in Groovy DSL or native Liquibase XML.

The change log file has a globally unique ID. (ID contains the username of the user who created the changelog)

Advance preparation

Edit bundle.gradle as follows to use the database-migration plugin. (Change the plug-in version as appropriate)

Note that bundle.gradle is a configuration file for Gradle, and Gradle is an open source build automation system based on the concept of Apache Ant and Apache Maven. (Reference: Wikipedia> Gradle)

buildscript {
  repositories {
    : <snip>
  }
  dependencies {
    : <snip>
    classpath 'org.grails.plugins:database-migration:3.0.4' # database-Added migration plugin
  }
}

dependencies {
    : <snip>
  compile 'org.grails.plugins:database-migration:3.0.4'
  compile 'org.liquibase:liquibase-core:3.5.5'
    : <snip>
}

  : <snip>

sourceSets {
  main {
    resources {
      srcDir 'grails-app/migrations'
    }
  }
}

Then set the database dbCreate to none. Otherwise, you will not get the difference when creating a migration file with the dbm-gorm-diff option.

grails-app/conf/application.yml


environments:
  development:
    dataSource:
      dbCreate: none
      url: jdbc:h2:./devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
  : <snip>

Create a change log file

Next, create a change log file.

You can choose the description format, but I will use Groovy DSL.

There are two ways to create a changelog, one is from the database and the other is from the domain class. Right now, I think that the database is automatically created from the domain class at startup, so select the method to create the change log from the database.

How to create a changelog from a database in Groovy DSL format


$ grails dbm-generate-changelog changelog.groovy
  : <snip>Plugins will be downloaded as needed
:compileJava NO-SOURCE
:compileGroovy UP-TO-DATE
:buildProperties UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:findMainClass
:dbmGenerateChangelog

BUILD SUCCESSFUL

Total time: 16.478 secs

This will create the following changelog:

Changelog(grails-app/migrations/changelog.groovy)


databaseChangeLog = {

  changeSet(author: "tatsurou (generated)", id: "1537102923747-1") {
    createTable(tableName: "EMPLOYEE") {
      column(autoIncrement: "true", name: "ID", type: "BIGINT(19)") {
        constraints(primaryKey: "true", primaryKeyName: "CONSTRAINT_7")
      }

      column(name: "VERSION", type: "BIGINT(19)") {
        constraints(nullable: "false")
      }

      column(name: "NAME", type: "VARCHAR(255)") {
        constraints(nullable: "false")
      }
    }
  }
}

Now you can build the DB based on the changelog. (Only the database schema is built, no data is migrated)

From now on, after editing the domain class, you will have to manually or automatically create a changelog and run dbm-update.

  1. Edit the domain class
  2. Edit the changelog --How to create a difference automatically (If you add the file name and the --add option, the changelog will be saved separately with the specified file name, and it will be added so that it will be read by include from changelog.groovy) $ grails dbm-gorm-diff add_birth_column_to_employee.groovy --add
  3. Back up the database (in case something goes wrong)
  4. Run grails dbm-update for any environment (development, test, production)

Groovy console

The Groovy console is an application that allows you to enter and run Groovy source code. You can also load a domain class implemented in your Grails application, or use the ORM of that domain class to work with your database.

You can start the Grails console by running the grails console command.

Launch the groovy console


$ cd ${application directory}
$ grails console

image.png

package grails.sample.app

def id = 1
def e = Employee.get(id)
if (e == null) {
  println("Not found employee id " + id)
  exit()
}
println("employee: " + e)
println("name: " + e.name)
e.name = 'test99'
e.save(flush: true)

Error handling

Describes the errors that occur while executing Grails commands and how to deal with them.

Could not acquire change log lock

Command execution error: Could not acquire change log lock.  Currently locked by ${COMPUTER_NAME} (${IP_ADDRESS}) since 18/09/17 0:09

--Delete grails dbm-release-locks or DB

Recommended Posts

Grails getting started
Getting started with Android!
1.1 Getting Started with Python
Getting Started with Golang 2
Getting started with apache2
Getting Started with Golang 1
Getting Started with Python
Getting Started with Django 1
Getting Started with Optimization
Getting Started with Golang 3
Getting started with Spark
Getting Started with Python
Getting Started with Pydantic
Getting Started with Golang 4
Getting Started with Jython
Getting Started with Django 2
Translate Getting Started With TensorFlow
Getting Started with Python Functions
Getting Started with Tkinter 2: Buttons
[Linux] [Initial Settings] Getting Started
Getting Started with PKI with Golang ―― 4
Django Getting Started: 2_ Project Creation
Django Getting Started: 1_Environment Building
Getting Started with Python Django (1)
Django Getting Started: 4_MySQL Integration
Getting Started with Python Django (3)
Getting Started with Python Django (6)
Getting Started with Django with PyCharm
Python3 | Getting Started with numpy
Getting Started with Python responder v2
Getting Started with Git (1) History Storage
Getting started with Sphinx. Generate docstring with Sphinx
Getting Started with Python Web Applications
Getting Started with Python for PHPer-Classes
Getting Started with Sparse Matrix with scipy.sparse
Getting Started with Julia for Pythonista
Getting Started with Python Basics of Python
Getting Started with Cisco Spark REST-API
Getting Started with Python Genetic Algorithms
Getting started with Python 3.8 on Windows
Getting Started with Python for PHPer-Functions
Getting Started with CPU Steal Time
Getting Started with python3 # 1 Learn Basic Knowledge
Getting Started with Flask with Azure Web Apps
Getting Started with Python Web Scraping Practice
Getting Started with Python for PHPer-Super Basics
Getting Started with Python Web Scraping Practice
Getting started with Dynamo from Python boto
started python
Getting started: 30 seconds to Keras Japanese translation
Getting Started with Lisp for Pythonista: Supplement
Getting Started with Heroku, Deploying Flask App
Getting Started with TDD with Cyber-dojo at MobPro
Getting started with Python with 100 knocks on language processing
MongoDB Basics: Getting Started with CRUD in JAVA
Getting Started with Drawing with matplotlib: Writing Simple Functions
Getting started with Keras Sequential model Japanese translation
[Translation] Getting Started with Rust for Python Programmers
Django Getting Started Part 2 with eclipse Plugin (PyDev)
Getting started with AWS IoT easily in Python
Getting Started with Python's ast Module (Using NodeVisitor)