在Grails中使用findOrSaveBy动态查找器时,如何避免出现重复键错误?

时间:2014-08-08 09:05:30

标签: grails gorm grails-2.0

在我尝试创建(或重复使用)并将关键字标签附加到图像库应用程序中的图像的并发情况(异步文件上载)中,我在创建时遇到唯一索引或主键违规 标记域对象。

控制器调用服务方法将图像添加到库以及属于它的关键字标签。

我是以错误的方式解决这个问题还是我错过了什么? 我目前正在使用Grails 2.3.5。

代码:

class ImageAssetService {

    def addTagToImage(ImageAsset imageAsset, String kw, String locale) {
        def tagName = kw.toLowerCase(Locale.forLanguageTag(locale))
        // OFFENDING LINE NEXT
        def tag = Tag.findOrSaveByNameAndLocale(tagName, locale, [lock: true])
        imageAsset.addToTags(tag)
        if(!imageAsset.save()) {
            throw new ImageAssetException()
        }
        imageAsset
    }

    def addTagsToImage(ImageAsset imageAsset, Set<String> keywords, String locale) {
        keywords.each { kw ->
            addTagToImage(imageAsset, kw, locale)
        }
        imageAsset
    }

    // CONTROLLER CALLS THIS METHOD
    def addImageAsset(InputStream inputStream, String filename, long fileSize, long authorId, String timeZoneOriginal, String heading, String description, Set tags, String imageCollectionName) {

        // Create the ImageAsset domain object
        ImageAsset imageAsset = new ImageAsset(
            filename: filename,
            fileSize: fileSize,
            author: Author.get(authorId),
            timeZoneOriginal: TimeZone.getTimeZone(timeZoneOriginal),
            heading: heading,
            description: description
        ).save()

        // Add any tags
        addTagsToImage(imageAsset, tags, 'en')

        /*
            ...
            CODE REMOVED FOR BREVITY
            ....
        */

        return imageAsset
    }

}
class Tag {

    String locale
    String name

    static hasMany = [ translations : Tag, synonyms : Tag ]

    void setName(String name) { this.@name = name.toLowerCase() }

    static constraints = {
        locale unique: ['name']
    }
    static belongsTo = ImageAsset
    static searchable = {
        except = ['locale']
        name boost: 2.0
        translations component: [prefix: 'translations_', maxDepth: 2]
        synonyms component: [prefix: 'synonyms_', maxDepth: 2]
    }
    static mapping = {
        table 'tags'
    }
}

class ImageAsset {

    String filename
    String heading
    String description
    String place
    String city
    String country
    String gps
    long fileSize = 0
    int pixelWidth = 0
    int pixelHeight = 0
    Date dateTimeOriginal
    TimeZone timeZoneOriginal

    boolean enabled = true
    Date dateCreated

    static belongsTo = [ 
        Author,
        ConceptualImageCategory,
        RepresentativeImageCategory,
        ImageCollection
    ]
    static hasOne = [ author : Author ]
    static hasMany = [
        conceptualCategories : ConceptualImageCategory,
        representativeCategories : RepresentativeImageCategory,
        collections : ImageCollection,
        metadata : Metadata,
        tags : Tag
    ]

    static constraints = {
        filename blank: false
        heading nullable: true
        description nullable: true
        place nullable: true
        city nullable: true
        country nullable: true
        gps nullable: true
        pixelWidth nullable: true
        pixelHeight nullable: true
        dateTimeOriginal nullable: true
        timeZoneOriginal nullable: true
    }

    static mapping = {
        description type: 'text'
    }

    static searchable = {
        //only = ['filename', 'heading', 'description', 'tags', 'metadata']
        author component: [prefix: 'author_']
        tags component: [prefix: 'tags_']
        metadata component: [prefix: 'metadata_']
    }
}

错误讯息:

Unique index or primary key violation: "CONSTRAINT_INDEX_27 ON PUBLIC.TAGS(NAME, LOCALE) VALUES ( /* key:11 */ 895, 0, 'en', 'work')"; SQL statement:
insert into tags (id, version, locale, name) values (null, ?, ?, ?) [23505-173]. Stacktrace follows:
Message: Unique index or primary key violation: "CONSTRAINT_INDEX_27 ON PUBLIC.TAGS(NAME, LOCALE) VALUES ( /* key:11 */ 895, 0, 'en', 'work')"; SQL statement:
insert into tags (id, version, locale, name) values (null, ?, ?, ?) [23505-173]
    Line | Method
->>  331 | getJdbcSQLException      in org.h2.message.DbException
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|    171 | get                      in     ''
|    148 | get . . . . . . . . . .  in     ''
|    101 | getDuplicateKeyException in org.h2.index.BaseIndex
|     68 | add . . . . . . . . . .  in org.h2.index.TreeIndex
|     52 | add                      in org.h2.index.MultiVersionIndex
|    125 | addRow . . . . . . . . . in org.h2.table.RegularTable
|    127 | insertRows               in org.h2.command.dml.Insert
|     86 | update . . . . . . . . . in     ''
|     79 | update                   in org.h2.command.CommandContainer
|    235 | executeUpdate . . . . .  in org.h2.command.Command
|    154 | executeUpdateInternal    in org.h2.jdbc.JdbcPreparedStatement
|    140 | executeUpdate . . . . .  in     ''
|    102 | doCall                   in org.grails.datastore.gorm.GormStaticApi$_methodMissing_closure2
|    105 | addTagToImage . . . . .  in se.originalab.imagedb.ImageAssetService
|     94 | doCall                   in se.originalab.imagedb.ImageAssetService$_addTagsToImage_closure3
|     93 | addTagsToImage . . . . . in se.originalab.imagedb.ImageAssetService
|     45 | addImageAsset            in     ''
|     31 | upload . . . . . . . . . in se.originalab.imagedb.UploadController
|    195 | doFilter                 in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter . . . . . . . . in grails.plugin.cache.web.filter.AbstractFilter
|     53 | doFilter                 in grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter
|     49 | doFilter . . . . . . . . in grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter
|     82 | doFilter                 in grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter
|   1110 | runWorker . . . . . . .  in java.util.concurrent.ThreadPoolExecutor
|    603 | run                      in java.util.concurrent.ThreadPoolExecutor$Worker
^    722 | run . . . . . . . . . .  in java.lang.Thread

1 个答案:

答案 0 :(得分:1)

我自己找到了解决方案。我不得不将Tag的创建分解为它自己的事务。现在,通过控制器的同步服务方法调用对Tag.findOrSaveByNameLocale()的每次调用。然后我将它们添加到ImageAsset。

这样做的一个问题是,如果ImageAsset的创建失败,标签仍然会被保留,但在我的用例中,这不是一个大问题。