-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: Avoiding duplicate rows with XML data
PostPosted: Sun Dec 02, 2007 10:23 pm 
Newbie

Joined: Fri Jul 27, 2007 3:20 pm
Posts: 1
I have a question about how Hibernate 3.1 detects when to use save or update/merge when persisting elements thru associations. To test various options I've created a subset of the classic Supplier/Parts database in MySQL 5.0 with the DDL below

CREATE TABLE Supplier (
SupplierId VARCHAR(36) NOT NULL,
Name VARCHAR(20) NOT NULL,
City VARCHAR(20) NULL,
PRIMARY KEY (SupplierId)
);


CREATE TABLE Part (
PartId VARCHAR(36) NOT NULL,
Name VARCHAR(20) NOT NULL,
Color VARCHAR(20) NOT NULL,
PRIMARY KEY (PartId)
);


CREATE TABLE SupplierPart (
SupPartId VARCHAR(36) NOT NULL,
SupplierId VARCHAR(36) NOT NULL,
PartId VARCHAR(36) NOT NULL,
Qty INTEGER NULL,
PRIMARY KEY (SupPartId),
FOREIGN KEY (PartId)
REFERENCES Part(PartId),
FOREIGN KEY (SupplierId)
REFERENCES Supplier(SupplierId)
);


At any given time, each supplier supplies 0, 1 or more parts while each part can be supplied by 0, 1 or more suppliers. This many-to-many
relationship is provided thru the association table SupplierPart. The keys are all surrogate. However, natural business keys have been
designated for the Part entity as the combined Name/Color fields while for the Supplier entity it is just the Name. Unique, non-null indices
have been created for the Part table on the Name/Color fields and on the Name field for the Supplier table. These fields are considered
immutable as well.

Data can come in thru XML files. For example, one file could be:

<supplier>
<name>Acme, Inc.</name>
<city>Boston</city>
<supplierpart>
<part>
<name>Bolt</name>
<color>Silver</color>
</part>
<qty>200</qty>
</supplierpart>
<supplierpart>
<part>
<name>Bolt</name>
<color>Black</color>
</part>
<qty>100</qty>
</supplierpart>
</supplier>

Note that there is no notion of an ID here which is appropriate since the keys are surrogates that are generated behind the scenes. Now a second

file could be:

<supplier>
<name>Apex</name>
<city>New York</city>
<supplierpart>
<part>
<name>Screw</name>
<color>Silver</color>
</part>
<qty>400</qty>
</supplierpart>
<supplierpart>
<part>
<name>Bolt</name>
<color>silver</color>
</part>
<qty>100</qty>
</supplierpart>
</supplier>

The problem is that a row in the Part table already exists for the Bolt/Silver part. By using the vanilla auto generated DTO classes and hbm files generated by a tool such as MyEclipse 5.1.1 I end up with problems using a save on the second supplier (i.e., Apex). That is, for a save I get a second record for the Bolt/Silver part. It can get more complicated if I have a third file such as

<supplier>
<name>Acme, Inc.</name>
<city>Boston</city>
<supplierpart>
<part>
<name>Bolt</name>
<color>Silver</color>
</part>
<qty>200</qty>
</supplierpart>
<supplierpart>
<part>
<name>Bolt</name>
<color>Black</color>
</part>
<qty>100</qty>
</supplierpart>
<supplierpart>
<part>
<name>Screw</name>
<color>Black</color>
</part>
<qty>100</qty>
</supplierpart>
</supplier>


Now I will use something such as a merge but I can still get into trouble because two of the parts already exist. I have tried a number of
options and have found that generating a surrogate key such as a UUID that is name based on the natural business key will work if I have a check in the getId and setId methods for null values that will then generate a proper key to replace the null value. Of course, I also need the proper cascade specs in the hbm files - namely, "all-delete-orphan" on the one-to-many relations and "none" as the class default - as well as specifying "undefined" for the unsaved-value in the id tag. The Supplier hbm file is

<hibernate-mapping default-cascade="none" default-lazy="true">
<class name="com.icesoft.icefaces.tutorial.crud.hibernate.Supplier" table="supplier" catalog="register">
<id name="supplierId" type="java.lang.Integer" unsaved-value="undefined">
<column name="SupplierId" />
<generator class="com.icesoft.icefaces.tutorial.crud.hibernate.IdGenerator" />
</id>
<property name="name" type="java.lang.String">
<column name="Name" length="45" not-null="true" unique="true" />
</property>
<property name="phone" type="java.lang.String">
<column name="Phone" length="45" />
</property>
<set name="supplierparts" inverse="true" cascade="all-delete-orphan">
<key>
<column name="SupplierId" not-null="true" />
</key>
<one-to-many class="com.icesoft.icefaces.tutorial.crud.hibernate.Supplierpart" />
</set>
</class>
</hibernate-mapping>

<hibernate-mapping default-cascade="save-update" default-lazy="true">
<class name="com.icesoft.icefaces.tutorial.crud.hibernate.Supplierpart" table="supplierpart" catalog="register">
<id name="supplierpartId" type="java.lang.Integer" unsaved-value="undefined">
<column name="SupplierpartId" />
<generator class="com.icesoft.icefaces.tutorial.crud.hibernate.IdGenerator" />
</id>
<many-to-one name="supplier" class="com.icesoft.icefaces.tutorial.crud.hibernate.Supplier" fetch="select">
<column name="SupplierId" not-null="true" />
</many-to-one>
<many-to-one name="part" class="com.icesoft.icefaces.tutorial.crud.hibernate.Part" fetch="select">
<column name="PartId" not-null="true" />
</many-to-one>
<property name="qty" type="java.lang.Integer">
<column name="Qty" />
</property>
</class>
</hibernate-mapping>

<hibernate-mapping default-cascade="none" default-lazy="true">
<class name="com.icesoft.icefaces.tutorial.crud.hibernate.Part" table="part" catalog="register">
<id name="partId" type="java.lang.Integer" unsaved-value="undefined">
<column name="PartId" />
<generator class="com.icesoft.icefaces.tutorial.crud.hibernate.IdGenerator" />
</id>
<property name="name" type="java.lang.String">
<column name="Name" length="45" not-null="true" unique="true" />
</property>
<property name="section" type="java.lang.String">
<column name="Section" length="45" not-null="true" />
</property>
<set name="supplierparts" inverse="true" cascade="all-delete-orphan">
<key>
<column name="PartId" not-null="true" />
</key>
<one-to-many class="com.icesoft.icefaces.tutorial.crud.hibernate.Supplierpart" />
</set>
</class>
</hibernate-mapping>

I have kept the description of the problem general because the many-to-many scenario I am describing is as basic as possible and getting data in the simple XML format without knowledge of internals such as keys seems like a case that I would think is not that exceptional. I have been surprised that I have not found any clearer info on how to handle this with Hibernate than I have to date - I have searched the web, the Hibernate and MyEclipse/Hibernate forums off and on for two months and did find the solution that I just described. However, while I have a solution that seems to work, I am still hoping that there is more direct support for scenarios such as I have described than I have been able to find.

BTW, one option I want to avoid if at all possible is to start writing a number of individual queries to check for the existence of various entities. In the example a check for the existence of the Supplier is reasonable to decide whether to use save or merge, but to check for all of the related child entities (Parts in this case) would be a major headache in the real scenario, since it is much more complex.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.