【问题标题】:Read Application Object from GemFire using Spring Data GemFire. Data stored using SpringXD's gemfire-json-server使用 Spring Data GemFire 从 GemFire 读取应用程序对象。使用 SpringXD 的 gemfire-json-server 存储的数据
【发布时间】:2015-09-02 19:34:57
【问题描述】:

我正在使用 SpringXD 中的 gemfire-json-server 模块来使用“Order”对象的 json 表示填充 GemFire 网格。我了解 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 库来读取集群的内容。读取 Grid 内容的代码 sn -p 如下:

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

如何使用 Spring Data GemFire 将从 GemFire 集群读取的数据转换为 Order 对象? 注意:数据最初是使用 SpringXD 的 gemfire-json-server-module 存储在 GemFire 中的

【问题讨论】:

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


    【解决方案1】:

    仍在等待 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;
        }
      }
    
    }
    

    【讨论】:

      【解决方案2】:

      因此,如您所知,GemFire(以及扩展的 Apache Geode)以 PDX 格式(作为PdxInstance)存储 JSON。这使得 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 泛型类型参数(在本例中为“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 '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() 和 read-serialized 设置为 false 的情况下。

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

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

      所以,我尝试为Order 对象实现自定义PdxSerializer。这也没有影响。

      此时我唯一能得出的结论是,在Order -&gt; JSON -&gt; PDXPDX -&gt; Order 之间的转换中丢失了一些东西。看起来,GemFire 需要 PDX 所需的其他类型元数据(类似于 PDXFormatter 识别的 JSON 数据中的 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type"),尽管我不确定它是否确实如此。

      注意,在我的测试课中,我使用 Jackson 的 ObjectMapperOrder 序列化为 JSON,然后使用 GemFire 的 JSONFormatter 将 JSON 序列化为 PDX,我怀疑 Spring XD 正在做类似的事情.事实上,Spring XD 使用 Spring Data GemFire 并且很可能使用 JSON Region Auto Proxy 支持。这正是 SDG 的 JSONRegionAdvice 对象所做的(参见 here)。

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

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

      干杯,敬请期待!

      测试代码见后续帖子。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-25
        • 1970-01-01
        相关资源
        最近更新 更多