Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / General / May 2005

Tip: Looking for answers? Try searching our database.

Hibernate allowing duplicates with idbag

Thread view: 
kevkev - 24 May 2005 13:52 GMT
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
iksrazal@terra.com.br - 24 May 2005 18:44 GMT
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
kevkev - 24 May 2005 22:51 GMT
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
Ross Bamford - 25 May 2005 11:41 GMT
> 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.

I think you are going to struggle here I'm afraid. Without the
possibility of creating a collection table you cannot map the many-to-
many association in the way I think you're aiming for.

The one-to-many relationship you're using explicitly prevents entities
existing in more than one collection, so even though a bidirectional
one-to-many looks at first sight promising, I still don't think it'll
work.

Hmm... :/

Signature

  [Ross A. Bamford]     [ross AT the.website.domain]
Roscopeco Open Tech ++ Open Source + Java + Apache + CMF
http://www.roscopec0.f9.co.uk/ + info@the.website.domain

kevkev - 26 May 2005 10:37 GMT
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
kevkev - 26 May 2005 14:09 GMT
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>
[/code]

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>
[/code]

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();
    }   
}
[/code]
Ross Bamford - 26 May 2005 23:19 GMT
> I'm now wondering if the problem is related to how I am persisting the
> data  to the database.
[quoted text clipped - 3 lines]
> 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 :)

Signature

  [Ross A. Bamford]     [ross AT the.website.domain]
Roscopeco Open Tech ++ Open Source + Java + Apache + CMF
http://www.roscopec0.f9.co.uk/ + info@the.website.domain

Ross Bamford - 27 May 2005 00:13 GMT
> // -----
> <class name="Album" table="ALBUM">
[quoted text clipped - 10 lines]
> </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?

Signature

  [Ross A. Bamford]     [ross AT the.website.domain]
Roscopeco Open Tech ++ Open Source + Java + Apache + CMF
http://www.roscopec0.f9.co.uk/ + info@the.website.domain

kevkev - 27 May 2005 09:54 GMT
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


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.