我在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中
答案 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 -> PDX
和PDX -> Order
之间的翻译中迷失了方向。看起来,GemFire需要PDX所需的其他类型元数据(类似于PDXFormatter识别的JSON数据中的@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@type")
,但我不确定它是什么。
注意,在我的测试课程中,我使用了杰克逊的ObjectMapper
将Order
序列化为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应该做正确的事情。对不起,我现在没有更好的答案。让我们看看我发现了什么。
干杯并敬请关注!
请参阅后续帖子了解测试代码。