na minha aplicação web, o usuário se loga e é criado um DaoFactory (a session). eu consigo listar e recuperar o registro na boa, mas quando tento alterar o registro (update):
NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [br.edu.unijales.sga.modelo.Cidade#5]
e quando tento incluir um novo (save):
identifier of an instance of br.edu.unijales.sga.modelo.Cidade was altered from 6 to null
“I called saveOrUpdate() or update() and got a NonUniqueObjectException!”
"The session maintains a unique mapping between persistent identity and object instance, in order to help you avoid data aliasing problems. You cannot simultaneously attach two objects with the same class and identifier to the same session.
The best way to avoid any possibility of this exception is to always call saveOrUpdate() at the beginning of the transaction, when the session is empty.
In Hibernate3, you can try using the merge() operation, which does not reattach the passed instance."
guilherme, eu jah havia lido essa parte da documentação. eu alterei o update e o save por merge, funcionou!
mas eu postei aqui porque achei estranho… pode me explicar melhor?
“The session maintains a unique mapping between persistent identity and object instance, in order to help you avoid data aliasing problems. You cannot simultaneously attach two objects with the same class and identifier to the same session.”
Isso significa que, para evitar que você faça besteira, o Hibernate não permite que você coloque dois objetos iguais na mesma Session, o que faz bastante sentido.
Isso tem a ver com o tipo de aplicação que você está programando e com a sua Session Factory. Possivelmente você está utilizando uma mesma instância de Session para várias threads, o que causaria este problema.
Fale um pouco mais sobre a sua aplicação (web, desktop) e coloca aqui o código da sua SessionFactory para que possamos nos aprofundar mais no seu caso.
eh o seguinte, eu tenho uma aplicação web (VRaptor).
nela tenho um interceptor, e toda lógica eh interceptada.
public class InterceptorAuthorization implements Interceptor {
@In(scope=ScopeType.SESSION, create=true)
private Usuario usuario;
@Out(scope=ScopeType.SESSION)
private DaoFactory factory = new DaoFactory();
public void intercept(LogicFlow flow) throws LogicException, ViewException {
if (usuario.getNome() == null) {
throw new LogicException(new InvalidLoginException(
"Vc não está logado... <a href='login.formulario.logic'>efetue o Login</a>"));
}
flow.execute();
}
}
as persistências funcionavam desta maneira, mas logo eu recebia uma exception pq lotava a heap jvm.
então decidi colocar o new DaoFactory() apenas quando o usuário faz o login no sistema, e esta sessão seria usada enquando o usuário estivesse conectado.
minha DaoFactory eh basicamente isto:
public class GenericDaoFactory {
protected HibernateSessionFactory hsf;
protected Session session;
private Transaction transaction;
public GenericDaoFactory(Usuario usuario) {
hsf = new HibernateSessionFactory(usuario);
session = hsf.getSession();
}
public Session getSession() {
return session;
}
public void beginTransaction() {
this.transaction = session.beginTransaction();
}
public void commit() {
this.transaction.commit();
this.transaction = null;
}
public void rollback() {
this.transaction.rollback();
this.transaction = null;
}
public void close() {
session.close();
}
public boolean hasTransaction() {
return this.transaction != null;
}
public <T> Dao<T> getDao(Class<T> clazz) {
return new Dao<T>(this.session, clazz);
}
}
Cara, você não deveria deixar a Session tanto tempo aberta. Procure por implementações de SessionFactory usando o pattern de ThreadLocal, como esse aqui: http://www.hibernate.org/207.html
Isso vai evistar que você tenha vários problemas e vai te poupar de um overhead grande.
Pegando um gancho no tópico… Desenvolvo uma aplicação swing e a única forma que encontrei para poder preencher um JTable com dados de vários objetos, foi deixar a sessão aberta enquanto este JTable estivesse sendo utilizado. Deixem eu tentar explicar… Eu tenho as classes:
public class Cliente {
private String nome;
private Uf uf;
...
}
public class Uf {
private String nome;
...
}
Então meu TableDataModel que é responsável por preencher meu JTable, quanto vai procurar pela unidade de federação para mostrar, utiliza um clientes.get(row).getUf(), onde clientes é uma coleção de clientes.
Se fecho a sessão logo após retornar uma coleção de clientes, logo o TableDataModel lança uma exceção do tipo Lazy Initialization porque estou tentando acessar a Uf com a sessão fechada.
Destes casos, tenho vários outros no software, pedido em relação a cliente, item de pedido em relação a pedido, pagamento em relação a pedido e por aí vai.
Aí foi que enfrentei o problema de não ter minhas atualizações efetuadas pela aplicação prontamente disponíveis para a mesma aplicação rodando em outros computadores em rede. Foi então que me orientaram aqui no Guj a fechar sempre a sessão após as atualizações, mas não posso fazê-lo, pois senão meu objetos em ArrayList(collections) passam a apresentar as Lazy Initialization Exception.
Pegando um gancho no tópico… Desenvolvo uma aplicação swing e a única forma que encontrei para poder preencher um JTable com dados de vários objetos, foi deixar a sessão aberta enquanto este JTable estivesse sendo utilizado. Deixem eu tentar explicar… Eu tenho as classes:
public class Cliente {
private String nome;
private Uf uf;
...
}
public class Uf {
private String nome;
...
}
Então meu TableDataModel que é responsável por preencher meu JTable, quanto vai procurar pela unidade de federação para mostrar, utiliza um clientes.get(row).getUf(), onde clientes é uma coleção de clientes.
Se fecho a sessão logo após retornar uma coleção de clientes, logo o TableDataModel lança uma exceção do tipo Lazy Initialization porque estou tentando acessar a Uf com a sessão fechada.
Destes casos, tenho vários outros no software, pedido em relação a cliente, item de pedido em relação a pedido, pagamento em relação a pedido e por aí vai.
Aí foi que enfrentei o problema de não ter minhas atualizações efetuadas pela aplicação prontamente disponíveis para a mesma aplicação rodando em outros computadores em rede. Foi então que me orientaram aqui no Guj a fechar sempre a sessão após as atualizações, mas não posso fazê-lo, pois senão meu objetos em ArrayList(collections) passam a apresentar as Lazy Initialization Exception.
Desabilita o lazy load, assim todas as “dependências” do seu objetos serão carregadas logo no primeiro momento que você buscar o objeto na Session, eliminando a possibilidade de você tomar esta exception
Já tive esse problema. No entanto eu uso o Hibernate, com a implementação do synchronized, que permite que na session tenha um metodo refresh. Não sei se na forma como tu usas tem. mas procura no objecto sesion esse metodo refresh e passa o objeto que tu gostaria de mostrar depois de uma pesquisa. E ai pode crer que todos os computadores estarão com os dados consistentes. Espero que ajude
esqueci de dizer que desabilitar o lazy load não é aconselhavel. Apenas chama o refresh. Da forma como tu usas hibernate (sem implementação do Synchronized) Talvez o metodo refresh esteja na SessionFactory ou DAO.Factory, ai tu tens que passar o objeto que tu pesquisou e passar a sessão daquele objeto. Nada de limpar sessão ou fecha-la. Beleza.
atualização. mas no teu caso facilitaria a tua vida e a minha se tu usasse o HibernateSynchronized, que geraria as classes DAO e os metodos de persistencia apropriados. Mas imagino que possa haver algo parecido pra ti. Vamos tentar por partes. Eu não tenho nenhum projeto aqui agora (mas assim que pegar o meu computador prometo olhar). Quando tu criar uma sessão para o objeto Usuario, por exemplo, evita fazer o new HibernateSessionFactoy dentro do metodo, pois assim tu vai criar mais de uma sessão, e é ai que mora o problema.
Tu tem que ter apenas uma sessão no programa inteiro para cada objeto. senão ele diz que tu tenta associao o objeto a mais de uma sessão! Sacou? Tu tens que fazer um singleton do objeto session, de forma a ter uma unica instancia. Vê se não tem uma classe SessionFactory (na implementação do synchronized tem), com um metodo static getSession(Object), ele te ajudaria a ter uma unica sessão, pois sempre retorna a mesma sessão. Vê se tem esse metdos!! Apesar de usar o hibernate com uma implementação que me abstrai esses problemas, acho que dá prazer do teu jeito!!! mas vou dar uma olhada!!!
Ah!!! Não esquei, tem que ter um unica sessão, e reabilita o lazy.
Ah! o metodo refresh é pra ajudar no problema do cara com problemas de atualização!!! Perdão pela falta de clareza!!! Mas no caso na sessão, manter uma unica instancia deve resolve o problema.
Qual o problema? A única coisa que vai acontecer é que você trará mais coisas do banco, só isso.
Outra coisa, essa implementação com synchronized pode trazer problemas de performance para a aplicação dependendo da quantidade de acessos ao banco de dados…
Vamos por partes. Desabilitar lazy loading deve ser evitado ao máximo. Usar somente eager é inviável em 99,9% dos casos.
O singleton não é do Session, e sim um HibernateUtil da vida que vai gerenciar sua SessionFactory e suas Sessions (sim, você vai ter mais de uma Session por aplicação).
Errado, você deve escolher uma forma de usar a sessão dentre as várias recomendadas(ou criar uma mais apropriada pra sua aplicação), mas essa definitivamente não é uma opção.
Não, isso vai criar mais problemas do que resolver.
Antes de continuarem respondendo alguma coisa, recomendo que todos leiam Sessions and transactions no wiki do hibernate.