Erro ao persistir entidades com relacionamento OneToMany e ManyToOne

Olá pessoal… tudo bem??
Estou começando a mexer com JPA e logo de cara tive um problema com relacionamento OneToMany e ManyToOne…
Estou desenvolvendo uma aplicação simples para venda de produtos. Na minha análise para resolver o que precisava criei 3 classes que solucionariam meu problema: Produto.java; Compra.java e ItensCompra.java…
A parte CRUD do Produto.java funciona perfeitamente… meu problema está no relacionamento da venda do produto… Não consigo persistir meu registro de venda…
Farei o que, registrarei na tabela de compra uma nova compra e na tabela de Itens de Compra, os itens que aquela determinada venda teve, referenciando tbm o produto que foi comprado e que estará armazenado numa tabela de registros dos produtos…

Minhas tabelas estão assim:

create table tb_produto
(
	id int identity,
	nome_prod varchar(100) not null,
	descricao_prod varchar(100),
	valor_prod decimal(4,2) not null,
	qtde_prod int not null,
	
	constraint pk_produto primary key(id)
)

create table tb_compra
(
	id int identity,
	valortotal_compra decimal(5,2) not null,
	aluno_compra int,	
	
	constraint pk_compra primary key(id),
	constraint fk_aluno_compra foreign key(aluno_compra) references tb_aluno(id),	
)

create table tb_itenscompra
(
	id int identity,
	compra_itens int,
	produto_itens int,
	qtde_itens int,
	valortotal_itens decimal(5,2),
	
	constraint pk_itenscompras primary key(id),
	constraint fk_produto_itens foreign key(produto_itens) references tb_produto(id),
	constraint fk_compra_itens foreign key(compra_itens) references tb_compra(id),
)

e minhas classes mapeei da seguinte forma:

@Entity
@Table(name="tb_produto")
public class Produto implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name="nome_prod")
    private String nomeProduto;
    @Column(name="descricao_prod")
    private String descricaoProduto;
    @Column(name="valor_prod")
    private BigDecimal valorProduto;
    @Column(name="qtde_prod")
    private int qtdeProduto;

    //getters and setters
}
@Entity
@Table(name="tb_compra")
public class Compra implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)    
    private Long id;    
    
    //private Aluno aluno;
    //@Column(name="valortotal_compra")
    //private BigDecimal valorTotalCompra;
    
    @OneToMany(mappedBy="compra",cascade=CascadeType.ALL,fetch= FetchType.EAGER)
    private List<ItensCompra> itensCompra = new ArrayList<ItensCompra>();

    // getters and setters
}

@Entity
@Table(name="tb_itenscompra")
public class ItensCompra implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
    @JoinColumn(name="compra_itens")
    private Compra compra;
    
    @OneToOne(cascade= CascadeType.ALL,fetch= FetchType.EAGER)
    @JoinColumn(name="produto_itens")    
    private Produto prod;
    
    @Column(name="qtde_itens")
    private int qtde;
    
    @Column(name="valortotal_itens")
    private BigDecimal valorTotal;

    //getters and setters
}

quando chamo o método para popular e persistir

private void registrarVenda() {        
        Compra c = new Compra();
        int cont = 0;
        try {
            for(Produto p : listaProdutos) {                
                ItensCompra item = new ItensCompra(c);
                item.setProd(p);
                item.setQtde(listaQtdes.get(cont));
                item.setValorTotal(listaValoresTotaisItens.get(cont));
                c.getItensCompra().add(item);              
            }
            new CompraControle().salvar(c);
            
        }catch(Exception e) {
            JOptionPane.showMessageDialog(null, "ERRO AO REGISTRAR COMPRA\n"
                    + e.getMessage(), "FALHA", JOptionPane.ERROR_MESSAGE);
            e.printStackTrace();
        }
    }

tenho o seguinte erro:

java.lang.IllegalArgumentException: Object: jr7.lojaescolar.entidades.Compra[ id=null ] is not a known entity type.

Jah segui orientação de vários materiais que encontrei na internet e nada de solucionar esse problema… Debuguei o código que o objeto de compra chega todo populado na DAO antes de persistir…
Meu persistence.xlm está assim:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="BibliotecaLojaEscolarPU" transaction-type="RESOURCE_LOCAL">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <class>jr7.lojaescolar.entidades.Aluno</class>
    <class>jr7.lojaescolar.entidades.Produto</class>
    <class>jr7.lojaescolar.entidades.ItensCompra</class>
    <class>jr7.lojaescolar.entidades.Compra</class>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:sqlserver://localhost\db_lojaescolar:1433;databaseName=db_lojaescolar"/>
      <property name="javax.persistence.jdbc.password" value="xxxxxxxxx"/>
      <property name="javax.persistence.jdbc.driver" value="com.microsoft.sqlserver.jdbc.SQLServerDriver"/>
      <property name="javax.persistence.jdbc.user" value="xxxxxx"/>
    </properties>
  </persistence-unit>
</persistence>

saberiam o que estou fazendo de errado nesse mapeamento??

desde já agradeço.

att.
Junior

Tenta tirar o cascade=CascadeType.ALL da anotação @ManyToOne e ve o que acontece

Olá Rogério… desde já agradeço sua ajuda…
Fiz a alteração que sugeriu e deu esse erro:

Exception Description: The list of fields to insert into the table [DatabaseTable(tb_compra)] is empty.  You must define at least one mapping for this table.
javax.persistence.RollbackException: Exception [EclipseLink-6023] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.QueryException
Exception Description: The list of fields to insert into the table [DatabaseTable(tb_compra)] is empty.  You must define at least one mapping for this table.

Ele diz que a lista estah vazia, mas na está populada com os itens que coloco…

O primeiro erro e esse estavam se alternando, digamos assim… hora da um, hora o outro… cada alteração que fazia e reiniciava o netbeans os erros se alternavam… Fiz a alteração que sugeriu e deu o primeiro erro

java.lang.IllegalArgumentException: Object: jr7.lojaescolar.entidades.Compra[ id=null ] is not a known entity type.

reiniciei o netbeans, e sem mexer numa linha do código o erro que apareceu foi esse ultimo que postei

Exception Description: The list of fields to insert into the table [DatabaseTable(tb_compra)] is empty.  You must define at least one mapping for this table.
javax.persistence.RollbackException: Exception [EclipseLink-6023] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.QueryException
Exception Description: The list of fields to insert into the table [DatabaseTable(tb_compra)] is empty.  You must define at least one mapping for this table.

novamente muito obrigado, Rogerio

Bom, eu fiz um teste aqui com as suas classes e não deu erro. Só que aqui eu utilizei o mysql e o hibernate ao invés do eclipselink. O código que eu fiz para criar os objetos foi o seguinte:

    Produto produto = new Produto();
    produto.setDescricaoProduto("desc");
    produto.setNomeProduto("nomeProduto");
    produto.setQtdeProduto(1);
    produto.setValorProduto(new BigDecimal(10));
    
    Compra c = new Compra();
    ItensCompra item = new ItensCompra();
    item.setProd(produto);
    item.setQtde(1);
    item.setValorTotal(new BigDecimal(50));
    item.setCompra(c);
    
    List<ItensCompra> listItens = new LinkedList<ItensCompra>();
    listItens.add(item);

    c.setItensCompra(listItens);

   em.persist(c);

Tenta rodar o código acima e ve se dá erro. Se não der é pq provavelmente tem algo errado quando vc monta os objetos pra gravar. A única coisa que vi de diferente é que no item eu seto o objeto compra (item.setCompra©). Se eu não me engano no seu código não tem esse valor.

Rogério, persistiu o mesmo problema…

Exception Description: The list of fields to insert into the table [DatabaseTable(tb_compra)] is empty.  You must define at least one mapping for this table.  
javax.persistence.RollbackException: Exception [EclipseLink-6023] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.QueryException  
Exception Description: The list of fields to insert into the table [DatabaseTable(tb_compra)] is empty.  You must define at least one mapping for this table.  

Utilizei o seu código, o meu e ambos dão o mesmo problema… apaguei o banco e reconstrui-o novamente e nada…

Quanto a setar o objeto de compra no itemCompra, eu passo no construtor do ItemCompra…

pelo jeito o problema está comigo … preciso me benzer… rs

grato msm pela ajuda, Rogerio

No seu persistence.xml, adiciona as propriedades:

		<property name="eclipselink.logging.level" value="FINE"/>

		<property name="eclipselink.logging.logger" value="DefaultLogger" />

		<property name="eclipselink.logging.session" value="true"/>

ele vai logar os comandos sql gerados. Ve o sql que ele está gerando, acho que vai ser possível ter alguma pista do que está acontecendo

Uma outra coisa que pensei. Aqui no meu log, os inserts que ele faz são estes:

Hibernate: insert into tb_compra values ( )
Hibernate: insert into tb_produto (descricao_prod, nome_prod, qtde_prod, valor_prod) values (?, ?, ?, ?)
Hibernate: insert into tb_itenscompra (compra_itens, produto_itens, qtde_itens, valortotal_itens) values (?, ?, ?, ?)

O primeiro insert não tem campo nenhum. O driver do mysql não “reclama” disso. Pela mensagem de erro parece que no caso do sql server ele não aceita um insert assim sem campo nenhum, então um outro teste que poderia ser feito é criar um campo qualquer na tabela compra do tipo texto, mapear este campo e colocar qualquer coisas nele antes de gravar (ou utilizar o campo que está comentado e que possui o valor total), pra ver se o erro continua.

Rogério, funcionou…
quando segui suas dicas, ele acusou o verdadeiro problema… Tinha um campo na tabela que não ia usar mas e esqueci de apagar e não estava setando o msm… por isso ele acusava o erro… :?

mas assim que apaguei o campo de teste o erro voltou… ou seja, terei que ter um campo ali para poder inserir dados corretamente?

Apaguei a coluna e tudo sussa… Uma ultima pergunta, como jah terei o produto vendido registrado, no cascade no mapeamento @OneToOne de Produto na entidade ItensCompra deixo apenas o CascadeType.Merge?? o CascadeType.Refresh, CascadeType.Detach e o CascadeType.Remove não precisa msm??

ficando apenas assim?

 @OneToOne(cascade= {CascadeType.MERGE},fetch= FetchType.EAGER)
    @JoinColumn(name="produto_itens")    
    private Produto prod;

valeu msm, Rogerio…e desculpe o incomodo
abs.

Se os dados dos produtos não serão alterados através deste relacionamento você pode deixar o cascade como merge ou nem utilizar cascade. O cascade deve ser utilizado quando vc quer que o jpa salve as alterações dos objetos nos relacionamentos sem vc ter que ficar chamando os métodos para inserir, atualizar ou apagar em cada objeto da coleção. Por exemplo, se vc quiser que quando a entidade “pai” for apagada, os “filhos” da entidade sejam apagados também, então vc configura o cascade para delete, porém você poderia não utilizar o cascade e apagar os objetos da coleção um a um, chamando o método delete para cada um deles antes de apagar a entidade pai,
então depende de como você controlará o ciclo de vida dos objetos na sua aplicação.

Até mais!