Oleg Atamanenko

thoughts about programming

Несколько слов о GORM

В данной заметке хочу поделиться некоторыми моментами использования GORM.

GORM - это ORM-фреймворк, используемый в Grails. Реализован он поверх Hibernate, но, при этом, с некоторыми отличными умолчаниями.

Для разработчиков, знающих Hibernate, рекомендую тщательно изучить GORM, так как его поведение в некоторых случаях отлично от Hibernate, что может приводить к различным сюрпризам.

Маппинг один-ко-многим

По умолчанию GORM для связей один ко многим (one-to-many) создаёт таблицу-связку, которая обычно нужна только при связях между сущностями вида многие ко многим. Чтобы исправить это поведение необходимо указать GORM, чтобы он не создавал таблицу связку.

class Person implements Serializable {
  static hasMany = [
    scores: ScoreSheet
  ]
  
  static mapping = {
    scores joinTable: false
  };
}

Использование однонаправленных связей

Если в приложении используются двунаправленные связи и вероятность изменения сущности одновременно несколькими пользователями высокая, то лучше использовать однонаправленные связи для сущностей. Кроме того, лучше проектировать доменные классы таким образом, чтобы связь была не один-ко-многим, а многие к одному.

class Note implements Serializable {
  static belongsTo = [
    person: Person
  ]
}

class Person implements Serializable {
  // person fields.
}

Для работы с Notes необходимо использовать такие запросы:

  Note.findByPerson(person).each { -> };

вместо

  person.notes.each { -> }

Маппинг иерархии классов доменных сущностей

GORM поддерживает только два варианта маппинга иерархии классов, в отличии от Hibernate: Таблица на всю иерархию (table-per-hierarchy), или таблица на каждый подкласс (table-per-subclass). У маппинга table-per-hierarchy есть серьёзный недостаток - подклассы не могут иметь ненулевые поля. Поэтому, если этот недостаток критичен, то необходимо использовать маппинг table-per-subclass.

class Payment {
  Long id
  Long version
  Integer amount

  static mapping = {
    tablePerHierarchy false
  }
}

class CreditCardPayment extends Payment {
  String cardNumber
}