Association cardinality

Just as relational databases define the cardinality of table relationships, ORM solutions also define the cardinality of entity associations. Before we discuss how to map associations, let's review how associations are created in a relational database. The following are the two types of relationship:

  • Foreign key relationship: You can relate a table to another by declaring a column as a foreign key of the other table. This is typically used for a 1:1 (one-to-one) or a M:1 (many-to-one) relationship. If you make the foreign key column UNIQUE, then you are defining a 1:1 relationship; otherwise, this is M:1. If you make it NULLABLE, then the relationship is optional; that is, 1:0, 1:1, M:0, or M:1.
  • Join table relationship: Another way to relate two tables is by creating a join table that will have at least two columns that store the primary keys of the rows that should be related. This is typically used for 1:M and M:N relationships. Both columns should be NON-NULLABLE.

One-to-one associations

Typically, in a relational database, when we refer to a one-to-one (1:1) relationship, we are really talking about one-to-zero (1:0) and one-to-one (1:1). This is because, to define a tight one-to-one relationship between OCCUPANT and ROOM, for example, we have to declare a foreign key from the OCCUPANT table to the ROOM table, and another foreign key from ROOM back to OCCUPANT table. There are two issues with this:

  • When inserting or deleting data, you have to defer integrity constraints and this complicates matters
  • Such a tight relationship indicates that two tables should really be one because one cannot exist without the other

This is why, in reality, a one-to-one association is really a one-to-zero_or_one. In this case, we identify an entity as the owning entity. For example, ROOM can exist without an OCCUPANT; hence, ROOM is the rightful owner of the relationship.

Another thing to keep in mind is the notion of direction. An association can be unidirectional or bidirectional. This simply defines a connection between two entities so that we can get to one from the other. If both can be reached either way, then it's a bidirectional relationship. Clearly, the notion of direction doesn't make sense in a relational database. If you can join two tables on a common column, then they are connected, but there is no such thing as direction, which implies reachability.

In a unidirectional association, if the referenced entity table has a foreign key constraint back to the owning entity table, you should use the @PrimaryKeyJoinColumn annotation on the referenced entity attribute. Let's revisit the ROOM example, this time adding the following occupant:

@Entity
public class Occupant {
  @Id
  private RoomId roomId;
  private Date checkinDate;
  private Date checkoutDate;
  // getters and setters
}

@Entity
public class Room {
  @Id
  private RoomId id;
  private double length;
  private double width;

  @OneToOne(cascade=CascadeType.ALL)
  @PrimaryKeyJoinColumn
  private Occupant occupant;
  // getters and setters
}

As you can see, it doesn't matter whether the ID of the owning entity is a simple or a composite ID. Ultimately, each ID column becomes a key in the secondary table.

In order to create a bidirectional association, both entities must have a reference to each other. To demonstrate this, let's rewrite the Car and Engine entities, as shown here (note that Engine no longer shares its ID with Car, as it did earlier.):

@Entity
public class Car {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
  private String model;
  @OneToOne
  private Engine engine;
  // getters and setters
}

@Entity
public class Engine {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;
  @OneToOne
  private Car car;
  private long size;
  // getters and setters
}

In this case, there is a tight association between the two entities. In fact, if you use Hibernate to generate the DDL for you, it will create two-way foreign key constraints, which we discussed earlier, and this might be problematic. Luckily, Hibernate creates a nullable foreign key, and it can insert the Car entity first with a NULL engine ID as it doesn't know the engine ID yet, then inserts the Engine entity, which now has an identifier. Finally, it updates the Car row to set the foreign key to the engine ID.

When persisting both Car and Engine, you need to make the association in Java; otherwise, Hibernate will not populate the foreign key columns:

    Car car = new Car();
    car.setModel("toyota");
    Engine engine = new Engine();
    engine.setSize(1500);
    engine.setCar(car);
    car.setEngine(engine);
    
    Session session = HibernateUtil
    .getSessionFactory()
    .getCurrentSession();
    Transaction transaction = session.beginTransaction();
    try {
    session.save(car);
    session.save(engine);
    
    transaction.commit();
    }
    catch (Exception e) {
    transaction.rollback();
    // log stack trace
    }
    finally {
    if (session.isOpen())
    session.close();
    }

In the next section, we will explore how to work with one-to-many associations.

One-to-many associations

When dealing with associations of the many nature, it's best if you think of the ownership of the association. Earlier, we defined ownership as the entity that knows of the other end of the association and is able to reach the associated entity. A hotel room can know a list of its occupants, but an occupant may not know to which room it belongs without exploring all rooms. In this case, the Room entity is the owner of the association.

Let's use the ROOM and OCCUPANT example to explain this.

If you create a one-to-many association between the Room and Occupant entities, Hibernate likes to create a join table in the database to keep track of this association. In this case, the Room entity keeps track of Occupants in a collection and Occupant has a simple ID:

@Entity
public class Room {
  @Id
  private RoomId id;
  private double length;
  private double width;
  @OneToMany(cascade=CascadeType.ALL)
  private Collection<Occupant> occupants;	
  // getters and setters
}

@Entity
public class Occupant {
  @Id
  @GeneratedValue
  private long id;
  private Date checkinDate;
  private Date checkoutDate;
  // getters and setters
}

This will only work if there is a join table between the ROOM table and the OCCUPANT tables (keep in mind that ROOM has a composite primary key, building number, and room number):

ROOM:

   

buildingnumber

roomnumber

length

width

4

5

8.5

12.4

OCCUPANT:

  

id

checkindate

checkoutdate

1

1/1/2015

1/3/2015

2

1/3/2015

1/6/2015

ROOM_OCCUPANT:

  

room_buildingnumber

room_roomnumber

occupants_id

4

5

1

4

5

2

If you'd like to eliminate the join table, you can add a reference to the room ID in the Occupant entity and set the mappedBy attribute in the @OneToMany annotation:

@Entity
public class Room {
  @Id
  private RoomId id;
  private double length;
  private double width;

  @OneToMany(cascade=CascadeType.ALL, mappedBy="roomId")
  private Collection<Occupant> occupants;
  // getters and setters
}

@Entity
public class Occupant {
  @Id
  @GeneratedValue
  private long id;
  private RoomId roomId;
  private Date checkinDate;
  private Date checkoutDate;
}

This will eliminate the need for a join table, but it adds additional columns to the OCCUPANT table to associate each record with the appropriate room. In this case, the primary key of the owner entity (Room) is stored as foreign key in the owned entity (Occupant):

One-to-many associations

Many-to-one associations

If you reverse the ownership of the association, then you will have a many-to-one relationship from Occupant to Room. This time, Hibernate can't get a list of the occupants for you, when you fetch a Room entity. You'll have to look up the occupants by room ID using an additional query. However, if you make the association bidirectional, that is, both entities know of each other, you can get to one from the other without any additional work.

Additionally, Hibernate provides a @NotFound annotation that is quite useful in the case of a many-to-one relationship. Use this annotation for cases where the other end of the association doesn't exist, perhaps because there are no integrity constraints. The default action is to throw an exception, but you can also set it to ignore. (Note that this is not available in JPA.)

Many-to-many associations

When entities should have a many-to-many association, you can let Hibernate know this using the @ManyToMany annotation. If you just add the @ManyToMany annotation to both sides of the association, Hibernate will create two join tables, one for each "owning entity." We saw similar behavior earlier with the one-to-many association. To eliminate the extra join table, we can use the same trick, that is, we use the mappedBy attribute of the @ManyToMany annotation. You can't add this to both sides. The mappedBy attribute is used on the "owned" entity, that is, if you consider teacher to be the owner of students, then add mappedBy to @ManyToMany of the Student entity:

@Entity
public class Teacher {
  …
  @ManyToMany
  private Collection<Student> students;
  …
}

@Entity
public class Student {
  …
  @ManyToMany(mappedBy="students")
  private Collection<Teacher> teachers;
  …
}

Self-referencing tables

You can think of a self-referencing table as an entity that has a one-to-many and a many-to-one association with itself. Based on our discussion on these topics, it should be easy to write the mapping for a self-referencing table, as follows:

@Entity
public class Node {

  @Id
  @GeneratedValue
  private long id;
  private String name;
  
  @ManyToOne(cascade=CascadeType.ALL)
  private Node parent;
  
  @OneToMany(mappedBy="parent")
  private Collection<Node> children = new ArrayList<Node>();
  // getters and setters
}

When you persist the entities, make sure that the associations are created on both sides; that is, add the children to the parent and set the parent for each child:

    Node parent = new Node();
    parent.setName("parent1");
    
    Node child1 = new Node();
    child1.setName("child1");
    child1.setParent(parent);
    
    parent.getChildren().add(child1);

    Node child2 = new Node();
    child2.setName("child2");
    child2.setParent(parent);
    
    parent.getChildren().add(child2);
    
    try {
      Session session = NodeHibernateUtil
        .getSessionFactory()
        .getCurrentSession();
      Transaction transaction = session.beginTransaction();
      try {
       session.save(parent);
       session.save(child1);
       session.save(child2);
     
       transaction.commit();
      }
      catch (Exception e) {
        transaction.rollback();
        // log stack trace
      }
      finally {
        if (session.isOpen())
          session.close();
       }
      
    }
    catch (Exception e) {
    e.printStackTrace();
  }

If the entities that are mapped to a self-referencing table have a many-to-many association, you have to create a join table to accommodate this association.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.244.250