I am trying to update a entity using JBoss Seam and JPA, but I am facing the very common problem of Detached Entity. I will explain better.
I have a two simple entity that are related. This relations are mapped with annotations. Take a look:
@Entity
@Table(name = "RATEIO", uniqueConstraints = { @UniqueConstraint(columnNames = { "cd_projeto", "cd_tarefa", "cd_colaborador", "dt_rateio_inicial",
"dt_rateio_final" }) })
@Audited
public class Rateio implements java.io.Serializable {
private static final long serialVersionUID = 1564844894403478898L;
private Long cdRateio;
private Colaborador colaborador;
private Tarefa tarefa;
private Projeto projeto;
private Date dtRateioInicial;
private Date dtRateioFinal;
private boolean flagAtividade;
private TipoRateio tipo;
private List<DetalheRateio> detalheRateios = new ArrayList<DetalheRateio>(0);
public Rateio() {
}
public Rateio(Long cdRateio, Colaborador colaborador, Projeto projeto, Date dtRateioInicial, Date dtRateioFinal) {
this.cdRateio = cdRateio;
this.colaborador = colaborador;
this.projeto = projeto;
this.dtRateioInicial = dtRateioInicial;
this.dtRateioFinal = dtRateioFinal;
}
public Rateio(Long cdRateio, Colaborador colaborador, Projeto projeto, Date dtRateioInicial, Date dtRateioFinal, List<DetalheRateio> detalheRateios) {
this.cdRateio = cdRateio;
this.colaborador = colaborador;
this.projeto = projeto;
this.dtRateioInicial = dtRateioInicial;
this.dtRateioFinal = dtRateioFinal;
this.detalheRateios = detalheRateios;
}
@Id
@SequenceGenerator(name = "sg_RATEIO_SEQ", sequenceName = "RATEIO_SEQ", allocationSize = 1)
@GeneratedValue(generator = "sg_RATEIO_SEQ")
@Column(name = "CD_RATEIO", nullable = false, scale = 0)
public Long getCdRateio() {
return this.cdRateio;
}
public void setCdRateio(Long cdRateio) {
this.cdRateio = cdRateio;
}
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "CD_COLABORADOR", nullable = true)
public Colaborador getColaborador() {
return this.colaborador;
}
public void setColaborador(Colaborador colaborador) {
this.colaborador = colaborador;
}
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "CD_PROJETO", nullable = true)
public Projeto getProjeto() {
return this.projeto;
}
public void setProjeto(Projeto projeto) {
this.projeto = projeto;
}
@Column(name = "DT_RATEIO_INICIAL", nullable = false)
public Date getDtRateioInicial() {
return this.dtRateioInicial;
}
public void setDtRateioInicial(Date dtRateioInicial) {
this.dtRateioInicial = dtRateioInicial;
}
@Column(name = "DT_RATEIO_FINAL", nullable = true)
public Date getDtRateioFinal() {
return this.dtRateioFinal;
}
public void setDtRateioFinal(Date dtRateioFinal) {
this.dtRateioFinal = dtRateioFinal;
}
@OneToMany(targetEntity=DetalheRateio.class, mappedBy = "rateio")
public List<DetalheRateio> getDetalheRateios() {
return this.detalheRateios;
}
public void setDetalheRateios(List<DetalheRateio> detalheRateios) {
this.detalheRateios = detalheRateios;
}
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "CD_TAREFA", nullable = true)
public Tarefa getTarefa() {
return tarefa;
}
public void setTarefa(Tarefa tarefa) {
this.tarefa = tarefa;
}
@Enumerated(EnumType.ORDINAL)
public TipoRateio getTipo() {
return tipo;
}
public void setTipo(TipoRateio tipo) {
this.tipo = tipo;
}
@Transient
public boolean isFlagAtividade() {
return flagAtividade;
}
public void setFlagAtividade(boolean flagAtividade) {
this.flagAtividade = flagAtividade;
}
}
And my second entity:
@Entity
@Table(name = "DETALHE_RATEIO")
@Audited
public class DetalheRateio implements Serializable {
private static final long serialVersionUID = 4109721744851677683L;
private Long cdDetalheRateio;
private Rateio rateio;
private AtividadeOs atividadeOs;
private float vlPorcentagem;
public DetalheRateio() {
}
public DetalheRateio(Long cdDetalheRateio, Rateio rateio, AtividadeOs atividadeOs, float vlPorcentagem) {
this.cdDetalheRateio = cdDetalheRateio;
this.rateio = rateio;
this.atividadeOs = atividadeOs;
this.vlPorcentagem = vlPorcentagem;
}
@Id
@SequenceGenerator(name = "sg_DET_RATEIO_SEQ", sequenceName = "DET_RATEIO_SEQ", allocationSize = 1)
@GeneratedValue(generator = "sg_DET_RATEIO_SEQ")
@Column(name = "CD_DETALHE_RATEIO", nullable = false, scale = 0)
public Long getCdDetalheRateio() {
return this.cdDetalheRateio;
}
public void setCdDetalheRateio(Long cdDetalheRateio) {
this.cdDetalheRateio = cdDetalheRateio;
}
@ManyToOne(targetEntity = Rateio.class, cascade=CascadeType.ALL)
@JoinColumn(name = "CD_RATEIO", nullable = false)
public Rateio getRateio() {
return this.rateio;
}
public void setRateio(Rateio rateio) {
this.rateio = rateio;
}
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CD_ATIVIDADE_OS", nullable = false)
public AtividadeOs getAtividadeOs() {
return this.atividadeOs;
}
public void setAtividadeOs(AtividadeOs atividadeOs) {
this.atividadeOs = atividadeOs;
}
@Column(name = "VL_PORCENTAGEM", nullable = false, precision = 19, scale = 2, columnDefinition = "NUMBER")
public float getVlPorcentagem() {
return this.vlPorcentagem;
}
public void setVlPorcentagem(float vlPorcentagem) {
this.vlPorcentagem = vlPorcentagem;
}
}
OK, so far, so good. Now, I have a class who extends EntityHome from JBoss Seam, and is this class who tries to update my entity. Here is the code:
@Name("rateioHome")
public class RateioHome extends EntityHome<Rateio> {
@SuppressWarnings("unchecked")
@Override
@Transactional
public String update() {
String result = null;
boolean valid = true;
if (getPercentualTotal().doubleValue() < 100 || instance.getDetalheRateios().size() < 2) {
facesMessages.addFromResourceBundle(Severity.ERROR, "rateio.erro.atividade");
valid = false;
}
if (instance.getDtRateioFinal() != null && instance.getDtRateioFinal().before(instance.getDtRateioInicial())) {
facesMessages.addFromResourceBundle(Severity.ERROR, "rateio.erro.data");
valid = false;
}
if (valid) {
Rateio rateio = super.find();
List<DetalheRateio> detalheRateios = getInstance().getDetalheRateios();
List<DetalheRateio> detalheRateiosBd = rateio.getDetalheRateios();
Map<Long, DetalheRateio> mapDetalheRateio = new HashMap<Long, DetalheRateio>();
if (detalheRateiosBd != null && detalheRateiosBd.size() > 0) {
Rateio _rateio = getInstance();
for (DetalheRateio item : detalheRateiosBd) {
if (item.getCdDetalheRateio() != null) {
item.getRateio().setDtRateioInicial(_rateio.getDtRateioInicial());
item.getRateio().setDtRateioFinal(_rateio.getDtRateioFinal());
mapDetalheRateio.put(item.getCdDetalheRateio(), item);
}
}
}
List<DetalheRateio> novos = (List<DetalheRateio>) CollectionUtils.subtract(detalheRateios, detalheRateiosBd);
List<DetalheRateio> excluidos = (List<DetalheRateio>) CollectionUtils.subtract(detalheRateiosBd, detalheRateios);
detalheRateiosBd.removeAll(excluidos);
detalheRateiosBd.addAll(novos);
setInstance(rateio);
result = super.update();
getEntityManager().flush();
}
return result;
}
}
Now, the problem. When I tr to update the entity on line: result = super.update();
, I am facing this exception:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: br.com.entity.DetalheRateio
I tried to find a the method merge
on EntityHome, but I have not found anything. This is a legacy code that I have to support and I am no expert on JBoss Seam. If you need more information, please, let me know.
Just found the solution. The list of object DetalheRateio
must be merged (persisted) before update the object Rateio
. I tought that the object DetalheRateio as a Rateio
child will be persisted "automatically by JPA. My mistake. So, I merged manually each DetalheRateio
object. The snipet that I used to do this:
List<DetalheRateio> novos = (List<DetalheRateio>) CollectionUtils.subtract(detalheRateios, detalheRateiosBd);
List<DetalheRateio> excluidos = (List<DetalheRateio>) CollectionUtils.subtract(detalheRateiosBd, detalheRateios);
List<DetalheRateio> detalhesAlterados = new ArrayList<DetalheRateio>();
detalheRateiosBd.removeAll(excluidos);
detalheRateiosBd.addAll(novos);
for (DetalheRateio detalheExcluido : excluidos) {
getEntityManager().remove(detalheExcluido);
}
for (DetalheRateio dRateio : detalheRateios) {
detalhesAlterados.add(getEntityManager().merge(dRateio));
}
rateio.setDetalheRateios(detalhesAlterados);
setInstance(rateio);
result = super.update();
getEntityManager().flush();