Show Navigation

Grails® Domain Read-Only Attribute

By Nirav Assar

October 2, 2018

Tags: #gorm

Recently I encountered a situation with a client Grails® application that required a read-only domain attribute. The application had a backend table where one column (call it A) was populated strictly by a batch database process. The table needed to be mapped to a Grails domain class. Certain columns (call them B through D) were to be updated from the UI. Attribute A should not be writable, but needed to be accessed for read-only operations.

The Problem

The catch here is when an update occurs on a mapped domain class, GORM with Hibernate issues an update on all columns, regardless of which columns have a value change. So even if attribute B is changed, an update will be issued on all columns A through D. If the database is configured with a specific column as read-only, you will get an error along the lines of: "failure to insert into a read-only column".

We propose two solutions:

  • Derived Properties
  • Hibernate's Dynamic update.

Example

Let's contrive a simple example with a domain class to understand the issue. Perhaps there is a Team with a name, coach, and nationalRank. name and coach are read-write attributes while nationalRank is the attribute which is read-only. It is updated strictly by a database batch process.

package grails.readonly.attribute

class Team {

    String name
    String coach
    Integer nationalRank

    static mapping = {
    }
}

Given a row already exists, we then issue an update with the statement:

team.name= "Raiders"
team.save(flush: true)

Hibernate will generate the log shown below. As you can see nationalRank is included in the statement, which will end up in an error due to the database constraint of read-only.

Hibernate: update team set version=?, national_rank=?, coach=?, name=? where id=? and version=?

Derived Properties

To prevent hibernate from including an attribute in the update statement, we can use the formula mapping for nationalRank. This means that GORM will allow us to access the property as though it were a normal column, but will ignore it for update operations (because formulas are, by definition, not updateable). In this case, the formula simply returns the value of the column, but the net effect gives us the desired behavior.

package grails.readonly.attribute

class Team {

    String name
    String coach
    Integer nationalRank

    static mapping = {
        nationalRank nullable: false, formula: 'NATIONAL_RANK'
    }
}

Now upon updates, the following hibernate log is generated (note the omission of nationalRank):

Hibernate: update team set version=?, coach=?, name=? where id=? and version=?

A derived property is one that takes its value from a SQL expression. E.g., tax formula: 'PRICE * TAX_RATE'. The formula expressed in the ORM DSL references to NATIONAL_RANK instead nationalRank. We are echoing a value which must be set elsewhere and cannot be inserted through GORM. So returning the column value directly achieves a read-only effect. DerivedProperties

DynamicUpdate

An alternate way of achieving the same result is to use hibernate's dynamicUpdate mapping. dynamicUpdate tells Hibernate whether to include unmodified properties in the SQL UPDATE statement. This excludes any attribute from the update statement which is not dirty.

package grails.readonly.attribute

class Team {

    String name
    String coach
    Integer nationalRank

    static mapping = {
        dynamicUpdate true
    }
}

You might also like ...