Должен ли я когда-либо явно скрывать вызовы GORM в grails?
У меня странная ситуация, которая, как представляется, указывает на проблему кеширования GORM
//begin with all book.status as UNREAD
Book.list().each { book.status = Status.READ ; book.save() }
println (Book.findAllByStatus (Status.READ)) //will print an empty list
println (Book.list().findAll (it.status == Status.READ)) // will print all books
Я не могу понять, почему последние два запроса могут возвращать разные результаты.
Однако, если я сделаю следующую модификацию book.save(flush: true). Оба оператора println возвратят все книги.
У меня создалось впечатление, что это не было необходимо в рамках одного приложения.
Для справки я использую
- DB: mysql
- Groovy: 1.7.10
- Grails: 1.3.7
@Hoàng Long
Моя проблема показана ниже, предположим, что action1/action2 вызываются много раз, ни в каком конкретном шаблоне
def action1 = {
Foo foo = Foo.get(params.id)
//... modify foo
foo.save() //if I flush here, it will be inefficient if action1 is called in sequence
}
def action2 = {
//if I flush here, it will be inefficient if action2 is called in sequence
List<Foo> foos = Foo.findAllByBar (params.bar)
//... do something with foos
}
Одним из решений было бы наличие флага, который устанавливается action1 и используется action2 для при необходимости, при необходимости. Моя проблема в том, что это слишком сложное решение, которое не масштабируется по мере увеличения сложности вызовов БД.
boolean isFlushed = true
def action1 = {
Foo foo = Foo.get(params.id)
//... modify foo
foo.save()
isFlushed = false
}
def action2 = {
if (!isFlushed) {
//flush hibernate session here
}
List<Foo> foos = Foo.findAllByBar (params.bar)
//... do something with foos
}
Ответы
Ответ 1
В вашем случае первый оператор возвращает пустой список, потому что он считывает данные из базы данных, но данных еще нет.
Как работает Hibernate: когда вы вызываете save с помощью (flush: true)
, он будет скрывать сеанс Hibernate, сохраняя все данные в сеансе в базе данных немедленно. Если вы не используете (flush:true)
, данные записываются только в сеанс Hibernate и сохраняются только в базе данных , когда сеанс Hibernate очищается. Время для очистки сеанса автоматически определяется Hibernate для оптимизации производительности.
Как правило, вы должны позволить Hibernate выполнять работу за вас (ради оптимизации) - если вы не хотите, чтобы данные сохранялись сразу.
По словам Питера Ледбрука:
Пусть Hibernate выполняет эту работу и только вручную очищает сеанс, когда вы должны или, по крайней мере, только в конце от партии обновлений. Вы должны действительно, если вы не видите данных в базе данных, когда это должно быть там. Я знаю, что немного нехорошо, но обстоятельства когда такое действие необходимо по внедрению базы данных и другие факторы.
Из GORM Gotchas - часть 1
UPDATE: чтобы было ясно, как очистить сеанс один раз после того, как все объекты будут сохранены:
import org.hibernate.*
class SomeController {
SessionFactory sessionFactory
def save = {
assert sessionFactory != null
// loop and save your books here
def hibSession = sessionFactory.getCurrentSession()
assert hibSession != null
hibSession.flush()
}
}
Ответ 2
Нужно ли мне явно скрывать вызовы GORM в grails?
Короче Да!, если вы хотите немедленно использовать объект, как вы делаете в своем коде.
Я столкнулся с такой же проблемой, так что это картина, которую я получил после прочтения некоторых ссылок.
Это проблема hibernate session.
Сеанс Hibernate создается, когда действие контроллера вызывается и заканчивается, когда действие возвращается (или рано умирает с ошибкой). Если код не вызывает какой-либо транзакционный код, взаимодействие Hibernate db может быть показано следующим образом:
Предположим, что имя действия для входа - actionName, и вызов действия завершается без каких-либо ошибок.
NB. Средний байт (кеш второго уровня отключен), поскольку никакого транзакционного кода нет.
![without transaction without error]()
если этот код имеет ошибку:
![without transaction with error]()
Но если ваше действие вызывает транзакционный метод или создает встроенную транзакцию с помощьюTransaction (и принять вызов к действию, выполненному без какой-либо ошибки).
![with transaction without error]()
Если приведенный выше код имеет ошибку:
![with transaction with error]()
Я надеюсь, что это поможет, но если я сделал какую-либо ошибку или пропустил, чтобы включить большую точку, прокомментируйте, что я обновлю свои фотографии.
Ответ 3
Интересно, какова была ваша настройка FlushMode.
По умолчанию установлено значение "авто", и это означает, что сеанс очищается перед каждым запросом, который напрямую обращается к БД (и, возможно, в других случаях тоже). В этом случае ваш Foo.findAllByBar должен сначала очистить сеанс (возможная проблема с производительностью!) И прочитать правильное значение из БД.
Есть два других значения для FlushMode, и если вы установите один из них, это объяснит ваши проблемы.
Во-первых, это "руководство", что означает, что вы решили провести сеанс ручного сброса (например, с помощью save (flush: true)). Если вы этого не сделаете, Foo.findAllByBar читает устаревшее состояние БД.
Второй - это "фиксация", что означает, что сеанс очищается при каждой транзакционной фиксации. Это очень удобно, если вы используете инструкцию "withTransaction" в grails.
Ресурсы:
http://schneide.wordpress.com/2011/03/08/the-grails-performance-switch-flush-modecommit/
http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/objectstate.html#d0e1215
Ответ 4
Для дополнительной информации вы не можете использовать flush или save (flush: true) в событиях класса домена (afterUpdate, beforeUpdate, ect). Это приведет к ошибке.
Вы можете использовать save() без flush, хотя.
gorm docs