m:n

Full source code is provided in package: de.laliluna.relation.many2many Uni-directional

Classes

Tables

images/c_relation_many2many1_classes.jpg

images/c_relation_many2many1_tables.jpg

Annotation mapping. 

import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
......... snip .........
@Entity
public class Concert1 implements Serializable {

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "concert_visitor",
         joinColumns = { @JoinColumn(name = "concert_id") },
         inverseJoinColumns = { @JoinColumn(name = "visitor_id") })
   private List<Visitor1> visitors = new ArrayList<Visitor1>();

@ManyToMany(cascade = CascadeType.ALL) specifies the relation. @JoinTable(name = "concert_visitor", specifies the join table. joinColumns specifies which columns reference the Concert primary key. inverseJoinColumns specifies which columns reference the Visitor primary key. The Visitor1 class has no relation related annotations.

XML Mapping. The relation is completely defined on the concert side. I used the tag

foreign-key

only to define the name of the foreign key as I reuse the tables for multiple mapping examples. Not naming the foreign keys would lead to random key names and double foreign key generation. Usually you do not need this property.

<hibernate-mapping package="de.laliluna.relation.many2many">
  <class name="Concert1" table="tconcert">
........ snip ........
    <list name="visitors" table="visitor_concert" >
      <key column="concert_id" foreign-key="visitor_concert_concert_id_fkey"/>
      <list-index column="list_index"></list-index>
      <many-to-many class="Visitor1"
         foreign-key="visitor_concert_visitor_id_fkey">
        <column name="visitor_id"  ></column>
      </many-to-many>
    </list>
  </class>
</hibernate-mapping>

The created tables are shown below:

CREATE TABLE tconcert
(
  id int4 NOT NULL,
  name varchar(255),
 PRIMARY KEY (id)
) ;
CREATE TABLE tvisitor
(
  id int4 NOT NULL,
  name varchar(255),
  PRIMARY KEY (id)
) ;
CREATE TABLE visitor_concert
(
  concert_id int4 NOT NULL,
  visitor_id int4 NOT NULL,
  list_index int4 NOT NULL,
  PRIMARY KEY (visitor_id, list_index),
  FOREIGN KEY (concert_id)
      REFERENCES tconcert (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  FOREIGN KEY (visitor_id)
      REFERENCES tvisitor (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
) ;

Samples of use:

/* create and set relation */
      Visitor1 visitor1 = new Visitor1(null, "Edgar");
      Concert1 concert1 = new Concert1(null, "Udo Juergens");
      concert1.getVisitors().add(visitor1);
      session.save(visitor1);
      session.save(concert1);

/* delete */
// reattach the visitor to our new session
      session.buildLockRequest(LockOptions.NONE).lock(visitor1);
      // remove visitor from all concerts
      List list = session.createQuery(
            "from Concert1 c where ? in elements(c.visitors)").setEntity(0,
            visitor1).list();
      for (Iterator iter = list.iterator(); iter.hasNext();)
      {
         Concert1 element = (Concert1) iter.next();
         element.getVisitors().remove(visitor1);
      }
      // delete visitor
      session.delete(visitor1);

/* select all concerts having a visitor named Carmen */
      List list = session
            .createQuery(
                "select distinct c from Concert1 c left join c.visitors v "
            +"where v.name like 'Carmen%' order by c.id").list();

Bi-directional

Classes

Tables

images/c_relation_many2many2_classes.jpg

images/c_relation_many2many2_tables.jpg

Annotation mapping. 

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
........ snip .........
@Entity
public class Concert2 implements Serializable {

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "concert_visitor_2",
      joinColumns = { @JoinColumn(name = "concert_id") },
      inverseJoinColumns = { @JoinColumn(name = "visitor_id") })
   private List<Visitor2> visitors = new ArrayList<Visitor2>();

@ManyToMany(cascade = CascadeType.ALL) specifies the relation. @JoinTable(name = "concert_visitor", specifies the join table. joinColumns specifies which columns reference the Concert primary key. inverseJoinColumns specifies which columns reference the Visitor primary key.

import java.util.ArrayList;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
........... snip ..........

@Entity
public class Visitor2 implements Serializable {

   @ManyToMany(mappedBy="visitors", cascade=CascadeType.ALL)
   private List<Concert2> concerts = new ArrayList<Concert2>();

@ManyToMany(mappedBy="visitors", cascade=CascadeType.ALL) specifies that the relation is managed by the property visitors of the Concert class. If both sides manage a relation, they will both try to write the relation into the database and cause a primary key violation.

XML Mapping. 

<hibernate-mapping package="de.laliluna.relation.many2many ">
  <class name="Concert2" table="tconcert">
......... snip ........
    <list name="visitors" table="visitor_concert" >
      <key column="concert_id" foreign-key="visitor_concert_concert_id_fkey"/>
      <list-index column="list_index"></list-index>
      <many-to-many class="Visitor2" column="visitor_id"
         foreign-key="visitor_concert_visitor_id_fkey"></many-to-many>
    </list>
  </class>
</hibernate-mapping>

<hibernate-mapping package="de.laliluna.relation.many2many ">
  <class name="Visitor2" table="tvisitor">
......... snip ........
    <list name="concerts" table="visitor_concert" inverse="true">
      <key column="visitor_id" foreign-key="visitor_concert_visitor_id_fkey"/>
      <list-index column="list_index"></list-index>
      <many-to-many class="Concert2" column="concert_id"
      foreign-key="visitor_concert_concert_id_fkey"></many-to-many>
    </list>
  </class>
</hibernate-mapping>

The created tables are the same as in our previous example. Examples of use can be found in the provided source code.

There are some important aspects you have to consider: column names If you do not set the foreign key column name of the visitor on the Concert side,

<many-to-many class="Visitor2" column="visitor_id"

then there will be a column created, even though the column is already defined on the visitor side with

 <list name="concerts" table="visitor_concert" inverse="true">
      <key column="visitor_id"

The column name used in this case is ELT. I don’t know why but I only warn you. Foreign-key Usually you will not need this tag. I needed it because I have two mappings (Concert1, Concert2) to the same table. When you map two classes to the same table you should name the foreign-keys or you will get the foreign keys created twice. Inverse="true" Inverse=true defines that this side will not manage the relation. You must set one side to inverse="true". If not, both sides try to write the relation into the database and cause a primary key violation. Correct lock usage We set inverse="true" on the visitor side or mappedBy on the visitor side without any cascades. As the visitor does not manage the relation, the deletion of a visitor will not remove the relation. You receive a foreign key exception. The reason is that if you reattach the visitor side, this does not reattach the concert side as well. The following code does not work as long as you do not uncomment the explicit lock for the concert.

      session.buildLockRequest(LockOptions.NONE).lock(visitor);
      List list = visitor.getConcerts();
      Concert2 concert = (Concert2) list.iterator().next();
      //session.buildLockRequest(LockOptions.NONE).lock(concert);
      concert.getVisitors().remove(visitor);
      visitor.getConcerts().remove(concert);

An alternative approach is to define Cascading on the visitor side.

    <list name="concerts" table="visitor_concert" inverse="true" cascade="lock">