Search code examples
grailsgrails-orm

Grails, property names in embedded object in domain class causing problems


I'm using grails 2.3.4 and I have a domain class that embeds an object. The embedded object has a property called 'version' and it seems that this is conflicting with the 'version'-field automatically added to the database-table by GORM. The result is that the 'version'-field belonging to my embedded object isn't created in the database and as a consequence my application doesn't work properly.

My code looks like this:

class Thing {
  String someText
  EmbeddedThing embeddedThing
  Date someDate

  static embedded = ['embeddedThing']

  static constraints = {
    embeddedThing(unique: true)
  }
}

class EmbeddedThing {
  String textOfSomeSort
  String version
  String textOfSomeOtherSort
}

You might think that a quick fix is to rename the 'version'-property of the embedded object but the class belongs to an included sub-project (i.e. a JAR-file) that I'm not allowed to touch since other projects use it. So the solution needs to be done completely within my domain class, or at least in a manner that doesn't change the class of the embedded object.


Solution

  • I actually found a solution to this problem by using a Hibernate UserType to represent the EmbeddedThing-class. My code now looks like this and works perfectly:

    Thing.groovy:

    import EmbeddedThingUserType
    
    class Thing {
      String someText
      EmbeddedThing embeddedThing
      Date someDate
    
      static embedded = ['embeddedThing']
    
      static mapping = {
        version false
        embeddedThing type: EmbeddedThingUserType, {
          column name: "embedded_thing_text"
          column name: "embedded_thing_version"
          column name: "embedded_thing_other_text"
        }
      }
    
      static constraints = {
        embeddedThing(unique: true)
      }
    }
    

    EmbeddedThing.groovy:

    class EmbeddedThing {
      String textOfSomeSort
      String version
      String textOfSomeOtherSort
    }
    

    EmbeddedThingUserType.groovy:

    class EmbeddedThingUserType implements UserType {
      int[] sqlTypes() {
        return [StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType(),
            StringType.INSTANCE.sqlType()]
      }
    
      Class returnedClass() {
        return EmbeddedThing
      }
    
      public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
          throws HibernateException, SQLException {
        if (resultSet && names) {
          return new EmbeddedThing(
              textOfSomeSort: resultSet?.getString(names[0] ?: '_missing_textOfSomeSort_'),
              version: resultSet?.getString(names[1] ?: '_missing_version_'),
              textOfSomeOtherSort: resultSet?.getString(names[2] ?: '_missing_textOfSomeOtherSort_'))
        } else {
          return null
        }
      }
    
      public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index)
          throws HibernateException, SQLException {
        if (value != null) {
          preparedStatement.setString(index, value?.textOfSomeSort)
          preparedStatement.setString(index + 1, value?.version)
          preparedStatement.setString(index + 2, value?.textOfSomeOtherSort)
        } else {
          preparedStatement.setString(index, '_missing_textOfSomeSort_')
          preparedStatement.setString(index + 1, '_missing_version_')
          preparedStatement.setString(index + 2, '_missing_textOfSomeOtherSort_')
        }
      }
    
      @Override
      public boolean isMutable() {
        return false
      }
    
      @Override
      public boolean equals(Object x, Object y) throws HibernateException {
        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
      }
    }
    

    Config.groovy:

    grails.gorm.default.mapping = {
        'user-type'( type: EmbeddedThingUserType, class: EmbeddedThing)
    }