Falha na inserção de dados do hibernate (JPA) em transações com objetos relacionados

Boa noite pessoal, estou com um grande problema e não sei bem como resolver, por isso postei aqui para ver se alguém tem alguma idéia para me ajudar…
A Questão é a seguinte: tenho uma aplicação war, baseada na plataforma JSF 2.0, com hibernate (JPA) e primefaces e banco de dados mysql. No geral ela funciona normalmente, porém em algumas situações as transações funcionam de forma errada e o JPA insere metade das informações ou insere não relacionadas( mais comum). Deixa eu explicar melhor, tenho uma entidade alunos que tem relacionamento com outra endereço, na maioria das vezes ocorre normalmente a inserção do novo aluno com referência ao id do endereço criado junto com o aluno, porém em determinadas situações que não sei localizar ocorre a inserção do aluno porém com id=0 do endereço e ora tem o registro do endereço corretamente na tabela e hora não é criado nem o registro. Nunca vi este tipo de erro em 4 anos trabalhando com java e JPA, nem nas versões iniciais de JPA 1.2. O erro acontece não somente neste relacionamento mais em outras entidades como fornecedor que também tem endereço, cliente e até em contas e parcelas que nada tem a haver com a tabela endereços.
Aguardo

Ao final de suas transações, force o flush() e veja o que vai aparecer no stacktrace. Algum erro vai aparecer e aí fica mais fácil de ajudar.

Se for detached/transient exception, olhe aqui: http://uaihebert.com/?p=1622

Boa noite amigo e obrigado pela ajuda, primeiro o projeto está em produção e passou pela desenvolvimento sem erros, inclusive roda no meu computador windows sem qualquer erro, não aparece… no servidor da máquina debian 6.07 no cliente é que está acontecendo estes problemas… e como posso força-lo ao flush? Ficará alguma pista no servidor glassfish?

[quote=androdana]Boa noite amigo e obrigado pela ajuda, primeiro o projeto está em produção e passou pela desenvolvimento sem erros, inclusive roda no meu computador windows sem qualquer erro, não aparece… no servidor da máquina debian 6.07 no cliente é que está acontecendo estes problemas… e como posso força-lo ao flush? Ficará alguma pista no servidor glassfish?[/quote]Ficará no log do servidor se você mandar o erro ser escrito lá…

Flush você força pelo entityManager.flush()…

[quote]Nunca vi este tipo de erro em 4 anos trabalhando com java e JPA, nem nas versões iniciais de JPA 1.2[/quote]Flush é coisa básica que já havia no JPA 1.2… http://docs.oracle.com/javaee/5/api/javax/persistence/EntityManager.html#flush()

Te aconselho então a estudar melhor o JPA, pois com 4 anos, já dá para ter uma noção básica com isso.

Bom dia amigo… obrigado pela dica, o que comentei foi que em 4 anos nunca vi esta questão de não atrelar uma entidade a outra dentro do seu relacionamento e muito menos nçao executar o rollback quando acontece tal erro… com relação ao flush eu utilizava-o quando trabalha com session

@Override
    public T salvar(T object) {
        Session session = entityManager.unwrap(Session.class);

        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
            session.getTransaction().begin();
            Long id = (Long) session.save(object);
            session.getTransaction().commit();
            session.flush();
            session.clear();
            object = pesquisarPorId((ID) id);
        } else {
            getEntityManager().persist(object);
        }

        return object;
    }

Com o JPA 2.0 e a facilidade do netbeans as implementações foram todas automáticas deixando-nos livres para trabalhar com a regra de negócio… mas estou vendo que terei que voltar a fazer na mão…
Obrigado pela disposição em ajudar.

[quote=androdana]Bom dia amigo… obrigado pela dica, o que comentei foi que em 4 anos nunca vi esta questão de não atrelar uma entidade a outra dentro do seu relacionamento e muito menos nçao executar o rollback quando acontece tal erro… com relação ao flush eu utilizava-o quando trabalha com session

@Override
    public T salvar(T object) {
        Session session = entityManager.unwrap(Session.class);

        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
            session.getTransaction().begin();
            Long id = (Long) session.save(object);
            session.getTransaction().commit();
            session.flush();
            session.clear();
            object = pesquisarPorId((ID) id);
        } else {
            getEntityManager().persist(object);
        }

        return object;
    }

Com o JPA 2.0 e a facilidade do netbeans as implementações foram todas automáticas deixando-nos livres para trabalhar com a regra de negócio… mas estou vendo que terei que voltar a fazer na mão…
Obrigado pela disposição em ajudar.[/quote]
Cara, mas não tem lógica esse método seu…

Se a transação estiver fechada, chama o persist?? Hora usa o session do hibernate e hora usa o entityManager? O.o

Bom dia amigo, realmente ficou incompleto, mas é porque na prática o glassfish sempre deixava a conexão aberta ou existia um filtro (interceptor) para pegar a informação quando era spring, então estava “ajustado” mas o que vc recomenda nesse caso para inserir o flush… pois mesmo com essa implementação não resolveu… ainda continuou o erro e não apareceu no log qualquer indício…

os métodos troquei para estes:

public void create(T entity) {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().persist(entity);
            getEntityManager().getTransaction().commit();
            getEntityManager().flush();            
        } catch (Exception e) {
            getEntityManager().getTransaction().rollback();
            e.printStackTrace();
        }
    }

    public void edit(T entity) {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().merge(entity);
            getEntityManager().getTransaction().commit();
            getEntityManager().flush(); 
        } catch (Exception e) {
            getEntityManager().getTransaction().rollback();
            e.printStackTrace();
        }
    }

se quiser anexo as classes tb…

Só coloque aí oq o getEntityManager faz.

[quote=androdana]os métodos troquei para estes:

public void create(T entity) {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().persist(entity);
            getEntityManager().getTransaction().commit();
            getEntityManager().flush();            
        } catch (Exception e) {
            getEntityManager().getTransaction().rollback();
            e.printStackTrace();
        }
    }

    public void edit(T entity) {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().merge(entity);
            getEntityManager().getTransaction().commit();
            getEntityManager().flush(); 
        } catch (Exception e) {
            getEntityManager().getTransaction().rollback();
            e.printStackTrace();
        }
    }

se quiser anexo as classes tb…[/quote]
Não é para menos que o log não pega… Está usando e.printStackTrace()…
Essa é uma péssima prática. Existem ferramentas para log muito mais adequadas, até mesmo o log4j (eu sugiro o Mentalog) e o próprio java.util.logging.Logger já dariam muito mais clareza ao que você precisa.
Outro detalhe que eu não acho legal é deixar a ferramenta escolher por você o que deve ser feito. Eu prefiro perder algumas horas programando de uma maneira que eu controle 100% do que acontece do que usar um automatizador que me levará a perder tempo tentando consertar as coisas depois. Enfim, gosto é gosto.

Herbert , esta é a classe “facade” generica criada para todas as classes:

public abstract class AbstractFacade<T> {

    private Class<T> entityClass;

    public AbstractFacade(Class<T> entityClass) {
        this.entityClass = entityClass;
    }

    protected abstract EntityManager getEntityManager();

    public T create(T entity) {
        try {
            getEntityManager().persist(entity);
            getEntityManager().flush();
            getEntityManager().clear();
        } catch (Exception e) {
            //getEntityManager().getTransaction().rollback();
            e.printStackTrace();
        }
        return entity;
    }

    public T edit(T entity) {
        try {
            entity = getEntityManager().merge(entity);
            getEntityManager().flush();
            getEntityManager().clear();
        } catch (Exception e) {
            //getEntityManager().
            e.printStackTrace();
        }
        return entity;
    }

    public void excluir(T entity) {
        try {
            getEntityManager().getTransaction().begin();
            getEntityManager().remove(getEntityManager().merge(entity));
            getEntityManager().getTransaction().commit();
            getEntityManager().flush();
        } catch (Exception e) {
            getEntityManager().getTransaction().rollback();
            e.printStackTrace();
        }

    }

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    public List<T> findAll() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> listaCompleta() {
        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }
        String queryS = "SELECT obj FROM " + entityClass.getSimpleName() + " obj";
        Query query = getEntityManager().createQuery(queryS);
        return query.getResultList();
    }

    public List<T> findRange(int[] range) {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0]);
        q.setFirstResult(range[0]);
        return q.getResultList();
    }

    public int count() {
        javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

    public List<T> listaPorParametros(String query, Map<String, Object> params) {

        Session session = getEntityManager().unwrap(Session.class);
        session.setCacheMode(CacheMode.IGNORE);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }
        Query q = getEntityManager().createQuery(query);
        for (String chave : params.keySet()) {
            q.setParameter(chave, params.get(chave));
        }
        return q.getResultList();
    }

    public List<T> listaPorParametros(String query, Map<String, Object> params,
            int maximo, int atual) {

        Session session = getEntityManager().unwrap(Session.class);
        session.setCacheMode(CacheMode.IGNORE);

        Query q = getEntityManager().
                createQuery(query).
                setMaxResults(maximo).
                setFirstResult(atual);

        for (String chave : params.keySet()) {
            q.setParameter(chave, params.get(chave));

        }
        return (List<T>) q.getResultList();
    }

    public void desacoplarLista(List<T> listaDesacoplar) {
        Session session = getEntityManager().unwrap(Session.class);
        for (T obj : listaDesacoplar) {
            session.evict(obj);
        }
        session.flush();
    }

    public void desacoplarObjeto(T objeto) {
        Session session = getEntityManager().unwrap(Session.class);
        session.evict(objeto);
        session.flush();
    }

    public List<T> listaPorPesquisa(String query) {
        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }

        Query q = getEntityManager().createQuery(query);
        return q.getResultList();
    }

    public T pesquisaPorParamametros(String query, Map<String, Object> params) {
        Query q = getEntityManager().createQuery(query);
        for (String chave : params.keySet()) {
            q.setParameter(chave, params.get(chave));
        }
        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }
        try {
            return (T) q.getSingleResult();
        } catch (NoResultException nre) {
            return null;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public T pesquisaNamedQueryPorParamametros(String namedQuery, Map<String, Object> params) {

        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }

        Query q = getEntityManager().createNamedQuery(namedQuery);
        for (String chave : params.keySet()) {
            q.setParameter(chave, params.get(chave));
        }
        try {
            return (T) q.getSingleResult();
        } catch (NoResultException nre) {
            return null;
        }
    }

    public List<T> listaAtualizacao(long codigoAtualizacao) {


        Session session = getEntityManager().unwrap(Session.class);
        session.setCacheMode(CacheMode.IGNORE);

        try {
            String queryS = "SELECT obj FROM " + entityClass.getSimpleName() + " obj";
            Query q = null;
            if (codigoAtualizacao > 0) {
                queryS += " WHERE obj.atualizacao NOT LIKE :cod";
                q = getEntityManager().createQuery(queryS);
                q.setParameter("cod", "%;" + codigoAtualizacao + ";%");
            } else {
                q = getEntityManager().createQuery(queryS);
            }

            return (List<T>) q.getResultList();
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public T salvar(T object) {
        Session session = getEntityManager().unwrap(Session.class);
        try {
            if (!session.isOpen()) {
                session.beginTransaction();
            }
            session.setCacheMode(CacheMode.IGNORE);
            session.getTransaction().begin();
            Long id = (Long) session.save(object);
            session.getTransaction().commit();
            session.flush();
            session.clear();
            object = pesquisarPorId((Long) id);
        } catch (Exception e) {
            session.getTransaction().rollback();
            e.printStackTrace();
        }
        return object;
    }

    public T pesquisarPorId(Long id) {
        return (T) getEntityManager().find(entityClass, id);
    }

    public T atualizar(T object) {

        if (object instanceof Atualizavel) {
            Atualizavel a = (Atualizavel) object;
            a.setAtualizacao("");
        }
        Session session = getEntityManager().unwrap(Session.class);
        try {
            if (!session.isOpen()) {
                session.beginTransaction();
            }
            session.setCacheMode(CacheMode.IGNORE);
            session.getTransaction().begin();
            object = (T) session.merge(object);
            session.getTransaction().commit();
            session.flush();
        } catch (Exception e) {
            session.getTransaction().rollback();
            e.printStackTrace();
        }
        return object;
    }

    public T atualizar(T object, Usuario responsavel) {

        throw new UnsupportedOperationException("Classe auditável, porém este método deve ser sobreescrito.");
    }

    public void excluir(T object, Long id) {
        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
            session.getTransaction().begin();
            object = (T) getEntityManager().find(entityClass, id);
            session.delete(object);
            session.getTransaction().commit();
            session.flush();
            session.clear();
        } else {
            getEntityManager().remove(object);
        }
    }

    public void limparCache() {
        try {

            Session session = getEntityManager().unwrap(Session.class);

            if (session.isOpen()) {
                session.setCacheMode(CacheMode.IGNORE);
                session.clear();
                session.flush();
            } else {
                getEntityManager().clear();
            }

        } catch (Exception ex) {
        }
    }

    public List<T> listaPorPesquisaWhere(String query, Map<String, Object> params) {


        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }

        String queryS = "SELECT obj FROM " + entityClass.getSimpleName() + " obj WHERE " + query;

        Query q = getEntityManager().createQuery(queryS);
        for (String chave : params.keySet()) {
            q.setParameter(chave, params.get(chave));

        }
        return q.getResultList();
    }

    public int executarQuery(String query, Map<String, Object> params) {

        Session session = getEntityManager().unwrap(Session.class);
        if (session.isOpen()) {
            session.setCacheMode(CacheMode.IGNORE);
        }

        org.hibernate.Query q = session.createQuery(query);

        for (String chave : params.keySet()) {
            q.setParameter(chave, params.get(chave));
        }

        session.beginTransaction();
        int val = q.executeUpdate();
        session.flush();

        return val;
    }

O entitymanager é recebido pelo glassfish que cria e gerencia as conexões…
machado tenho o loj4 no sistema mas sendo sincero não configurei ele e nem tentei outro, vou procurar pesquisar algo para poder implementar…

Que salada.
Por que não, simplesmente, usa os métodos do EntityManager e precisa instanciar a Session?
Basicamente, o getEntityManager não está sendo executado, provavelmente o Glassfish não o está injetando.
Até onde eu sei de application servers, você poderia injetar o EntityManager usando a annotation @PersistenceContext. Ao menos no JBoss eu uso assim.

Machado eu já tentei simplesmente o entitymanager e vamos lá funciona! De todas as formas que mostrei aqui funciona e apenas em alguns casos que ainda não consegui pegar ocorre o erro da inconsistência ou seja cria/salva apenas parte do objeto ou não referencia-o por exemplo salva o fornecedor sem endereco referenciado (id do endereço na tabela do fornecedor=0), entendeu?
o @Persistence é implementado no beans ou o @EJB como é o meu caso com o JSF2, Glassfish 3.1 e primefaces… ressalto que se for só para malhar ou para criticar sem ajuda não precisa escrever mais nada… acho que estes foruns aqui são para ajudar e dividir conhecimento e não para sacanear ninguém!

Leia a definição de fórum, por favor.
Eu não entendo por que fazer estas coisas, simples assim.

Bem, eu pedi um método, você postou uma classe enorme. Eu concordo com o machado por uma coisa… protected abstract EntityManager getEntityManager();
Se é abstrato, alguém tem que implementar. Servidor nenhum vai jogar magicamente um EntityManager aí.

Bem, espero que alguém consiga te ajudar.

Bom dia Herbert, vamos ao que vc pediu:

@Stateless
public class FornecedorFacade extends AbstractFacade<Fornecedor> {

    @PersistenceContext(unitName = "SistemaEscola-ejbPU")
    private EntityManager em;

    @Override
    protected EntityManager getEntityManager() {
        return em;
    }

    public FornecedorFacade() {
        super(Fornecedor.class);
    }
...

Esta é a classe que implementa a classe abstrata generica que te passei…

@Named("cadastrador")
@ConversationScoped
@Stateful
public class Cadastrador extends MasterBean implements java.io.Serializable {

    private Map<String, Object> mapa;  
    @EJB
    private FornecedorFacade fornecedorFacade;
...

public String salvarFornecedor() {
        try {
            if (fornecedor.getNome_fantasia().equals("")) {
                FacesUtils.mensErro("Defina um nome Fantasia para o fornecedor!");
                return null;
            }
            ControleModificacao.setarMaiusculas(fornecedor);
            ControleModificacao.setarMaiusculas(fornecedor.getEndereco());
            /*
            if (fornecedor.getEndereco().getId() == null) {
                getEnderecoFacade().salvar(fornecedor.getEndereco());
            }*/
            if (fornecedor.getId() == null) {
                fornecedorFacade.salvar(fornecedor);
            } else {
                fornecedorFacade.atualizar(fornecedor);
            }
            fornecedor = new Fornecedor();
            forn = null;
            produtos = new ArrayList<ProdutoEstocado>();

            FacesUtils.mensInfo("fornecedor Salvo com sucesso!");
        } catch (Exception e) {
            e.printStackTrace();
            FacesUtils.mensErro("Erro ao Salvar");
        }
        return null;
    }

Este é o bean que atua na página com o método salvar…
Estou discutindo em outros foruns também a respeito e foi levantada duas possibilidades, 1 pode ser em função de pool de conexões estar pouco dimensionado ou causando coneões leak de algum forma, sugeriram que eu lesse algo a respeito de C3P0… que já li e pelo que entendi as novas versões do Hibernate já vem com esta implementação, mas de qualquer forma já implementei monitoramento no glassfish para locar isso… e por falar nisso através de sl4j…
A segunda idéia é relacionada ao relacionamento ser bidirecional e não realizar o rollback neste caso quando algo der errado e sugeriram implementar no método forçando o rollbak e por último se não der certo salvar separadamente as classes, primeiro o endereço e depois o fornecedor já com o endereço no banco…

Bem, que bom que você já tem muitas teorias para poder avaliar.

Na boa?

Eu acho que o problema pode estar na estrutura do seu projeto…

[quote]@Named(“cadastrador”)
@ConversationScoped
@Stateful
public class Cadastrador extends MasterBean[/quote]Sério uma classe tá sendo usada como View e como EJB? Isso não pode ser o problema, mas se um view se mistura com EJB pode estar acontecendo problemas também com relacionamento do JPA, configurações das transações e assim vai.

Eu começaria limpando toda a estrutura do projeto, dividindo as classes em acordo com a responsabilidade de cada uma. Isso me permitiria isolar exatamente o que está causando o problema e, então, atacá-lo.
Essa limpeza passaria por escolher entre Session e EntityManager em definitivo. Embora funcione, não acho uma boa prática misturar ambos. É o mesmo que tentar usar primefaces e richfaces em um mesmo projeto, funciona, mas as dores de cabeça que essa junção pode causar não vale o esforço.
Se o gerenciamento das transações está com o container, eu descartaria o uso de c3p0, ele seria uma camada a mais no processo e, pela estrutura do que você precisa, é totalmente desnecessária, caso mantenha o uso de JPA, se for usar apenas o hibernate, aí eu sugiro desvincular o controle da transação do container e colocar o c3p0 e fazer tudo manualmente (resource_local, certo?).

Bom dia pessoal, entendi a ideia de cada um de vcs e muito obrigado pela ajuda… Herbert esta estrutura peguei de um tutorial que aprendi no próprio netbeans a um tempo atrás e não acredito que invocando apenas o ejb no bean esteja misturando funções, eu apenas invoco um ejb para acesso a funções no banco nada mais, quando existe alguma modificação a coisa fica toda na outra camada (facade)… mas vou procurar outra forma e estudar um pouco mais para ver o que posso separar…
Quanto a sua ideia Machado, concordo de que o C3P0 não é o problema e já separei as classes dentro da sua responsabilidade a única coisa que fiz para ganhar temo foi a confecção de
um único bean para cuidar das pequenas classes que só faziam o crud, então neste eu atribui várias funções, com o intuito de diminuir os beans do projeto. Segui o seu conselho e estou trabalhando apenas com JPA (EntityManager) e estou aguardando e até agora não teve mais nenhum erro no banco de dados… entendi quando vc comparou com a questão do prime e do rich… concordo que pode ter sido isto que gerou o conflito… tentei verificar dentro do monitoramento e encontrei 2 falhas de conexão que podem ter sido geradas por isso, ainda estou avaliando…
No mais obrigado aos dois pelos conselhos e ajuda. Vou tentando aqui e se der certo os ajustes posto aqui a solução final para ajudar outros que passem pela mesma situação problema…

Então, eu sugiro fortemente que procure um tutorial mais adequado. Eu abandonaria o netbeans, começaria com o eclipse, que acho ser mais adequado.
Enfim, opinião.