I'm running a Spring Boot app.
I have these two tables in my database. I fill an example of data that contains these tables.
Table user
Here columns id and century are PKs, and column state_id is a FK from table state with a reference of column id and century is also a FK from table state. So the century column is a FK and a PK at the same time.
| id | century | name | state_id |
|---|---|---|---|
| 33 | 21 | John Doe | 1 |
Table state
Here, columns id and century are both primary keys of the table
| id | century | label |
|---|---|---|
| 1 | 21 | cold |
I want to model these two tables as JPA entities, so I did it like this:
StatePK.java:
@Embeddable
public class StatePK implements Serializable {
private Long id;
private Integer century;
}
State.java:
@Entity
@Table(name = "state")
public class State {
@EmbeddedId
private StatePK id;
private String label;
}
UserPK.java:
@Embeddable
public class UserPK implements Serializable {
private Long id;
private Integer century;
}
User.java:
@Entity
@Table(name = "user")
public class User implements Serializable {
@EmbeddedId
private UserPK id;
private String name;
@MapsId("century")
@ManyToOne(optional = false)
@JoinColumns(value = {
@JoinColumn(name = "century", referencedColumnName = "century"),
@JoinColumn(name = "state_id", referencedColumnName = "id")})
private State state;
}
When I launch the application, I have the following error:
Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.IllegalStateException: PostInitCallback queue could not be processed...\r\n - PostInitCallbackEntry - EmbeddableMappingType(fr.xxx.User#{id})#finishInitialization
How can I resolve this error?
Your current configuration cannot work with @MapsId. This is because the primary key of the dependent entity (user) contains only part of the primary key of the parent entity (state).
According to the docs of @MapsId
Designates a
ManyToOneorOneToOnerelationship attribute that provides the mapping for anEmbeddedIdprimary key, an attribute within anEmbeddedIdprimary key, or a simple primary key of the parent entity.
The attributes in the @EmbeddedId field of type UserPK are not sufficient to uniquely identify the related state. A state is identified by the pair (id;century), while a user contains only century in its @EmbeddedId. This is not enough for @MapsId to map the parent entity's primary key into the dependent entity's primary key.
To fix your problem, you should refactor UserPK, and replace century with a field of type StatePK. Then, annotate the field state in User with @MapsId, and supply the name of the new StatePK attribute.
For further reference, here is a link to section 2.4.1. Primary Keys Corresponding to Derived Identities of Jakarta Specification. This paragraph covers how the identity of an entity can be derived from another entity. Below (section 2.4.1.3. Examples of Derived Identities) are also showcased all possible derivation scenarios. Your situation falls under example 3 - case (b). Notice how in all examples, the dependent entity's primary key includes entirely the parent entity's primary key.
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Data
public class StatePK implements Serializable {
private Long id;
private Integer century;
}
@Entity
@Table(name = "state")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class State {
@EmbeddedId
private StatePK id;
private String label;
}
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserPK implements Serializable {
private Long id;
private StatePK stateKey;
}
@Entity
@Table(name = "users")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class User implements Serializable {
@EmbeddedId
private UserPK id;
private String name;
@MapsId("stateKey")
@ManyToOne(optional = false)
@JoinColumns({
@JoinColumn(name = "state_id", referencedColumnName = "id"),
@JoinColumn(name = "state_century", referencedColumnName = "century")
})
private State state;
}
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Bean
public CommandLineRunner demo(EntityDemoService service) {
return args -> service.demonstrateEntities();
}
}
@Service
class EntityDemoService {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void demonstrateEntities() {
State state = new State();
state.setId(new StatePK(1L, 21));
state.setLabel("California");
entityManager.persist(state);
System.out.println("Inserted State: " + state);
User user = new User();
user.setId(new UserPK(100L, new StatePK(1L, 21)));
user.setName("John Doe");
user.setState(state);
entityManager.persist(user);
System.out.println("Inserted User: " + user.getName());
entityManager.flush();
entityManager.clear();
System.out.println("\n--- Reading back from database ---\n");
UserPK searchKey = new UserPK();
searchKey.setId(100L);
searchKey.setStateKey(new StatePK(1L, 21));
User foundUser = entityManager.find(User.class, searchKey);
System.out.println("Found User: " + foundUser);
}
}