2

我需要从客户端域中的某些地址字段计算坐标,因此为了将域类与此要求分离,我添加了一个自定义侦听器,该侦听器使用 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]
        }
    }
}        
}

更新:

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

打算试试 SaveOrUpdate...


大更新:

我越来越近了,但仍然无法避免重复的呼叫。

由于我需要一个“saveOrUpdate”侦听器,而这个侦听器是特定于 Hibernate 的侦听器,因此我最终有两个侦听器:

  • ClientMailListener(Grails 自定义监听器,postinsert,postupdate,postdelete,对象没有变化)
  • ClientGeoListener(休眠监听器,映射为“保存更新”)

这个组合的状态是:

  • 更新:好的,两个监听器都只调用一次
  • 插入: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对象

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

3.随后再次调用postUpdate grails 监听器,:(


最后更新

最后尝试只使用一个听众来保持简单。看起来重点是在插入之后执行休眠“保存更新”侦听器,详细信息:

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

1.插入客户端(空白坐标)并调用saveupdate监听器:

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

2.坐标更新,有更新

抱歉这个大更新,想法?

4

2 回答 2

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. 域对象和持久对象现在匹配。冬眠很开心。

避免双重更新的最简单解决方案是使用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 */ }
于 2013-02-09T07:10:14.760 回答
1

而不是直接lat/ lngsetter 访问,也许尝试使用EntityAccess. 有关示例,请参阅Grails 核心中的AutoTimestamp 侦听器。

于 2013-02-08T16:19:39.160 回答