Grails自定义监听器,更新属性会产生其他事件吗?

时间:2013-02-04 15:19:43

标签: grails google-maps-api-3 listener gorm

我需要从客户端域中的某些地址字段计算coords,因此为了将域类与此要求分离,我添加了一个使用Google Maps API获取正确值的自定义侦听器。

一切正常,但是调试'我已经意识到更新侦听器内的域属性会引发另一个更新事件,并且我的听众每次更新都会调用Google API

有人遇到过这个问题吗?我做错了什么?

class Client{
  String address
  String city
  String country
  String postalCode
  double lat
  double lng
  ....
}

服务

class GoogleMapsService{
      static transactional=false
      def grailsApplication

      def geocode(String address){
        address=address.replaceAll(" ", "+")
        def urlApi=grailsApplication.config.googleMaps.apiUrl+"&address=${address}"     
        def urlJSON = new URL(urlApi)
        def geoCodeResultJSON = new JsonSlurper().parseText(urlJSON.getText())              
        return geoCodeResultJSON            
      }
}

事件监听器:

class ClientListener extends AbstractPersistenceEventListener{
public boolean supportsEventType(Class<? extends ApplicationEvent> eventClass) {
    switch(eventClass){
        case [PreInsertEvent,
                        PreUpdateEvent, 
                        PostInsertEvent,
                        PostUpdateEvent,
                        PostDeleteEvent]:
            return true
        default:
            return false
    }
}        

protected void onPersistenceEvent(AbstractPersistenceEvent event) {
    if(!(event.entityObject instanceof Client)){
        return
    }

    switch(event.eventType) {
        //GOOGLE MAPS geocode
        case [EventType.PreInsert,EventType.PreUpdate]:
            this.updateCoords(event.entityObject)
            break

        //OTHER STUFF (notifications, no changing inside)
        case EventType.PostUpdate:
            //....
    }
}

private String composeAddress(Client cli){
    def resul=[cli.address,cli.city,cli.country,cli.postalCode]     
    return resul.findAll{it}.join(",")          
}

private void updateCoords(Client cli){
    def fullAddress=this.composeAddress(cli)
    if(fullAddress){
        def coords=googleMapsService.geocode(fullAddress)
        if (coords.status=="OK"){
            //**IMPORTANT STUFF THESE TWO LINES RAISE AN EVENT
            cli.lat=coords.results.geometry.location.lat[0]
            cli.lng=coords.results.geometry.location.lng[0]
        }
    }
}        
}

更新

实体在侦听器中为null,因此我无法使其从preUpdate运行, 看起来有一个未解决的问题(http://jira.grails.org/browse/GRAILS-9374

试试SaveOrUpdate ......


大更新:

我越来越近但仍然无法避免重复的电话。

由于我需要一个'saveOrUpdate'监听器,而且这个监听器是Hibernate特定的监听器,我最终会有两个监听器:

  • ClientMailListener(Grails自定义侦听器,postinsert,postupdate,postdelete,对象无变化)
  • ClientGeoListener(Hibernate侦听器,映射为'save-update')

此组合的状态为:

  • 更新:好的,两个听众只被调用一次
  • 插入:KO !!,让我们深入了解一下。

1。它执行插入并调用postInsert grails侦听器,为什么????

ClientMailListener.onPersistenceEvent(AbstractPersistenceEvent) line: 45    
ClientMailListener(AbstractPersistenceEventListener).onApplicationEvent(ApplicationEvent) line: 46  
SimpleApplicationEventMulticaster.multicastEvent(ApplicationEvent) line: 97 
GrailsWebApplicationContext(AbstractApplicationContext).publishEvent(ApplicationEvent) line: 324    
ClosureEventTriggeringInterceptor.publishEvent(AbstractEvent, AbstractPersistenceEvent) line: 163   
ClosureEventTriggeringInterceptor.onPostInsert(PostInsertEvent) line: 129   
EntityIdentityInsertAction.postInsert() line: 131   
EntityIdentityInsertAction.execute() line: 90   
ActionQueue.execute(Executable) line: 273   
ClosureEventTriggeringInterceptor.performSaveOrReplicate(Object, EntityKey, EntityPersister, boolean, Object, EventSource, boolean) line: 250   
ClosureEventTriggeringInterceptor(AbstractSaveEventListener).performSave(Object, Serializable, EntityPersister, boolean, Object, EventSource, boolean) line: 203    
ClosureEventTriggeringInterceptor(AbstractSaveEventListener).saveWithGeneratedId(Object, String, Object, EventSource, boolean) line: 129    
ClosureEventTriggeringInterceptor(DefaultSaveOrUpdateEventListener).saveWithGeneratedOrRequestedId(SaveOrUpdateEvent) line: 210 
ClosureEventTriggeringInterceptor(DefaultSaveOrUpdateEventListener).entityIsTransient(SaveOrUpdateEvent) line: 195  
ClosureEventTriggeringInterceptor(DefaultSaveOrUpdateEventListener).performSaveOrUpdate(SaveOrUpdateEvent) line: 117    
ClosureEventTriggeringInterceptor(DefaultSaveOrUpdateEventListener).onSaveOrUpdate(SaveOrUpdateEvent) line: 93  
ClosureEventTriggeringInterceptor.onSaveOrUpdate(SaveOrUpdateEvent) line: 108   
SessionImpl.fireSaveOrUpdate(SaveOrUpdateEvent) line: 685   

2. 然后调用save-update侦听器,并更新de object

ClientGeoListener.onSaveOrUpdate(SaveOrUpdateEvent) line: 34    
SessionImpl.fireSaveOrUpdate(SaveOrUpdateEvent) line: 685   
SessionImpl.saveOrUpdate(String, Object) line: 677  
SessionImpl.saveOrUpdate(Object) line: 673  

3。然后,再次调用postUpdate grails侦听器 :(


上次更新

最后尝试只使用一个监听器来保持简单。看起来重点是插入后执行hibernate'save-update'监听器,详情:

如果我禁用自定义侦听器而离开Hibernate侦听器(GEO),则会发生以下情况:

1。插入客户端(空白坐标)并调用saveupdate Listener:

ClientGeoListener.onSaveOrUpdate(SaveOrUpdateEvent) line: 34    
SessionImpl.fireSaveOrUpdate(SaveOrUpdateEvent) line: 685   
SessionImpl.saveOrUpdate(String, Object) line: 677  

2。更新了Coords,并且有更新

对不起,这个大型更新,想法??

2 个答案:

答案 0 :(得分:4)

Grails在后台使用Hibernate。通常,您不会更新PreUpdate事件中的属性。您可以这样做,但如果您不更新属性和基础PersistentEntity,您可能会遇到一些不良行为。

解释您获得两次更新的原因的工作流程:

  1. 您对域对象进行了更改,现在是时候保持事件了。
  2. Hibernate决定要坚持什么。
  3. BeforeUpdate触发,更新lat / lng属性。
  4. Hibernate会保留对象,但使用lat / lng的旧值。
  5. 域对象现在是dirty
  6. Hibernate决定它需要持久化脏对象(现在使用正确的值)。
  7. 引发BeforeUpdate,从API获取lat / lng的相同值。
  8. Hibernate使用新值保留对象。
  9. 域对象和持久对象现在匹配。 Hibernate很高兴。
  10. 避免重复更新的最简单的解决方案是使用SaveOrUpdate事件而不是BeforeUpdate,因为这实际上发生在流程的早期。

    但是,,因为您的目标是尽量减少不必要的API调用次数,所以仍然值得使用BeforeUpdate,因为在调用API之前手动检查您的实体是否有点肮脏可能比它的更多努力价值。所以,你要做的是,正如@Andrew建议的那样,只要确保你更新属性,即:

    event.entity.setProperty('lat', coords.results.geometry.location.lat[0])
    event.entity.setProperty('lng', coords.results.geometry.location.lng[0])
    cli.lat = coords.results.geometry.location.lat[0]
    cli.lng = coords.results.geometry.location.lng[0] 
    

    更新:我的假设是检查域对象是否脏,可能很烦人实际上是基于我必须为NHibernate编写的代码。这当然是Grails,它应该像这样简单:

    if (event.entityObject.isDirty()) { /* Call API */ }
    

答案 1 :(得分:1)

可以尝试使用lat,而不是直接lng / EntityAccess设置者访问权限。有关示例,请参阅Grails核心中的AutoTimestamp listener