[RESOLVIDO] Problema JSF + Hibernate: persistência de dados de um <h:selectOneMenu>

Olá a todos os colegas GUJ.

Estou com um problema tem alguns dias. Implementei um formulário que contem um <h:selectOneMenu> onde o <f:selectItems> é populado por uma lista do banco de dados.
Tenho vários usuários (Classe User) que podem possuir vários cargos (Classe Cargo).

Problema: não estou conseguindo inserir no banco a seleção do usuário. Não sei dizer se o erro está no preenchimento do <f:selectItems> ou se está na regra de persistência dos dados com o Hibernate. Segue o exemplo do código:

Classe User

@Entity
@Table(name="user")
public class User implements Serializable, UserDetails {

    private static final long serialVersionUID = -8451679170281063697L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    private Long id;
    
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(name="user_cargo", 
            joinColumns = @JoinColumn(name="user_id" , referencedColumnName="id"), 
            inverseJoinColumns = @JoinColumn(name="cargos_id" , referencedColumnName="id"))
    private List&lt;Cargo&gt; cargos;
    ...

Classe Cargo

@Entity
@Table(name="cargo")
public class Cargo implements Serializable {
    
    private static final long serialVersionUID = 1886820991361556707L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    private Long id;
    
    @NotNull
    private String nome;
    
    @ManyToMany(cascade=CascadeType.ALL)
    @JoinTable(name="user_cargo", 
            joinColumns = @JoinColumn(name="cargos_id" , referencedColumnName="id"), 
            inverseJoinColumns = @JoinColumn(name="user_id" , referencedColumnName="id"))
    private List&lt;User&gt; users;
    ...

Classe UserController

@ManagedBean(name="userController")
@ViewScoped
public class UserController implements Serializable{


    private User user = new User();    
    private List&lt;User&gt; listaUsers = new ArrayList&lt;User&gt;(); 
    private UserDao userDao = (UserDao) BeanFactory.getBean("userDao", UserDao.class);
 
    public void gravar(){
        userDao.gravar(getUser());
        atualizarTela();
    }
    ...

Classe UserDao

public class UserDao{
    
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        this.session = sessionFactory.openSession();
    }
        
    public void gravar(User user) {
        try {
            if (!session.isOpen()) {
                session = sessionFactory.openSession();
            }
            transaction = session.beginTransaction();
            session.save(user);
            transaction.commit();
        } catch (HibernateException e) {
            System.out.println("Erro: " + e.getMessage());
            transaction.rollback();
        } finally {
            try {
                if (session.isOpen()) {
                    session.close();
                }
            } catch (Throwable e) {
                System.out.println("Erro ao finalizar inserção: " + e.getMessage());
            }
        }
    }
...

Classe CargoController

@ManagedBean(name="cargoController")
@ViewScoped
public class CargoController implements Serializable{
    
    private Cargo cargo = new Cargo();
    private List&lt;Cargo&gt; listaCargos = new ArrayList&lt;Cargo&gt;(); 
    private CargoDao cargoDao = (CargoDao) BeanFactory.getBean("cargoDao", CargoDao.class);
    
    public void gravar(){
        cargoDao.gravar(getCargo());
        atualizarTela();
    }
    public List&lt;Cargo&gt; getListaCargos() {
        listaCargos = cargoDao.buscarTodos();
        return listaCargos;
    }

CargoDao

public class CargoDao {
    
    private SessionFactory sessionFactory;
    private Session session;
    private Transaction transaction;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        this.session = sessionFactory.openSession();
    }
    
    public List&lt;Cargo&gt; buscarTodos(){
        if (!session.isOpen()) {
            session = sessionFactory.openSession();
        }
        transaction = session.beginTransaction();
        List lista = session.createQuery("from Cargo").list();
        if (session.isOpen()) {
            session.close();
        }
        return lista;
    }

cadastro.xhtml

&lt;h:selectOneMenu id="cargos"&gt;
    &lt;f:selectItem itemValue="Selecione..." itemLabel="Selecione..." /&gt;
    &lt;f:selectItems value="#{cargoController.listaCargos}" itemValue="#{cargos.id}" /&gt;
&lt;/h:selectOneMenu&gt;

Muito obrigado pela ajuda.

Abraços!

qual a mensagem de erro?

Aí é que esta o problema LPJava: não tenho mensagens de erro. Como fiz um construtor vazio ele insere os outros dados do formulário no banco normalmente. Meu problema é apenas com os itens dentro dos selectOneMenu, ou melhor, com os itens que possuem relacionamentos. Não sei se é algum erro de lógica no view ou se estou esquecendo de fazer alguma coisa para que o hibernate veja que se trata de um relacionamento e referencie na tabela que criou user_cargo o id de cada novo objeto.

No banco esta assim:

Tabela user
Id
password
nome

Tabela cargo
id
nome

Tabela user_cargo
user_id
cargo_id

Achei o erro. Estava no mapeamento das entidades mesmo.

Depois de colocar o cascade “floriu” numa boa:

@ManyToMany(cascade={ CascadeType.ALL, CascadeType.MERGE })
    @JoinTable(name="user_cargo", 
            joinColumns = @JoinColumn(name="user_id" , referencedColumnName="id"), 
            inverseJoinColumns = @JoinColumn(name="cargos_id" , referencedColumnName="id"))
    private List<Cargo> cargos;

Aí foi só arrumar o selectOneMenu pra fechar o pacote.

Abraços.

Vocês que entendem das anotações, como faço pra referenciar uma classe com outra?

Digamos que o User terá somente um Cargo…

E quando eu carregar ou setar salve um único id, não duplicando informações?

Não sou “expert” mas, acredito Ivan, que esteja falando dos relacionamentos entre tabelas no banco de dados. Isto que você tem que entender e ficar bem claro para depois partir para as anotações utilizando persistência.

Em referência as suas perguntas, primeiro acredito que você deve saber se o relacionamento é unidirecional ou bidirecional. Exemplo aqui: http://www.guj.com.br/java/112495-relacionamentos-unidirecional-e-bidirecional-onetomany-manytoone-manytomany

Depois partir para as anotações.

Neste exemplo que eu estava com dificuldades, por exemplo:

@ManyToMany(cascade={ CascadeType.ALL, CascadeType.MERGE })  
    @JoinTable(name="user_cargo",   
            joinColumns = @JoinColumn(name="user_id" , referencedColumnName="id"),   
            inverseJoinColumns = @JoinColumn(name="cargos_id" , referencedColumnName="id"))  
    private List<Cargo> cargos;  

O @ManyToMany signfica que existem um relacionamento muitos pra muitos. Acredito que no caso da sua pergunta seria um relacionamento muitos pra um utilizando a anotação @ManyToOne.

No meu caso, eu não gosto de relacionar as tabelas diretamente. Prefiro sempre criar uma terceira tabela apenas para armazenar os relacionamentos. Neste exemplo, a terceira tabela seria a user_cargo criando a coluna user_id que faz referência ao id do User da classe User e a coluna cargo_id fazendo referência ao id do Cargo na classe Cargo.

Espero que tenha ajudado um pouco.

Abraços.

A anotação FetchType.EAGER devo usar somente quando é pra carregar as informações das classes referenciadas correto?

Quando referenciar um pra um achei esta resposta: quando a classe referenciada não tiver id ele automaticamente remove.

@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@PrimaryKeyJoinColumn

É mais ou menos por aí…rs
O FetchType.EAGER você usa pra carregar as informações da classe e junto com ela já carregar os dados referenciados dentro da classe.

No meu exemplo, o FetchType.EAGER, ao dar um “get” em User ele já buscaria o Cargo do User.

Quanto a segunda questão não entendi muito bem, mas para esclarecer brevemente para o que cada item serve:

  • Cascade: serve para definir o conjunto de operações que serão propagadas para a entidade que você está associado. No caso da sua pergunta, todas as operações, pois está como ALL.

  • Fetch: respondido acima sobre como usar. Se marcar como LAZY terá que fazer os outros selects “no braço”.

  • OrphanRemoval: se o valor que esta sendo mapeado é deletado, esta entidade também será deletada.

Quer uma dica top: dá uma olhada nesse mini-livro de JPA do UAIHEBERT http://uaihebert.com/jpa-mini-livro-primeiros-passos-e-conceitos-detalhados/

Tem uma explicação “show de bola” na página 23 sobre os itens acima, sem contar que esse mini-livro inteiro é muito bacana. Vale a pena dar uma lida.

Espero ter ajudado.

Abraços.

Certo! Vlw Dr!