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:
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:
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.
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
):
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.)
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; … }
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.
3.144.244.250