Ok, I have no idea what I was doing wrong before, but I got it working the way I want using an ordered <bag> and bidirectional mapping and everything. It's incredibly simple and works fine. I assume my original problem had something to do with not having a surrogate key in the mapping tables (I'd still like to see an example that works without the surrogate key).
Anyway, since I've seen this topic a bunch of times in the forums and I've never seen a complete answer, I'm providing mine here for others to examine.
The complete self-contained example can be downloaded from
http://slac.com/jim/hibernate_test.zip. It includes everything you need to test out, just change your DB settings in resources/hibernate.properties, and examine the DB schema in db/schema.sql (remove the "type = InnoDB" part and "auto_increment" for non-MySQL). You'll probably have to set your classpath to run it outside of Eclipse (Eclipse project metadata is included as well).
This example demonstrates how to implement bi-directional many-to-many mapping with additional data in the mapping tables, and obtain the mapping objects as an ordered List. I implemented two different many-to-many mappings as well to make sure I didn't run across any of the duplicate column problems I was having earlier.
Here's the table schema:
Code:
create table person (
person_id int not null auto_increment,
lastname varchar(32) null,
firstname varchar(16) null,
primary key (person_id)
);
create table phone (
phone_id int not null auto_increment,
number varchar(32) not null,
type varchar(16) null,
primary key (phone_id)
);
create table person_phone_map (
person_phone_map_id int not null auto_increment,
person_id int not null,
phone_id int not null,
priority tinyint not null,
primary key (person_phone_map_id),
foreign key (person_id) references person (person_id),
foreign key (phone_id) references phone (phone_id)
);
create table location (
location_id int not null auto_increment,
address1 varchar(128) not null,
address2 varchar(128) null,
primary key (location_id)
);
create table person_location_map (
person_location_map_id int not null auto_increment,
person_id int not null,
location_id int not null,
priority tinyint not null,
primary key (person_location_map_id),
foreign key (person_id) references person (person_id),
foreign key (location_id) references location (location_id)
);
Here's the hibernate mapping:
Code:
<hibernate-mapping package="test.domain">
<class name="Person" table="person" lazy="false">
<id name="id" type="long" column="person_id" unsaved-value="null">
<generator class="native"/>
</id>
<property name="lastName" column="lastname" length="32"/>
<property name="firstName" column="firstname" length="16"/>
<bag name="personPhones" table="person_phone_map" order-by="priority asc">
<key column="person_id"/>
<composite-element class="PersonPhone">
<parent name="person"/>
<many-to-one name="phone" class="Phone" column="phone_id" not-null="true"/>
<property name="priority" column="priority" not-null="true"/>
</composite-element>
</bag>
<bag name="personLocations" table="person_location_map" order-by="priority asc">
<key column="person_id"/>
<composite-element class="PersonLocation">
<parent name="person"/>
<many-to-one name="location" class="Location" column="location_id" not-null="true"/>
<property name="priority" column="priority" not-null="true"/>
</composite-element>
</bag>
</class>
<class name="Phone" table="phone" lazy="false">
<id name="id" type="long" column="phone_id" unsaved-value="null">
<generator class="native"/>
</id>
<property name="number" column="number" length="32" not-null="true"/>
<property name="type" column="type" length="16" not-null="true"/>
<bag name="personPhones" table="person_phone_map">
<key column="phone_id"/>
<composite-element class="PersonPhone">
<parent name="phone"/>
<many-to-one name="person" class="Person" column="person_id" not-null="true"/>
<property name="priority" column="priority" not-null="true"/>
</composite-element>
</bag>
</class>
<class name="PersonPhone" table="person_phone_map" lazy="false">
<id name="id" type="long" column="phone_phone_map_id" unsaved-value="null">
<generator class="native"/>
</id>
<many-to-one name="person" column="person_id" insert="false" update="false" not-null="true"/>
<many-to-one name="phone" column="phone_id" insert="false" update="false" not-null="true"/>
<property name="priority" column="priority" not-null="true"/>
</class>
<class name="Location" table="location" lazy="false">
<id name="id" type="long" column="location_id" unsaved-value="null">
<generator class="native"/>
</id>
<property name="address1" column="address1" length="128" not-null="true"/>
<property name="address2" column="address2" length="128" not-null="true"/>
<bag name="personLocations" table="person_location_map">
<key column="location_id"/>
<composite-element class="PersonLocation">
<parent name="location"/>
<many-to-one name="person" class="Person" column="person_id" not-null="true"/>
<property name="priority" column="priority" not-null="true"/>
</composite-element>
</bag>
</class>
<class name="PersonLocation" table="person_location_map" lazy="false">
<id name="id" type="long" column="location_location_map_id" unsaved-value="null">
<generator class="native"/>
</id>
<many-to-one name="person" column="person_id" insert="false" update="false" not-null="true"/>
<many-to-one name="location" column="location_id" insert="false" update="false" not-null="true"/>
<property name="priority" column="priority" not-null="true"/>
</class>
</hibernate-mapping>