2015-07-03 5 views
9

Szukam sposobu na konwersję POJO na obiekt avro w sposób ogólny. Implementacja powinna być odporna na wszelkie zmiany w klasie POJO. Osiągnąłem to, ale wyraźnie zapełniłem rekord avro (patrz przykład poniżej).Ogólna konwersja z POJO do rekordu Avro

Czy istnieje sposób na pozbycie się nazw zakodowanych pól i wypełnienie rekordu avro z obiektu? Czy odbicie jest jedynym sposobem, czy też avro dostarcza tę funkcjonalność po wyjęciu z pudełka?

import java.util.Date; 
import java.util.HashMap; 
import java.util.Map; 

import org.apache.avro.Schema; 
import org.apache.avro.generic.GenericData.Record; 
import org.apache.avro.reflect.ReflectData; 

public class PojoToAvroExample { 

    static class PojoParent { 
     public final Map<String, String> aMap = new HashMap<String, String>(); 
     public final Map<String, Integer> anotherMap = new HashMap<String, Integer>(); 
    } 

    static class Pojo extends PojoParent { 
     public String uid; 
     public Date eventTime; 
    } 

    static Pojo createPojo() { 
     Pojo foo = new Pojo(); 
     foo.uid = "123"; 
     foo.eventTime = new Date(); 
     foo.aMap.put("key", "val"); 
     foo.anotherMap.put("key", 42); 
     return foo; 
    } 

    public static void main(String[] args) { 
     // extract the avro schema corresponding to Pojo class 
     Schema schema = ReflectData.get().getSchema(Pojo.class); 
     System.out.println("extracted avro schema: " + schema); 
     // create avro record corresponding to schema 
     Record avroRecord = new Record(schema); 
     System.out.println("corresponding empty avro record: " + avroRecord); 

     Pojo foo = createPojo(); 
     // TODO: to be replaced by generic variant: 
     // something like avroRecord.importValuesFrom(foo); 
     avroRecord.put("uid", foo.uid); 
     avroRecord.put("eventTime", foo.eventTime); 
     avroRecord.put("aMap", foo.aMap); 
     avroRecord.put("anotherMap", foo.anotherMap); 
     System.out.println("expected avro record: " + avroRecord); 
    } 
} 
+1

Dlaczego nie używać [Avro za ReflectDatumWriter] (http: // stackoverflow .com/questions/11866466/using-apache-avro-reflect) do serializacji POJO? –

+0

Używam avro w kontekście hadoop. Serializacja Chciałbym użyć AvroParquetOutputFormat – fab

+1

Nieefektywne podejście miałby [ReflectDatumWriter napisać POJO do bajtów następnie GenericDatumReader odczytuje bajty do GenericRecord] (http://stackoverflow.com/questions/26435299/write-pojos-to-parquet -file-using-reflection). –

Odpowiedz

-1

Sam potrzebowałem właśnie takiej rzeczy. Biblioteka, której potrzebujesz, znajduje się w plikach JAR, ale, co dziwne, nie ma sposobu na wywołanie jej z wiersza poleceń avro-tools.

Wywołuje ją jako: java GenerateSchemaFromPOJO com.example.pojo.Person Person.java

import java.io.FileWriter; 
import java.io.IOException; 
import java.io.Writer; 

import org.apache.avro.Schema; 

import com.fasterxml.jackson.databind.ObjectMapper; 
import com.fasterxml.jackson.dataformat.avro.AvroFactory; 
import com.fasterxml.jackson.dataformat.avro.AvroSchema; 
import com.fasterxml.jackson.dataformat.avro.schema.AvroSchemaGenerator; 
import com.fasterxml.jackson.dataformat.avro.schema.VisitorFormatWrapperImpl; 

public class GenerateSchemaFromPOJO { 

    public static void main(String[] args) { 
     String className = null; 
     String outputFile = null; 
     Writer outputWriter = null; 
     try { 
      if(args.length != 2) { 
       System.out.println("Usage: java " + GenerateSchemaFromPOJO.class.getCanonicalName() + " classname output-schema-file.json"); 
       System.exit(1); 
      } 
      className = args[0]; 
      outputFile = args[1]; 

      Class<?> clazz = Class.forName(className); 

      AvroFactory avroFactory = new AvroFactory(); 
      ObjectMapper mapper = new ObjectMapper(avroFactory); 

      AvroSchemaGenerator gen = new AvroSchemaGenerator(); 
      mapper.acceptJsonFormatVisitor(clazz, gen); 
      AvroSchema schemaWrapper = gen.getGeneratedSchema(); 

      Schema avroSchema = schemaWrapper.getAvroSchema(); 
      String asJson = avroSchema.toString(true); 

      outputWriter = new FileWriter(outputFile); 
      outputWriter.write(asJson); 
     } catch (Exception ex) { 
      System.err.println("caught " + ex); 
      ex.printStackTrace(); 
      System.exit(1); 
     } finally { 
      if(outputWriter != null) { 
       try { 
        outputWriter.close(); 
       } catch (IOException e) { 
        System.err.println("Caught " + e + " while trying to close outputWriter to " + outputFile);; 
        e.printStackTrace(); 
       } 
      } 
     } 
    } 
} 
+0

Jak rozumiem z twojej odpowiedzi, twój kod generuje schemat avro dla danego 'clazz'. Nie o to pytałem w pytaniu. Robię to samo w linii 'ReflectData.get(). GetSchema (Pojo.class);'. Szukałem sposobu na zastąpienie 'avroRecord.put (..., ...);' z ogólnym wariantem – fab

3

Tutaj jest ogólny sposób przekonwertować

public static <V> byte[] toBytesGeneric(final V v, final Class<V> cls) { 
     final ByteArrayOutputStream bout = new ByteArrayOutputStream(); 
     final Schema schema = ReflectData.get().getSchema(cls); 
     final DatumWriter<V> writer = new ReflectDatumWriter<V>(schema); 
     final BinaryEncoder binEncoder = EncoderFactory.get().binaryEncoder(bout, null); 
     try { 
      writer.write(v, binEncoder); 
      binEncoder.flush(); 
     } catch (final Exception e) { 
      throw new RuntimeException(e); 
     } 


     return bout.toByteArray(); 
    } 

public static void main(String[] args) { 
    PojoClass pojoObject = new PojoClass(); 
    toBytesGeneric(pojoObject, PojoClass.class); 
} 
5

Używasz Spring? I buildet do tego za pomocą funkcji Spring. Ale jest to również możliwe, aby zbudować ten mapowania poprzez odbicie Utils zbyt surowy:

import org.apache.avro.Schema; 
import org.apache.avro.generic.GenericData; 
import org.apache.avro.reflect.ReflectData; 
import org.springframework.beans.PropertyAccessorFactory; 
import org.springframework.util.Assert; 

public class GenericRecordMapper { 

    public static GenericData.Record mapObjectToRecord(Object object) { 
     Assert.notNull(object, "object must not be null"); 
     final Schema schema = ReflectData.get().getSchema(object.getClass()); 
     final GenericData.Record record = new GenericData.Record(schema); 
     schema.getFields().forEach(r -> record.put(r.name(), PropertyAccessorFactory.forDirectFieldAccess(object).getPropertyValue(r.name()))); 
     return record; 
    } 

    public static <T> T mapRecordToObject(GenericData.Record record, T object) { 
     Assert.notNull(record, "record must not be null"); 
     Assert.notNull(object, "object must not be null"); 
     final Schema schema = ReflectData.get().getSchema(object.getClass()); 
     Assert.isTrue(schema.getFields().equals(record.getSchema().getFields()), "Schema fields didn't match"); 
     record.getSchema().getFields().forEach(d -> PropertyAccessorFactory.forDirectFieldAccess(object).setPropertyValue(d.name(), record.get(d.name()) == null ? record.get(d.name()) : record.get(d.name()).toString())); 
     return object; 
    } 

} 

Z tego odwzorowującym można wygenerować GenericData.Record które można łatwo odcinkach do Avro. Kiedy deserializowania się Avro ByteArray można używać go odbudować POJO od rozszeregować rekordu:

Serialize

byte[] serialized = avroSerializer.serialize("topic", GenericRecordMapper.mapObjectToRecord(yourPojo)); 

deserializowania

GenericData.Record deserialized = (GenericData.Record) avroDeserializer.deserialize("topic", serialized); 

YourPojo yourPojo = GenericRecordMapper.mapRecordToObject(deserialized, new YourPojo());