Search code examples
javahibernatenhibernatehibernate-mapping

One to many association in Hibernate without composite key


Table:

tradeId | actionTradeId | type | date

where tradeId+actionTradeId - composite key

Hibernate mapping:

<class name="Trade" table="TRADE">
 <composite-id name="id" class="TradePK">
        <key-property name="tradeId" type="long" column="trade_id"/>
        <key-property name="actionTradeId" type="long"        column="action_trade_id"/>
  </composite-id>
<property name="type" length="1"/>
<property name="date"/>
</class>

What I need

I want to have mapping on the same table like in this query:

select * 
from Trade 
where action_trade_id = trade_id and type = 'S'

First I tried this way, but it fails because I have composite key:

<set name="sellTrades" inverse="false" lazy="true" where="type='S'">
            <key>
                <column name="action_trade_id" not-null="true" />
            </key>
            <one-to-many class="Trade" />
</set>

Sample data:

 tradeId| actionTradeId | type| date 
 --------------------------------------
    11         22          S    date (so for this entity I need list with <55, 66>)
    33         44          S    date 
    55         11          S    date
    66         11          S    date

Solution

  • public class ClassWithCompositeKey
    {
        private int id1;
        private int id2;
    
        public ClassWithCompositeKey()
        {
            Children = new List<ClassWithCompositeKey>();
        }
    
        public virtual int Id1
        {
            get { return id1; }
            set { id1 = value; }
        }
        public virtual int Id2
        {
            get { return id2; }
            set { id2 = value; }
        }
    
        public virtual string Type { get; set; }
    
        public virtual ICollection<ClassWithCompositeKey> Children { get; protected set; }
    
        public override bool Equals(object obj)
        {
            var other = obj as ClassWithCompositeKey;
            return other != null && Id1 == other.Id1 && Id2 == other.Id2;
        }
    
        public override int GetHashCode()
        {
            return (Id1 << 16) | Id2;  // optimized for Id's < 16 bit
        }
    }
    

    mapping

    public class ClassWithCompositeKeyMap : ClassMap<ClassWithCompositeKey>
    {
        public ClassWithCompositeKeyMap()
        {
            CompositeId()
                .KeyProperty(x => x.Id1)
                .KeyProperty(x => x.Id2);
    
            Map(x => x.Type);
    
            Map(x => id1).Column("Id1").Access.Using("field").ReadOnly();
    
            HasMany(x => x.Children)
                .Where("Type = 'S'")
                .PropertyRef("id1")
                .KeyColumns.Add("Id2")
                .Inverse();
        }
    
        public virtual int id1 { get; set; }
    }
    

    and query

    using (var tx = session.BeginTransaction())
    {
        session.Save(new ClassWithCompositeKey { Id1 = 11, Id2 = 22, Type = "F" });
        session.Save(new ClassWithCompositeKey { Id1 = 55, Id2 = 11, Type = "S" });
        session.Save(new ClassWithCompositeKey { Id1 = 66, Id2 = 11, Type = "S" });
        tx.Commit();
    }
    session.Clear();
    
    var x = session.Get<ClassWithCompositeKey>(new ClassWithCompositeKey { Id1 = 11, Id2 = 22 });
    Assert.Equal(2, x.Children.Count);
    

    Note

    • lazyloading for the collection is disabled because of property-ref
    • be carefull to sync the Children collection with instances which should belong in it in code so you won't have a broken model

    Update as hbm

    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
      <class xmlns="urn:nhibernate-mapping-2.2" name="ClassWithCompositeKey">
        <composite-id>
          <key-property name="Id1" column="Id1"/>
          <key-property name="Id2" column="Id2"/>
        </composite-id>
        <bag inverse="true" lazy="true" name="Children" where="Type = 'S'">
          <key property-ref="id1">
            <column name="Id2" />
          </key>
          <one-to-many class="ClassWithCompositeKey" />
        </bag>
        <property name="Type" column="Type" />
        <property access="field" name="id1" column="id1" insert="false" update="false"/>
      </class>
    </hibernate-mapping>