Hibernate: Protegendo relacionamento bi-direcional [RESOLVIDO]

No tutorial do Hibernate ele explica como criar e proteger relacionamentos bi-direcionais. Só ficou a seguinte dúvida:

Pessoas tem eventos e Eventos tem pessoas.

    private Set participants = new HashSet();
    
    protected void setParticipants(Set participants) {
        this.participants = participants;
    }
    
    protected Set getParticipants() {
        return participants;
    }
    
    public void addParticipant(Person p) {
        participants.add(p);
        p.events.add(this); // é! isso funciona para protected...
    }
    
    public void delParticipant(Person p) {
        participants.remove(p);
        p.events.remove(this);
    }

Beleza. Não tem como ninguém modificar o set participants sem passar pelo add e pelo del.

Só que eu quero ter acesso ao Set para listar os participantes, isto é, quero ter um método publico getParticipants(). Só que aí a segurança é perdida, pois qualquer um poderia meter um participante no set depois de charmar getParticipants().

Soluções:

  1. No getParticipants() fazer return Collections.unmodifiableSet(participants); (NAO FUNCIONA NO HIBERNATE!)

Erro: Exception in thread “main” org.hibernate.AssertionFailure: collection was not processed by flush()

  1. No getParticipants() dar um clone no Set antes de retornar. Não testei isso, mas se a lista for muito grande isso deve ser uma bosta. Ainda mais que o Hibernate tá acessando essa função toda hora!

  2. Criar um outro método público e deixar o getParticipants() privado mesmo. Ex:

public Set getParticipantsSet() {
    return Collections.unmodifiableSet(participants);
}

Mas aí o nome vai está fora do padrão JavaBean e vou ter que fazer: user.participantsSet na camada view.

  1. Deixar aberto mesmo e confiar que ninguem vai adicionar um participante por fora.

Qual é a melhor solução: 1), 2), 3) ou 4) ???

Só encontrei um problema nesta abordagem!
Melhor, é uma crítica…

Suponha que vc cria uma uma classe que extenda ArrayList, chamada MeuArrayList… e tendo o seguinte código

MeuArrayList participantes = new MeuArrayList ();

public List getParticipantsSet() {
     return Collections.unmodifiableList(participants);
 }

Quando vc tentar fazer isso:

MeuArrayList participantes = (MeuArrayList ) meuObj.getParticipantesList();

Será lançado um ClassCastException!

Talvez nem precise criar o MeuArraYList… acho que se vc fazer qualquer tipo de cas com uma lista que não pode ser modificado sempre acontecerá este erro!

Abraços!
Thiago Senna

Pelo que entendi a desvantagem de usar unmodifiedSet é que ele sempre vai retornar um Set e nunca um MySet.

Beleza, valeu pela dica pois eu realmente nao sabia disso, mas a questão não é essa. :slight_smile:

A questão é como proteger o set ???

1), 2), 3) ou 4 ?

  1. Retorna apenas o Iterator dos participantes.

Thiago Senna

Boa idéia. Mas tem dois problemas que vc não pensou:

  1. Vou ter que mudar de qualquer jeito o nome da função, fugindo do padrão. (Ex: getParticipantsIterator())

  2. E principalmente iterator tem remove() ! :wink:

humm… entendi…

entaum eu ficaria com a opção 4!

Isso é uma opção do hibernate mesmo. Você tem que escolher de qual lado atualizar. Isso é para não violar as espectativas que temos dos pojos.

Eu tenho o habito de escrever unit tests e evitar usar qualquer API do hibernate nos meus POJOs. Usando apenas java.util não vou ter como garantir que as relações bidirecionais sejam preservadas sem escrever toneladas de código.

Bele, não faço isso e deixo meus pojos durante os testes sem isso. Quando eles estiverem sendo usados pelo hibernate, e tivessemos relacionamentos bidirecionais, ia dar um porrada de problemas.

Coloca access=“field” para esta propriedade e seja feliz.

:shock: :shock: :shock:

Fui feliz !

:lol:

Sérgio, só tome cuidado com qual lado da tua relação atualiza ela. E nunca coloque os dois para fazer isso.

Não estou usando hibernate, mas deve ser por isso que ontem meus testes unitários dos DAO’s pularam de 3 segundos para 96 segundos! :smiley:

Oi, louds, por que não?

valeuz…

bom… naum sei se te ajudaria jack, mas comigo foi assim…

empresa carregava servicço prestado, servicoprestado carregava empresa, empresa carrega servico prestado, servicoprestado carrega empresa, empresa carregava servicço prestado, servicoprestado carregava empresa, empresa carrega servico prestado, servicoprestado carrega empresa, empresa carregava servicço prestado, servicoprestado carregava empresa, empresa carrega servico prestado, servicoprestado carrega empresa, empresa carregava servicço prestado, servicoprestado carregava empresa, empresa carrega servico prestado, servicoprestado carrega empresa, empresa carregava servicço prestado, servicoprestado carregava empresa, empresa carrega servico prestado, servicoprestado carrega empresa, empresa carregava servicço prestado, servicoprestado carregava empresa, empresa carrega servico prestado, servicoprestado carrega empresa.

É isso né???

Não thiago, isso é por que você não está usando first level caching :wink:

O problema é o seguinte:

A.getBs().add(B);
B.getAs().add(A);

Isso vai gerar:

//atualizando relacionamentos de A
insert into AxB(A.id, B.id)
//atualizando relacionamentos de B
insert into AxB(A.id, B.id)

Estou inserindo em ambas as listas em ambos os lados, obviamente sem colocar a coisa num loop infinito.

Não devo fazer isso ???

No tutorial do hibernate ele diz que não há problema nisso.

???

Não tem problema sergio, se você seguir as recomendações anteriores do tutorial, e vc seguiu pelo visto. Ele não fala para nunca colocar os dois lados de um relacionamento cuidando de atualizá-lo?

Valeu pelas dicas Louds!

Por um momento pensei que ele fizesse dois inserts na tabela N-N.

Veja o que o tutorial fala:

Do link: http://www.hibernate.org/hib_docs/v3/reference/en/html/tutorial.html