Abaixo está o link para baixar o componente
Download: LINK
Há um tempo atrás, em 2011, precisei implementar em um projeto o conceito de versionamento de registros no banco. A medida que o usuário alterava algumas entidades, o sistema deveria na verdade gerar uma copia do objeto original contendo as alterações realizadas, incrementando o número de versão do registro e efetuar um novo insert no banco ao invés de um update. Isso manteria as versões anteriores a alteração realizada. Ontem, um amigo aqui do trabalho me solicitou ajuda com o mesmo problema. E eu vi que mais pessoas poderiam estar precisando disso. Então resolvi postar aqui para ajudar a quem necessite.
O problema
Inicialmente pensasse logo que é algo simples de se fazer. Algo como 'apenas seto o id para null e mando fazer um novo insert'. Mas não é bem assim. Pensar dessa forma é dar um tiro no pé. Isso vai gerar inconsistência na base e problemas no sistema. Porque? O problema é que ao fazermos uma copia fiel do objeto persistente nós queremos todo o contexto persistente do mesmo. Isso quer dizer que precisamos de todos os seus relacionamentos replicados também. E todos os relacionamentos dos relacioanamentos, dos relacionamento, dos relacionamentos, enfim.... precisamos replicar toda a árvore de relacionamentos do objeto original. E quando falo de relacionamentos são de todos os tipos, 1 x N, N x N, 1 x 1, enfim... Exemplo do problema. Para exemplificar, vou mostrar um exemplo com 1 x N, mas que segue a mesma necessidade, problema e solução para os outros tipos de relacionamento. Imagine que temos 3 tabelas e suas respectivas entidades. Pessoa, Carro e Multas. Onde os relacionamentos são como abaixo: Pessoa 1 x N Carro Carro 1 x N Multa O problema é que o lado N sempre tem uma referência ao lado 1, pois esse lado 1 é a chave estrangeira ao qual o relacionamento pertence. Então Carro tem uma propriedade Pessoa, que é o dono do carro. E Multa tem uma propriedade Carro, que é o carro que tem a multa. Então imagine as seguintes instancias.
Observe que a pessoa é proprietaria de dois carros. Cada carro tem uma referência para a pessoa de id=1. Cada carro tem sua lista de multas. Onde cada multa tem uma referência que para o carro ao qual a multa pertence. Dito isto, o problema em simplesmente fazer uma copia da pessoa copiando as propriedades do original para o novo é que os carros contém a referência a pessoa original, bem como suas multas contem uma referencia a seu respectivo carro original. Desta forma, apenas fazer a copia e setar null no ID para que o Hibernate ou JPA faça um novo insert, vai causa inconsistência no banco, pois o relacionamento entre a pessoa e os carros, bem como carros e as multas, continua apontando para suas respetivas referências originais, e não para a copia. Vou dar um exemplo de dois casos que podem acontecer.
- Caso 1
Se nós fizermos uma cópia da pessoa original, copiarmos as propriedades do original para a copia e setarmos null no id da copia de modo a forçar o hibernate ou jpa a fazer um novo insert, vamos ver como a figura ficará. Abaixo, coloquei a mesma figura anterior só que marquei a instancia original de pessoa com a letra X, para que diferenciemos a original da copia, quando a fizermos. Então, observe que a pessoa abaixo, é a original e é a instancia X.
Observe que os carros apontam para a pessoa (proprietário) correta e que ao qual realmente pertencem. Instancia X. Agora se fizermos uma copia e criarmos outra instancia de Pessoa, teremos a instância Y, essa instância é a copia e nós trasnferiremos tudo da original para ela. Setando o id como null, queremos forçar ao mecanismo de persistência adotado, que se faça um novo insert. Então passando as propriedades da original para a nova instancia, teremos a instancia Y de pessoa como abaixo:
Observe que agora temos a instância Y e pegamos todas as propriedades da instancia X e passamos para a Y. Observe que os carros são os mesmos da instância X, então eles contém como referência de proprietário a pessoa original (instância X) e suas multas são as mesmas do original, e apontam para seus respectivos carros da pessoa X, e não da Y. Na verdade o correto seria termos novas instâncias dos carros, apontando para a copia da pessoa, a instancia Y, e novas instâncias de multas apontando para estes novos carros e por ai sucessivamente. Persistir a pessoa da forma como está na imagem, se não ocasionar em nenhum erro na validação dos dados por parte do framework de persistencia adotado, ocasionaria no insert apenas de uma nova pessoa, só que essa nova pessoa não teria nenhum carro, pois não existe relacionamento com nenhum carro. Pois os carros apontam para a instancia X e já foram persistidos. O mesmo aconteceria com as multas, que já foram persistidas e apontam para os carros da pessoa original X.
- Caso 2
A outra forma que você poderia pensar em fazer, seria ao invés de criar uma nova instância da Pessao X, usar a mesma instância e apenas setar null no ID. Como os outros objetos carro se referem a pessoa por referência, então mudar o id da instancia X, refletiria na mudança em todos os proprietarios dos carros. Então no momento do insert, eles apontariam para o carro correto.
Ai é que você se engana. Você se esquece que os carros e as multas possuem seus próprios id´s já setados,como mostrado na imagem, pois já foram persistidos e foram recuperados junto com a pessoa para que fosse feito o versionamento. Fazer um update em pessoa dessa forma resultaria em um update, pois eles ja possuem id e ja existem na base, nos objetos Carro alterando a FK (referencia) do proprietario. Ou seja, os carros deixariam de ser do proprietario X para serem da nova pessoa inserida no banco. Então o relacionamento da pessoa original seria perdido.
Outro problema seria que um objeto recuperado do banco com hibernate ou jpa, eles vem com um PROXY, que gerenciam todo o acesso a classe real. São estes proxies que fazer e tornam possível por exemplo, o mecanimso de LAZY, que é a consulta ao banco do objetos que compoem a entidade, apenas quando necessário, quando chamado o método get. Isso é o proxy que faz pois ele intercepta a chamada ao metodo. Isso é transparente para você. Este mesmo proxy garante que a primary key (ID) não seja alterado. Então uma tentativa de setar null em um objeto recuperado da base resultará numa exceção. Remover o proxy é possível mas isso não resolverá o problema.
Para este pequeno exemplo, o correto algoritmo seria o algoritmo abaixo:
1 - Criar uma nova instancia de pessoa
2 - Copiar todas as propriedades menos o ID a lista de carros
3 - Criar uma nova instancia de cada carro existente na lista original de carros da pessoa original
4 - Copiar cada propriedade de um carro para o outro, menos o ID e a lista de multas
5 - Criar uma nova instancia de cada multa existente em cada carro na lista original de multas do carro original
6 - Setar a referência ao carro ao qual a multa pertence como a nova copia do carro original
7 - Adicionar as copias das multas do carro original a cada copia de cada carro criado
8 - Adicionar as copias dos carros criados a nova pessoa criada
9 - Fazer o insert
Isso resultará em:
- 1 insert de uma nova pessoa
- 2 inserts de novos carros apontando para esta nova pessoa
- 4 inserts de novas multas. Sendo 2 multas apontando para o primeiro carro e 2 multas para o segundo
Observe quanta coisa você teria que fazer na mão e só para replicar o contexto persistente dessa simples classe pessoa. E mesmo essa simples classe, qualquer alteração nos relacionamentos, a adição de mais relacionamentos 1 x N ou N x N ou 1 x 1, a remoção de outros, etc.. resultaria na mudança e na revisão de todo este algoritmo.
Leve em consideração também que o algoritmo muda de acordo com o tipo de relacionamento. De acordo com o mapeamento realizado na classe. Por exemplo, em um relacionamento 1 x 1, a FK pode estar mapeada na própria classe ou no outro lado do relacionamento. Se for do outro lado, o algoritmo tem que mudar a referencia da instancia do outro lado do relacionamento, para a nova instancia (copia) criada. Se for N x N, o algoritmo é outro, e por ai vai.
Agora imagine ter que manter isso. Agora imagine que você precise disso em várias entidades de seus sistema. Ou até mesmo que um requisito seja versionar todas as entidades principais. Imagine você tendo que fazer isso para todos os contextos persistentes, que mudam de uma classe para outra, pois os relacionamentos são diferentes de uma classe para outra, bem como seus tipos. Imagine manter tudo isso a cada mudança nesses relacionamentos. Vai fazer na mão?
A solução
Pesquisei na época que precisei disto e não encontrei nada que me desse suporte a fazer isso. Na verdade encontrei foram posts de pessoas com o mesmo problema e sempre batiam neste problemas na consistência dos relacionamentos. Mas nunca resultavam em um solução. Muito menos automatizada. Então decidi por mim mesmo fazer, na época em 2011. Comecei a pensar em cada algoritmo de replicação de acordo com cada tipo de relacionamento, de acordo com cada casa que pude vislumbrar. Então criei um "carinha" que analisa os mapeamentos com base nas anotações do javax.persistence como @Entity, @OneToMany, @OneToOne, etc... e com base nas informações de meta dados das classes das entidades envolvidas decide o que fazer e aplica a estrategia correta para gerar a replicação de cada atributo corretamente, gerando assim uma copia completa e correta de todo o contexto persistente, de toda a árvore de relacionamentos de uma entidade persistente. Esse recurso é muito fácil de usar e é transparente para qualquer um que precise.Estou disponibilizando as classes e os fontes das mesmas de forma livre para todos que precisarem. Da mesma forma, peço favor de manter a autoria e os devidos créditos.
Abaixo vai um exemplo e algumas instruções simples de uso. No final tem o link para baixar o componente.
Exemplo:
Pessoa pessoa = pessoaDAO.find(1);
PersistenceCloner
Resumo
Para utilizar, basta criar um PersistenceCloner passando a referência do objeto original que se deseja copiar, e chamar o método generateCopyToPersist. Pronto, a copia esta pronta e toda a complexidade de se fazer isto está abstraída para você.Abaixo está o link para baixar o componente
Download: LINK
Qualquer dúvida, bug, sugestão ou agradecimento, postar comentário aqui os me enviar email pelo victorlindberg713@gmail.com. Abraço, espero ter ajudado.
ATUALIZAÇÃO DESSE POST EM 24/02/2023.
Pessoal, há muitos anos eu parei de evoluir o lindberg-framework. Pelo menos essa versão que continha essa solução de copia de contexto persistente. Com o fim do google code, acabou que não levei todo o projeto pro git-hub. Mas essa parte de cópia de contexto persistente eu coloquei lá sim, mas com outro nome. Eu escrevi essa solução há mais de 10 anos atrás e até hoje pessoas passam pelo mesmo problema e sei que isso pode ajudar. Então, estou atualizando esse post aqui colocando o link atual da lib no git-hub. Abraço e espero ter ajudado a resolver o seu problema.
Cara, eh antigo isso o topico, mas hoje me resolveu um problemão. Valeu mesmo
ResponderExcluirObrigado amigo. Fico feliz!
ExcluirCara, eh antigo isso o topico, mas hoje me resolveu um problemão. Valeu mesmo
ResponderExcluirIdem!
ExcluirBoa Victor,
ResponderExcluirCara, estou na GRANDE necessidade de resolver um problema como o mencionado por você. No entanto, o link pra lib está quebrado.
Me ajuda cara! kkkkkk
Opa,,, cara. Desculpe. Faz muito tempo que não entro aqui e o email com tua mensagem aqui no post ficou perdido no limbo, então não vi a sua mensagem. Hoje, pesquisando aqui no email, foi que vi o email não lido e entrei pra ver sua mensagem. Desculpe! Na época espero que tenha conseguido resolver. Conseguiu? De qualquer forma, seu email me trouxe até aqui todos esses anos depois e com isso eu vi que o link da lib tava quebrado. Foi bom pq atualizei com o link do git hub. Se mais pessoas precisarem, agora o link ta correto. Grande abraço!
ExcluirPessoal, depois de muito tempo sem entrar aqui, vi que o link de download da lib estava quebrado. Com o fim do google code, levei a solução pro git hub e mudei o nome. Talvez por isso algumas pessoas que precisaram não tenham encontrado. Atualizei o post com o link correto do git hub. Espero ter ajudado. Abraço!
ResponderExcluir