Hibernate allowing duplicates with idbag

K

kevkev

I'm trying to work with an existing database schema and am having
difficulty with the mapping documents to allow me to persist duplicate
data to a table.

I have an ALBUM and a TRACK table. The business logic tells me that an
ALBUM can have many TRACK's and a track can be in many ALBUM's.

So what I would eventually like to have is something like


ALBUM Table

ALBUM_ID | ALBUM_NAME
------------------------------
1 | Album One
------------------------------
2 | Album Two
------------------------------
3 | Album Three

and

TRACK Table

TRACK_ID | NAME | ALBUM_ID
------------------------------------------
1 | Track One | 1
------------------------------------------
2 | Track Two | 1
------------------------------------------
3 | Track One | 2
------------------------------------------
4 | Track Two | 3





My mapping document is using a Set and looks as follows.


<class name="Album" table="ALBUM">
<id name="id" type="int" column="ALBUM_ID">
<meta attribute="scope-set">protected</meta>
<generator class="native"/>
</id>

<property name="albumName" column="NAME" type="string"/>

<set
name="albumTrack"
lazy="true"
cascade="save-update">
<key column="ALBUM_ID"/>
<one-to-many class="com.kbilly.hibernate.Track"/>
</set>
</class>




I know that a Set won't do though as it doesn't allow duplicates and
this is why I get.


TRACK Table

TRACK_ID | NAME | ALBUM_ID
------------------------------------------
1 | Track One | 3
------------------------------------------
2 | Track Two | 2




I've tried to use <idbag> but I can only get this working with an
association table, ALBUM_TRACK for instance. But the database schema
I'm working with doesn't have this join table.

So my question is how do I implement <idbag> to allow duplicates in the
the TRACK table without using a join table?



The other option I've tried is to have a bag in the Album.hbm.xml and
many-to-one in the Track.hbm.xml as below.



<class name="Track" table="TRACK">

<id name="id" type="int" column="TRACK_ID">

<meta attribute="scope-set">protected</meta>

<generator class="native"/>

</id>



<property name="trackName" column="NAME" type="string"/>




<many-to-one

name="album"

column="ALBUM_ID"

class="com.chello.mdr.mdrhibernate.Album"

not-null="true"/>

</class>



<class name="Album" table="ALBUM">

<id name="id" type="int" column="ALBUM_ID">

<meta attribute="scope-set">protected</meta>

<generator class="native"/>

</id>



<property name="albumName" column="NAME" type="string"/>



<bag

name="albumTrack"

inverse="true">

<key column="ALBUM_ID"/>

<many-to-many class="com.chello.mdr.mdrhibernate.Track"/>

</bag>

</class>



Even with this though I still get updates instead of inserts for the
duplicates.



Hibernate: insert into ALBUM (NAME) values (?)

Hibernate: insert into TRACK (NAME, ALBUM_ID) values (?, ?)

Hibernate: insert into TRACK (NAME, ALBUM_ID) values (?, ?)

Hibernate: insert into ALBUM (NAME) values (?)

Hibernate: update TRACK set NAME=?, ALBUM_ID=? where TRACK_ID=?

Hibernate: insert into ALBUM (NAME) values (?)

Hibernate: update TRACK set NAME=?, ALBUM_ID=? where TRACK_ID=?



And again this gives...



TRACK Table

TRACK_ID | NAME | ALBUM_ID
------------------------------------------
1 | Track One | 3
------------------------------------------
2 | Track Two | 2


Any help would be really appreciated with this.

Regards,
Kevin
 
I

iksrazal

Two quick things:

1) Might be worth looking at Oreilly Hibernate notebook examples, they
are similair - having tracks but unfortunately using artists instead
as albums for the example. Hint: The chapter is called "Mapping
Collection" .

2) Why not try List instead of Set and IdBag?

iksrazal
 
K

kevkev

I've got the O'Reilly Hibernate book but he uses join tables which my
legacy schema will not allow (although I really do wish I could!)

I can't see how I could use a list instead. Do you have an example by
any chance?

Regards,
Kevin
 
K

kevkev

Cheers Ross,

I see what you are saying about the many-to-many needing the
association table but what about using a one to many relationship?
From a relational point of view is it not possible to have something
like

ALBUM Table

ALBUM_ID | ALBUM_NAME
------------------------------
1 | Album One
------------------------------
2 | Album Two
------------------------------
3 | Album Three

and

TRACK Table

TRACK_ID | NAME | ALBUM_ID
------------------------------------------
1 | Track One | 1
------------------------------------------
2 | Track Two | 1
------------------------------------------
3 | Track One | 2
------------------------------------------
4 | Track Two | 3


It seams like quite a everyday type of relationship that Hibernate
should be able to handle.

Regards,
Kevin
 
K

kevkev

I'm now wondering if the problem is related to how I am persisting the
data to the database.

I've got a one-to-many relationship with the mapping documents below.
Now when I persist the data by running the test class three times, once
for each Album and it's tracks then the Track table is updated as I
want.

TRACK_ID | NAME | ALBUM_ID
------------------------------------------
1 | Track One | 1
------------------------------------------
2 | Track Two | 1
------------------------------------------
3 | Track One | 2
------------------------------------------
4 | Track Two | 3

But when I persist all the albums and their tracks in one go then the
TRACK table is updated rather than having a row inserted and I get the
following which I don't want.
TRACK Table
TRACK_ID | NAME | ALBUM_ID
------------------------------------------
1 | Track One | 2
------------------------------------------
2 | Track Two | 3
Now I understand that the data only gets persisted once the connection
is closed so how do I get Hibernate to either persist the data straight
away/or not to update the data and perform a new insert.

Any help would be really appreciated with this.

Regards,
Kevin

Code:
<class name="Album" table="ALBUM">
<id name="id" type="int" column="ALBUM_ID" unsaved-value="any">
<meta attribute="scope-set">protected</meta>
<generator class="native"/>
</id>

<property name="albumName" column="NAME" type="string"/>

<set name="tracks" cascade="all" inverse="true" lazy="true">
<key column="ALBUM_ID"/>
<one-to-many class="com.kbilly.mdr.mdrhibernate.Track"/>
</set>
</class>

And

Code:
<class name="Track" table="TRACK">
<id name="id" type="int" column="TRACK_ID" unsaved-value="any">
<meta attribute="scope-set">protected</meta>
<generator class="native"/>
</id>

<property name="trackName" column="NAME" type="string"/>
<many-to-one
name="album"
column="ALBUM_ID"
cascade="all"
class="com.kbilly.mdr.mdrhibernate.Album"/>
</class>

The code I have to persist data is as follows.

Code:
public class TestAlbums {
public static void main(String[] args) {

Track t1 = new Track();
t1.setTrackName("Track One");
Track t2 = new Track();
t2.setTrackName("Track Two");
Track t3 = new Track();
t3.setTrackName("Track Three");

Album one = new Album();
one.setAlbumName("Album One");
Album two = new Album();
two.setAlbumName("Album Two");
Album three = new Album();
three.setAlbumName("Album Three");

Vector list1 = new Vector();
list1.add(t1);
list1.add(t2);

Vector list2 = new Vector();
list2.add(t1);

Vector list3 = new Vector();
list3.add(t2);

Connection conn = new Connection();
AlbumDAO album = new AlbumDAO();
try {
conn.create();
album.setAssociations(one, list1);
album.setAssociations(two, list2);
album.setAssociations(three, list3);
conn.close();

} catch (HibernateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}


public class AlbumDAO extends BaseAlbumDAO {

public void setAssociations(Album album, List tracks ) throws
HibernateException{
saveAlbum(album);
for(int i = 0; i <tracks.size(); i++){
Track track = (Track) tracks.get(i);
track.setAlbum(album);
saveTrack(track);
}
}

public void saveAlbum(Album album) throws HibernateException{
Session session = Connection.getSession();
Transaction tx = session.beginTransaction();
save(album, session);
session.flush();
tx.commit();
}

public void saveTrack(Track track) throws HibernateException{
Session session = Connection.getSession();
Transaction tx = session.beginTransaction();
save(track, session);
session.flush();
tx.commit();
}
}
 
R

Ross Bamford

I'm now wondering if the problem is related to how I am persisting the
data to the database.

I've got a one-to-many relationship with the mapping documents below.
Now when I persist the data by running the test class three times, once
for each Album and it's tracks then the Track table is updated as I
want.

Right, I imported this onto my bench, and I think you were quite close,
there were just a few of Hibernates 'quirks' in the mappings that were
throwing you. I believe I have the behaviour you're after now, with the
mappings:


// -----
<class name="Album" table="ALBUM">
<id name="id" type="long" column="ALBUM_ID">
<generator class="increment"/>
</id>

<property name="albumName" column="NAME"/>

<set name="tracks" cascade="save-update" lazy="true">
<key column="ALBUM_ID"/>
<one-to-many class="Track"/>
</set>
</class>
// -----

// -----
<class name="Track" table="TRACK">
<id name="id" type="long" column="TRACK_ID">
<generator class="increment"/>
</id>
<property name="trackName" column="NAME"/>
<many-to-one name="album" column="ALBUM_ID" class="Album"/>
</class>
// -----

This deviates from your original specification, in that a track can
belong to only one album, and at most once, but I think this is what
you're after?

With this relationship you must ensure you link tracks to their album,
as well as adding them to the set. I usually protect the bean getters
and setters and provide a public interface with something like:

public void addTrack(Track track) {
track.album = this;
tracks.add(track);
}

It is possible to have this done for you, but that kind of setup has
caused me some pain so now I keep it simple where I can...

A quick usage example (assumes constructors and the above method):

public makeSomeAlbums() {
Session session = Hibernate.currentSession();
Transaction tx = session.beginTransaction();

Album greatest = new Album("Greatest Hits");
Album worstest = new Album("Worstest Bits");

greatest.addTrack(new Track("Track one"));
greatest.addTrack(new Track("Track two"));
worstest.addTrack(new Track("Track one"));
worstest.addTrack(new Track("Track two"));

session.save(greatest);
session.save(worstest);

tx.commit();
Hibernate.closeSession();
}

Gives you an almost identical (save for the actual data) table to that
you originally posted.

Well, hope that helps :)
 
R

Ross Bamford

// -----
<class name="Album" table="ALBUM">
<id name="id" type="long" column="ALBUM_ID">
<generator class="increment"/>
</id>

<property name="albumName" column="NAME"/>

<set name="tracks" cascade="save-update" lazy="true">
<key column="ALBUM_ID"/>
<one-to-many class="Track"/>
</set>
</class>
// -----

The set in this mapping should be 'inverse=true'. Sorry.

Also, to address another point, Remember that with the Set you don't get
any ordering, and you don't have enough data in your legacy tables to
provide track ordering per album. Does that matter?
 
K

kevkev

Ross, you are a star!

Reading you mail made me realise that I was trying to persist the same
track object to different albums and therefore was an update the to the
existing transient track object.

Of course I needed to made a new track each time. I feel so dumb, but
hey it's sorted now. I'm sure I'll have more questions in the future
though! :)

Thank you so much for all your help with this. You're a wonderful
example of the community spirit.

Regards,
Kevin
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,813
Latest member
lawrwtwinkle111

Latest Threads

Top