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

8 respostas
A

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!

8 Respostas

L

qual a mensagem de erro?

A

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

A

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.

I

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?

A

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.

I

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

A

É 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.

I

Certo! Vlw Dr!

Criado 10 de julho de 2013
Ultima resposta 28 de mai. de 2014
Respostas 8
Participantes 3