Memorizzazione dei grafici degli oggetti con evoluzione delle classi in Java con trasformazione (archiviazione a lungo termine)

4

Astratto

Un problema comune è archiviare oggetti (con un grafico) e caricarli di nuovo. Questo è facile finché la rappresentazione dell'oggetto memorizzata corrisponde al codice in esecuzione. Ma col passare del tempo, i requisiti cambiano e gli oggetti memorizzati non corrispondono più al codice. Gli oggetti memorizzati non dovrebbero perdere i loro dati e il codice nei client dovrebbe funzionare con gli ultimi modelli di oggetti.

Quindi una trasformazione deve avvenire in qualche modo tra il caricamento dei dati e la restituzione di un oggetto al client.

So che esistono alcune librerie come XStream, gson, protobuf e avro. Potrebbero caricare oggetti più vecchi, ma afaik ignora semplicemente i dati che non corrispondono più ai campi della classe (forse mi sono perso qualcosa).
(Quando parlo di memorizzazione e serializzazione non intendo il meccanismo di serializzazione incorporato di Java.)

Domanda

Quindi qual è la domanda? Ora sto facendo ricerche per un po 'di tempo e non sembra esistere una libreria che indirizzi questo problema (evoluzione della classe) senza perdita di dati. Spero di trovare un'altra soluzione funzionante o un'idea su come implementarla da solo qui.

Ho alcuni requisiti:

  • Basato su file - Voglio poter archiviare l'oggetto serializzato sul disco
  • Appendibile - Voglio aggiungere più oggetti a un file senza caricare l'intero file in memoria ancora e ancora
  • Supporto per più versioni in un unico file: un file può contenere oggetti con versioni diverse (solo dello stesso tipo)
  • Trasformazione: i dati devono essere accessibili utilizzando lo stesso tipo, anche se modificati tra.
  • Generico - Il meccanismo stesso deve essere generico, quindi potrei usarlo per oggetti diversi (oggetti diversi non si mischiano in un file, solo versioni diverse di un tipo).

Sarebbe bello se il formato di memorizzazione fosse leggibile dall'uomo.

Gli oggetti già memorizzati non sono aggiornabili (almeno non senza grandi sforzi). Pensa a un archivio a lungo termine.

Esempio

Potrei dare un esempio per una migliore illustrazione. Supponiamo di avere due Pojos che vogliamo serializzare.

public class MyPojo {
    String text;
    Long number;
    Integer[] values;
    SubPojo pojo;
}

public class SubPojo {
    List<String> items;
}

Nella prossima versione avremmo potuto rinominare un campo (testo e contenuto), aver cambiato un tipo (Intero [] - > Elenco) e aver trasformato un campo in Elenco (Sottopunto - > Elenco) dove il il campo precedente ora è il primo elemento della nuova lista (non perdere dati, semplicemente trasformando in nuova rappresentazione).

public class MyPojo {
    String content;
    Long number;
    List<Integer> values;
    List<SubPojo> pojo;
}

public class SubPojo {
    List<String> items;
}

Alcuni pseudocodici su come il client potrebbe usare questo:

// Write
Serializer ser = new Serializer();
MyPojo pojo = new MyPojo();
pojo.xxx = ...; // set fields
ser.store(pojo, file, append);

// Read (a version later)
Serializer ser = new Serializer();
ser.registerTransformer(new TransformerV1ToV2());
List<MyPojo> pojos = ser.load(file);

Questo approccio presenta alcuni inconvenienti:

  • Il trasformatore deve lavorare su un qualche tipo di formato intermedio (potrebbe essere il formato memorizzato di backup come json o xml)
  • Non sai come fosse il formato della classe ad un certo punto nel tempo, dal momento che stai solo trasformando la versione precedente e la mappatura alla classe finale, rendendo difficile la ricerca di errori
  • Prestazioni (a seconda di come avviene la trasformazione)
posta Dag 18.11.2013 - 13:35
fonte

3 risposte

1

Ho deciso di utilizzare l'approccio alla trasformazione. JSON è usato come formato intermedio, in questo modo posso garantire la correttezza usando Json Schema tra versioni (opzionale).

Concepzionale puoi pensare in due modi, se ti piace: come object-mapper, o come json-format che è espresso in oggetti java.

Ho aperto la mia soluzione al link

La documentazione verrà estesa la prossima volta. Ho anche aggiunto il supporto per serializzazione personalizzata / deserializzatore e tipi di polimorfo.

    
risposta data 08.05.2014 - 16:17
fonte
1

Uso l'interfaccia Externalizable per risolvere questo problema. Non soddisfa il tuo requisito Appendable, ma potrebbe aiutarti a iniziare.

Externalizable ti permette di scrivere ogni oggetto da solo. Quello che faccio è includere un numero di versione per ogni classe. Poi, quando cambio la classe, aggiusto il metodo readExternal in modo che possa leggere il nuovo formato e anche i vecchi e cambiare il metodo writeExternal per scrivere il nuovo formato con il nuovo numero di versione. Potrebbe essere necessario modificare il codice per leggere i vecchi formati per riempire nuovi campi (e non compilare campi rimossi). Allora sono pronto ad andare.

Di solito c'è una sorta di gerarchia di oggetti nel grafico, quindi se una classe di livello 2 viene seriamente modificata, la nuova scrittura / lettura per quella classe può produrre / inserire qualcosa di completamente diverso da quello vecchio, con un livello inferiore completamente nuovo classi.

Qualche cura è necessaria perché a volte non esiste una gerarchia. Devi ricordare che, dopo aver letto, non puoi impostare un campo estraendo i dati dagli oggetti nei campi perché potrebbero non essere ancora lì. (A volte aggiungo un metodo setUp a tutte le mie classi che vengono richiamate dopo il% di primo livello diobjectRead così gli oggetti nel grafico possono essere organizzati dopo è stato letto l'intero grafico.

E a volte aiuta a scrivere gli oggetti manualmente usando un metodo che scrive solo semplici byte. (Ma sulla lettura devi sapere con precisione cosa stai leggendo, dove readExternal leggerà un oggetto completo di qualsiasi classe.)

Il codice ha un aspetto simile a:

// version 3 -- November 16, 2013
// version 2 -- March 22, 2013
// version 1 -- April 1, 2012
public void writeExternal( ObjectOutput out )  throws IOException  {
    out.writeShort( 3 )
    out.writeLong( longData );
    out.writeObject( something );
    ....
}

public void readExternal( ObjectInput in )  throws IOException,
             ClassNotFoundException  {
    short version = in.readShort();
    if (version > 3)  {
        // Admit program is too old to read file.
    }
    else if (version == 3)  {
        longData = in.readLong();
        something = readobject();
    }
    else if (version == 2)  {
        longData = 5;
        something = readobject();
    }
    else
       ...
}
    
risposta data 18.11.2013 - 20:23
fonte
0

Vorrei iniziare creando una classe di oggetti serializzabili di uso generale che siano in grado di resistere a qualsiasi oggetto che deve essere serializzato. Ogni oggetto dovrebbe contenere una rappresentazione di ogni campo che deve essere serializzato, e il tipo e la versione dell'oggetto. Potresti voler usare JSON o qualcosa di simile per questa parte.

Ogni classe serializzabile avrebbe bisogno di un costruttore che accetta un oggetto generico e lancia un'eccezione se il tipo, la versione oi campi non corrispondono a ciò di cui la classe ha bisogno. Per l'altra direzione ogni classe avrebbe bisogno di un metodo che converta un oggetto di quella classe in un oggetto di uso generale. Se si desidera che la serializzazione includa più riferimenti a un singolo oggetto, è possibile passare attorno a un oggetto aggiuntivo per occuparsi della mappatura tra POJO e oggetti generici.

Infine ogni classe serializzabile richiede un metodo statico per la conversione da vecchie versioni alla versione corrente. Per la prima versione ciò non farebbe nulla, ma ogni volta che si modifica la classe si incrementerebbe la versione corrente e si aggiungerà un passaggio di conversione che converte un oggetto generico per la versione precedente in un oggetto generico per la nuova versione. Potresti avere un'istruzione switch senza break s che attiva la versione dell'oggetto dato. Il primo caso converte un oggetto della prima versione in un oggetto della seconda versione, seguito da un caso per convertire la seconda versione nella terza versione, e quindi si può cadere fino a quando non si ha un oggetto dell'attuale versione in basso. Ogni nuova versione richiede semplicemente l'aggiunta di un singolo caso alla parte inferiore dell'istruzione switch che rappresenta le modifiche apportate.

    
risposta data 19.11.2013 - 06:03
fonte

Leggi altre domande sui tag