Em um projeto que trabalhei tivemos a necessidade de representar em XML a nossa principal entidade de banco de dados, com [quase] todas as suas relações diretas e indiretas (uma grande árvore). A opção que se mostrou mais fácil foi o uso da biblioteca XStream. No entanto, esbarramos em alguns problemas uma vez que o Hibernate trabalha com Proxies e implementações próprias das Collections do Java, afim de promover a abordagem Lazy para a obtenção de entidades relacionadas. Nesse post demonstro como resolvemos esse problema.
Se estiver usando maven, adicione ao pom.xml da sua aplicação a dependência xstream-hibernate:
<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream-hibernate</artifactId> <version>1.4.7</version> </dependency> |
É muito importante que você crie e configure uma única instância de XStream que deverá ser usada tanto na serializaçaõ quanto na deserialização, para que tudo funcione. Configure-a da seguinte forma:
import org.hibernate.collection.internal.PersistentBag; import org.hibernate.collection.internal.PersistentList; import org.hibernate.collection.internal.PersistentMap; import org.hibernate.collection.internal.PersistentSet; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentCollectionConverter; import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentMapConverter; import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentSortedMapConverter; import com.thoughtworks.xstream.hibernate.converter.HibernatePersistentSortedSetConverter; import com.thoughtworks.xstream.hibernate.converter.HibernateProxyConverter; import com.thoughtworks.xstream.hibernate.mapper.HibernateMapper; import com.thoughtworks.xstream.mapper.MapperWrapper; public class MeuXStream { public static XStream xs; static { xs = new XStream() { @Override protected MapperWrapper wrapMapper(final MapperWrapper next) { return new HibernateMapper(next); } }; xs.registerConverter(new HibernateProxyConverter()); xs.registerConverter(new HibernatePersistentCollectionConverter(xs.getMapper())); xs.registerConverter(new HibernatePersistentMapConverter(xs.getMapper())); xs.registerConverter(new HibernatePersistentSortedMapConverter(xs.getMapper())); xs.registerConverter(new HibernatePersistentSortedSetConverter(xs.getMapper())); xs.alias("Bag", PersistentBag.class); xs.alias("Map", PersistentMap.class); xs.alias("List", PersistentList.class); xs.alias("Set", PersistentSet.class); xs.autodetectAnnotations(true); } ... } |
A maior parte das linhas acimas é para tratar a conversão de coleções para XML. Suas Classes de Entidades podem conter coleções, e estas vão estar com os tipos abstratos das Collections (Ex: Set, List, Map). Em tempo de execução, o Hibernate instanciará essas propriedades com suas próprias implementações dessas Collections (ex: org.hibernate.collection.PersistentList para o caso de List). No entanto você não vai querer que no seu XML final conste detalhes dessas coleções específicas do hibernate. Para isso, defina o HibernateMapper como o MapperWrapper do seu XStream. Além disso, registre os conversores do Hibernate fornecidos pela biblioteca xstream-hibernate. Por fim, é interessante definir alias para as 4 coleções do hibernate, pois, mesmo depois de convertidas, o XML final utiliza no atributo class o nome da classe do Hibernate, o que é irrelevante, mas fica poluído e verboso.
Caso você tenha mapeado alguma herança, certifique-se também de colocar a anotação @Proxy com lazy=false na super-classe. Isso evita que o Hibernate utilize Proxies nas instâncias dessa classe, o que leva ao XStream a serializar essas Proxies como se fossem instancias apenas da super-classe, gerando conflitos quando ele esbarra com uma propriedade da sub-classe e causando erros de de conversão (ConversionException). Lembrando que mesmo que você coloque essa anotação, isso não impedirá que carregamento das propriedades do tipo anotado seja Lazy, pois ainda há a configuração de cada propriedade nas próprias entidades (FetchType), apenas evitará a confusão com Proxies da super-classe.
@Entity @Inheritance(strategy = InheritanceType.JOINED) @Proxy(lazy = false) public class MinhaSuperClasse implements Serializable { ... } |
Provavelmente você não desejará representar toda sua cadeia de classes no XML, podando alguns pontos. Para isso, anote as propriedades que não desejar serializar com a anotação @XStreamOmitField. Para que essas anotações sejam reconhecidas, foi preciso aquele ultimo comando do bloco estático de inicialização do XStream.
Por fim, para serializar, o mais importante é carregar a sua entidade logo antes de transformar em XML, pois dessa forma, com a sessão ainda aberta, o próprio XStream irá percorrer todos os getters das propriedades, forçando o carregamento de tudo que foi configurado com a abordagem Lazy.
... MinhaEntidade entidade = minhaEntidadeDAO.carregarPeloId(id); String xml = xs.toXML(entidade) ... |
Para deserializar, obtenha de volta o mesmo XStream usado na serialização e faça:
... String xml = lerXmlDeAlgumLugar(); MinhaEntidade entidade = (MinhaEntidade) xs.fromXML(xml); ... |
Atente para a limitação de que o XML gerado em um momento só poderá ser deserializado em um outro momento se a mesma estrutura de classes e propriedades se mantiver. Qualquer mudança de nome de classe ou de propriedade, ou mesmo alteração de pacote, resultará em um erro de conversão. Para contornar esse problema pode-se definir alias para todas as propriedades e classes com as anotações @XStreamAlias e @XStreamAliasType, ou fazendo chamadas à xs.alias(…) ou xs.aliasType(…). Pode-se ainda criar Converters customizados para definir como serializar/deserializar uma classe específica, ou no caso de um comportamento geral para todas a classes, o ideal é escrever um Mapper customizado. Detalhes sobre esse processo de customização você encontra nessa ajuda do XStream.