Hibernate executa update assim que eu altero atributo [RESOLVIDO]

Senhores, estou aqui postando meu problema porque não consigo resolver nem com mandinga…
Antes de descrever o problema informo que eu tive que formatar o computador e antes da formatação funcionava que era uma beleza.

Eu faço a busca dos dados de um objeto e assim que altero sua propriedade, o hibernate executa um comando de alteração (update).

Segue abaixo os códigos

mysql_hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>  
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/sistemasaas</property>  
    
    <property name="hibernate.connection.username">root</property>  
    <property name="hibernate.connection.password"></property>    

    
    <property name="hibernate.show_sql">true</property> 
    
    <!-- Define o tamanho do cache para processamentos em lote -->
    <property name="hibernate.jdbc.batch_size">100</property>
    
    <!-- define que o método getCurrentSession retornará a sessão corrente -->
    <property name="hibernate.current_session_context_class">thread</property>
    
    <property name="hibernate.hbm2ddl.auto">create-update</property> 
    
    <property name="hibernate.c3p0.min_size">10</property>
	<property name="hibernate.c3p0.max_size">40</property>
 	<property name="hibernate.c3p0.timeout">1800</property>
	<property name="hibernate.c3p0.max_statements">100</property>    
    <!-- 
    	Mapeando as classes de entidade         
    -->
    <mapping class="apisaas.adesao.Adesao"/>    
    <mapping class="apisaas.administrador.Administrador"/>
    <mapping class="apisaas.cancelamento.Cancelamento"/>
    <mapping class="apisaas.endereco.Cidade"/>    
    <mapping class="apisaas.cliente.Cliente"/>
    <mapping class="apisaas.contrato.Contrato"/>
    <mapping class="apisaas.endereco.Estado"/>
    <mapping class="apisaas.modulo.Modulo"/>
    <mapping class="apisaas.pagamento.Pagamento"/>
    <mapping class="apisaas.parametro.ParametroAdmin"/>
    <mapping class="apisaas.parametro.ParametroGlobal"/>    
    <mapping class="apisaas.pessoa.Pessoa"/>    
    <mapping class="apisaas.politicadepreco.PoliticaDePreco"/>
    <mapping class="apisaas.tipodecliente.TipoDeCliente"/>
    
  </session-factory>
</hibernate-configuration>

Classe Adesao:

package apisaas.adesao;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.NotEmpty;

import apisaas.modulo.Modulo;

@Entity
@Table
@Inheritance(strategy=InheritanceType.JOINED)
public class Adesao implements Serializable {

	private static final long serialVersionUID = 858345758170078424L;

	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column	
	private int id;

	@Column(length=50, nullable=false, unique = true)
	@NotEmpty(message="Informe a descrição da adesão")
	@Size(min=1, max=50, message="A descrição da adesão deve possuir no mínimo {min} e no máximo {max} caracteres")
	private String descricao;

	@Column(nullable=false)
	@org.hibernate.annotations.Check(constraints="duracao > 0")
	@Min(value=1, message="A duração da adesão deve ter no mínimo um mês")
	private int duracao;    

	@Column(nullable=false)
	@NotNull(message="Informe se a adesão é grátis ou não")
	private boolean gratis;

	@Lob
	@Column(nullable=false)
	@NotEmpty(message="Informe o detalhamento da adesão")    
	private String detalhamento;

	@Column(nullable=false)
	@NotNull(message="Informe se a adesão está ativa ou não")
	private boolean ativo;

	@ManyToMany(fetch=FetchType.EAGER)
	@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SELECT)    
	@JoinTable(
			name = "AdesaoModulo",
			joinColumns = @JoinColumn(name = "adesao"),
			inverseJoinColumns = @JoinColumn(name = "modulo"),
			uniqueConstraints = @UniqueConstraint(columnNames = {"adesao", "modulo" })	
			)      
	@NotNull(message="Uma adesão deve estar associada a pelo menos um módulo")
	private List<Modulo> modulos;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public int getDuracao() {
		return duracao;
	}

	public void setDuracao(int duracao) {
		this.duracao = duracao;
	}

	public String getDescricao() {
		return descricao;
	}

	public void setDescricao(String descricao) {
		this.descricao = descricao;
	}

	public boolean isGratis() {
		return gratis;
	}

	public void setGratis(boolean gratis) {
		this.gratis = gratis;
	}

	public String getDetalhamento() {
		return detalhamento;
	}

	public void setDetalhamento(String detalhamento) {
		this.detalhamento = detalhamento;
	}

	public boolean isAtivo() {
		return ativo;
	}

	public void setAtivo(boolean ativo) {
		this.ativo = ativo;
	}

	public List<Modulo> getModulos() {
		return modulos;
	}

	public void setModulos(List<Modulo> modulos) {
		this.modulos = modulos;
	}	
	
	@Override
	public boolean equals(Object arg0) {

		if (this == arg0) {
			return true;
		}

		if (!(arg0 instanceof Adesao))
		{
			return false;	    
		}

		Adesao  planoInformado =(Adesao) arg0;

		return (planoInformado.getId() == this.id);

	}

	@Override
	public int hashCode() {
		return this.id == 0 ? System.identityHashCode(this) : this.id;
	}

}

Teste:

		try {
			
			HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
			
			AdesaoBusiness<Adesao> business = AdesaoFactory.criarAdesaoBusiness();
			
			Adesao adesao = business.buscar(1);
			
			adesao.setDescricao("teste2");
			
			HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
			
		} catch(Exception e) {
			HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
			e.printStackTrace();
		}

Observação: Assim que o teste executa o comando adesao.setDescricao(“teste2”); é executado um comando de update.
Creio que seja alguma configuração…

Como o objeto está no estado managed, qualquer alteração nesse estado e refletido como um update após um commit.
Se quiser fazer apenas a pesquisa.
Tirei isso:

HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();  

E isso:

HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();  

Deixando assim:

try {  
      
      
    AdesaoBusiness<Adesao> business = AdesaoFactory.criarAdesaoBusiness();  
      
    Adesao adesao = business.buscar(1);  
      
    adesao.setDescricao("teste2");  
      
      
} catch(Exception e) {  
    e.printStackTrace();  
}  

O objeto tem que está no estado detached para não refletir no banco.
testa ai e posta o resultado.

Assim como vc explicou funciona, mas a questão e um pouco mais complexa.

Após alterar a descrição, o sistema verifica se já existe um outro objeto no banco com a mesma descrição. Se existie, exibe mensagem de erro.
Toda a operação deve funcionar dentro de um contexto de transação uma vez que na implementação do sistema, eu vou criar um filtro pra controlar isso pra mim.

Estou colocando um teste que simula a realidade.

	public static void main(String[] args) {
		
		try {
			
			SessionFactory factory = HibernateUtil.getSessionFactory(); 
			
			factory.getCurrentSession().beginTransaction();
			
			IAdesaoDAO<Adesao> dao = AdesaoFactory.criarAdesaoDAO();

			// Aqui simulo a escolha da adesão que será alterada
			Adesao adesao = dao.buscar(1);
			// Aqui simulo a alteração do atributo na tela
			adesao.setDescricao("teste2");

                                               // Verifica se existe algum plano de adesão cadastrado com a mesma descrição mas com id diferente
                                               boolean descricaoCadastrada = dao.descricaoCadastrada(adesao);
                                               if (descricaoCadastrada) {
                                                   throw new Exception('Já existe um plano de adesão cadastrado com a descrição informada');
                                               }

			// Altera o objeto
			dao.salvar(adesao);
			
			factory.getCurrentSession().getTransaction().commit();
			
		} catch(Exception e) {
			HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
			e.printStackTrace();
		}
		
	}

A questão é que no momento da atribuição do novo valor (adesao.setDescricao(“teste2”):wink: o hibernate está disparando o update e como o atributo é unique dá erro.

Como faço para que esse update seja executado apenas no salvar?

Voce pode separar essas transações, assim em métodos por exemplo:

1 - metodo de pesquisa:
Esse metodo

SessionFactory factory = HibernateUtil.getSessionFactory();   
        IAdesaoDAO<Adesao> dao = AdesaoFactory.criarAdesaoDAO();  
  
        Adesao adesao = dao.buscar(1);  
        adesao.setDescricao("teste2"); 

       descricaoCadastrada(adesao);
                 
       salvar(adesao)

2 - metodo de descricaoCadastrada
Esse metodo recebe um obj do tipo Adesao

public boolean descricaoCadastrada(Adesao adesao) {
SessionFactory factory = HibernateUtil.getSessionFactory();   
        IAdesaoDAO<Adesao> dao = AdesaoFactory.criarAdesaoDAO();  
factory.getCurrentSession().beginTransaction();  
boolean descricaoCadastrada = dao.descricaoCadastrada(adesao);  
                                              if (descricaoCadastrada) {  
                                                  throw new Exception('Já existe um plano de adesão cadastrado com a descrição informada');  
                                              }  
factory.getCurrentSession().getTransaction().commit();  
return descricaoCadastrada
}

3 - metodo de salvar

public void salvar(Adesao adesao) {
SessionFactory factory = HibernateUtil.getSessionFactory();   
        IAdesaoDAO<Adesao> dao = AdesaoFactory.criarAdesaoDAO();  
factory.getCurrentSession().beginTransaction();  
dao.salvar(adesao); 
factory.getCurrentSession().getTransaction().commit();  
}

Bom o correto nas transações é algum framework gereciar pra vc.

Fiz o rascunho mais ou menos pra vc estender o que é, o resto é só implementar.
Não sei se vai rolar, não testei fiz na mão mesmo, mas a idéia é essa.
E para cada metodo lembre-se de fechar as SessionFactory, já que será aberta outras.

Espero que tenha entendido.

Abraços.

Fica com DEUS.

Bem, a idéia é válida, mas eu tenho uma péssima mania de esgotar as possibilidades.
O primeiro ponto que destaco é que a implementação com Filtro para controle de transação é amplamente utilizada.
O segundo ponto é que em um processo mais complexo que esse, eu teria que ter apenas uma transação mesmo. Por exemplo, tem um requisito do meu sistema que é a seguinte…Após a confirmação do primeiro pagamento, o contrato é ativado. Para implementar esse requisito eu preciso de algumas consultas e pelo menos 2 atualizações…e tudo isso tem que estar dentro da mesma transação.
O terceiro ponto que destaco é que esse problema só começou após a formatação do meu computador. Não sei explicar, uma vez que as configurações do projeto permanecem as mesmas.

Vou continuar pesquisando…

Senhores, consegui resolver parte do problema.

Seguinte…utilizei o comando HibernateUtil.getSessionFactory().getCurrentSession().setFlushMode(FlushMode.COMMIT); para que os comandos gerados pelo hibernate só fossem executados no commit. O comando foi executado antes de iniciar a transação.

Eu gostaria de entender uma coisa: Essa atribuição nunca fez parte do meu código e por default nenhum exemplo de persistência com hibernate utiliza esse comando…agora porque deu esse problema é que ainda é um mistério pra mim.

Não vou considerar o post como resolvido por um simples fato: Se eu não entender a solução pra mim não está resolvido.

Gostaria da ajuda de vcs.

Não sei se é concreto mais para toda atualização é necessário o método flush() até para caminhar no ciclo certo, evitar qualquer coisa fragmentada, ou em “cache”. Para a questão do atualizar pode também está acontecendo o seguinte: No seu dao pode ter sido declarado o método saveOrUpdate() e o que este método faz, se não existe o dado salva e faz um commit, caso já tenha o dado ele não salva, mas sim atualiza,por isso save or update, e ao realizar esta ação é necessário limpar qualquer fragmento, até por que às vezes passamos plea situação de que realizamos a ação de atualização e na view não é alterado, mas se apertar o F5 a alteração é vista. Entendeu?

Espero ter ajudado.

No DAO eu utilizo os métodos separadamente… se i id do objeto for 0, eu chamo save. Se o id for <> 0 eu chamo o update.
Ainda não consegui entender o que está acontecendo…o hibernate está executando um comando update a cada alteração de atributo ou pior em alguma consulta.
A minha exceção é gerada porque o atributo descrição é unique e assim que eu altero o valor, com a execução do update, estoura a exceção.

Sempre funcionou perfeitamente, mas agora está com esse problema…

Resumindo: Ainda não cheguei a nenhuma conclusão

Galera, depois de muito pesquisar eu continuo sem entender como meu sistema que estava OK passou a dar erro.
Mas entre mortos e feridos, eu pelo menos consegui resolver.

Fiz o seguinte:
Antes de iniciar a transação, eu atribui o valor FlushMode.MANUAL ao método setFlushMode da sessão en antes de executar o commit, eu chamei o comando flush.
Com essa codificação, o hibernate só executa o comando update no momento da execução do médodo flush.