Atualização de objeto transiente

Estou brigando com esse problema alguns dias já.

Tenho duas classes persistentes conforme descritas abaixo, e mapeadas
para as tabelas correspondentes.

A classe Person possui os atributos “fieldA” e “fieldB” que identificam um registro em ‘persons’ exclusivamente. Mas estou tendo problemas com o identificador (id).

Se eu tenho um objeto Person transiente e quero salvá-lo no banco eu preciso antes verificar se já não existe um registro no banco cujos atributos
"fieldA" e “fieldB” coincidam. No caso de já existir eu preciso apenas atualizar aquele registro baseado na instância do objeto que eu recebo no argumento do método save(Person). Se não existir, um novo Person será salvo no banco de dados.

O comportamento que eu estou observando, com a implementação atual do método save(Person) é: ele atualiza o registro na tabela ‘persons’ corretamente mas replica os filhos (sons) na tabela ‘sons’. Não tenho idéia do que fazer para corrigir o problema.

Seguem as tabelas, mapeamentos (JPA) e a implementação do método save(Person).

[code]TABLE persons (
id INT …,
name VARCHAR(40),
a_field VARCHAR(10),
b_field VARCHAR(10)
)

TABLE sons (
id INT …,
person_id INT REFERENCES persons(id),
name VARCHAR(40)
)[/code]

Mapeamento:

[code]@Entity
@Table(name=“persons”)
class Person {

// …

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() {
return this.id;
}

@Column(name=“name”, length=40, nullable=false)
public String getName() {
return this.name;
}

@Column(name=“a_field”, length=10, nullable=false)
public String getFieldA() {
return this.fieldA;
}

@Column(name=“b_field”, length=10, nullable=false)
public String getFieldB() {
return this.fieldB;
}

@OneToMany(cascade=CascadeType.ALL, mappedBy=“person”)
public Set<Son> getSons() {
return this.sons;
}

}

@Entity
@Table(name=“sons”)
public class Son {

// …

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
public Long getId() {
return this.id;
}

@Column(name=“name”, length=40, nullable=false)
public String getName() {
return this.name;
}

@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name=“person_id”)
public Person getPerson() {
return this.person;
}

}[/code]

E a implementação do método save(Person):

[code]public void save(Person person) throws HibernateException {

Session session = …
session.beginTransaction();

Person aperson = (Person)session.createCriteria(Person.class)
.add(Restrictions.eq(“fieldA”, person.getFieldA()))
.add(Restrictions.eq(“fieldB”, person.getFieldB()))
.uniqueResult();

if (aperson != null) {
person.setId(aperson.getId());
session.evict(aperson);
}

session.saveOrUpdate(person);
session.getTransaction().commit();

}[/code]

Na verdade o título deveria ser “Atualização através de um objeto transiente”.

O argumento “person” passado para o método save() possui os fillhos populados? Se sim, estes filhos estão com a PK populada?

Só complementando o comportamento que você espera é que caso existam elementos populados em Sons ocorra um “sincronismo” ou seja apagar os diferentes, atualizar os com o mesmo ID e inserir os novos?

[]s

Rapaz!!! Será?
É, na prática sim… os IDs não estão definidos nos filhos. É que o objeto “Person” (já populado com os "Son"s) vem de uma fonte que não tem idéia de persistência. Por isso não tenho IDs. Na verdade eu quero mesmo um sincronismo (citação abaixo). Mas já funciona muito bem, se todos os "Son"s forem excluídos antes de os novos serem incluídos. Eu tentei isso usando um session.delete(aperson) e um session.evict(aperson) mas, claro, não funcionou.

Exato!
Conforme disse acima, se eu conseguir remover os "Son"s antes de atualizar o “Person” que contém outros "Son"s (ou os mesmos, semanticamente) já fica jóia.