Статьи

Материал, который я узнал от Grails Consulting

Я мало занимаюсь консультированием Grails, так как работаю в инженерной группе, и у нас есть отличная группа инженеров поддержки, которые обычно работают напрямую с клиентами. Иногда я преподаю 3-дневный курс по Groovy и Grails, но пока я участвовал только в двух выездных консультациях, и один из них был двухнедельным мероприятием, которое закончилось на прошлой неделе. Как часто бывает, когда вы чему-то учите или помогаете кому-то другому, я многому научился, и мне напомнили о многих вещах, о которых я забыл, поэтому я подумал, что было бы хорошо записать некоторые из них для дальнейшего использования.

Ведение журнала SQL

Есть два способа просмотра вывода SQL из запросов; добавление logSql = true в DataSource.groovy и настройка регистраторов Log4j. Подход Log4j намного более гибок, поскольку он не просто создает дамп в стандартный вывод, его можно направить в файл или другое приложение, удобно включить и отключить. Но оказывается, что logSql SQL консоли легко переключать. Получите ссылку на sessionFactory компонент sessionFactory (например, используя внедрение зависимостей с def sessionFactory ) и включите его с помощью

1
sessionFactory.settings.sqlStatementLogger.logToStdout = true

и покончить с

1
sessionFactory.settings.sqlStatementLogger.logToStdout = false

stacktrace.log

Файл stacktrace.log становился очень большим, и они хотели сконфигурировать его для использования плавающего файла appender. Казалось, достаточно просто, но это заняло гораздо больше времени, чем я ожидал. Хитрость в том, чтобы создать аппендер с именем 'stacktrace' ; логика Grails, которая анализирует DSL Log4j, ищет существующий appender и использует его, а также настраивает стандартный по умолчанию, если он еще не настроен. Итак, вот тот, который настраивает RollingFileAppender с максимум 10 файлами, каждый размером до 10 МБ, и со стандартным шаблоном макета. Кроме того, он включает логику, позволяющую определить, развернут ли он в Tomcat, чтобы он мог выполнять запись в папку журналов Tomcat или в целевую папку, если вы используете run-app.

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

01
02
03
04
05
06
07
08
09
10
11
appenders {
   String logDir = grails.util.Environment.warDeployed ?
                       System.getProperty('catalina.home') + '/logs' :
                       'target'
   rollingFile name: 'stacktrace',
               maximumFileSize: 10 * 1024 * 1024,
               file: '$logDir/stacktrace.log',
               layout: pattern(
                   conversionPattern: ''%d [%t] %-5p %c{2} %x - %m%n''),
               maxBackupIndex: 10
}

Динамическое свойство fooId

Во множестве к одному, где у вас есть поле Foo foo (или static belongsTo = [foo: Foo] которое вызывает добавление поля ‘foo’), вы можете получить доступ к его внешнему ключу с fooId свойства dynamic fooId . Это можно использовать несколькими способами. Так как подобные ссылки по умолчанию являются ленивыми, проверка, существует ли пустая ссылка с использованием foo != null включает загрузку всего экземпляра из базы данных. Но проверка fooId != null не требует доступа к базе данных.

Другие запросы или обновления, которым действительно нужен только внешний ключ, будут дешевле, если использовать fooId . Например, чтобы установить ссылку в другом экземпляре, вы обычно используете такой код:

1
2
bar2.foo = bar1.foo
bar2.save()

Но вы можете использовать метод load

1
2
bar2.foo = bar1.fooId ? Foo.load(bar1.fooId) : null
bar2.save()

и избегайте загрузки экземпляра Foo, просто чтобы установить его внешний ключ во втором экземпляре, а затем отбросить его.

Удаление по id тоже дешевле; Обычно вы используете get для загрузки экземпляра и вызова его метода delete , но извлечение всего экземпляра не требуется. Вы можете сделать это вместо этого:

1
Foo.load(bar.fooId).delete()

СУХИЕ ограничения

Вы можете использовать метод importFrom внутри блока constraints в классе домена, чтобы избежать повторения ограничений. Вы можете импортировать все ограничения из другого класса домена:

1
2
3
4
5
static constraints = {
   someProperty nullable: true
   ...
   importFrom SomeOtherDomainClass
}

и необязательно использовать свойства include и / или exclude для использования подмножества:

1
2
3
4
5
static constraints = {
   someProperty nullable: true
   ...
   importFrom SomeOtherDomainClass, exclude: ['foo', 'bar']
}

Flush слушатель события

Они наблюдали странное поведение, когда коллекции, которые не были явно изменены, изменялись и сохранялись, вызывая StaleObjectStateException . Неясно, что вызвало такое поведение, поэтому я предложил зарегистрировать Hibernate FlushEventListener для регистрации состояния грязных экземпляров и коллекций во время каждого сброса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.burtbeckwith.blog
import org.hibernate.HibernateException
import org.hibernate.collection.PersistentCollection
import org.hibernate.engine.EntityEntry
import org.hibernate.engine.PersistenceContext
import org.hibernate.event.FlushEvent
import org.hibernate.event.FlushEventListener
class LoggingFlushEventListener implements FlushEventListener {
   void onFlush(FlushEvent event) throws HibernateException {
      PersistenceContext pc = event.session.persistenceContext
      pc.entityEntries.each { instance, EntityEntry value ->
         if (instance.dirty) {
            println 'Flushing instance $instance'
         }
      }
      pc.collectionEntries.each { PersistentCollection collection, value ->
         if (collection.dirty) {
            println 'Flushing collection '$collection.role' $collection'
         }
      }
   }
}

В этом случае недостаточно использовать стандартную карту hibernateEventListeners (описанную в документации здесь ), поскольку этот подход добавляет ваших слушателей в конец списка, и этот слушатель должен быть в начале. Поэтому вместо этого используйте этот код в BootStrap.groovy, чтобы зарегистрировать его:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
import org.hibernate.event.FlushEventListener
import com.burtbeckwith.blog.LoggingFlushEventListener
class BootStrap {
  def sessionFactory
  def init = { servletContext ->
    def listeners = [new LoggingFlushEventListener()]
    def currentListeners = sessionFactory.eventListeners.flushEventListeners
    if (currentListeners) {
      listeners.addAll(currentListeners as List)
    }
    sessionFactory.eventListeners.flushEventListeners =
            listeners as FlushEventListener[]
  }
}


«Только для чтения» объекты и сеансы

Метод read был добавлен в Grails некоторое время назад, и он работает как get за исключением того, что он помечает экземпляр как доступный только для чтения в Hibernate Session. На самом деле это не только для чтения, но если он будет изменен, он не будет кандидатом на автоматическую очистку с использованием грязного обнаружения. Но вы можете явно вызвать save() или delete() и действие будет выполнено успешно.

Это может быть полезно во многих отношениях, и, в частности, оно более эффективно, если вы не будете изменять экземпляр, поскольку Hibernate не будет сохранять копию исходных данных базы данных для грязной проверки во время сброса, поэтому каждый экземпляр будет использовать около половины памяти, что было бы иначе.

Одним из ограничений метода read является то, что он работает только для экземпляров, загружаемых индивидуально по id. Но есть и другие подходы, которые влияют на несколько экземпляров. Одним из них является сделать весь сеанс только для чтения:

1
session.defaultReadOnly = true

Теперь все загруженные экземпляры будут по умолчанию доступны только для чтения, например, экземпляры из критериев запросов и поиска.

Удобным способом доступа к сеансу является метод withSession в произвольном классе домена:

1
2
3
SomeDomainClass.withSession { session ->
   session.defaultReadOnly = true
}

Редко, что весь сеанс будет доступен только для чтения. Вы можете установить результаты запроса отдельных критериев только для чтения с setReadOnly метода setReadOnly :

1
2
3
4
5
6
7
def c = Account.createCriteria()
def results = c {
   between('balance', 500, 1000)
   eq('branch', 'London')
   maxResults(10)
   setReadOnly true
}

Одним существенным ограничением этого метода является то, что на присоединенные коллекции не влияет состояние только для чтения экземпляра-владельца (и, похоже, нет способа настроить коллекцию таким образом, чтобы она игнорировала изменения для каждого экземпляра).

Подробнее об этом читайте в документации по Hibernate.

Ссылка: Материал, которому я научился консультироваться у нашего партнера JCG Берта Беквита в блоге « Армия солипсистов» .