Пакетное обновление записей в Grails - PullRequest
2 голосов
/ 28 мая 2010

Мне нужно рассчитать «общее» для каждого пользователя на основе отдельных действий, то есть у пользователя есть много действий, и у каждого действия есть точка. Поэтому мне нужно в основном получить сумму всех баллов за все действия.

Действия добавляются и вычитаются регулярно. Поскольку это может быть довольно трудоемкая операция, я не могу выполнить «итого» каждый раз, когда мне это нужно. Поэтому я думаю о том, чтобы запускать операцию один раз в день для каждого пользователя и сохранять общее количество баллов для пользователя в виде целого числа.

Поскольку у меня тысячи пользователей, я пытаюсь найти лучший способ сделать это. Должен ли я в основном перебирать всех пользователей и каждый раз вызывать User.save (), или есть какой-то механизм пакетного обновления, который я могу использовать в Grails / GORM?

Ответы [ 2 ]

1 голос
/ 31 мая 2010

Является ли модель в вашем вопросе вашей реальной моделью или ее упрощенной версией? Если все, что вы делаете, это действия пользователя hasMany, и каждое действие имеет точечное значение (целое число?), Которое вы хотите суммировать, то это действительно то, в чем превосходные реляционные базы данных. Если у вас есть правильные индексы в вашей базе данных, я думаю, это будет очень быстрый вызов, если вы выполняете запрос groupBy (или используете критерии проекции grails).

Вот пример метода, который возьмет список пользователей и вернет массив, который соединяет user.id с количеством точек, которые пользователь имеет в данный момент:

def calculateCurrentPoints(users) {
    Action.executeQuery('select a.user.id, sum(points) from Action a where a.user.id in (:userIds) group by a.user.id', [userIds: users.id])
}

При желании вы можете легко превратить это в карту с идентификатором пользователя в точках для более удобного поиска:

def calculateCurrentPoints(users) {
    def result = Action.executeQuery('select a.user.id, sum(points) from Action a where a.user.id in (:userIds) group by a.user.id', [userIds: users.id])
    result.inject([:]) { map, userWithPoints -> 
        map[userWithPoints[0]] = userWithPoints[1]
        return map
    }
}

Если это действительно медленнее, чем я думаю, вы можете использовать executeUpdate с HQL, аналогичным приведенному выше, чтобы обновить поле «totalPoints» для каждого пользователя (или сохранять его обновленным как часть службы всякий раз, когда вы добавляете новое действие для этого пользователя).

0 голосов
/ 30 мая 2010

Вызов User.save() на самом деле не запишет изменение в базу данных, только при сбросе сеанса Hibernate записывается изменение ( docs )

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

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

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

...