Search code examples
hibernategrailsforeign-keysgrails-ormcomposite-primary-key

Grails 3.1.6, Hibernate 4. Composite foreign key


class Tooth {
  Integer id
  ToothDisease toothDisease
   static mapping = {
    table name: 'AK_TOOTH'
    id generator: 'sequence', params: [sequence: 'SEQ_AK_TOOTH']
    columns{
        toothDisease {
        column: 'FK_AK_TOOTH_ID' 
        column: 'FK_AK_TOOTH_NR_VERSION' 
        }
    }
} 
class ToothDisease implements Serializable{
  Integer idColumn
  Integer nrVersion
   static mapping = {
    table name: 'AK_TOOTH_DISEASE'
    idColumn column: 'ID', generator: 'sequence', params: [sequence: 'SEQ_AK_TOOTH_DISEASE']
    nrVersion  column: 'NR_VERSION',
    id composite ['idColumn','nrVersion']

   int hashCode () {
    def builder = new HashCodeBuilder ()
    builder.append (idColumn)
    builder.append (nrVersion)
    builder.toHashCode ()
  }

  boolean equals (other) {
    if (other == null) {
      return false
    }
    def builder = new EqualsBuilder ()
    builder.append (idColumn, other.idColumn)
    builder.append (nrVersion, other.nrVersion)
    builder.isEquals ()
  }
} 

When I try to bootRun my app I get BeanCreationException:

Caused by: org.hibernate.MappingException: Foreign key (FK_1rnajvolkf4rkav5w1hl0l9fk:AK_TOOTH [tooth_disease_id,FK_AK_TOOTH_ID,FK_AK_TOOTH_NR_VERSION,tooth_disease_id_column,tooth_disease_nr_version])) must have same number of columns as the referenced primary key (AK_TOOTH_DISEASE [ID,NR_VERSION]).

When I removed column mapping section in Tooth, then I got the same error without those columns

[tooth_disease_id,tooth_disease_id_column,tooth_disease_nr_version].

Is it possible to explicitly define key column and referencedColumnName like in hibernate? Why there are three columns? especially tooth_disease_id and tooth_disease_id_column columns in the same time. I tried to use 'primaryKey' name as composite primary key for Tooth and 'id' simple as id column, but in this way, I had the same error

Caused by: org.hibernate.MappingException: Foreign key (FK_1rnajvolkf4rkav5w1hl0l9fk:AK_TOOTH [tooth_disease_id,FK_AK_TOOTH_ID,FK_AK_TOOTH_NR_VERSION,tooth_disease_id_column,tooth_disease_nr_version])) must have same number of columns as the referenced primary key (AK_TOOTH_DISEASE [ID]).But there is only one referenced primary key.

Unfortunately, I have no chance to change schema structure and avoid composite key.


Solution

  • I found here the workaround from Burt Beckwith. Since GrailsAnnotationConfiguration was replaced by HibernateMappingContextConfiguration.groovy I made some changes in his solution:

    class CompositeForeignKeyConfiguration extends HibernateMappingContextConfiguration {
      private static final long serialVersionUID = 1
      private static final String TOOTH_CLASS_NAME = 'com.project.TOOTH'
        private static final String TOOTH_ID_FK = 'FK_AK_TOOTH_ID'
      private static final String TOOTH_VERSION_FK = 'FK_AK_TOOTH_NR_VERSION'
    
      private boolean _alreadyProcessed
    
      @SuppressWarnings ("unchecked")
      @Override
      protected void secondPassCompile () throws MappingException {
    
        for (PersistentEntity pc : hibernateMappingContext.getPersistentEntities ()) {
          if (pc.name == TOOTH_CLASS_NAME) {
            pc.getAssociations ().each { Association mp ->
              if (mp.name == 'toothDisease') {
                PropertyConfig config = (PropertyConfig) mp.getMapping ().getMappedForm ()
                if (config.columns.size () == 1) {
                  config.columns.clear ()
                  final ColumnConfig fk1 = new ColumnConfig (name: TOOTH_ID_FK )
                  final ColumnConfig fk2 = new ColumnConfig (name: TOOTH_VERSION_FK )
                  config.columns << fk1
                  config.columns << fk2
                }
              }
            }
          }
    
        }
    
        super.secondPassCompile ()
        if (_alreadyProcessed) {
          return
        }
        _alreadyProcessed = true
      }
    }