使用Spring Data GemFire从GemFire中读取应用程序对象。使用SpringXD的gemfire-json-server存储数据

时间:2015-09-02 19:34:57

标签: spring-xd gemfire spring-data-gemfire geode

我在SpringXD中使用gemfire-json-server模块使用json表示“Order”对象来填充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中

2 个答案:

答案 0 :(得分:2)

还在等待GemFire PDX工程团队的回复,特别是在Region.get(key)上,但是,有趣的是,如果您使用...注释您的应用程序域对象

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

这有效!

我知道GemFire JSONFormatter类(参见here)使用Jackson的API来解析(去/序列化)与PDX之间的JSON数据。

但是,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;
    }
  }

}

答案 1 :(得分:1)

因此,正如您所知,GemFire(以及扩展名为Apache Geode)以PDX格式存储JSON(作为PdxInstance)。这样,GemFire可以与许多不同的基于语言的客户端(本机C ++ / C#,面向Web,(JavaScript,Pyhton,Ruby等)使用Developer REST API除了Java之外)进行互操作,并且能够使用OQL查询JSON数据。

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

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

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

无论如何,Spring Data GemFire提供MappingPdxSerializer类,使用相同的&#34;映射元数据&#34;将PDX实例转换回应用程序域对象。存储库基础结构使用的。很酷,所以我把它塞进......

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

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

  return cacheFactoryBean;
}

您还会注意到,我设置了PDX&#39; read-serialized&#39; property(cacheFactoryBean.setPdxReadSerialized(false);)to false ,以确保数据访问操作返回域对象而不是PDX实例。

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

orderRepository.findOne(amazonOrder.getTransactionId());

ordersRegion.get(amazonOrder.getTransactionId());

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

使用从SELECT * FROM /Orders WHERE transactionId = $1生成的OQL查询(findByTransactionId(String id)),存储库基础结构对GemFire查询引擎将根据调用者(OrderRepository)期望的内容返回的内容的控制较少(基于在泛型类型参数上),因此运行OQL语句的行为可能与使用get直接进行区域访问的行为不同。

接下来,我尝试修改Order类型以实现PdxSerializer,以便在数据访问操作期间处理转换(使用get,OQL或其他方式直接进行区域访问)。这没有任何影响。

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

我现在唯一可以得出的结论就是在Order -> JSON -> PDXPDX -> Order之间的翻译中迷失了方向。看起来,GemFire需要PDX所需的其他类型元数据(类似于PDXFormatter识别的JSON数据中的@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type"),但我不确定它是什么。

注意,在我的测试课程中,我使用了杰克逊的ObjectMapperOrder序列化为JSON,然后将GemFire JSON Region Auto Proxy序列化,以将JSON序列化为PDX ,我怀疑Spring XD在引擎盖下的表现同样如此。事实上,Spring XD使用Spring Data GemFire,并且最有可能使用JSONRegionAdvice支持。这正是SDG的here对象所做的事情(见JsFiddle Example)。

无论如何,我对GemFire工程团队的其他成员进行了调查。还有一些事情可以在Spring Data GemFire中完成,以确保转换PDX数据,例如,如果数据确实是类型{,则直接使用MappingPdxSerializer代表调用者自动转换数据{1}}。与JSON Region Auto Proxying的工作方式类似,您可以为Orders Region编写AOP拦截器,以自动将PDX转换为PdxInstance

尽管如此,我不认为任何这些都是必要的,因为在这种情况下GemFire应该做正确的事情。对不起,我现在没有更好的答案。让我们看看我发现了什么。

干杯并敬请关注!

请参阅后续帖子了解测试代码。