Home » JVM Languages » Groovy » Custom User types in GORM

About Alex Staveley

Alex Staveley

Custom User types in GORM

Recently, I wanted to model a Merchant which like many things in a domain model had an Address. I thought it made sense that the Address was embedded inside the Merchant. Reasons:

  • It had no lifecycle outside the Merchant. Merchant dies so should the address.
  • It only ever belonged to one and only one Merchant

So pretty obvious this was a composition relationship.

Now, it is possible to model composition relationships in GORM. See here. However, this approach comes with the caveat that the Address must be a GORM object. I didn’t want the Address being a GORM object because GORM objects are powerful in Grails. With all their dynamic finders and GORM APIs they are essentially like really a DAO on steroids. If a developer gets their hands on can do lots of things (not always good things). I didn’t want or need any of this. In addition, a good architecture makes it difficult for developers to make mistakes when they are working under pressure at fast speeds. That means, when you are making design decisions you need to think about the power you need to give, should give and will give.

So with that in mind, I looked into wiring up a custom type for Address. This would just be a data structure that would model the address, could be reused outside the Merchant (thus promoting consistency and again thus promoting good design) and wouldn’t come with the power of the GORM. There is some documentation in the GORM doc’s for custom types but there isn’t a full working example. I had a look at some Hibernate examples and then put managed to put this together and get working.

Here is my address object.

@Immutable
class Address {

    private final String city;
    private final String country;
    private final String state;
    private final String street1;
    private final String street2;
    private final String street3;
    private final String zip;

    public String getCity() {
        return city;
    }

    public String getCountry() {
        return country;
    }

    public String getZip() {
        return zip;
    }

    public String getState() {
        return state;
    }

    public String getStreet1() {
        return street1;
    }

    public String getStreet2() {
        return street2;
    }

    public String getStreet3() {
        return street3;
    }
}

Here is my AddressUserType object:

class AddressUserType implements UserType {

    public int[] sqlTypes() {
        return [
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType()
        ] as int[]
    }

    public Class getReturnedClass() {
        return Address.class;
    }

    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws SQLException {
        assert names.length == 7;
        log.debug(">>mullSafeGet(name=${names}")
        String city = StringType.INSTANCE.get(rs, names[0], session); // already handles null check
        String country = StringType.INSTANCE.get(rs, names[1], session ); // already handles null check
        String state = StringType.INSTANCE.get(rs, names[2], session ); // already handles null check
        String street1 = StringType.INSTANCE.get(rs, names[3], session ); // already handles null check
        String street2 = StringType.INSTANCE.get(rs, names[4], session ); // already handles null check
        String street3 = StringType.INSTANCE.get(rs, names[5], session ); // already handles null check
        String zip = StringType.INSTANCE.get(rs, names[6], session ); // already handles null check

        return city == null && v == null ? null : new GAddress(city: city, country: country, state: state, street1: street1, street2: street2,  street3: street3, zip: zip);
    }

    void nullSafeSet(java.sql.PreparedStatement st, java.lang.Object value, int index, org.hibernate.engine.spi.SessionImplementor session) throws org.hibernate.HibernateException, java.sql.SQLException {
        if ( value == null ) {
            StringType.INSTANCE.set( st, null, index );
            StringType.INSTANCE.set( st, null, index+1 );
            StringType.INSTANCE.set( st, null, index+2 );
            StringType.INSTANCE.set( st, null, index+3 );
            StringType.INSTANCE.set( st, null, index+4 );
            StringType.INSTANCE.set( st, null, index+5 );
            StringType.INSTANCE.set( st, null, index+6 );
        }
        else {
            final Address address = (Address) value;
            StringType.INSTANCE.set( st, address.getCity(), index,session );
            StringType.INSTANCE.set( st, address.getCountry(), index+1,session);
            StringType.INSTANCE.set( st, address.getState(), index+2,session);
            StringType.INSTANCE.set( st, address.getStreet1(), index+3,session);
            StringType.INSTANCE.set( st, address.getStreet2(), index+4,session);
            StringType.INSTANCE.set( st, address.getStreet3(), index+5,session);
            StringType.INSTANCE.set( st, address.getZip(), index+6,session);
        }
    }


    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        // for now
        return x.equals(y);
    }

    @Override
    public int hashCode(Object x) throws HibernateException {
        assert (x != null);
        return x.hashCode();
    }

    @Override
    public Object deepCopy(Object value) throws HibernateException {
        return value;
    }

    @Override
    public Object replace(Object original, Object target, Object owner)
            throws HibernateException {
        return original;
    }

    @Override
    public Serializable disassemble(Object value) throws HibernateException {
        return (Serializable) value;
    }

    @Override
    public Object assemble(Serializable cached, Object owner)
            throws HibernateException {
        return cached;
    }

    public Class returnedClass() {
        return Address.class;
    }
}

And here is my Merchant which has an Address.

class Merchant {
    UUID id;

    String color;
    String displayName;
    //...
    //...

    Address address
    
    static mapping = {
        address type: AddressUserType, {
            column name: "city"
            column name: "country"
            column name: "zip"
            column name: "state"
            column name: "street1"
            column name: "street2"
            column name: "street3"
        }
    }

}

As stated, with this approach, the Address data structure could be used in other GORM objects. Until the next time take care of yourselves.

Reference: Custom User types in GORM from our JCG partner Alex Staveley at the Dublin’s Tech Blog blog.
(0 rating, 0 votes)
You need to be a registered member to rate this.
Start the discussion Views Tweet it!
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
Notify of