Skip to main content

Many To One

This chapter introduces how to use the @org.babyfish.jimmer.sql.ManyToOne annotation to declare many-to-one association properties.

There are two ways to implement many-to-one associations: based on foreign key and based on join table.

1. Based on Foreign Key

Book.java
@Entity
public interface Book {

@ManyToOne
BookStore store();

...Omit other code...
}

Here @JoinColumn is not used together to explicitly specify the foreign key column name. Jimmer will deduce the column name corresponding to the store property based on the naming strategy.

If the default naming strategy is not overridden by the user, the foreign key column name of the store property is STORE_ID. So the previous code is equivalent to:

Book.java
@Entity
public interface Book {

@ManyToOne
@JoinColumn(name = "STORE_ID")
BookStore store();

...Omit other code...
}

The foreign key can be real or fake. Fake foreign keys are discussed in subsequent documents. Here we assume the foreign key is real, so the corresponding constraint in the database is:

// If foreign key pointing to associated entity is real, 
// create foreign key constraint
alter table BOOK
add constraint FK_BOOK__BOOK_STORE
/* highlight-next-line */
foreign key(STORE_ID)
references BOOK_STORE(ID);

2. Based on Join Table

Book.java
@Entity  
public interface Book {

@Nullable
@ManyToOne
@JoinTable
BookStore store();

...
}

Here, no properties are specified for @JoinTable. Jimmer will deduce the column names corresponding to the store property based on the naming strategy.

If the default naming strategy is not overridden by the user, the deduced join table information is:

  • Join table name: BOOK_BOOK_STORE_MAPPING
  • Join table foreign key column pointing to current entity: BOOK_ID
  • Join table foreign key column pointing to associated entity: STORE_ID

So the previous code is equivalent to:

Book.java
@Entity
public interface Book {

@Null
@ManyToOne
@JoinTable(
name = "BOOK_BOOK_STORE_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "STORE_ID"
)
BookStore store();

...
}

The join table BOOK_BOOK_STORE_MAPPING is defined as:

create table BOOK_BOOK_STORE_MAPPING(
BOOK_ID bigint not null,
STORE_ID bigint not null
);

alter table BOOK_STORE_MAPPING
add constraint PK_BOOK_STORE_MAPPING
primary(BOOK_ID, STORE_ID);

// If foreign key pointing to current entity is real,
// create foreign key constraint
alter table BOOK_BOOK_STORE_MAPPING
add constraint FK_BOOK_BOOK_STORE_MAPPING__BOOK
foreign key(BOOK_ID)
references BOOK(ID);

// If foreign key pointing to associated entity is real,
// create foreign key constraint
alter table BOOK_BOOK_STORE_MAPPING
add constraint FK_BOOK_BOOK_STORE_MAPPING__STORE
foreign key(STORE_ID)
references BOOK_STORE(ID);

// This constraint is very important.
// Otherwise this join table expresses a many-to-many association
// rather than a many-to-one association
alter table BOOK_BOOK_STORE_MAPPING
add constraint UQ_BOOK_BOOK_STORE_MAPPING__BOOK_ID
unique(BOOK_ID);
  • The join table has only two foreign keys, both non-null. The join table maintains the association by inserting and deleting data, never storing null data itself.

  • The join table has no corresponding ORM entity and does not need an independent primary key. The two foreign keys combined can serve as the primary key.

  • By default, the join table represents a many-to-many association. To degrade it to a many-to-one association, a unique constraint must be specified for the foreign key BOOK_ID pointing to the current entity.

caution
  • Unless for compatibility with existing database design, many-to-one associations are recommended to use foreign keys directly instead of join tables.

  • Once a many-to-one association is mapped using a join table, the Jimmer association property must be nullable, because the database cannot guarantee that any entity must have corresponding data in the join table.