尽管没有关联,但在调用.list()时每行都会运行一次SQL

时间:2016-11-01 19:37:40

标签: hibernate grails gorm

我正在使用grails 2.5.3

我的配置如下:

grails {
    hibernate {
        cache.queries = true
        cache.use_second_level_cache = true
        cache.use_query_cache = true
        cache.provider_class =  'net.sf.ehcache.hibernate.SingletonEhCacheProvider'

我有域类:

class AuthorizedDevice implements JSONFormat {
    int id
    String authKey = generateAuthKey()
    String owner
    String name
    String permittedUsers
    String userAgent
    Date   lastVisit
    Date   lastInitialized
    String lastUser
    String lastIpAddress
    Date   dateCreated
    boolean enabled = true
    String notes

    static constraints = {
        authKey(blank: false, unique: true, maxSize: 8)
        owner(blank: false, validator: GormValidators.isCorpUserName)
        name(blank: false, unique: 'owner')
        permittedUsers(nullable: true, validator: permittedUsersValidator)
        userAgent(nullable: true, maxSize: 500)
        lastVisit(nullable: true)
        lastInitialized(nullable: true)
        lastUser(nullable: true, maxSize: 50)
        lastIpAddress(nullable: true, maxSize: 50)
        notes(nullable: true, maxSize: 500)
    }

    def auditService
    def afterInsert() {auditService.noteDeviceChange('Created Device', id)}
    def afterUpdate() {auditService.noteDeviceChange('Updated Device', id)}
    def afterDelete() {auditService.noteDeviceChange('Deleted Device', null)} // Not allowed by GUI, but just in case.


    public Object formatForJSON() {
        return [
                id: id,
                authKey: authKey,
                owner: owner,
                name: name,
                permittedUsers: permittedUsers,
                userAgent: userAgent,
                lastVisit: lastVisit,
                lastInitialized: lastInitialized,
                lastUser: lastUser,
                lastIpAddress: lastIpAddress,
                enabled: enabled,
                notes: notes
        ]
    }

    //------------------
    // Implementation
    //------------------
    private String generateAuthKey() {
         ....
    }

    static permittedUsersValidator = {String val, Object obj, Errors errors ->
        if (!val || val.trim().equals('*')) return
        val.split(',').each {
            if (!getCorprUser(it.trim())) {
                errors.rejectValue('permittedUsers', '',
                        "Unknown User ${it}.  Use a comma-delimited list of usernames or  * to indicate all users."
                )
            }
        }
    }

}

我构建了一个设备列表,如:

def devices = AuthorizedDevice.list()

我注意到每次调用AuthorizedDevice.list()时,GORM / hibernate都会对表中的每一行执行一次SQL查询。

我们在模型字段中没有会产生N + 1个查询的任何关联。

即使没有关联,有人知道是什么促使这种N + 1行为吗?

第一次调用.list()时,以下SQL只运行一次:

select
    this_.id as id4_0_,
    this_.version as version4_0_,
    this_.auth_key as auth3_4_0_,
    this_.date_created as date4_4_0_,
    this_.enabled as enabled4_0_,
    this_.last_initialized as last6_4_0_,
    this_.last_ip_address as last7_4_0_,
    this_.last_user as last8_4_0_,
    this_.last_visit as last9_4_0_,
    this_.name as name4_0_,
    this_.notes as notes4_0_,
    this_.owner as owner4_0_,
    this_.permitted_users as permitted13_4_0_,
    this_.user_agent as user14_4_0_ 
from
    authorized_device this_

每次调用.list()之后,都会为表中的每一行运行此SQL:

select
    authorized0_.id as id4_0_,
    authorized0_.version as version4_0_,
    authorized0_.auth_key as auth3_4_0_,
    authorized0_.date_created as date4_4_0_,
    authorized0_.enabled as enabled4_0_,
    authorized0_.last_initialized as last6_4_0_,
    authorized0_.last_ip_address as last7_4_0_,
    authorized0_.last_user as last8_4_0_,
    authorized0_.last_visit as last9_4_0_,
    authorized0_.name as name4_0_,
    authorized0_.notes as notes4_0_,
    authorized0_.owner as owner4_0_,
    authorized0_.permitted_users as permitted13_4_0_,
    authorized0_.user_agent as user14_4_0_
from
    authorized_device authorized0_
where
    authorized0_.id=?

1 个答案:

答案 0 :(得分:1)

https://dzone.com/articles/pitfalls-hibernate-second-0

  

如果查询缓存了结果,则会返回实体ID列表   然后针对二级缓存解析。如果实体用   那些没有配置为可缓存的ID或它们已过期的ID   然后,select将按实体Id命中数据库。

     

例如,如果缓存的查询返回1000个实体ID,而非   那些缓存在二级缓存中的实体,然后是1000   将通过Id选择将针对数据库发出。

     

此问题的解决方案是配置查询结果到期   与...返回的实体的到期一致   查询。

在您的情况下,解决方案可能只是添加到AuthorizedDevice:

static mapping = { cache true } 

为了在域类AuthorizedDevice上启用二级缓存(hibernate默认不启用它)。

因为它似乎是第一个sql日志的结果,所以你给出了:

select
    this_.
...
from

位于缓存中(查询缓存)。所以它没有被执行两次。在我给出的链接中,解释了结果被缓存为实体ID列表。但并非每个实体的所有数据都在缓存中,只有ids。 然后在对.list()的另一个调用期间,hibernate会在缓存中有这些id,并且会尝试在二级缓存中检索相应的实体,但是它会失败,然后hibernate会对每个ID的数据库进行查询。 / p>