[RESOLVIDO] Relacionamento @ManyToMany: detached entity passed to persist

Olá pessoal

deixa eu explicar meu problema: em um sistema que estou fazendo, um usuário (User) pode ter vários privilégios (Role), um privilégio pode estar associado a vários usuários. Ou seja, um relacionamento many-to-many unidirecional.

Imagens valem mais que palavras então… é disso que estou falando:

Quando eu adiciono um objeto Role, tudo funciona normalmente, mas na hora de cadastrar um usuário, obtenho o seguinte erro:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.klimp.entities.Role

Já segui um tutorial (http://uaihebert.com/?p=52) e estou desconfiando que seja alguma coisa relacionada ao atributo “mappedBy”.

Outras referências onde pesquisei:
https://forum.hibernate.org/viewtopic.php?f=9&t=970398
http://www.guj.com.br/java/127260-resolvido-manytomany-problemas-com-relacionamento-jpa
http://www.guj.com.br/java/85186-relacionamento-manytomany-hibernate

Classe “Role”

@Entity
@Table(name="roles",uniqueConstraints=@UniqueConstraint(columnNames="code"))
public class Role implements Serializable {
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    protected int identifier;
    
    protected String code;
    
    protected String summary;
   
   ...

}

Classe User:

@Entity
@Table(name = "users")
public class User implements Serializable {
    
    public static final int PASSWORD_MIN_LENGTH = 6;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected int userID;
    
    protected String userName;
    
    @Column(length=128)
    protected String password;
    
    protected String name;
    
    protected String lastName;
    
    @Temporal(TemporalType.DATE)
    @Column(nullable = true)
    protected Date accountExpirationDate;
    
    @ManyToMany(
            fetch = FetchType.EAGER,
            targetEntity = Role.class,
            cascade=CascadeType.ALL)
    protected List<Role> roles;

   ...

}

Qualquer ajuda será muito bem vinda!

Obrigado!

Faça um find ou um getReference em com.klimp.entities.Role antes de adicioná-lo a um relacionamento.

aqui mostra como utilizar o getRefernce que ajuda no desempenho: JPA Consultas e Dicas.

jakefrog

Quando a tela é criada eu faço um find() para popular a lista de privilégios.

De qualquer maneira, vou dar uma lida no link que você mandou.

Abraço!

[quote=marcos.9306]jakefrog

Quando a tela é criada eu faço um find() para popular a lista de privilégios.

De qualquer maneira, vou dar uma lida no link que você mandou.

Abraço![/quote]Você faz o find antes de salvar? Ou antes d enviar pra tela do usuário?
Se for antes de enviar para a tela do usuário não irá fazer diferença, pois o objeto se torna detached quando a transação é finalizada (exceção é Statefull EJB).

Na hora de popular a tela.

Mas porque exatamente eu teria que dar o find() antes de salvar?

A consulta que eu faço para popular a tela me trás objetos com o id da Role carregado :?

O código para submeter as alterações é esse aqui:

public class UserDAO extends DataAccessObject<User> {

    public void save(User user) {
        createTransactionAndBegin();
        entityManager.persist(user);
        commitTransaction();
    }
   
   ...
}

Isso é conceito do JPA/HIbernate.

Uma transação para ser finalizada com sucesso, todo objeto que for salvo/alterado/excluído tem que estar “atualizado” na transação.

Esse é o conceito de attached. Dettached é quando um objeto não está atualizado na transação.

Esse livro é muito bom: http://www.amazon.com/Pro-JPA-Mastering-Persistence-Technology/dp/1430219564/ref=sr_1_1?ie=UTF8&qid=1342208712&sr=8-1&keywords=jpa+2+pro

Você pode achar no google explicações mais detalhadas sobre esse assunto. [=

Esse código abaixo funcionou, mas haveria outra solução mais elegante (ou menos feia) que essa?

    public void save(User user) {
        createTransactionAndBegin();
        
        ArrayList<Role> attachedRoles = new ArrayList<>();
        
        for(Role role: user.getRoles()) {
            Role attached = entityManager.find(Role.class, role.getIdentifier());
            attachedRoles.add(attached);
        }
        user.setRoles(attachedRoles);
        
        entityManager.persist(user);
        commitTransaction();
    }

Eu gosto de usar o getReference.

Mas aí fica a gosto do cliente. [=

Com getReference() também funciona perfeitamente.

Pelo o que eu li, o getReference evita a consulta ao banco de dados, usando a cache do próprio JPA/Hibernate.

Então pelo jeito, é a melhor solução mesmo…

Muito obrigado pela atenção!

[quote=marcos.9306]Com getReference() também funciona perfeitamente.

Pelo o que eu li, o getReference evita a consulta ao banco de dados, usando a cache do próprio JPA/Hibernate.

Então pelo jeito, é a melhor solução mesmo…

Muito obrigado pela atenção![/quote]O find também evitaria. Mas em ambos os casos só evita caso o objeto esteja dentro do PersistenceContext. Caso o objeto não esteja, ambos irão no DB.

O x da questão é que o cada um traz do DB que facilita o consumo de banda. [=