Fake Delete no banco com JPA

Oi Pessoal!

Boa tarde!

Estou com um problema interessante aqui comigo, talvez alguns dos colegas ja tenha ouvido falar de algo parecido ou ja tenha implementado na prática.

Um cliente me pediu em seu projeto um recurso de fake delete. Como no Microsiga Protheus, para quem ja usou, deve se lembrar, tem uma coluna em cada tabela chamada D_E_L_E_T_, que quando tem um asterisco quer dizer que esta entidade ja foi removida do sistema, e reside fantasma no banco de dados, só retornando ao uso quando o administrador resolver restaura-la, ou mesmo passa desta pra melhor quando alguem resolver executar o tal do PACK, que deleta definitivamente todas elas.

Seria simples fazer isto? A resposta é SIM e NAO ao mesmo tempo.

Pensando de forma simples, basta fazer tudo com herança nas entidades e um bom Dao generico.

Assim cada entidade herdaria uma propriedade Boolean, que bem poderia se chamar “deletado”, a qual diria se esta entidade ainda existe ou nao.
Lógico, a partir de agora, qualquer query JPQL teria que possuir " AND Entidade.deletado = false ", para evitar recuperar indesejadamente entidades fantasmas.
Tudo isto é facil de resolver com um bom Dao generico encapsulando tudo.

Porem surgem dois problemas: Chaves Unicas e Deletes tipo Restrict e Cascade.

Chaves unicas nao funcionam mais! Os motivos sao simples, quando alguem cria uma entidade, pode ja existir uma entidade deletada na tabela com os mesmos dados-chaves da recem criada, gerando um erro. E nao adianta colocar a propriedade “deletado” na chave também, pois somente iriamos adiar o problema. Ao criar e excluir duas vezes uma entidade com os mesmos dados cadastrais, teriamos novamente duas entidades iguais, pois os possiveis valores de um Boolean ( a tal propriedade “deletado” ), são somente dois! True e False, logo teriamos duas entidades excluidas com True na propriedade “deletado”. Bang! Chave violada.

Resolver foi simples, criar um mecanismo de chave via Dao, ao invez de usar Banco+JPA, reinvenção de roda para satisfazer o cliente. Utilizei Generics, Reflection e Annotatios e consegui resolver com umas 20 linhas de código apenas, tudo generico, no DaoGenerico (Beleza, até que ficou bonito!).

Agora quando a exclusão de entidades, estou pensando como seria problemático. Em uma exclusão do tipo Cascade, o certo seria ir buscando em todas as entidades, as quais tem esta como propriedade, e fazer uma pesquisa nesta também, tudo recursivamente até acabar todas! setando todas elas como “deletado = true” Ufa! Putzzzzzzzzzzzzz…

Agora se a exclusão fosse do tipo Restrict, nao haveria necessidade de recursividade. Bastava ir buscando em todas as entidades, as quais tem esta como propriedade, na primeira ocorrência de um caso cuja pesquisa retornasse dados, seria simples gerar uma Exception para quem chamou o metodo.

Da pra encarar? kkkkkkkkkkkkk
Foda né… triste. Reinvenção de roda. Implementar via Java os recursos do banco.

Mais tudo bem, estão pagando bem! hehehhehe

Entao eu queria sujestões dos colegas, sobre a parte de deleção em Cascata e Restrição de deleção, alguem tem alguma ideia melhor, ou no caso de ter que varrer as entidades, pensei em procura-las no persistence.xml e depois ir tratando uma a uma por reflection, para tentar achar as entidades que dependem desta, e tomar uma decisão, cascade ou restrict.

Opiniões, Soluções, Ajuda, etc…

Obrigado Amigos! Abraços a Todos.

[quote=jeferson.lucio]Oi Pessoal!

Boa tarde!

Estou com um problema interessante aqui comigo, talvez alguns dos colegas ja tenha ouvido falar de algo parecido ou ja tenha implementado na prática.

Um cliente me pediu em seu projeto um recurso de fake delete. Como no Microsiga Protheus, para quem ja usou, deve se lembrar, tem uma coluna em cada tabela chamada D_E_L_E_T_, que quando tem um asterisco quer dizer que esta entidade ja foi removida do sistema, e reside fantasma no banco de dados, só retornando ao uso quando o administrador resolver restaura-la, ou mesmo passa desta pra melhor quando alguem resolver executar o tal do PACK, que deleta definitivamente todas elas.

Seria simples fazer isto? A resposta é SIM e NAO ao mesmo tempo.

Pensando de forma simples, basta fazer tudo com herança nas entidades e um bom Dao generico.

Assim cada entidade herdaria uma propriedade Boolean, que bem poderia se chamar “deletado”, a qual diria se esta entidade ainda existe ou nao.
Lógico, a partir de agora, qualquer query JPQL teria que possuir " AND Entidade.deletado = false ", para evitar recuperar indesejadamente entidades fantasmas.
Tudo isto é facil de resolver com um bom Dao generico encapsulando tudo.

Porem surgem dois problemas: Chaves Unicas e Deletes tipo Restrict e Cascade.

Chaves unicas nao funcionam mais! Os motivos sao simples, quando alguem cria uma entidade, pode ja existir uma entidade deletada na tabela com os mesmos dados-chaves da recem criada, gerando um erro.

[/quote]

Não.
Primeiro, vc nunca deve salvar algo que não é válido. Os seus validadores devem impedir que save algo se ele já existe. nunca haverá exceção.
Segundo, se foir marcado como deleted significa que a qualquer momento que o sistema quiser esses dados podem ser expurgados. Sendo assim,eles podem ser removidos fisicamente sempre que necessário. Portanto, se vc tenta gravar uma entidade que já existia antes ,mas está deletes, vc apaga ela fisicamente e cria a nova normalmente.

Em relação ao cascade o problema apenas acontece na hora se setar o campo delete como true. Ai não te jeito , vc tem que código que faça isso e repare que não é apenas seguir as referencias “para baixo” na entidade. Podem haver entidades apontando essa que ela não conhece.

Os cascades do banco, esses não tem problema, já que uma vez dado o delete fisico, o banco deve remover os registros fisicamente associados.

Sim, perfeito, por isto que eu pensei em reflection em tudo que for entidade (Afff… kkkkk), para isto pensei em ler o caminho delas (pacote+nome) no persistence.xml.

[quote=jeferson.lucio][quote=sergiotaborda]

… e repare que não é apenas seguir as referencias “para baixo” na entidade. Podem haver entidades apontando essa que ela não conhece.

[/quote]

Sim, perfeito, por isto que eu pensei em reflection em tudo que for entidade (Afff… kkkkk), para isto pensei em ler o caminho delas (pacote+nome) no persistence.xml.[/quote]

Vc pode usar isso como default, mas certifique-se que a sua api permite poder modificar programáticamente o como será feita a propagação do delete.

javax.persistence.spi.PersistenceUnitInfo

Esta inferface implementa o Método:

List getManagedClassNames();

Que mto provavelmente me devolverá a lista de entidades do PersistenceUnit.
porem, difícil esta encontrar uma implementação desta interface no JPA ou no Hibernate(meu provedor), para conseguir uma instância.

[quote=jeferson.lucio]javax.persistence.spi.PersistenceUnitInfo
porem, difícil esta encontrar uma implementação desta interface no JPA ou no Hibernate(meu provedor), para conseguir uma instância.[/quote]

Que pesadelo encontrar isto! Desde ontem estou tentando!

Bom, consegui fazer este metodo para conseguir para mim da JPA todas as Classes gerenciadas como Entidades.
Percebam que tive que envonver diretamente a implementação do Hibernate.
Espero que na JPA 2.0 nao haja mais necessidades deste tipo de coisa.

    public static List<Class> getMappedClasses() {

        List<Class> mappedClasses = new ArrayList<Class>();
        
        EntityManagerImpl entityManagerImpl = (org.hibernate.ejb.EntityManagerImpl) Persistence.createEntityManagerFactory(persistenceUnit).createEntityManager();

        Collection<SingleTableEntityPersister> tablesEntities = entityManagerImpl.getSession().getSessionFactory().getAllClassMetadata().values();

        for (SingleTableEntityPersister singleTableEntityPersister : tablesEntities) {

            mappedClasses.add( singleTableEntityPersister.getMappedClass(EntityMode.POJO) );

        }

        return mappedClasses;

    }