coding, howto

How entity associations/relationships are mapped by an ORM

To demonstrate how mapping is carried out by ORM, Hibernate was used with JPA2 annotation syntax and MySQL as database. Two trivial entities are used, “Book” and “Store” for both, one-to-many/many-to-one and many-to-many demo. However, this is no particularly good design, since an individual book cannot be physically located in two places, but for this demo, it is an appropiate abstraction.

A book is considered as being owned by one (many) store(s).

1. One-To-Many/Many-To-One

1.1 Unidirectional

1.1.1 Many-To-One

First, let’s have a look at an excerpt the “Book” class. The association has got to be defined here, because many using unidirectional association of the many-to-one type, this one is the “many” side:

@Entity
public class Book extends BusinessObject {
    // Unidirectional Many-to-one
    // No assoc. in Book required
    // getters/setters required
    @ManyToOne
    @JoinColumn(name = "store_fk")
    private Store store;
...
}

Since this is an unidirectional association coming from the Book side, the Store (one) side needs no further association.

@Entity@Table(name = "store")
public class Store extends BusinessObject {
    // No assoc. required
...
}

Running this will result into two tables created, one per entity (ignoring the obligatory and hibernate specific “hibernate_sequences” table). “store_fk” as defined by @JoinColumn of the Book class is mapped as an attribute of the table “Book”.

mysql> show tables from dbjava;
+---------------------+
| Tables_in_dbjava    |
+---------------------+
| book                |
| hibernate_sequences |
| store               |
+---------------------+
3 rows in set (0.00 sec)

mysql> describe `dbjava`.BOOK;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| id          | int(11)  | NO   | PRI | NULL    |       |
| store_fk    | int(11)  | YES  | MUL | NULL    |       |
+-------------+----------+------+-----+---------+-------+
6 rows in set (0.00 sec)

mysql> describe `dbjava`.STORE;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id_store | int(11)      | NO   | PRI | NULL    |       |
+----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
1.1.2 One-To-Many

In this case, the association is defined from the inverse point of view, leaving Book without an association to be declared, while the association is now declared by the Store entity.

@Entity
public class Book extends BusinessObject {
    // No assoc. required
}
@Entity
public class Store extends BusinessObject {

    // Unidirectional One-To-Many
    // No assoc. in Book required
    // getters/setters required
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "store_fk")
    private Set<Book> books;

    public Set<Book> getBooks() {
        return books;
    }

    public void setBooks(Set<Book> books) {
        this.books = books;
    }
    ...
}

And the resulting database which, using these queries, looks identically:

mysql> show tables from dbjava;
+---------------------+
| Tables_in_dbjava    |
+---------------------+
| book                |
| hibernate_sequences |
| store               |
+---------------------+
3 rows in set (0.00 sec)

mysql> describe `dbjava`.BOOK;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| id          | int(11)  | NO   | PRI | NULL    |       |
| store_fk    | int(11)  | YES  | MUL | NULL    |       |
+-------------+----------+------+-----+---------+-------+
6 rows in set (0.00 sec)

mysql> describe `dbjava`.STORE;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id_store | int(11)      | NO   | PRI | NULL    |       |
+----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

1.2 Bidirectional with the “many” side as the owner

In order to define a bidirectional association between both entities, one side is considered to be the owner. In this case, it is the Store that owns a Book. This fact is realized by the “mappedBy” parameter on the owner side, while the owned side carries the Owner’s id within the database.

@Entity
public class Store extends BusinessObject {

    // Bidirectional One-To-Many
    // This side is owner, thus, this collection is mappedBy
    // getters/setters required
    @OneToMany(mappedBy = "store")
    private Set<Book> books;

    public Set<Book> getBooks() {
        return books;
    }

    public void setBooks(Set<Book> books) {
        this.books = books;
    }
...
}
@Entity
public class Book extends BusinessObject {

    // Bidirectional Many-To-one
    // Book is owned by one store
    // getters/setters required
    @ManyToOne
    @JoinColumn(name="store_fk")
    private Store store;

    public Store getStore() {
        return store;
    }

    public void setStore(Store store) {
        this.store = store;
    }
...
}
mysql> show tables from dbjava;
+---------------------+
| Tables_in_dbjava    |
+---------------------+
| book                |
| hibernate_sequences |
| store               |
+---------------------+
3 rows in set (0.00 sec)

mysql> describe `dbjava`.BOOK;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| id          | int(11)  | NO   | PRI | NULL    |       |
| store_fk    | int(11)  | YES  | MUL | NULL    |       |
+-------------+----------+------+-----+---------+-------+
6 rows in set (0.01 sec)

mysql> describe `dbjava`.STORE;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id_store | int(11)      | NO   | PRI | NULL    |       |
+----------+--------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

2. Many-to-Many

Many-to-many relations typically need another table that provides the association of the associated entities. Both classes need to define the association, however, since one side is regarded as the owner, the definition is asymetric.

@Entity
public class Book extends BusinessObject {

    // Many to many, the other side is the owned
    // Getters & setters are required
    @ManyToMany(
            cascade = {CascadeType.PERSIST, CascadeType.MERGE},
            mappedBy = "books",
            targetEntity = Store.class
    )
    private Collection stores;

    public Collection getStores() {
        return stores;
    }

    public void setStores(Collection stores) {
        this.stores = stores;
    }
@Entity
@Table(name = "store")
public class Store extends BusinessObject {
    @ManyToMany(
            targetEntity = de.tayefeh.businessobjects.Book.class,
            cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(
            name="store_book",
            joinColumns = @JoinColumn(name = "store_id"),
            inverseJoinColumns = @JoinColumn(name = "book_id")
    )
    private Collection books;

    public Collection getBooks() {
        return books;
    }

    public void setBooks(Collection books) {
        this.books = books;
    }
...
}

In this case, the “store_book” table is actually created and we have three entity tables for two entities. However, there are no additional columns added to the tables of the original entities:

mysql> show tables from dbjava;
+---------------------+
| Tables_in_dbjava    |
+---------------------+
| book                |
| hibernate_sequences |
| store               |
| store_book          |
+---------------------+
4 rows in set (0.00 sec)

mysql> describe `dbjava`.BOOK;
+-------------+----------+------+-----+---------+-------+
| Field       | Type     | Null | Key | Default | Extra |
+-------------+----------+------+-----+---------+-------+
| id          | int(11)  | NO   | PRI | NULL    |       |
+-------------+----------+------+-----+---------+-------+
5 rows in set (0.00 sec)

mysql> describe `dbjava`.STORE;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id_store | int(11)      | NO   | PRI | NULL    |       |
+----------+--------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

mysql> describe `dbjava`.STORE_BOOK;
+----------+---------+------+-----+---------+-------+
| Field    | Type    | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+-------+
| store_id | int(11) | NO   | MUL | NULL    |       |
| book_id  | int(11) | NO   | MUL | NULL    |       |
+----------+---------+------+-----+---------+-------+
2 rows in set (0.01 sec)
Advertisements
Standard

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s