47

I am working on a legacy code base with an existing DB schema. The existing code uses SQL and PL/SQL to execute queries on the DB. We have been tasked with making a small part of the project database-engine agnostic (at first, change everything eventually). We have chosen to use Hibernate 3.3.2.GA and "*.hbm.xml" mapping files (as opposed to annotations). Unfortunately, it is not feasible to change the existing schema because we cannot regress any legacy features.

The problem I am encountering is when I am trying to map a uni-directional, one-to-many relationship where the FK is also part of a composite PK. Here are the classes and mapping file...

CompanyEntity.java

public class CompanyEntity {
    private Integer id;
    private Set<CompanyNameEntity> names;
    ...
}

CompanyNameEntity.java

public class CompanyNameEntity implements Serializable {
    private Integer id;
    private String languageId;
    private String name;
    ...
}

CompanyNameEntity.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.jboss.org/dtd/hibernate/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.example">

    <class name="com.example.CompanyEntity" table="COMPANY">
        <id name="id" column="COMPANY_ID"/>
        <set name="names" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
            <key column="COMPANY_ID"/>
            <one-to-many entity-name="vendorName"/>
        </set>
    </class>

    <class entity-name="companyName" name="com.example.CompanyNameEntity" table="COMPANY_NAME">
        <composite-id>
            <key-property name="id" column="COMPANY_ID"/>
            <key-property name="languageId" column="LANGUAGE_ID"/>
        </composite-id>
        <property name="name" column="NAME" length="255"/>
    </class>

</hibernate-mapping>

This code works just fine for SELECT and INSERT of a Company with names. I encountered a problem when I tried to update and existing record. I received a BatchUpdateException and after looking through the SQL logs I saw Hibernate was trying to do something stupid...

update COMPANY_NAME set COMPANY_ID=null where COMPANY_ID=?

Hibernate was trying to dis-associate child records before updating them. The problem is that this field is part of the PK and not-nullable. I found the quick solution to make Hibernate not do this is to add "not-null='true'" to the "key" element in the parent mapping. SO now may mapping looks like this...

CompanyNameEntity.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.jboss.org/dtd/hibernate/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.example">

    <class name="com.example.CompanyEntity" table="COMPANY">
        <id name="id" column="COMPANY_ID"/>
        <set name="names" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
            <key column="COMPANY_ID" not-null="true"/>
            <one-to-many entity-name="vendorName"/>
        </set>
    </class>

    <class entity-name="companyName" name="com.example.CompanyNameEntity" table="COMPANY_NAME">
        <composite-id>
            <key-property name="id" column="COMPANY_ID"/>
            <key-property name="languageId" column="LANGUAGE_ID"/>
        </composite-id>
        <property name="name" column="NAME" length="255"/>
    </class>

</hibernate-mapping>

This mapping gives the exception...

org.hibernate.MappingException: Repeated column in mapping for entity: companyName column: COMPANY_ID (should be mapped with insert="false" update="false")

My problem now is that I have tryed to add these attributes to the key-property element but that is not supported by the DTD. I have also tryed changing it to a key-many-to-one element but that didn't work either. So...

How can I map "insert='false' update='false'" on a composite-id key-property which is also used in a one-to-many FK?

Jesse Webb
  • 36,395
  • 25
  • 99
  • 138
  • After looking at the Hibernate annotations doc, it looks like I might be able to create the desired mapping with JPA/Hibernate annotations. I will experiment with this a little more and post my results. – Jesse Webb Feb 04 '11 at 20:49
  • The mapping I am attempting to specify is NOT possible using *.hbm.xml files. The only way to accomplish this is to use annotations as mentioned in mcyalcin's answer below. – Jesse Webb Feb 18 '11 at 19:44
  • 2
    I guess this post may solve the problem in XML configuration, http://stackoverflow.com/questions/9381029/hibernate-mapping-exception-repeated-column-in-mapping-for-entity – Dino Tw Sep 13 '13 at 23:27
  • @DinoTw your hint with the link really was useful. Marking "inverse=true" really allows the table with composite key to take responsibility of updating and inserting and it solves the problem in XML mapping. – Narendran Solai Sridharan Nov 21 '16 at 13:53

2 Answers2

99

I think the annotation you are looking for is:

public class CompanyName implements Serializable {
//...
@JoinColumn(name = "COMPANY_ID", referencedColumnName = "COMPANY_ID", insertable = false, updatable = false)
private Company company;

And you should be able to use similar mappings in a hbm.xml as shown here (in 23.4.2):

http://docs.jboss.org/hibernate/core/3.3/reference/en/html/example-mappings.html

mcyalcin
  • 1,954
  • 14
  • 11
  • 1
    Thank you for your answer. Within the JBoss documentation link you provided, it does not contain any examples of the case I am having problems with. I tried using the mappings exactly like how they showed and it still did not work for me. It appears that the particular mapping I am trying to accomplish is not possible with the current HBM DTD spec. That being said, your solution with annotations does not face the same problem I am experiencing so it is a good solution for anyone who has the option of using annotations. I normally recommend annotations to everyone who has the choice. – Jesse Webb Feb 18 '11 at 19:25
  • Interesting. I've only used annotations with Hibernate so far, finding that more convenient but I thought annotation functionality was a subset of the xml mapping definitions, not the other way around. I'll fiddle with the xmls, for if there really is a missing functionality, a bug should be reported. I'm glad the answer helped! – mcyalcin Feb 19 '11 at 09:11
  • 1
    But if I want to insert and update, is there any other work around to get out of this? Because I suppose, insertable = false and updatable=false will not allow me to change the "company". – kinshuk4 Nov 09 '11 at 09:56
  • What if you do not want a `Company` object reference within `CompanyName`? I mean, a `CompanyName` is just a value and the relationship is already defined by the `one-to-many`. It makes no sense at all to have a `Company` property within `CompanyName`. – plalx Dec 19 '16 at 17:30
  • CompanyName is just an example here. If the object you are using is really 'just a value' you don't have a table/class for it, you add a column/field to the parent object and be done with it. – mcyalcin Dec 20 '16 at 19:01
0

"Dino TW" has provided the link to the comment Hibernate Mapping Exception : Repeated column in mapping for entity which has the vital information.

The link hints to provide "inverse=true" in the set mapping, I tried it and it actually works. It is such a rare situation wherein a Set and Composite key come together. Make inverse=true, we leave the insert & update of the table with Composite key to be taken care by itself.

Below can be the required mapping,

<class name="com.example.CompanyEntity" table="COMPANY">
    <id name="id" column="COMPANY_ID"/>
    <set name="names" inverse="true" table="COMPANY_NAME" cascade="all-delete-orphan" fetch="join" batch-size="1" lazy="false">
        <key column="COMPANY_ID" not-null="true"/>
        <one-to-many entity-name="vendorName"/>
    </set>
</class>
Community
  • 1
  • 1