Utilizando tabelas NxN com ID própria em Hibernate/PGSQL

Saudações,

Sou recente em Java. Estou trabalhando com a tecnologia “Hibernate 3” e estou tendo problemas na hora de executar uma (ou várias) inserções numa tabela NxN, segue o esquema abaixo:

Author (pk, Description)
Work (pk, Description)
Author_Work (pk, fkAuthor, fkWork)

OBS: É necessário que não exista a junção dos valores das foreign keys em uma primary keys na tabela Author_Work!

O Problema: Antes de adicionar as tags “set” em ambos os mapeamentos, tudo estava perfeito. Depois de adicionar estas tags nos mapeamentos e os métodos getters e setters nas classes Author.java e Sector.java, estou tendo problemas em tempo de execução. Segundo o console do java, estou tendo problemas com a chave primária da tabela “Author_Work”.

O mapeamento XML que utilizo para classe Author é o seguinte:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="com.proj">
    
    <class name="Author" table="Author">
        <id name="pk" column="pk" type="int" unsaved-value="0">
            <generator class="increment"/>
        </id>
        <property name="Description" column="Description" type="string"/>

        <set name="Sectors" table="Author_Work">
            <key column="fkAuthor"/>
            <many-to-many class="Sector" column="fkSector"/>
        </set>        
          
    </class>
</hibernate-mapping>

O mapeamento XML que utilizo para classe Sector é o seguinte:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
    
    <hibernate-mapping package="com.proj">
    
    <class name="Sector" table="Sector">
        <id name="pk" column="pk" type="int" unsaved-value="0">
            <generator class="increment"/>
        </id>
        <property name="Description" column="Description" type="string"/>
        
        <set name="Authors" table="Author_Work">
            <key column="fkSector"/>
            <many-to-many class="Author" column="fkAuthor"/>
        </set>        
  
    </class>
</hibernate-mapping>

Vale lembrar que estou tendo sucesso na hora de executar os códigos se eu não utilizar as tags “set” no mapeamento. Não acho que seria uma boa prática de programação utilizar um mapeamento exclusivo para Author_Work, Sujestões?

Olá, bem vindo.

Pode postar o stack traces da exceção?

Olá Filipe,

Este problema acima foi um caso hipotético que bolei para todos entederem meu problema, meu código é bem parecido. Mas, basicamente, você deverá substituir as entidades/classes Author por Sector, Work por Contact e Author_Work por SectorContact. As chaves primárias são nomeadas como “pk” e as chaves estrangeiras como fkSector/fkContact.

A tabela intermediária SectorContact contém (pk, fkContact, fkSector)

Segue abaixo a StackTrace do meu código:

2005-08-05 08:55:41,187 ERROR JDBCExceptionReporter  -> Entrada em lote 0 insert into sectorcontact (fkSector, fkContact) values ( foi abortada. Chame getNextException() para ver a causa.
2005-08-05 08:55:41,187 ERROR JDBCExceptionReporter  -> ERROR: null value in column "pk" violates not-null constraint

2005-08-05 08:55:41,203 ERROR AbstractFlushingEventListener  -> Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
	at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:63)
	at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
	at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:181)
	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:226)
	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140)
	at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:274)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:730)
	at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:324)
	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:86)
	at com.pacto4.dao.DAO.save(DAO.java:106)
	at com.pacto4.dao.person.Teste.main(Teste.java:65)
Caused by: Entrada em lote 0 insert into sectorcontact (fkSector, fkContact) values ( foi abortada. Chame getNextException() para ver a causa.
	at org.postgresql.jdbc2.AbstractJdbc2Statement.executeBatch(AbstractJdbc2Statement.java:107)
	at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:57)
	at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:174)
	... 9 more
Exception in thread "main" org.hibernate.HibernateException: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
	at com.pacto4.dao.DAO.save(DAO.java:109)
	at com.pacto4.dao.person.Teste.main(Teste.java:65)
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
	at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:63)
	at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
	at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:181)
	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:226)
	at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:140)
	at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:274)
	at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
	at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:730)
	at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:324)
	at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:86)
	at com.pacto4.dao.DAO.save(DAO.java:106)
	... 1 more
Caused by: Entrada em lote 0 insert into sectorcontact (fkSector, fkContact) values ( foi abortada. Chame getNextException() para ver a causa.
	at org.postgresql.jdbc2.AbstractJdbc2Statement.executeBatch(AbstractJdbc2Statement.java:107)
	at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:57)
	at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:174)
	... 9 more

Agradeço antecipadamente.

O problema não é o Hibernate, deve ser a sua tabela. Vc criou uma sequence e colocou como valor default da sua pk?

[]'s

Rodrigo

Não, o problema não é a tabela. O problema é o mapeamento do Hibernate que não sei como fazer para este caso. Tal mapeamento não é exemplificado no manual no site do Hibernate, não tenho pistas de como faze-lo pelo DTD, nem é comentado na API Javadoc.

OBS: A pk da tabela intermediária realmente é uma sequence.

Veja a seção 6.2.4 da documentação do Hibernate.

E você tem certeza que a database está gerando a PK de Author_Work? Já tentou fazer direto no banco:

insert into tAuthor_Work( IDAuthor, IDWork ) values( 1, 1 )

?

Olá Filipe,

Eu estava enganado, fui consultar o script do banco e realmente não tem sequences nas tabelas. Isso foi decidido por motivos de compatibilidade. Notem que nos mapeamentos é o Hibernate que está responsável de cuidar dos auto-incrementos. Estamos progredindo… mas ainda não sei como tratar isso, sequences no banco estão fora de questão, devemos utilizar o auto-incremento do hibernate!

Leia cuidadosamente todos os generators de IDs que o Hibernate possui.

E tem certeza que não basta usar as duas FKs como uma “PK composta” na tabela de união?

Eu até poderia fazer tal relacionamento com esta tabela, mas no banco tenho relacionamentos mais complexos onde até 10 tabelas são plugadas numa tabela intermediária. Nestes casos, é importante ter uma pk para a tabela intermediária. isto implica em performance e, ao mesmo tempo, facilidade de uso na hora de importar a chave primária desta tabela intermediária.

Eu acredito que o Hibernate tenha solução para isso, mas não estou achando nada na documentação nem no DTD. Também acredito que deva existir algo na tag “set” ou “id” que resolva esta situação.

De qualquer forma, obrigado pela ajuda.

Cara, o problema está na PK da tabela de união. Dependendo do tipo de ID declarado o Hibernate gerará um query diferente.

Por exemplo, usamos o SQL Server e a maioria das tabelas usa Identity. Sendo assim o Hibernate omite o ID em todas as queries de inserção.
O mesmo não ocorreia com um ID declarado como “assigned” por exemplo.

Portanto estude o comportamento do banco e depois os generators do Hibernate. Então decida a melhor forma.

Olá pessoal!

Após pesquisar inúmeras vezes o manual do Hibernate, achei o seguinte
trecho:

6.7. Using an <idbag>

If you've fully embraced our view that composite keys are a bad thing
and that entities should have synthetic identifiers (surrogate keys), then 
you might find it a bit odd that the many to many associations and 
collections of values that we've shown so far all map to tables with 
composite keys! Now, this point is quite arguable; a pure association table 
doesn't seem to benefit much from a surrogate key (though a collection of 
composite values might). Nevertheless, Hibernate provides a feature that 
allows you to map many to many associations and collections of values to 
a table with a surrogate key.

The <idbag> element lets you map a List (or Collection) with bag 
semantics.

<idbag name="lovers" table="LOVERS" lazy="true">
    <collection-id column="ID" type="long">
        <generator class="hilo"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="eg.Person" outer-join="true"/>
</idbag>

As you can see, an <idbag> has a synthetic id generator, just like an 
entity class! A different surrogate key is assigned to each collection row. 
Hibernate does not provide any mechanism to discover the surrogate key 
value of a particular row, however.

Note that the update performance of an <idbag> is much better than a 
regular <bag>! Hibernate can locate individual rows efficiently and update 
or delete them individually, just like a list, map or set.

In the current implementation, the native identifier generation strategy is 
not supported for <idbag> collection identifiers. 

Esta é a solução para o problema, utilizei o trecho do mapeamento acima
substituindo os nome das classes, atributos e a tag generator
(class=“increment”) para minha realidade. Tudo funciona perfeito agora!

Abraços