Search code examples
hibernategrailsgroovygrails-ormgrails-2.0

Grails GORM using immutable embedded object


I have a GORM class which is using an embedded instance in it. And the embedded instance is an immutable class. When I try to start the app, it is throwing the setter property is not found exception.

Caused by: org.hibernate.PropertyNotFoundException: Could not find a setter for property amount in class com.xxx.Money.

This is my GORM class:

class Billing {
    static embedded = ['amount']
    Money amount
}

And Money is defined as immutable:

final class Money {
    final Currency currency
    final BigDecimal value

    Money(Currency currency, BigDecimal value) {
        this.currency = currency
        this.value = value
    }
}

Anyway to resolve this without making Money mutable?

Thanks!


Solution

  • Grails and hibernate generally need full domain classes to be mutable to support all the features hibernate provides.

    Rather than embedding a Money domain class, you can store the Money amount with a multi-column hibernate UserType. Here's an example of how to write the UserType:

    import java.sql.*
    import org.hibernate.usertype.UserType
    
    class MoneyUserType implements UserType {
    
        int[] sqlTypes() {
            [Types.VARCHAR, Types.DECIMAL] as int[]
        }
    
        Class returnedClass() {
            Money
        }
    
        def nullSafeGet(ResultSet resultSet, String[] names, Object owner)  HibernateException, SQLException {
            String currency = resultSet.getString(names[0])
            BigDecimal value = resultSet.getBigDecimal(names[1])
            if (currency != null && value != null) {
                new Money(currency, value)
            } else {
                new Money("", 0.0)
            }
        }
    
        void nullSafeSet(PreparedStatement statement, Object money, int index) {
            statement.setString(index, money?.currency ?: "")
            statement.setBigDecimal(index+1, money?.value ?: 0.0)
        }
    
        ...
    
    }
    

    To use it in a domain class, map the field to the UserType instead of embedding it:

    class Billing {
        static mapping = {
            amount type: MoneyUserType
        }
        Money amount
    }