[RESOLVIDO]Hibernate: definindo uma foreign key nula

Bom, a situação é a seguinte:

Tenho uma tabela Pessoa, que tem uma foreign key da tabela Grupo.
Uma Pessoa pode pertencer à 1 Grupo, e 1 Grupo pode ter N Pessoas.

No banco de dados, defini que a FK “id_grupo” não é NOT NULL, portanto, era
para aceitar um null.
Tudo funciona bunitinho quando adiciono esse Grupo no cadastro da Pessoa.

O problema nasce quando eu tento adicionar uma pessoa sem definir um grupo para ela, eu recebo um erro
dizendo que devo persistir o objeto Grupo antes de referenciá-lo. Mas, eu NÃO quero referenciá-lo, uma vez que
ele é nulo.

O stacktrace é o seguinte:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: br.com.jm.beans.Eleitor org.hibernate.engine.internal.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:249) org.hibernate.type.EntityType.getIdentifier(EntityType.java:456) org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:276) org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:286) org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:294) org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:3820) org.hibernate.event.internal.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:536) org.hibernate.event.internal.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:243) org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:172) org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225) org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99) org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51) org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1127) org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:325) org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101) org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175) br.com.jm.dao.DemandaDAO.insert(DemandaDAO.java:28) br.com.jm.factory.DemandaFactory.executeInsert(DemandaFactory.java:30) br.com.jm.actions.DemandaAction.incluirDemanda(DemandaAction.java:129) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) java.lang.reflect.Method.invoke(Unknown Source) com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:453) com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:292) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:255) org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:256) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:176) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:265) org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:68) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:211) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:211) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:190) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:75) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:90) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:243) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:100) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:141) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:145) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:176) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:192) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:187) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54) org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:510) org.apache.struts2.dispatcher.FilterDispatcher.doFilter(FilterDispatcher.java:434)

Já procurei em inúmeros fóruns, e o pessoal insiste em me aconselhar à usar um Cascade, mas isso não faz o menor sentido!
Eu não quero que ele insira no banco o objeto Grupo quando ele for nulo!!!

Será que alguem poderia me dar uma luz??

Creio que o problema esteja na forma como você fez o mapeamento das entidades.
Quando analisamos apenas o plano relacional e a tabela A possui uma FK da tabela B, esta coluna (id_b) é, por padrão, nullable.
Agora, o hibernate tem alguns detalhes meio sórdidos, chatos mesmo. Principalmente em relações bidirecionais (como é o que parece ser esta tua situação).

Poste as classes e os respectivos mapeamentos (caso não seja annotation).

Como está sua Entity?
Como estão as annotations?

Aqui vão as classes:

Classe Eleitor:

package br.com.jm.beans;

import java.io.Serializable;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
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.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;


@Entity
@Table(name="eleitor")
public class Eleitor implements Serializable{
	private static final long serialVersionUID = -6226011868072777381L;
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name="id_eleitor")
	private Integer idEleitor;
	@ManyToOne(fetch=FetchType.EAGER)
	@JoinColumn(name="id_cidade")
	private Cidade cidade;
	@ManyToOne(fetch=FetchType.EAGER)
	@JoinColumn(name="id_grupo")
	private Grupo grupo;
	@Column(name="el_nome")
	private String nome;
	@Column(name="el_apelido")
	private String apelido;
	@Column(name="el_sexo")
	private String sexo;
	@Column(name="el_data_nasc")
	@Temporal(TemporalType.DATE)
	private Date dataNasc;
	
        //INFINIDADE DE VARIÁVEIS

	@ManyToMany(cascade={CascadeType.MERGE,CascadeType.REMOVE},fetch=FetchType.LAZY)
	@JoinTable(name="cursos_eleitor",
	joinColumns = @JoinColumn(name="id_eleitor", referencedColumnName="id_eleitor"),
	inverseJoinColumns = @JoinColumn(name="id_curso", referencedColumnName="id_curso"))
	private List<Curso> listaCursos = new ArrayList<Curso>();
	@OneToMany(mappedBy="eleitor", cascade={CascadeType.MERGE,CascadeType.REMOVE},fetch=FetchType.LAZY)
	private List<CampanhasEleitor> listaCampanhasEleitor = new ArrayList<CampanhasEleitor>();
	@OneToMany(mappedBy="eleitor", cascade={CascadeType.MERGE,CascadeType.REMOVE},fetch=FetchType.LAZY)
	private List<Experiencia> listaExperiencias = new ArrayList<Experiencia>();
	@OneToMany(mappedBy="eleitor", cascade={CascadeType.MERGE,CascadeType.REMOVE},fetch=FetchType.LAZY)
	private List<Demanda> listaDemandas = new ArrayList<Demanda>();
	@Transient
	private DateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
	@Transient
	private DecimalFormat df = new DecimalFormat();
	@Transient
	private String dia;
	@Transient
	private String mes;
	@Transient
	private String ano;
	
	//------------------------Getters e Setters--------------------------------
}

Classe Grupo:

package br.com.jm.beans;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "grupo")
public class Grupo implements Serializable {
	private static final long serialVersionUID = -7112823581533993573L;
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name = "id_grupo")
	private Integer idGrupo;
	@Column(name = "gp_nome")
	private String nome;
	
	public Grupo(){
		
	}

	//GETTERS E SETTERS
}

O mesmo acontece com outra classe que tem relacionamento com Eleitor, a classe Demanda.
Nesta classe, é o Eleitor que deveria ser nulo dentro da Demanda, porém, recebo o mesmo stacktrace
ao tentar gravar uma Demanda com uma FK de Eleitor sendo nula.

Classe Demanda:

package br.com.jm.beans;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
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.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

@Entity
@Table(name="demanda")
public class Demanda implements Serializable{
	private static final long serialVersionUID = 4959562413119839409L;
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name="id_demanda")
	private Integer idDemanda;
	@ManyToOne(fetch=FetchType.EAGER)
	@JoinColumn(name="id_eleitor", nullable=true)
	private Eleitor eleitor;
	@Column(name="dm_solic")
	private String solic;
	@Column(name="dm_obs")
	private String obs;
	@Column(name="dm_status")
	private String status;
	@OneToMany(mappedBy="demanda", fetch=FetchType.EAGER, cascade={CascadeType.DETACH,CascadeType.REMOVE})
	private List<Ocorrencia> listaOcorrencias;
	@Transient
	private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
	@Transient
	private Date periodoIni;
	@Transient
	private Date periodoFin;
	
	//GETTERS E SETTERS
}

Dei uma olhada no meu banco, e na tabela Eleitor, a chave extrangeira referente ao Grupo estava com cascade definido como “RESTRICT”.
Fiz a alteração e vou testar agora, mas não creio que isso irá surtir efeito, uma vez que na tabela Demanda, o cascade está como "NO ACTION"
e ainda assim tenho o mesmo resultado.

Então, consegui solucionar o problema!

Com a ajuda de um programador de outro fórum, descobri que o objeto agregado, o qual estava sendo acusado no erro, estava sendo passado
como um objeto Grupo de atributos nulos. Sendo assim, o Hibernate procurava por esse objeto de atributos nulos no banco, mas não encontrava (lógicamente).

Com isso, coloquei a seguinte linha de código:

if(ele.getGrupo()!=null && ele.getGrupo().getIdGrupo()==null && (ele.getGrupo().getNome()==null || ele.getGrupo().getNome().equals("")))
	ele.setGrupo(null);
edao.insert(ele);

Isso faz com que: caso o Java verifique que o objeto está com atributos nulos, então ele irá tornar o objeto nulo. Desta forma, o Hibernate entende que o objeto é nulo
e não deve procurar por ele no banco.

Esse programador que me ajudou, me disse que eu poderia ter dado um “new” nesse objeto em algum momento da aplicação, mas eu procurei e não encontrei.

Mas, o problema foi solucionado com essa linha de código aí.
Creio não ser o jeito certo, mas pra quem está com pressa, pode ser uma solução!