Aerospike - 如何反序列化包含 Java 中对象的地图?
Aerospike - How to deserialize a map containing an object in Java?
我在从 Aerospike DB 加载地图时遇到问题。当我获取记录并尝试打印它时,出现以下错误
主要
Key key = new Key( "test", "users", 2 );
Map<Integer, Widgets> map = new HashMap<>();
map.put(1, new Widgets(2, 2));
map.put(2, new Widgets(3, 0));
Bin bin = new Bin("widgets", map);
client.put( policy, key, bin );
Record record = client.get(policy, key); // using same key for testing
map = (Map<Integer, Widgets>) record.getMap("widgets"); // here, I do get a map back... but its serialized
map.forEach( (k,v) -> System.out.println(k)); <------- ERROR
错误
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
Aerospike 中存储的数据
| | 2 | MAP('{1:AC ED 00 05 73 72 00 07 57 69 64 67 65 74 73 6F F3 7E F4 7F CD 1C 92 02 00 02 49 00 0A 63 6C 69 63 6B 43 6F 75 6E 74 49 00 09 76 69 65 77 43 6F 75 6E 74 78 70 00 00 00 02 00 00 00 02, 2:AC ED 00 05 73 72 00 07 57 69 64 67 65 74 73 6F F3 7E F4 7F |
+--------------------+----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.098 secs)
小部件 class
public class Widgets implements Serializable{
private int viewCount;
private int clickCount;
// getters, setters and constructors here
}
数据正在毫无问题地存储在数据库中,但它是以字节数组的形式存储的。我在反序列化时遇到问题。
编辑:
当我尝试打印地图时,确实得到了输出,但是当我尝试使用 foreach 时,它显示错误
System.out.println(map); // works fine
输出
{1=Widgets@7e774085, 2=Widgets@3f8f9dd6}
Aerospike 始终将整数类型(short、int、long 等)存储为数据库中的 64 位 long。如果您插入较短的数字类型,它会自动将其向上转换为长数字类型。这是为了支持没有较短数字类型的语言。
因此,当您检索地图时,您的地图键将作为长整数返回。因此,此代码应替换您的检索代码:
Record record = client.get(null, key); // using same key for testing
Map<Long, Widgets> map2 = (Map<Long, Widgets>) record.getMap("widgets"); // here, I do get a map back... but its serialized
map2.forEach( (k,v) -> System.out.println(k));
注意:您提到 Widgets() 类 将作为 Java 序列化对象存储在 Aerospike 中是正确的。这可能是不可取的。如果在未来的某个时候您将技术更改为其他技术,您的数据将无法读取。 Aerospike 与语言无关,因此您可以在 Java 中写入数据,然后在 C 中读回。通过存储 Java 个序列化对象,您可以防止这种能力。
您可以在 AQL 中看到这一点(注意 0xaced Java 幻数):
aql> select * from test.users
*************************** 1. row ***************************
widgets: MAP('{1:AC ED 00 05 73 72 00 34 63 6F 6D 2E 74 69 6D 2E...
通常最好使用 Spring Data 或自己序列化它们。例如,您可以将代码更改为:
package com.aerospike.play;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
public class WhosebugQuestion {
public static class Widgets implements Serializable{
private static final String VIEW_FIELD = "view";
private static final String CLICK_FIELD = "click";
private int viewCount;
private int clickCount;
public Widgets(int viewCount, int clickCount) {
this.viewCount = viewCount;
this.clickCount = clickCount;
}
public int getViewCount() {
return viewCount;
}
public int getClickCount() {
return clickCount;
}
@Override
public String toString() {
return String.format("{view: %d, count: %s}", viewCount, clickCount);
}
public Map<String, Object> asMap() {
Map<String, Object> values = new HashMap<>();
values.put(VIEW_FIELD, this.viewCount);
values.put(CLICK_FIELD, this.clickCount);
return values;
}
public static Widgets fromMap(Map<String, Object> map) {
return new Widgets((int)(long)map.get(VIEW_FIELD), (int)(long)map.get(CLICK_FIELD));
}
}
public static void main(String[] args) {
AerospikeClient client = new AerospikeClient("172.28.128.4", 3000);
Key key = new Key( "test", "users", 2 );
Map<Integer, Map<String, Object>> map = new HashMap<>();
map.put(1, new Widgets(2, 2).asMap());
map.put(2, new Widgets(3, 0).asMap());
Bin bin = new Bin("widgets", map);
client.put( null, key, bin );
Record record = client.get(null, key); // using same key for testing
Map<Long, Map<String, Object>> map2 = (Map<Long, Map<String, Object>>) record.getMap("widgets");
map2.forEach( (k,v) -> {
Widgets w = Widgets.fromMap(v);
System.out.printf("%d -> %s\n", k, w);
});
client.close();
}
}
在这种情况下,输出是您所期望的:
1 -> {view: 2, count: 2}
2 -> {view: 3, count: 0}
但是数据库中存储的数据使用的是原生类型:
aql> select * from test.users
*************************** 1. row ***************************
widgets: MAP('{1:{"view":2, "click":2}, 2:{"view":3, "click":0}}')
附带说明一下,有更有效的方法来存储此数据,例如列表,但这种表示方式更具说明性。
我在从 Aerospike DB 加载地图时遇到问题。当我获取记录并尝试打印它时,出现以下错误
主要
Key key = new Key( "test", "users", 2 );
Map<Integer, Widgets> map = new HashMap<>();
map.put(1, new Widgets(2, 2));
map.put(2, new Widgets(3, 0));
Bin bin = new Bin("widgets", map);
client.put( policy, key, bin );
Record record = client.get(policy, key); // using same key for testing
map = (Map<Integer, Widgets>) record.getMap("widgets"); // here, I do get a map back... but its serialized
map.forEach( (k,v) -> System.out.println(k)); <------- ERROR
错误
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.Integer
Aerospike 中存储的数据
| | 2 | MAP('{1:AC ED 00 05 73 72 00 07 57 69 64 67 65 74 73 6F F3 7E F4 7F CD 1C 92 02 00 02 49 00 0A 63 6C 69 63 6B 43 6F 75 6E 74 49 00 09 76 69 65 77 43 6F 75 6E 74 78 70 00 00 00 02 00 00 00 02, 2:AC ED 00 05 73 72 00 07 57 69 64 67 65 74 73 6F F3 7E F4 7F |
+--------------------+----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.098 secs)
小部件 class
public class Widgets implements Serializable{
private int viewCount;
private int clickCount;
// getters, setters and constructors here
}
数据正在毫无问题地存储在数据库中,但它是以字节数组的形式存储的。我在反序列化时遇到问题。
编辑:
当我尝试打印地图时,确实得到了输出,但是当我尝试使用 foreach 时,它显示错误
System.out.println(map); // works fine
输出
{1=Widgets@7e774085, 2=Widgets@3f8f9dd6}
Aerospike 始终将整数类型(short、int、long 等)存储为数据库中的 64 位 long。如果您插入较短的数字类型,它会自动将其向上转换为长数字类型。这是为了支持没有较短数字类型的语言。
因此,当您检索地图时,您的地图键将作为长整数返回。因此,此代码应替换您的检索代码:
Record record = client.get(null, key); // using same key for testing
Map<Long, Widgets> map2 = (Map<Long, Widgets>) record.getMap("widgets"); // here, I do get a map back... but its serialized
map2.forEach( (k,v) -> System.out.println(k));
注意:您提到 Widgets() 类 将作为 Java 序列化对象存储在 Aerospike 中是正确的。这可能是不可取的。如果在未来的某个时候您将技术更改为其他技术,您的数据将无法读取。 Aerospike 与语言无关,因此您可以在 Java 中写入数据,然后在 C 中读回。通过存储 Java 个序列化对象,您可以防止这种能力。
您可以在 AQL 中看到这一点(注意 0xaced Java 幻数):
aql> select * from test.users
*************************** 1. row ***************************
widgets: MAP('{1:AC ED 00 05 73 72 00 34 63 6F 6D 2E 74 69 6D 2E...
通常最好使用 Spring Data 或自己序列化它们。例如,您可以将代码更改为:
package com.aerospike.play;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import com.aerospike.client.AerospikeClient;
import com.aerospike.client.Bin;
import com.aerospike.client.Key;
import com.aerospike.client.Record;
public class WhosebugQuestion {
public static class Widgets implements Serializable{
private static final String VIEW_FIELD = "view";
private static final String CLICK_FIELD = "click";
private int viewCount;
private int clickCount;
public Widgets(int viewCount, int clickCount) {
this.viewCount = viewCount;
this.clickCount = clickCount;
}
public int getViewCount() {
return viewCount;
}
public int getClickCount() {
return clickCount;
}
@Override
public String toString() {
return String.format("{view: %d, count: %s}", viewCount, clickCount);
}
public Map<String, Object> asMap() {
Map<String, Object> values = new HashMap<>();
values.put(VIEW_FIELD, this.viewCount);
values.put(CLICK_FIELD, this.clickCount);
return values;
}
public static Widgets fromMap(Map<String, Object> map) {
return new Widgets((int)(long)map.get(VIEW_FIELD), (int)(long)map.get(CLICK_FIELD));
}
}
public static void main(String[] args) {
AerospikeClient client = new AerospikeClient("172.28.128.4", 3000);
Key key = new Key( "test", "users", 2 );
Map<Integer, Map<String, Object>> map = new HashMap<>();
map.put(1, new Widgets(2, 2).asMap());
map.put(2, new Widgets(3, 0).asMap());
Bin bin = new Bin("widgets", map);
client.put( null, key, bin );
Record record = client.get(null, key); // using same key for testing
Map<Long, Map<String, Object>> map2 = (Map<Long, Map<String, Object>>) record.getMap("widgets");
map2.forEach( (k,v) -> {
Widgets w = Widgets.fromMap(v);
System.out.printf("%d -> %s\n", k, w);
});
client.close();
}
}
在这种情况下,输出是您所期望的:
1 -> {view: 2, count: 2}
2 -> {view: 3, count: 0}
但是数据库中存储的数据使用的是原生类型:
aql> select * from test.users
*************************** 1. row ***************************
widgets: MAP('{1:{"view":2, "click":2}, 2:{"view":3, "click":0}}')
附带说明一下,有更有效的方法来存储此数据,例如列表,但这种表示方式更具说明性。