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

Grails自定义侦听器,更新属性会生成其他事件吗?,grails,google-maps-api-3,listener,gorm,Grails,Google Maps Api 3,Listener,Gorm,我需要从客户机域中的一些地址字段计算coords,因此为了将域类与此要求解耦,我添加了一个自定义侦听器,它使用GoogleMapsAPI来获取正确的值 一切都正常,但调试时我意识到更新监听器中的域属性会启动另一个更新事件,并且我的监听器会连续为每次更新调用Google API两次 有人遇到了这个问题??我做错了什么 域: class Client{ String address String city String country String postalCode doub

我需要从客户机域中的一些地址字段计算coords,因此为了将域类与此要求解耦,我添加了一个自定义侦听器,它使用GoogleMapsAPI来获取正确的值

一切都正常,但调试时我意识到更新监听器中的域属性会启动另一个更新事件,并且我的监听器会连续为每次更新调用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]
        }
    }
}        
}
服务:

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]
        }
    }
}        
}
事件侦听器:

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]
        }
    }
}        
}
2。然后调用保存更新侦听器,并更新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 listener:(


上次更新

最后,尝试只使用一个侦听器以保持简单。整个要点似乎是在插入之后执行hibernate“save update”侦听器,详细信息:

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

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

ClientGeoListener.onSaveOrUpdate(SaveOrUpdateEvent) line: 34    
SessionImpl.fireSaveOrUpdate(SaveOrUpdateEvent) line: 685   
SessionImpl.saveOrUpdate(String, Object) line: 677  
2.坐标已更新,并且有一个更新


很抱歉进行了这么大的更新,Ideas???

代替直接
lat
/
lng
setter访问,也许可以尝试使用
EntityAccess
。请参阅Grails内核中的示例。

Grails在后台使用Hibernate。通常情况下,您不更新
预更新
事件中的属性。您可以这样做,但如果如果不同时更新属性和基础的
persistenentity
,可能会导致一些不希望的行为

该工作流解释了为什么您会获得两个更新:

  • 您对域对象进行了更改,该是持久化事件的时候了
  • Hibernate决定保存什么
  • 在更新火灾之前,更新
    lat
    /
    lng
    属性
  • Hibernate保持对象,但使用
    lat
    /
    lng
    的旧值
  • 域对象现在是脏的
  • Hibernate决定需要持久化脏对象(现在具有正确的值)
  • 在更新点火之前,从API获取与
    lat
    /
    lng
    相同的值
  • Hibernate使用新值持久化对象
  • 域对象和持久对象现在匹配。Hibernate很高兴
  • 避免双重更新的最简单解决方案是使用
    SaveOrUpdate
    事件,而不是在更新之前使用

    然而,由于您的目标是最大限度地减少不必要的API调用次数,因此在更新之前仍然值得使用
    ,因为在调用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 */ }
    

    项目中是否有多个数据源?有一个可能与此相关的错误,请看:没有,只有一个数据源,我尝试使用cli。@lat为了跳过setter方法,但它也不起作用。:(更新了我的答案,其中有一个明显的遗漏。:)@ivo_èes如果不想让两个单独的类侦听,我假设您可以创建子类。不知道在没有看到代码的情况下还可以建议什么。顺便说一句,EventTriggeringInterceptor类所做的只是打开事件类型,然后使用
    event.getNativeEvent()将事件转换为休眠事件
    所以,我想知道你是否可以退回到使用
    预更新
    和/或
    预插入
    然后从
    event.nativeEvent.entity
    中获取实体。也许有人忘了在某个地方正确地分配引用。我将把问题简化为基本问题,并询问是否存在bug或其他问题saveupdate侦听器。谢谢您,:DUpdated link:仍然停留在grails 2.5上,并且存在此问题。该AutoTimestampListener仅用于单元测试。在集成测试或运行的应用程序中,未设置entityAccess:(