1

我正在使用 SpringXD 中的 gemfire-json-server 模块来使用“Order”对象的 json 表示填充 GemFire 网格。我了解 gemfire-json-server 模块在 GemFire 中以 Pdx 形式保存数据。我想在我的应用程序中将 GemFire 网格的内容读入“Order”对象。我得到一个 ClassCastException,内容如下:

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

我正在使用 Spring Data GemFire 库来读取集群的内容。读取 Grid 内容的代码片段如下:

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 来取消/编组(反/序列化)与 PDX 之间的 JSON 数据。

但是,orderRepository.findOne(ID)andordersRegion.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)以 PDX 格式(作为PdxInstance)存储 JSON。这使得 GemFire 可以与许多不同的基于语言的客户端(本机 C++/C#、使用Developer REST API以及 Java 之外的面向 Web 的(JavaScript、Pyhton、Ruby 等))进行互操作,并且还能够使用 OQL查询 JSON 数据。

经过一些实验,我很惊讶 GemFire 的行为不像我预期的那样。我创建了一个示例,自包含的测试类(即没有 Spring XD,当然)模拟您的用例......本质上将 JSON 数据作为 PDX 存储在 GemFire 中,然后尝试将数据作为 Order 应用程序域对象读回类型使用Repository 抽象,足够合乎逻辑。

鉴于使用了来自 Spring Data GemFire 的 Repository 抽象和实现,基础设施将尝试基于 Repository 泛型类型参数(在本例中为“OrderRepository”定义中的“Order”)访问应用程序域对象。

但是,数据存储在 PDX 中,那么现在怎么办?

无论如何,Spring Data GemFire 提供了MappingPdxSerializer类,以使用与 Repository 基础设施相同的“映射元数据”将 PDX 实例转换回应用程序域对象。太好了,所以我把它插入...

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

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

  return cacheFactoryBean;
}

您还会注意到,我将 PDX 'read-serialized' 属性 ( cacheFactoryBean.setPdxReadSerialized(false);) 设置为false,以确保数据访问操作返回域对象而不是 PDX 实例。

但是,这对查询方法没有影响。事实上,它对以下操作也没有影响......

orderRepository.findOne(amazonOrder.getTransactionId());

ordersRegion.get(amazonOrder.getTransactionId());

两个调用都返回了 PdxInstance。注意, 的实现OrderRepository.findOne(..)基于SimpleGemfireRepository.findOne(key),它使用GemfireTemplate.get(key),它只是执行Region.get(key),因此实际上与 ( ordersRegion.get(amazonOrder.getTransactionId();) 相同。结果不应该是,尤其是在Region.get()and read-serialized 设置为false的情况下。

使用从 生成的 OQL 查询 ( SELECT * FROM /Orders WHERE transactionId = $1) findByTransactionId(String id),Repository 基础设施对 GemFire 查询引擎将根据调用者 (OrderRepository) 的期望(基于通用类型参数)返回的内容的控制较少,因此运行 OQL 语句可能行为不同于使用get.

接下来,我继续尝试修改Order类型以实现PdxSerializable,以处理数据访问操作期间的转换(使用 get、OQL 或其他方式直接访问区域)。这没有影响。

因此,我尝试为对象实现自定义PdxSerializerOrder。这也没有影响。

在这一点上,我唯一能得出的结论是,在Order -> JSON -> PDXPDX -> Order. 看起来,GemFire 需要 PDX 所需的其他类型元数据(类似于@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type")PDXFormatter 识别的 JSON 数据,但我不确定它是否确实如此。

请注意,在我的测试课程中,我使用 Jackson将 JSONObjectMapper序列化为OrderJSON,然后使用 GemFire 的JSONFormatter将 JSON 序列化为 PDX,我怀疑 Spring XD 正在做类似的事情。事实上,Spring XD 使用 Spring Data GemFire 并且很可能使用JSON 区域自动代理支持。这正是 SDG 的JSONRegionAdvice对象所做的(参见此处)。

无论如何,我要询问 GemFire 工程团队的其他成员。在 Spring Data GemFire 中还可以做一些事情来确保 PDX 数据被转换,例如MappingPdxSerializer如果数据确实是 type ,则使用直接代表调用者自动转换数据PdxInstance。与 JSON 区域自动代理的工作方式类似,您可以为订单区域编写 AOP 拦截器,以自动将 PDX 转换为Order.

不过,我认为这些都不是必要的,因为 GemFire 在这种情况下应该做正确的事情。抱歉,我现在没有更好的答案。让我们看看我发现了什么。

干杯,敬请期待!

测试代码见后续帖子。

于 2015-09-03T19:18:04.233 回答