2

クライアント ドメインのいくつかの住所フィールドから座標を計算する必要があるため、ドメイン クラスをこの要件から切り離すために、Google Maps API を使用して正しい値を取得するカスタム リスナーを追加しました。

すべてが機能しますが、デバッグ中です。リスナー内のドメイン プロパティを更新すると、別の更新イベントが起動され、その結果、更新ごとにリスナーが Google API を 2 回呼び出すことに気付きました。

誰かがこの問題を経験していますか?? 私は何を間違っていますか??

ドメイン:

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 固有のリスナーであるため、最終的に 2 つのリスナーができます。

  • ClientMailListener (Grails カスタムリスナー、postinsert、postupdate、postdelete、オブジェクトに変更なし)
  • ClientGeoListener (「save-update」としてマッピングされた Hibernate リスナー)

このコンボのステータスは次のとおりです。

  • 更新:OK、両方のリスナーが一度だけ呼び出されます
  • 挿入: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 リスナーを呼び出し、オブジェクトを更新します

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

3.続いて、postUpdate grails リスナーが再度呼び出されます:(


最後の更新

最後に、シンプルにするためにリスナーを 1 つだけにしてみてください。全体のポイントは、挿入後に hibernate の「save-update」リスナーが実行されることです。詳細は次のとおりです。

Hibernate リスナー (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望ましくない動作が発生する可能性があります。

2 つの更新を取得する理由を説明するワークフロー:

  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 呼び出しの数を最小限に抑えることが目標であるため、APIBeforeUpdateを呼び出す前にエンティティの汚れを手動でチェックすることは、おそらくその価値よりも労力がかかるため、使用する価値があります。したがって、@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/lngセッターアクセスの代わりに、おそらく使用してみてくださいEntityAccess。例については、Grails コアのAutoTimestamp リスナーを参照してください。

于 2013-02-08T16:19:39.160 に答える