Javafree

Prevayler: Serialização e Snapshot

Publicado por Tutoriais Admin em 17/08/2009 - 4.228 visualizações

No artigo anterior sobre Prevayler (Prevayler: Persistência totalmente OO ), construímos uma pequena aplicação para armazenar os nomes das pessoas utilizando o Prevayler. Neste, vamos tratar de assuntos um pouco mais sérios, como por exemplo mudar uma classe de dados sem que o Java danifique as informações já salvas nos arquivos da serialização.

Ja tentou alterar a classe Pessoa que construímos? Não? Tente! Você verá que pode alterar a classe sem que o Prevayler perca as suas informações. É claro que se você remover o atributo nome o Prevayler não conseguirá recuperar este atributo e, portanto ele não será carregado. Mas não irá ocorrer erros.

Como explicar isso? Simples, os objetos armazenados não são instâncias de Pessoa . Isso mesmo, o arquivo de log com o qual estamos trabalhando não grava objetos Pessoa , mas grava objetos de AdicionaPessoa . Uma vez que você implementa um objeto da classe Command do Prevayler o seu objeto já é serializável, visto que a classe Command herda de Serializable . Prova disso? Tente alterar a nossa classe AdicionaPessoa adicionando o seguinte atributo:

 private long teste;   
Agora execute novamente o programa. Você verá o erro:
 Reading PrevalenceBase / 000000000000000000001.commandLog... 
java.io.InvalidClassException: AdicionaPessoa; local class incompatible: stream classdesc serialVersionUID =
1273287439490966696, local class serialVersionUID = - 4265660114243414788
Some commands might have been lost. Looking for the next file...
Esta simples mudança na classe fará com que o Java não consiga buscar os dados serializados para o retorno do Log. Isso é sinônimo de problemas? Não, fique tranquilo.

Primeiramente permita-me explicar o que é Serialização . Quando você tem um objeto em memória, ele pode não estar armazenado sequencialmente na memória. Por esse motivo existe a serialização, que nada mais é do que colocar os valores que o objeto está utilizando juntamente com suas propriedades de uma forma que fique sequencial. Tornando um objeto Serializable , estamos atribuindo essa qualidade a ele, e dando privilégios para que o mesmo possa ser gravado em disco ou enviado por rede (Serial - > Bit a Bit).

Mas se a serialização é tão sensível assim, como podemos implementar um grande sistema com isso? Não se preocupe, a serialização também tem seus truques. O trunfo é um atributo chamado serialVersionUID, isso mesmo, aquele que aparece na mensagem de erro acima. É com esse atributo que o java mantém um versionamento das classes. É com ele que o mesmo detecta se uma classe foi alterada e por isso não é mais compatível. Sendo assim, o que podemos fazer é passar o controle desse atributo para o desenvolvedor.

Adicionando o seguinte atributo nas classes AdicionaPessoa e Pessoa :

private static final long serialVersionUID = 1L;
De agora em diante é o desenvolvedor que decide quando uma classe não é mais compatível com a versão anterior.

Feito isso, você pode apagar os dados salvos pelo prevayler, executar a aplicação, salvar algumas informações e tentar modificar ambas as classes. Não irá mais ocorrer erros de incompatibilidade de classes.

Regras para a recuperação de dados serializados

As regras são simples, e muito intuitivas:

  • Se o java tentar recuperar um objeto, ele irá verificar quais os atributos ainda existem, e estes serão carregados. O resto é jogado fora.
  • A ordem dos atributos não interessa, portanto você pode alterar a ordem que não haverá nenhum problema. A serialização salva e busca com o nome do atributo. Por isso mesmo que o nome não pode ser alterado.
  • Sempre é uma boa prática manter, em todas as classes serializáveis o atributo serialVersionUID .

Log do Prevayler

Já descobrimos que o log do Prevayler armazena simplesmente os objetos Command , tendo isso ele pode re-executar todos os objetos encontrados para voltar os dados a situação atual.

Se quizer fazer um teste, coloque um System.out.println no método public Serializable execute (PrevalentSystem system) da classe AdicionaPessoa . Quando você executar a aplicação poderá ver os objetos sendo carregados.

Snapshot: Um Print Screen nos dados

Um bom banco de dados não fica armazenando Logs a vida inteira, fica? Por esse motivo que o Prevayler implementou um Snapshot dos dados. O Snapshot busca os objetos em memória e salva em disco, este será o nosso sistema de backup.

Se você notar bem, a nossa classe Main, já está utilizando o SnapshotPrevayler no seguinte bloco:

SnapshotPrevayler prevayler = null;

try {
prevayler = new SnapshotPrevayler(new ListaPessoas());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

Isso quer dizer que já estamos prontos para gravar o nosso backup. Para fazer isso é só invocar o método prevayler.takeSnapshot (); Este método varre os objetos associados a instância prevayler e salva-os em disco, no nosso caso, salvará todos os integrantes da ListaPessoas incluindo o ArrayList de Pessoa e seus objetos internos.

Para recuperar os dados, o Prevayler busca o último snaptshot retirado dos dados e a partir daí começa a executar os logs de dados. Não tem erro. Todos os dados voltam para a aplicação.

Para fazer um melhor uso do Snapshot pode-se implementar uma Thread para fazer os backups:

public class SnapshotTimer extends Thread {
SnapshotPrevayler prevayler;

public SnapshotTimer(SnapshotPrevayler prevayler) {
this.prevayler = prevayler;
}

public void run() {
super.run();

try {
while (true) {
Thread.sleep(1000 * 5); // 5 segundos de espera.
prevayler.takeSnapshot();
System.out.println("Snapshot disparado as " + new java.util.Date() + "...");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}

E com algumas mudanças já temos a nossa classe Main de teste:

import java.io.BufferedReader; 
import java.io.IOException;
import java.io.InputStreamReader;

import org.prevayler.implementation.SnapshotPrevayler;

public class Main {
public static void main(String[] args){
System.out.println("Iniciando Prevayler...");
SnapshotPrevayler prevayler = null;
SnapshotTimer snapshot = null;

try {
prevayler = new SnapshotPrevayler(new ListaPessoas());
snapshot = new SnapshotTimer(prevayler);
snapshot.start();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

System.out.print("Digite o nome da pessoa ou FIM para sair: ");
String nome = lerTeclado();

while (!nome.equals("FIM")) {
try {
prevayler.executeCommand(new AdicionaPessoa(nome));
} catch (Exception e1) {
e1.printStackTrace();
}

System.out.println("Pessoa armazenada.");
System.out.print("Digite o nome da pessoa ou FIM para sair: ");

nome = lerTeclado();
}

snapshot.interrupt();
System.out.println("Imprimindo pessoas persistidas.");
ListaPessoas lista = ((ListaPessoas)prevayler.system());

for (int i=0; i<lista.size(); i++) {
System.out.println(lista.get(i).getNome());
}
}

public static String lerTeclado() {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
return reader.readLine();
} catch (IOException e1) {
return "FIM";
}
}
}

É isso aí pessoal. Espero que tenham gostado! Aguardem os próximos artigos sobre o Prevayler.

Vitor Fernando Pamplona

comentários: 0

Tópicos Relacionados