JPA não reconhece atualização realizada direta no BD

Pessoal estou usando JPA(EclipseLink) com Glassfish.

PROBLEMA:
A primeira vez que faço o select dos registros com status = INSERIDO, ele recupera 5 registros e faz a atualização do status para ENVIADO e tudo belezinha. Mas se eu alterar o status desses registros no banco de dados ‘na mão’ para INSERIDO, logo em seguida a essa atualização e rodar o método de novo acontece uma coisa estranha(pelo menos pra mim… hehehe :p)
O que acontece dessa segunda vez é:
Qdo faço o select no banco, ele traz os 5 registros que estão com status = INSERIDO (atualizado por mim, ‘na mão’, direto no banco de dados). Mas qdo dou um System.out.println no status desses registros, ele imprime ENVIADO e, logicamente, qdo vai fazer o merge, o campo STATUS não é atualizado, visto que para o JPA esse registro já estava com status ENVIADO (o que é mentira, pois no banco ele está com status INSERIDO).

Segue as classes que fazem parte do sistema e mais abaixo a saída do console da primeira e da segunda atualização.

Agradeço demais a quem puder me dar uma luz sobre esse assunto… Obrigado a todos.

JMSInicio

@RequestScoped
@Named(value="jmsInicio")
public class JMSInicio {
    @EJB()
    CadastroGeralLocal persCadastros;
    
    List<LocCadastroGeral> listaLocCadastroGeral = new ArrayList<LocCadastroGeral>();

    public List<LocCadastroGeral> enviarRegistrosInseridos() {
        listaLocCadastroGeral = new ArrayList<LocCadastroGeral>();
        System.out.println("Pesquisa Itens A Serem Enviados");
        listaLocCadastroGeral = persCadastros.getCadastrosPendentes(StatusMsgType.INSERIDO, DadosFilas.qtdeMsgPacote);
        System.out.println("Tamanho: " + listaLocCadastroGeral.size());
        for (LocCadastroGeral cad : listaLocCadastroGeral) {
            System.out.println("STATUS DO OBJETO RECUPERADO: " + cad.getLocCadastroLocal().getStatus());
            cad = persCadastros.atualizaCadastroEnviado(cad);
        }

        return listaLocCadastroGeral;
    }
}

CadastroGeralLocal

@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@TransactionManagement(TransactionManagementType.CONTAINER)
public class CadastroGeralLocal extends BasicSession {

    public List<LocCadastroGeral> getCadastrosPendentes(StatusMsgType tipoStatus, Integer qtde) {
        Query q = getEm().createQuery("select cad "
                + "from LocCadastroGeral cad "
                + "where cad.locCadastroLocal.idCadastroServidor is null "
                + "and cad.locCadastroLocal.status = ?1 "
                + "order by cad.locCadastroLocal.numTentativas, cad.locCadastroLocal.dtInsert");
        q.setParameter(1, tipoStatus);
        if (qtde > 0) q.setMaxResults(qtde);
        return q.getResultList();
    }

    public LocCadastroGeral atualizaCadastroEnviado(LocCadastroGeral cad) {
        System.out.println("ENTROU atualizaCadastroEnviado - ATUALIZANDO campos");
        cad.getLocCadastroLocal().setStatus(StatusMsgType.ENVIADO);
        cad.getLocCadastroLocal().setNumTentativas( cad.getLocCadastroLocal().getNumTentativas() + 1 );
        cad = atualizar(cad);
        System.out.println("CADASTRO MUDOU PARA STATUS: " + cad.getLocCadastroLocal().getStatus());
        return cad;
    }

    public LocCadastroGeral atualizar(LocCadastroGeral cad) {
        System.out.println("VAI REALIZAR MERGE - STATUS DO CADASTRO: " + cad.getLocCadastroLocal().getStatus());
        cad = getEm().merge(cad);
        System.out.println("REALIZOU MERGE");
        return cad;
    }

}

LocCadastroGeral

@Entity
@Table(name = "loc_cadastro_geral", catalog = "db_civil", schema = "local")
public class LocCadastroGeral implements Serializable {
    private static final long serialVersionUID = 1L;

    @OneToOne(cascade = CascadeType.ALL, mappedBy = "locCadastroGeral", fetch=FetchType.EAGER)
    private LocCadastroLocal locCadastroLocal;

}

LocCadastroLocal

@Entity
@Table(name = "loc_cadastro_local", catalog = "db_civil", schema = "local")
public class LocCadastroLocal implements Serializable {
    private static final long serialVersionUID = 1L;

    @JoinColumn(name = "fk_cadastro_local", referencedColumnName = "id_cadastro_local", insertable = false, updatable = false)
    @OneToOne(optional = false)
    private LocCadastroGeral locCadastroGeral;
}

Saída Console - 1a vez
[i]
INFO: Pesquisa Itens A Serem Enviados
BOM: SELECT t1.id_cadastro_local AS id_cadastro_local1, t1.fk_naturalidade_estado AS fk_naturalidade_estado2, t1.fk_naturalidade_cidade AS fk_naturalidade_cidade3, t1.num_cedula_foto AS num_cedula_foto4, t1.sexo AS sexo5, t1.rg AS rg6, t1.end_comp AS end_comp7, t1.cert_tipo AS cert_tipo8, t1.cer_livro AS cer_livro9, t1.end_cidade AS end_cidade10, t1.impresso AS impresso11, t1.cep AS cep12, t1.cpf AS cpf13, t1.rg_ric AS rg_ric14, t1.dt_nascimento AS dt_nascimento15, t1.end_estado AS end_estado16, t1.cert_folha AS cert_folha17, t1.fk_via AS fk_via18, t1.num_cedula_texto AS num_cedula_texto19, t1.nm_mae AS nm_mae20, t1.pis_pasep AS pis_pasep21, t1.cert_dt_exp AS cert_dt_exp22, t1.nm_pai AS nm_pai23, t1.fk_pagamento AS fk_pagamento24, t1.matricula AS matricula25, t1.doador AS doador26, t1.cert_num AS cert_num27, t1.cartorio AS cartorio28, t1.end_num AS end_num29, t1.nome AS nome30, t1.foto AS foto31, t1.endereco AS endereco32 FROM db_civil.local.loc_cadastro_local t0, db_civil.local.loc_cadastro_geral t1 WHERE (((t0.id_cadastro_servidor IS NULL) AND (t0.status = ?)) AND (t0.fk_cadastro_local = t1.id_cadastro_local)) ORDER BY t0.num_tentativas ASC, t0.dt_insert ASC LIMIT ? OFFSET ?
bind => [0, 5, 0]
BOM: SELECT fk_cadastro_local, num_tentativas, dt_insert, status, id_cadastro_servidor, fk_posto_identificacao FROM db_civil.local.loc_cadastro_local WHERE (fk_cadastro_local = ?)
bind => [52]
BOM: SELECT id_posto_identificacao, email_prefeito, cel_prefeito, tel_prefeito, descricao, VERSION, bairro, cep, impressao, email, responsavel, end_num, end_comple, tefefone, endereco, prefeito, celular FROM db_civil.local.loc_posto_identificacao WHERE (id_posto_identificacao = ?)
bind => [2]
BOM: SELECT fk_cadastro_local, num_tentativas, dt_insert, status, id_cadastro_servidor, fk_posto_identificacao FROM db_civil.local.loc_cadastro_local WHERE (fk_cadastro_local = ?)
bind => [125]
BOM: SELECT fk_cadastro_local, num_tentativas, dt_insert, status, id_cadastro_servidor, fk_posto_identificacao FROM db_civil.local.loc_cadastro_local WHERE (fk_cadastro_local = ?)
bind => [89]
BOM: SELECT fk_cadastro_local, num_tentativas, dt_insert, status, id_cadastro_servidor, fk_posto_identificacao FROM db_civil.local.loc_cadastro_local WHERE (fk_cadastro_local = ?)
bind => [132]
BOM: SELECT id_posto_identificacao, email_prefeito, cel_prefeito, tel_prefeito, descricao, VERSION, bairro, cep, impressao, email, responsavel, end_num, end_comple, tefefone, endereco, prefeito, celular FROM db_civil.local.loc_posto_identificacao WHERE (id_posto_identificacao = ?)
bind => [1]
BOM: SELECT fk_cadastro_local, num_tentativas, dt_insert, status, id_cadastro_servidor, fk_posto_identificacao FROM db_civil.local.loc_cadastro_local WHERE (fk_cadastro_local = ?)
bind => [56]
INFO: Tamanho: 5

INFO: STATUS DO OBJETO RECUPERADO: INSERIDO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ?, status = ? WHERE (fk_cadastro_local = ?)
bind => [35, 1, 52]

INFO: STATUS DO OBJETO RECUPERADO: INSERIDO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ?, status = ? WHERE (fk_cadastro_local = ?)
bind => [47, 1, 125]

INFO: STATUS DO OBJETO RECUPERADO: INSERIDO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ?, status = ? WHERE (fk_cadastro_local = ?)
bind => [48, 1, 89]

INFO: STATUS DO OBJETO RECUPERADO: INSERIDO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ?, status = ? WHERE (fk_cadastro_local = ?)
bind => [48, 1, 132]

INFO: STATUS DO OBJETO RECUPERADO: INSERIDO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ?, status = ? WHERE (fk_cadastro_local = ?)
bind => [49, 1, 56]
[/i]

Saída Console - 2a atualização (fail)
[i]
INFO: Pesquisa Itens A Serem Enviados
BOM: SELECT t1.id_cadastro_local AS id_cadastro_local1, t1.fk_naturalidade_estado AS fk_naturalidade_estado2, t1.fk_naturalidade_cidade AS fk_naturalidade_cidade3, t1.num_cedula_foto AS num_cedula_foto4, t1.sexo AS sexo5, t1.rg AS rg6, t1.end_comp AS end_comp7, t1.cert_tipo AS cert_tipo8, t1.cer_livro AS cer_livro9, t1.end_cidade AS end_cidade10, t1.impresso AS impresso11, t1.cep AS cep12, t1.cpf AS cpf13, t1.rg_ric AS rg_ric14, t1.dt_nascimento AS dt_nascimento15, t1.end_estado AS end_estado16, t1.cert_folha AS cert_folha17, t1.fk_via AS fk_via18, t1.num_cedula_texto AS num_cedula_texto19, t1.nm_mae AS nm_mae20, t1.pis_pasep AS pis_pasep21, t1.cert_dt_exp AS cert_dt_exp22, t1.nm_pai AS nm_pai23, t1.fk_pagamento AS fk_pagamento24, t1.matricula AS matricula25, t1.doador AS doador26, t1.cert_num AS cert_num27, t1.cartorio AS cartorio28, t1.end_num AS end_num29, t1.nome AS nome30, t1.foto AS foto31, t1.endereco AS endereco32 FROM db_civil.local.loc_cadastro_local t0, db_civil.local.loc_cadastro_geral t1 WHERE (((t0.id_cadastro_servidor IS NULL) AND (t0.status = ?)) AND (t0.fk_cadastro_local = t1.id_cadastro_local)) ORDER BY t0.num_tentativas ASC, t0.dt_insert ASC LIMIT ? OFFSET ?
bind => [0, 5, 0]
BOM: SELECT fk_cadastro_local, num_tentativas, dt_insert, status, id_cadastro_servidor, fk_posto_identificacao FROM db_civil.local.loc_cadastro_local WHERE (fk_cadastro_local = ?)
bind => [60]
INFO: Tamanho: 5
INFO: STATUS DO OBJETO RECUPERADO: ENVIADO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ? WHERE (fk_cadastro_local = ?)
bind => [36, 52]

INFO: STATUS DO OBJETO RECUPERADO: ENVIADO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ? WHERE (fk_cadastro_local = ?)
bind => [48, 125]

INFO: STATUS DO OBJETO RECUPERADO: INSERIDO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ?, status = ? WHERE (fk_cadastro_local = ?)
bind => [49, 1, 60]

INFO: STATUS DO OBJETO RECUPERADO: ENVIADO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ? WHERE (fk_cadastro_local = ?)
bind => [49, 89]

INFO: STATUS DO OBJETO RECUPERADO: ENVIADO
INFO: ENTROU atualizaCadastroEnviado - ATUALIZANDO campos
INFO: VAI REALIZAR MERGE - STATUS DO CADASTRO: ENVIADO
INFO: REALIZOU MERGE
INFO: CADASTRO MUDOU PARA STATUS: ENVIADO
BOM: UPDATE db_civil.local.loc_cadastro_local SET num_tentativas = ? WHERE (fk_cadastro_local = ?)
bind => [49, 132]
[/i]

Isso acontece por causa do cache do Hibernate ou da implementação da JPA que você está usando e (in)felizmente não tem como alterar esse comportamento.
Se você reiniciar sua aplicação você vai perceber que ai sim, os dados estarão de acordo com o estado do banco.

[]´s

Exatamente isso. Se eu reinicia-la, o cache desaparece e ela atualiza normalmente, como na primeira vez.

Não tem mesmo como forçar essa ‘ida’ ao banco de dados pra trazer os valores reais que estão no BD?

‘Engraçado’ é que a consulta no banco retorna os 5 registros q tiveram seus status atualizados na mão. Ou seja, qdo é feito o select, ele reconhece os registros atualizados na mão, ou seja, tá indo no banco de dados.

O que me parece é que o select faz a sua parte corretamente, mas qdo esses registros são carregados pra memória, o JPA reconhece que os mesmo já estão lá e não atualiza os valores desses registros que já foram carregados.

É isso mesmo? Tem alguma forma de forçar uma atualização desses objetos carregados do banco de dados?

Valeu.

Seguinte, antes de dormir, resolvi fazer uma ultima tentativa e… adivinhem, funcionou… agora n sei se isso é certo.

Vejam se existe uma maneira mais correta de se fazer isso.

Alterei o método getCadastrosPendentes, fazendo agora um foreach e fazendo um refresh pra cada item da lista retornado:

public List<LocCadastroGeral> getCadastrosPendentes(StatusMsgType tipoStatus, Integer qtde) {
        String str = "select cad "
                + "from LocCadastroGeral cad "
                + "where cad.locCadastroLocal.idCadastroServidor is null "
                + "and cad.locCadastroLocal.status = ?1 "
                + "order by cad.locCadastroLocal.numTentativas, cad.locCadastroLocal.dtInsert";
        List<LocCadastroGeral> t = getLimitedList(LocCadastroGeral.class, str, qtde,tipoStatus);
        for (LocCadastroGeral locCadastroGeral : t) {
            getEm().refresh(locCadastroGeral);
        }
        
        return t;
}

Além disso coloquei um campo VERSION nas entidades.

@Column(name="version")
    @Version
    private Long version;

    public Long getVersion() {
        return version;
    }

    public void setVersion(Long version) {
        this.version = version;
    }

Existe outra maneira, através de annotation ou alguma propriedade para que o JPA faça isso ‘automaticamente’ pra mim, ou alguem sabe outra solução? Obrigado.

Ola, tem um tempão que não mexo com JPA, mas tinha um metodo refresh em algum lugar para forçar esta ida ao banco… só não lembro onde.

o refresh vai buscar todos os relacionamentos entre os objetos, outra alternativa é dar um clear na sessao antes de dar o select assim o cache é zerado e os dados buscados diretamente do BD, faça um teste quando voce faz o refresh para cada objeto ele varre todos os relacionamentos mesmo estando em LAZY