Прочтите объект приложения из GemFire ​​с помощью Spring Data GemFire. Данные, хранящиеся с использованием gemfire-json-server SpringXD

Я использую модуль gemfire-json-server в SpringXD для заполнения сетки GemFire ​​json-представлением объектов «Порядок». Я понимаю, что модуль gemfire-json-server сохраняет данные в форме Pdx в GemFire. Я хотел бы прочитать содержимое сетки 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? Примечание: данные изначально хранились в GemFire ​​с использованием модуля SpringXD gemfire-json-server-module.


person Utkarsh Nadkarni    schedule 02.09.2015    source источник


Ответы (2)


Все еще ждем ответа от команды инженеров GemFire ​​PDX, особенно на Region.get(key), но, что интересно, если вы аннотируете объект домена своего приложения с помощью ...

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

Это работает!

Изнутри я знал, что GemFire ​​JSONFormatter (см. здесь) использовал API Джексона для un / marshal (де / сериализации) данных JSON в PDX и обратно.

Однако 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;
    }
  }

}
person John Blum    schedule 03.09.2015

Итак, как вы знаете, GemFire ​​(и, как следствие, Apache Geode) хранит JSON в формате PDX (как PdxInstance). Это сделано для того, чтобы GemFire ​​мог взаимодействовать со многими клиентами на разных языках (нативный C ++ / C #, веб-ориентированный (JavaScript, Pyhton, Ruby и т. Д.), Используя Developer REST API, в дополнение к Java), а также возможность использовать OQL для запроса данных JSON.

После небольших экспериментов я удивлен, что GemFire ​​ведет себя не так, как я ожидал. Я создал пример автономного тестового класса (то есть, конечно, без Spring XD), который имитирует ваш вариант использования ... по существу, сохраняя данные JSON в GemFire ​​как PDX, а затем пытаюсь прочитать данные обратно как объект домена приложения Order Типа с использованием абстракции Repository, достаточно логично.

Учитывая использование абстракции и реализации репозитория из Spring Data GemFire, инфраструктура попытается получить доступ к объекту домена приложения на основе параметра универсального типа репозитория (в данном случае «Порядок» из определения «Репозиторий»).

Однако данные хранятся в 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 (ключ), в котором используется GemfireTemplate.get (key), который выполняет только Region.get(key) и т. д. фактически то же самое, что и (ordersRegion.get(amazonOrder.getTransactionId();). Результат не должен быть таким, особенно если для Region.get() и для чтения-сериализации установлено значение false.

С запросом OQL (SELECT * FROM /Orders WHERE transactionId = $1), сгенерированным из findByTransactionId(String id), инфраструктура репозитория имеет немного меньший контроль над тем, что будет возвращать механизм запросов GemFire ​​в зависимости от того, что ожидает вызывающий (OrderRepository) (на основе параметра универсального типа), поэтому выполнение операторов OQL потенциально может вести себя иначе, чем прямой доступ к региону с использованием get.

Затем я попытался изменить тип Order для реализации PdxSerializable, чтобы обрабатывать преобразование во время операций доступа к данным (прямой доступ к региону с помощью get, OQL или иным образом). Это не повлияло.

Итак, я попытался реализовать собственный PdxSerializer для Order объектов. Это тоже не повлияло.

Единственное, что я могу сделать на данный момент, это что-то теряется при переводе между Order -> JSON -> PDX и затем PDX -> Order. По-видимому, GemFire ​​нужны метаданные дополнительных типов, требуемые PDX (что-то вроде @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type") в данных JSON, которые распознает PDXFormatter, хотя я не уверен, что это так.

Обратите внимание, в моем тестовом классе я использовал ObjectMapper Джексона для сериализации Order в JSON, а затем JSONFormatter для сериализации JSON в PDX, что, как я подозреваю, Spring XD делает под капотом. Фактически, Spring XD использует Spring Data GemFire ​​и, скорее всего, использует Поддержка JSON Region Auto Proxy. Это именно то, что SDG имеет JSONRegionAdvice объект имеет (см. здесь).

В любом случае, у меня есть запрос к остальной команде инженеров GemFire. В Spring Data GemFire ​​также можно сделать кое-что, чтобы гарантировать преобразование данных PDX, например, использовать MappingPdxSerializer напрямую для автоматического преобразования данных от имени вызывающего, если данные действительно имеют тип PdxInstance. Подобно тому, как работает автоматическое проксирование области JSON, вы можете написать перехватчик AOP для области заказов, чтобы автоматически преобразовывать PDX в Order.

Хотя я не думаю, что что-либо из этого должно быть необходимо, поскольку GemFire ​​должен поступать правильно в этом случае. Извините, у меня сейчас нет лучшего ответа. Посмотрим, что я узнаю.

Ура и следите за обновлениями!

Смотрите следующий пост для тестового кода.

person John Blum    schedule 03.09.2015