1

SpringXD の gemfire-json-server モジュールを使用して、GemFire グリッドに「Order」オブジェクトの json 表現を設定しています。gemfire-json-server モジュールがデータを GemFire の Pdx 形式で保存することを理解しています。アプリケーションで、GemFire グリッドの内容を「注文」オブジェクトに読み込みたいと考えています。次の ClassCastException を取得します。

java.lang.ClassCastException: com.gemstone.gemfire.pdx.internal.PdxInstanceImpl cannot be cast to org.apache.geode.demo.cc.model.Order

Spring Data GemFire ライブラリを使用してクラスターのコンテンツを読み取ります。グリッドの内容を読み取るコード スニペットは次のとおりです。

public interface OrderRepository extends GemfireRepository<Order, String>{
    Order findByTransactionId(String transactionId);
}

Spring Data GemFire を使用して、GemFire クラスターから読み取ったデータを Order オブジェクトに変換するにはどうすればよいですか? 注: データは最初、SpringXD の gemfire-json-server-module を使用して GemFire に保存されました

4

2 に答える 2

2

まだ、GemFire PDX エンジニアリング チームからの連絡、具体的には を待っていますRegion.get(key)が、興味深いことに、アプリケーション ドメイン オブジェクトに ...

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type")
public class Order ... {
  ...
}

これはうまくいきます!

内部的には、GemFire JSONFormatterクラス (こちらを参照) が Jackson の API を使用して、JSON データを PDX との間で un/marshal (de/serialize) することを知っていました。

ただし、orderRepository.findOne(ID)ordersRegion.get(key)はまだ期待どおりに機能しません。詳細については、以下の更新されたテスト クラスを参照してください。

詳しい情報が入り次第また報告します。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = GemFireConfiguration.class)
@SuppressWarnings("unused")
public class JsonToPdxToObjectDataAccessIntegrationTest {

  protected static final AtomicLong ID_SEQUENCE = new AtomicLong(0l);

  private Order amazon;
  private Order bestBuy;
  private Order target;
  private Order walmart;

  @Autowired
  private OrderRepository orderRepository;

  @Resource(name = "Orders")
  private com.gemstone.gemfire.cache.Region<Long, Object> orders;

  protected Order createOrder(String name) {
    return createOrder(ID_SEQUENCE.incrementAndGet(), name);
  }

  protected Order createOrder(Long id, String name) {
    return new Order(id, name);
  }

  protected <T> T fromPdx(Object pdxInstance, Class<T> toType) {
    try {
      if (pdxInstance == null) {
        return null;
      }
      else if (toType.isInstance(pdxInstance)) {
        return toType.cast(pdxInstance);
      }
      else if (pdxInstance instanceof PdxInstance) {
        return new ObjectMapper().readValue(JSONFormatter.toJSON(((PdxInstance) pdxInstance)), toType);
      }
      else {
        throw new IllegalArgumentException(String.format("Expected object of type PdxInstance; but was (%1$s)",
          pdxInstance.getClass().getName()));
      }
    }
    catch (IOException e) {
      throw new RuntimeException(String.format("Failed to convert PDX to object of type (%1$s)", toType), e);
    }
  }

  protected void log(Object value) {
    System.out.printf("Object of Type (%1$s) has Value (%2$s)", ObjectUtils.nullSafeClassName(value), value);
  }

  protected Order put(Order order) {
    Object existingOrder = orders.putIfAbsent(order.getTransactionId(), toPdx(order));
    return (existingOrder != null ? fromPdx(existingOrder, Order.class) : order);
  }

  protected PdxInstance toPdx(Object obj) {
    try {
      return JSONFormatter.fromJSON(new ObjectMapper().writeValueAsString(obj));
    }
    catch (JsonProcessingException e) {
      throw new RuntimeException(String.format("Failed to convert object (%1$s) to JSON", obj), e);
    }
  }

  @Before
  public void setup() {
    amazon = put(createOrder("Amazon Order"));
    bestBuy = put(createOrder("BestBuy Order"));
    target = put(createOrder("Target Order"));
    walmart = put(createOrder("Wal-Mart Order"));
  }

  @Test
  public void regionGet() {
    assertThat((Order) orders.get(amazon.getTransactionId()), is(equalTo(amazon)));
  }

  @Test
  public void repositoryFindOneMethod() {
    log(orderRepository.findOne(target.getTransactionId()));
    assertThat(orderRepository.findOne(target.getTransactionId()), is(equalTo(target)));
  }

  @Test
  public void repositoryQueryMethod() {
    assertThat(orderRepository.findByTransactionId(amazon.getTransactionId()), is(equalTo(amazon)));
    assertThat(orderRepository.findByTransactionId(bestBuy.getTransactionId()), is(equalTo(bestBuy)));
    assertThat(orderRepository.findByTransactionId(target.getTransactionId()), is(equalTo(target)));
    assertThat(orderRepository.findByTransactionId(walmart.getTransactionId()), is(equalTo(walmart)));
  }

  @Region("Orders")
  @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type")
  public static class Order implements PdxSerializable {

    protected static final OrderPdxSerializer pdxSerializer = new OrderPdxSerializer();

    @Id
    private Long transactionId;

    private String name;

    public Order() {
    }

    public Order(Long transactionId) {
      this.transactionId = transactionId;
    }

    public Order(Long transactionId, String name) {
      this.transactionId = transactionId;
      this.name = name;
    }

    public String getName() {
      return name;
    }

    public void setName(final String name) {
      this.name = name;
    }

    public Long getTransactionId() {
      return transactionId;
    }

    public void setTransactionId(final Long transactionId) {
      this.transactionId = transactionId;
    }

    @Override
    public void fromData(PdxReader reader) {
      Order order = (Order) pdxSerializer.fromData(Order.class, reader);

      if (order != null) {
        this.transactionId = order.getTransactionId();
        this.name = order.getName();
      }
    }

    @Override
    public void toData(PdxWriter writer) {
      pdxSerializer.toData(this, writer);
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == this) {
        return true;
      }

      if (!(obj instanceof Order)) {
        return false;
      }

      Order that = (Order) obj;

      return ObjectUtils.nullSafeEquals(this.getTransactionId(), that.getTransactionId());
    }

    @Override
    public int hashCode() {
      int hashValue = 17;
      hashValue = 37 * hashValue + ObjectUtils.nullSafeHashCode(getTransactionId());
      return hashValue;
    }

    @Override
    public String toString() {
      return String.format("{ @type = %1$s, id = %2$d, name = %3$s }",
        getClass().getName(), getTransactionId(), getName());
    }
  }

  public static class OrderPdxSerializer implements PdxSerializer {

    @Override
    public Object fromData(Class<?> type, PdxReader in) {
      if (Order.class.equals(type)) {
        return new Order(in.readLong("transactionId"), in.readString("name"));
      }

      return null;
    }

    @Override
    public boolean toData(Object obj, PdxWriter out) {
      if (obj instanceof Order) {
        Order order = (Order) obj;
        out.writeLong("transactionId", order.getTransactionId());
        out.writeString("name", order.getName());
        return true;
      }

      return false;
    }
  }

  public interface OrderRepository extends GemfireRepository<Order, Long> {
    Order findByTransactionId(Long transactionId);
  }

  @Configuration
  protected static class GemFireConfiguration {

    @Bean
    public Properties gemfireProperties() {
      Properties gemfireProperties = new Properties();

      gemfireProperties.setProperty("name", JsonToPdxToObjectDataAccessIntegrationTest.class.getSimpleName());
      gemfireProperties.setProperty("mcast-port", "0");
      gemfireProperties.setProperty("log-level", "warning");

      return gemfireProperties;
    }

    @Bean
    public CacheFactoryBean gemfireCache(Properties gemfireProperties) {
      CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();

      cacheFactoryBean.setProperties(gemfireProperties);
      //cacheFactoryBean.setPdxSerializer(new MappingPdxSerializer());
      cacheFactoryBean.setPdxSerializer(new OrderPdxSerializer());
      cacheFactoryBean.setPdxReadSerialized(false);

      return cacheFactoryBean;
    }

    @Bean(name = "Orders")
    public PartitionedRegionFactoryBean ordersRegion(Cache gemfireCache) {
      PartitionedRegionFactoryBean regionFactoryBean = new PartitionedRegionFactoryBean();

      regionFactoryBean.setCache(gemfireCache);
      regionFactoryBean.setName("Orders");
      regionFactoryBean.setPersistent(false);

      return regionFactoryBean;
    }

    @Bean
    public GemfireRepositoryFactoryBean orderRepository() {
      GemfireRepositoryFactoryBean<OrderRepository, Order, Long> repositoryFactoryBean =
        new GemfireRepositoryFactoryBean<>();

      repositoryFactoryBean.setRepositoryInterface(OrderRepository.class);

      return repositoryFactoryBean;
    }
  }

}
于 2015-09-03T21:09:02.420 に答える
1

ご存知のように、GemFire (および拡張により Apache Geode) は JSON を PDX 形式で ( PdxInstanceとして) 保存します。これは、GemFire がさまざまな言語ベースのクライアント (ネイティブ C++/C#、 Java に加えてDeveloper REST APIを使用する Web 指向 (JavaScript、Pyhton、Ruby など)) と相互運用し、OQL を使用できるようにするためです。 JSON データをクエリします。

少し実験した後、GemFire が期待どおりに動作しないことに驚きました。ユースケースをシミュレートする自己完結型のテストクラス(もちろん、Spring XDはありません)の例を作成しました...基本的にJSONデータをGemFireにPDXとして保存し、データをOrderアプリケーションドメインオブジェクトとして読み戻そうとしますリポジトリの抽象化を使用して入力します。十分に論理的です。

Spring Data GemFire からのリポジトリの抽象化と実装を使用すると、インフラストラクチャはリポジトリのジェネリック型パラメーター (この場合は「OrderRepository」定義の「Order」) に基づいてアプリケーション ドメイン オブジェクトにアクセスしようとします。

ただし、データは PDX に保存されます。

いずれにせよ、Spring Data GemFire はMappingPdxSerializerクラスを提供し、リポジトリ インフラストラクチャが使用するのと同じ「マッピング メタデータ」を使用して、PDX インスタンスをアプリケーション ドメイン オブジェクトに変換します。かっこいいので、プラグインします...

@Bean
public CacheFactoryBean gemfireCache(Properties gemfireProperties) {
  CacheFactoryBean cacheFactoryBean = new CacheFactoryBean();

  cacheFactoryBean.setProperties(gemfireProperties);
  cacheFactoryBean.setPdxSerializer(new MappingPdxSerializer());
  cacheFactoryBean.setPdxReadSerialized(false);

  return cacheFactoryBean;
}

また、データ アクセス操作で PDX インスタンスではなくドメイン オブジェクトが返されるようにするために、PDX の「read-serialized」プロパティ ( cacheFactoryBean.setPdxReadSerialized(false);) をfalseに設定していることにも気付くでしょう。

ただし、これはクエリ メソッドには影響しませんでした。実際、次の操作にも影響はありませんでした...

orderRepository.findOne(amazonOrder.getTransactionId());

ordersRegion.get(amazonOrder.getTransactionId());

どちらの呼び出しも PdxInstance を返しました。の実装は、GemfireTemplate.get (key)を使用するSimpleGemfireRepository.findOne(key)OrderRepository.findOne(..)に基づいていることに注意してください。特に、 read-serialized がfalseに設定されている場合、結果はそうであってはなりません。Region.get(key)ordersRegion.get(amazonOrder.getTransactionId();Region.get()

SELECT * FROM /Orders WHERE transactionId = $1から生成された OQL クエリ ( )findByTransactionId(String id)では、リポジトリ インフラストラクチャは、呼び出し元 (OrderRepository) が期待するもの (ジェネリック型パラメータに基づく) に基づいて GemFire クエリ エンジンが返すものを少し制御できません。そのため、OQL ステートメントを実行すると、潜在的にを使用した直接のリージョン アクセスとは動作が異なりますget

次に、型を変更してPdxSerializableOrderを実装し、データ アクセス操作 (get、OQL などを使用した直接のリージョン アクセス) 中に変換を処理できるようにしました。これは影響がありませんでした。

そこで、オブジェクト用のカスタムPdxSerializerを実装しようとしましたOrder。これも影響はありませんでした。

この時点で結論付けられる唯一のことは、Order -> JSON -> PDXとの間の変換で何かが失われているということですPDX -> Order。どうやら、GemFire は PDX が必要とする追加の型のメタデータを必要とするようです ( @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type")PDXFormatter が認識する JSON データのようなものですが、確実ではありません。

私のテストクラスでは、Jackson の JSONFormatter を使用して JSON にObjectMapperシリアライズしOrder、GemFire のJSONFormatterを使用して JSON を PDX にシリアライズしました。実際、Spring XD は Spring Data GemFire を使用しており、おそらくJSON Region Auto Proxyサポートを使用しています。これはまさに SDG のJSONRegionAdviceオブジェクトが行うことです (こちらを参照)。

とにかく、GemFire エンジニアリング チームの他のメンバーに問い合わせがあります。また、Spring Data GemFire で PDX データが確実に変換されるようにすることもできます。たとえばMappingPdxSerializer、データが実際にPdxInstance. JSON Region Auto Proxying の仕組みと同様に、Orders Region の AOP インターセプターを記述して、PDX をOrder.

ただし、この場合、GemFire は正しいことを行う必要があるため、これは必要ではないと思います。申し訳ありませんが、今はより良い答えがありません。私が見つけたものを見てみましょう。

乾杯、お楽しみに!

テスト コードについては、後続の投稿を参照してください。

于 2015-09-03T19:18:04.233 に答える