I have been doing side spring-boot demo project about heroes and villains. Below is the Character class which is extended by Superhero and Villain classes:
@Data
@NoArgsConstructor
@Entity
@Table(name = "characters")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = Superhero.class, name = "superhero"),
@JsonSubTypes.Type(value = Villain.class, name = "villain")})
public abstract class Character {
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
private Integer id;
private String firstName;
private String lastName;
@Transient
private String fullName;
@Convert(converter = GenderConverter.class)
private Gender gender;
@Column(nullable = false)
private String alias;
@Convert(converter = StatusConverter.class)
private Status status;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
private String originStory;
@Convert(converter = UniverseConverter.class)
private Universe universe;
public Character(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Character(String firstName, String lastName, String alias) {
this.firstName = firstName;
this.lastName = lastName;
this.alias = alias;
}
public String getFullName() {
return firstName + " " + lastName;
}
}
Then comes Team which can consist of multiple heroes or villains, although one hero or villain can only be associated with one team only:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "teams")
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL)
private List<Character> characters;
@Convert(converter = StatusConverter.class)
private Status status;
}
Here is how I’m trying to initialize and those entities in repositories:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
/** Disable before running Tests! */
@Bean
public CommandLineRunner commandLineRunner(CharacterRepository characterRepository,
TeamRepository teamRepository) {
return args -> {
// Initilaizing
Superhero ironMan = new Superhero("Tony", "Stark", "Iron Man");
Superhero thor = new Superhero("Thor", "Odinson", "God of Thunder");
Team avengers = Team.builder().name("Avengers").status(DISBANDED).build();
// Setting attributes
ironMan.setGender(MALE); thor.setGender(MALE);
ironMan.setUniverse(MCU); thor.setUniverse(MCU);
ironMan.setStatus(DECEASED); thor.setStatus(ALIVE);
// Saving to repositories
characterRepository.saveAll(List.of(ironMan, thor));
teamRepository.save(avengers);
// Trying to assign foreign keys
avengers.setCharacters(List.of(ironMan, thor));
ironMan.setTeam(avengers);
thor.setTeam(avengers);
};
}
}
The result in postgres looks like this:
demo2=# SELECT * FROM teams;
id | name | status
----+----------+-----------
1 | Avengers | Disbanded
(1 row)
demo2=# SELECT * FROM superheroes;
id | team_id | alias | first_name | gender | last_name | origin_story | status | universe
----+---------+----------------+------------+--------+-----------+--------------+----------+----------
1 | | Iron Man | Tony | Male | Stark | | Deceased | MCU
2 | | God of Thunder | Thor | Male | Odinson | | Alive | MCU
(2 rows)
As you see the team_id
as foreign keys for both heroes are empty. I tried adding referencedColumnName
in JoinColumn
annotation, adding cascade types, fetching data and then setting setting attributes but neither helped.
2
Answers
Looks like I missed the order:
Firstly, I should have saved the team instance (not necessarily with characters attribute instantiated). Secondly, I should have assigned team attribute to player instances before saving them in the repository. Thirdly, After the first and second steps, I should have saved players in the repository.
So, the modified version looks like this:
I often find it useful to have helper methods in bidirectional @OneToMany and @ManyToOne relationships. For example in the Team Entity I would add the following one:
And then change the command line runner as follows: