JPA não atualizar entidade automaticamente
Boa noite, estou com a seguinte dúvida, percebi que ao utilizar a JPA (Hibernate) + Spring em uma aplicação JSF (PrimeFaces) minhas entidades não são atualizadas de imediato ao realizar um cadastro, por exemplo, possuo uma tela de cadastro de Usuários onde ao realizar o mesmo tudo ocorre normalmente, as informações são persistidas no banco de dados, mas ao tentar utilizar por exemplo um componente auto-complete em outra página o registro da entidade recém adicionada não aparece, a menos que eu reinicie o servidor, após a reinicialização tudo funciona normalmente.
Criei o tópico na sessão de JPA pois pesquisando pela internet, creio que seja algo relacionado a configuração de Cache do hibernate porém ainda não consegui encontrar a solução, caso alguém ja tenha passo por isso ou tenha alguma luz sobre o assunto me avise por favor.
Obrigado, até mais
Obs: já tive o mesmo problema, até citado em tópicos anteriores, na ocasião acabei resolvendo utilizando a configuração <shared-cache-mode>none<shared-cache-mode> no meu arquivo persistence.xml... Não sei se foi a melhor solução mas na epoca resolveu. Acontece que agora estou utilizando Spring nessa aplicação e não disponho do arquivo persistence.xml.
Criei o tópico na sessão de JPA pois pesquisando pela internet, creio que seja algo relacionado a configuração de Cache do hibernate porém ainda não consegui encontrar a solução, caso alguém ja tenha passo por isso ou tenha alguma luz sobre o assunto me avise por favor.
Obrigado, até mais
Obs: já tive o mesmo problema, até citado em tópicos anteriores, na ocasião acabei resolvendo utilizando a configuração <shared-cache-mode>none<shared-cache-mode> no meu arquivo persistence.xml... Não sei se foi a melhor solução mas na epoca resolveu. Acontece que agora estou utilizando Spring nessa aplicação e não disponho do arquivo persistence.xml.
Bruno Santana
Curtidas 0
Respostas
Ronaldo Lanhellas
10/02/2015
Bom, para entender o seu problema você tem que entender como o JPA trabalha e isso não da pra explicar em alguns linhas, por isso vou resumir. Quando você "persist()" uma informação no banco ela fica em uma área chamada PersistenceContext (uma tipo de memória que armazena os beans).
Estes beans que estão no PersistenceContext ficam lá até que um comando de flush() seja dado para que o sincronismo com o banco seja feito. Quando esse "flush" é executado então os beans desta área de memória são realmente persistidos no banco.
O seu problema é que esse flush() ocorre de forma tardia, mas porque ?
- O flush pode ocorrer de várias formas mas vou citar 2:
1 - Quando você executa alguma consulta no banco de dados, ele faz o flush() dos objetos em memória;
2- VocÊ mesmo pode forçar o flush(), chamando-o explicitamente;
Minha dica, leia o material sobre PersistenceContext: https://www.devmedia.com.br/entendendo-o-java-persistencecontext-extended-e-transient/30493
Estes beans que estão no PersistenceContext ficam lá até que um comando de flush() seja dado para que o sincronismo com o banco seja feito. Quando esse "flush" é executado então os beans desta área de memória são realmente persistidos no banco.
O seu problema é que esse flush() ocorre de forma tardia, mas porque ?
- O flush pode ocorrer de várias formas mas vou citar 2:
1 - Quando você executa alguma consulta no banco de dados, ele faz o flush() dos objetos em memória;
2- VocÊ mesmo pode forçar o flush(), chamando-o explicitamente;
Minha dica, leia o material sobre PersistenceContext: https://www.devmedia.com.br/entendendo-o-java-persistencecontext-extended-e-transient/30493
GOSTEI 0
Bruno Santana
10/02/2015
Valeu Ronaldo pela explicação e pelo tópico, ja tinha em mente esses conceitos sobre JPA, fiz a alteração no meu código para realizar um flush ao adicionar o objeto, porém o problema continua ...
@Transactional(propagation=Propagation.SUPPORTS)
public abstract class DaoGenerico<T> implements DaoInterface<T> {
@Autowired
private SessionFactory sessionFactory;
public SessionFactory getSessionFactory() {return sessionFactory;}
public void setSessionFactory(SessionFactory sf) {sessionFactory = sf;}
protected Session getSession() {
return getSessionFactory().getCurrentSession();
}
@SuppressWarnings("rawtypes")
protected abstract Class getClazz();
public void adicionar(T objeto) {
getSession().saveOrUpdate(objeto);
getSession().flush();
}GOSTEI 0
Ronaldo Lanhellas
10/02/2015
Depois do "flush()" se você consultar a informação no banco ela está lá ?
GOSTEI 0
Ronaldo Lanhellas
10/02/2015
Depois do "flush()" se você consultar a informação no banco ela está lá ?
GOSTEI 0
Bruno Santana
10/02/2015
Então Ronaldo, as informações no banco de dados são atualizadas no mesmo segundo, com ou sem o flush após o meu saveOrUpdate... fiz um "teste" aqui colocando um sysout chamando a lista onde foi inserida antes e depois da chamada do adicionar...
E ela está atualizada logo após o adicionar, mesmo sem utilizar o flush() :S porém quando vou acessar outra página que puxa essa lista a mesma não está completa, o objeto recém adicionado não aparece, seja em algum componente tipo o autocomplete, datatable etc do primefaces...
Será que isso é algum problema fora do "escopo JPA" ?
E ela está atualizada logo após o adicionar, mesmo sem utilizar o flush() :S porém quando vou acessar outra página que puxa essa lista a mesma não está completa, o objeto recém adicionado não aparece, seja em algum componente tipo o autocomplete, datatable etc do primefaces...
Será que isso é algum problema fora do "escopo JPA" ?
GOSTEI 0
Bruno Santana
10/02/2015
Ronaldo consegui resolver aqui, mas nada relacionado a JPA, veja... eu populava as listas em meu bean através de um método anotado com @PostConstruct.. para assim não receber um null pointer exception em meu serviço...
em teoria toda vez que o bean é criado ele executa o @PostConstruct certo? meu bean está como @ViewScoped, então toda vez que faço uma navegação pra uma página que utiliza ele, ele é criado e assim popula as listas, estou enganado ? pois nao é esse o comportamento que acontece, consegui resolver o problema populando a lista fora do @postconstruct ao chamar o método do autocomplete...
veja abaixo
como pode ver, tive que popular novamente a lista em meu método completeModalidade... o problema foi resolvido, agora entidades recém adicionadas a partir de uma tela de cadastro aparecem no auto-complete, mas aí fica uma dúvida, estou fazendo algo errado? Qual a melhora maneira de popular lists em um bean.. Pois por exemplo, em alguma outra página posso ter um dataTable que use uma lista populada da mesma forma, como eu faria já que não teria um método "complete" para chamar a mesma
em teoria toda vez que o bean é criado ele executa o @PostConstruct certo? meu bean está como @ViewScoped, então toda vez que faço uma navegação pra uma página que utiliza ele, ele é criado e assim popula as listas, estou enganado ? pois nao é esse o comportamento que acontece, consegui resolver o problema populando a lista fora do @postconstruct ao chamar o método do autocomplete...
veja abaixo
@Named
@ViewScoped
public class MatriculaBean {
@Autowired
private ServicoModalidade servicoModalidade;
@Autowired
private ServicoPerfil servicoPerfil;
private List<Modalidade> modalidadesDisponiveis;
private List<Modalidade> modalidadesSelecionadas = new ArrayList<Modalidade>();
@PostConstruct
private void init(){
modalidadesDisponiveis = servicoModalidade.listarEager();
alunosDisponiveis = servicoPerfil.listar();
}
public List<Modalidade> completeModalidade(String busca) {
modalidadesDisponiveis = servicoModalidade.listarEager();
List<Modalidade> filtrados = new ArrayList<Modalidade>();
if(modalidadesSelecionadas == null){modalidadesSelecionadas = new ArrayList<Modalidade>();}
for (Modalidade mod : modalidadesDisponiveis) {
if (mod.getNome().contains(busca) && !modalidadesSelecionadas.contains(mod)) {
filtrados.add(mod);
}
}
return filtrados;
}
como pode ver, tive que popular novamente a lista em meu método completeModalidade... o problema foi resolvido, agora entidades recém adicionadas a partir de uma tela de cadastro aparecem no auto-complete, mas aí fica uma dúvida, estou fazendo algo errado? Qual a melhora maneira de popular lists em um bean.. Pois por exemplo, em alguma outra página posso ter um dataTable que use uma lista populada da mesma forma, como eu faria já que não teria um método "complete" para chamar a mesma
GOSTEI 0
Ronaldo Lanhellas
10/02/2015
Ronaldo consegui resolver aqui, mas nada relacionado a JPA, veja... eu populava as listas em meu bean através de um método anotado com @PostConstruct.. para assim não receber um null pointer exception em meu serviço...
em teoria toda vez que o bean é criado ele executa o @PostConstruct certo? meu bean está como @ViewScoped, então toda vez que faço uma navegação pra uma página que utiliza ele, ele é criado e assim popula as listas, estou enganado ? pois nao é esse o comportamento que acontece, consegui resolver o problema populando a lista fora do @postconstruct ao chamar o método do autocomplete...
veja abaixo
como pode ver, tive que popular novamente a lista em meu método completeModalidade... o problema foi resolvido, agora entidades recém adicionadas a partir de uma tela de cadastro aparecem no auto-complete, mas aí fica uma dúvida, estou fazendo algo errado? Qual a melhora maneira de popular lists em um bean.. Pois por exemplo, em alguma outra página posso ter um dataTable que use uma lista populada da mesma forma, como eu faria já que não teria um método "complete" para chamar a mesma
em teoria toda vez que o bean é criado ele executa o @PostConstruct certo? meu bean está como @ViewScoped, então toda vez que faço uma navegação pra uma página que utiliza ele, ele é criado e assim popula as listas, estou enganado ? pois nao é esse o comportamento que acontece, consegui resolver o problema populando a lista fora do @postconstruct ao chamar o método do autocomplete...
veja abaixo
@Named
@ViewScoped
public class MatriculaBean {
@Autowired
private ServicoModalidade servicoModalidade;
@Autowired
private ServicoPerfil servicoPerfil;
private List<Modalidade> modalidadesDisponiveis;
private List<Modalidade> modalidadesSelecionadas = new ArrayList<Modalidade>();
@PostConstruct
private void init(){
modalidadesDisponiveis = servicoModalidade.listarEager();
alunosDisponiveis = servicoPerfil.listar();
}
public List<Modalidade> completeModalidade(String busca) {
modalidadesDisponiveis = servicoModalidade.listarEager();
List<Modalidade> filtrados = new ArrayList<Modalidade>();
if(modalidadesSelecionadas == null){modalidadesSelecionadas = new ArrayList<Modalidade>();}
for (Modalidade mod : modalidadesDisponiveis) {
if (mod.getNome().contains(busca) && !modalidadesSelecionadas.contains(mod)) {
filtrados.add(mod);
}
}
return filtrados;
}
como pode ver, tive que popular novamente a lista em meu método completeModalidade... o problema foi resolvido, agora entidades recém adicionadas a partir de uma tela de cadastro aparecem no auto-complete, mas aí fica uma dúvida, estou fazendo algo errado? Qual a melhora maneira de popular lists em um bean.. Pois por exemplo, em alguma outra página posso ter um dataTable que use uma lista populada da mesma forma, como eu faria já que não teria um método "complete" para chamar a mesma
Seu racíocinio sobre o @PostConstruct está correto, ele é chamado (no caso do ViewScoped) toda vez que você acessa a página novamente. A sua solução é um "workaround" ao problema e não é uma boa prática, o correto mesmo é popular apenas 1 vez como você estava fazendo.
Vamos analisar seu problema: Pelo que entendi a variável 'modalidadesSelecionadas' é populada normalmente no @PostConstruct mas como você adicionou um novo item este ainda não aparece na variável 'modalidadesSelecionadas', sendo assim você precisa repopular através do método completeModalidade() que trás o novo item do banco de dados. Vejamos as possibilidades:
1 - O seu @PostConstruct está sendo chamado apenas 1 vez. Depois que você adiciona o novo item e tentar ver a lista o @PostConstruct já foi chamado e por isso o novo item não aparece. Se for isso você terá que checar se o ManagedBean que você está usando está de fato sendo "recarregado", caso contrário pode tentar mudar o escopo para @RequestedScope
GOSTEI 0
Bruno Santana
10/02/2015
Ronaldo consegui resolver, nem com @RequestScoped funcionava, o problema era que como estou utilizando o Spring para injeção, as anotações CDI em relação ao scopo não funcionavam direito, o bean tomava o escopo padrão do Spring que é o Singleton, assim sendo criado uma única vez...
Tive que alterar minhas anotações de escopo para @Scope... o problema agora é a limitação dessas anotações do Spring que não possuem a opção view... mas isso é outro problema...
créditos para Victor Kendy Harada que desevendou esse mistério em um outro forum
Valeu ai !!!
Tive que alterar minhas anotações de escopo para @Scope... o problema agora é a limitação dessas anotações do Spring que não possuem a opção view... mas isso é outro problema...
créditos para Victor Kendy Harada que desevendou esse mistério em um outro forum
Valeu ai !!!
GOSTEI 0
Ronaldo Lanhellas
10/02/2015
Certo, obrigado por compartilhar a solução.
GOSTEI 0