從 POJO 到 Avro 記錄的轉換
一、簡介
在 Java 應用程式中使用 Apache Avro 時,我們經常需要將普通舊 Java 物件 (POJO) 轉換為其 Avro 等效項。雖然透過單獨設定每個欄位來手動執行此操作是完全可以接受的,但使用泛型執行此轉換是一種更好且更易於維護的方法。
在本文中,我們將探討如何將 POJO 轉換為 Avro 物件。我們將以穩健的方式來解決這個問題,對原始 Java 類別結構進行更改。
2. 直接的方法
假設我們有一個帶有 POJO 的程式碼區域,我們想要將其轉換為 Avro 物件。
讓我們看看我們的 POJO:
public class Pojo {
private final Map<String, String> aMap;
private final long uid;
private final long localDateTime;
public Pojo() {
aMap = new HashMap<>();
uid = ThreadLocalRandom.current().nextLong();
localDateTime = LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
aMap.put("mapKey", "mapValue");
}
//getters
}
然後,我們有一個使用其特定方法進行映射的類別:
public static Record mapPojoToRecordStraightForward(Pojo pojo){
Schema schema = ReflectData.get().getSchema(pojo.getClass());
GenericData.Record avroRecord = new GenericData.Record(schema);
avroRecord.put("uid", pojo.getUid());
avroRecord.put("localDateTime", pojo.getLocalDateTime());
avroRecord.put("aMap", pojo.getaMap());
return avroRecord;
}
正如我們所看到的,直接的方法涉及明確設定每個欄位。只要看看這個解決方案,我們就可以看到未來可能出現的問題。這個解決方案很脆弱,只要 POJO 結構改變就需要更新。這不是最好的解決方案。
請注意,我們可以從 POJO 本身以外的來源提取模式;例如,我們也可以透過模式版本來尋找它。
3. 使用反射的通用轉換
另一種方法是使用 Java 反射。此方法使用反射並迭代 POJO 中的每個欄位。接下來,它設定 Avro 記錄中的每個欄位。
看起來是這樣的:
public static Record mapPojoToRecordReflection(Pojo pojo) throws IllegalAccessException {
Class<?> pojoClass = pojo.getClass();
Schema schema = ReflectData.get().getSchema(pojoClass);
GenericData.Record avroRecord = new GenericData.Record(schema);
for (Field field : pojoClass.getDeclaredFields()) {
field.setAccessible(true);
avroRecord.put(field.getName(), field.get(pojo));
}
然後,它遍歷每個超類別並在記錄中設定這些欄位:
// Handle superclass fields
Class<?> superClass = pojoClass.getSuperclass();
while (superClass != null && superClass != Object.class) {
for (Field field : superClass.getDeclaredFields()) {
field.setAccessible(true);
avroRecord.put(field.getName(), field.get(pojo));
}
superClass = superClass.getSuperclass();
}
return avroRecord;
}
最重要的是,此方法很簡單,但對於大型物件或頻繁呼叫時速度較慢。
4.使用Avro的ReflectDatumWriter類
Avro 有一個針對這種情況的內建功能, ReflectDatumWriter類別。最初,我們從 POJO 類別產生 Avro 模式。接下來,我們建立一個ReflectDatumWriter來序列化 POJO。然後,我們設定一個ByteArrayOutputStream 和BinaryEncoder用來編寫:
public static GenericData.Record mapPojoToRecordReflectDatumWriter(Object pojo) throws IOException {
Schema schema = ReflectData.get().getSchema(pojo.getClass());
ReflectDatumWriter<Object> writer = new ReflectDatumWriter<>(schema);
ByteArrayOutputStream out = new ByteArrayOutputStream();
BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(out, null);
接下來,我們將 POJO 序列化為二進位格式:
writer.write(pojo, encoder);
encoder.flush();
最後,我們建立一個BinaryDecoder來讀取序列化數據,並使用GenericDatumReader將二進位資料反序列化為GenericData.Record:
BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(out.toByteArray(), null);
GenericDatumReader<GenericData.Record> reader = new GenericDatumReader<>(schema);
return reader.read(null, decoder);
}
此方法使用 Avro 的序列化和反序列化功能將 POJO 轉換為 Avro 記錄。請注意,此轉換版本對於複雜物件更有效,但對於簡單物件會帶來複雜性。
5. 結論
在本文中,我們探索了在 Java 中轉換 Avro 記錄中的 POJO 的不同方法。我們從一種簡單的方法開始,雖然簡單,但在可維護性和靈活性方面存在缺點。接下來,我們分析了使用 Java 反射的解決方案。這樣更加健壯,更容易適應類別結構的變化。但是,對於較大的物件或頻繁的調用,它存在效能問題。
最後,我們提出了一個使用 Avro 的ReflectDatumWriter類別的解決方案。該類適合此特定目的,是滿足我們需求的最合適選擇。此外,這受益於 Avro 的內部優化,建議用於複雜場景。
總而言之,評估我們需求的具體背景非常重要。這樣,我們就可以選擇最適合我們的效能、可維護性和可擴展性標準的方法。
與往常一樣,程式碼可以在 GitHub 上取得。