I am working with a MySQL database and using Hibernate (6.6.0.Final) without Spring. My schema involves two tables, film
and film_text
, which are implicitly related in a one-to-one relationship. The film
table has a generated primary key (film_id
), and I want this key to be shared with the film_text
table as its primary key.
The logical relationship is such that a Film
entity knows about its corresponding Text
entity, but Text
does not hold a reference back to Film
. Here’s the structure of my tables:
DDL for film
:
CREATE TABLE `film` (
`film_id` smallint unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(128) NOT NULL,
`description` text,
`release_year` year DEFAULT NULL,
`language_id` tinyint unsigned NOT NULL,
`original_language_id` tinyint unsigned DEFAULT NULL,
`rental_duration` tinyint unsigned NOT NULL DEFAULT '3',
`rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
`length` smallint unsigned DEFAULT NULL,
`replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
`rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
`special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`film_id`),
KEY `idx_fk_language_id` (`language_id`),
KEY `idx_fk_original_language_id` (`original_language_id`),
CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB;
DDL for film_text
:
CREATE TABLE `film_text` (
`film_id` smallint NOT NULL,
`title` varchar(255) NOT NULL,
`description` text,
PRIMARY KEY (`film_id`),
FULLTEXT KEY `idx_title_description` (`title`, `description`)
) ENGINE=InnoDB;
In my Hibernate model, I use the @MapsId
annotation to ensure that the Text
entity reuses the primary key of the associated Film
. Here is the code for my entities:
Film
entity:
@Entity
@Table(schema = "movie", name = "film")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class Film {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "film_id")
private Integer id;
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@MapsId
@JoinColumn(name = "film_id", nullable = false)
@NonNull
private Text text;
// other fields and relationships
}
Text
entity:
@Entity
@Table(schema = "movie", name = "film_text")
@Getter
@Setter
@NoArgsConstructor
@RequiredArgsConstructor
public class Text {
@Id
@Column(name = "film_id")
@Setter(AccessLevel.NONE)
private Integer id;
@Column(nullable = false)
@NonNull
private String title;
@Column(length = 65_535)
private String description;
}
When I attempt to persist the Film
entity (which should cascade the persistence of Text
), I encounter the following error:
Identifier of entity 'Text' must be manually assigned before calling 'persist()'
org.hibernate.id.IdentifierGenerationException: Identifier of entity 'Text' must be manually assigned before calling 'persist()'
I expect that when I persist a Film
entity, Hibernate should generate an ID for the film
entity and automatically assign this ID to the associated Text
entity (since they share the same primary key).
@MapsId
in the Film
entity, which should map the primary key of Film
to Text
.CascadeType.ALL
.@JoinColumn
to ensure the correct mapping between the film_id
columns in both entities.Why is Hibernate not automatically assigning the film_id
from Film
to Text
during the persist()
operation, and how can I resolve this IdentifierGenerationException
? Is there something I am missing in my entity mapping that would allow the ID to be shared correctly between the two entities?
Any insights or suggestions would be greatly appreciated!
You have your relationships backwards, and misused MapsId annotation. You want that annotation on the 'other' side, on the Text->Film relationship as it tells JPA that this relationship (and its FK column) controls the ID column/attribute specified in the annotation. More like this:
@Entity
public class Film {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "film_id")
private Integer id;
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
@NonNull
private Text text;
// other fields and relationships
}
@Entity
public class Text {
@Id
@Column(name = "film_id")
private Integer id;
@MapsId
@JoinColumn(name = "film_id")
Film film;
// other fields and relationships
}
You can also get away without having the Integer id property in Text, as JPA allows relationships to be marked as the ID:
@Entity
public class Text {
@Id
@JoinColumn(name = "film_id")
Film film;
// other fields and relationships
}