javahibernatejpahibernate-annotations

IdentifierGenerationException when persisting @OneToOne relationship with shared primary key


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()'

The expected behavior:

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).

What I've tried:

  1. I've set up @MapsId in the Film entity, which should map the primary key of Film to Text.
  2. I've ensured that cascading is properly configured with CascadeType.ALL.
  3. I've used @JoinColumn to ensure the correct mapping between the film_id columns in both entities.

Question:

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!


Solution

  • 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
    }