skip to Main Content

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


  1. Chosen as BEST ANSWER

    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:

        @Bean
        public CommandLineRunner commandLineRunner(CharacterRepository characterRepository,
                                                   TeamRepository teamRepository) {
            return args -> {
                // Initializing
                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);
    
                // Step 1
                teamRepository.save(avengers);
    
                // Step 2
                ironMan.setTeam(avengers);
                thor.setTeam(avengers);
    
                // Step 3
                characterRepository.saveAll(List.of(ironMan, thor));
    
                // Trying to assign foreign keys
                avengers.setCharacters(List.of(ironMan, thor));
            };
        }
    

  2. 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:

    @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;
    
        // Helper Method
        public void addCharacter(Character character){
          characters.add(character);
          character.setTeam(this);
        }
       // You can have a similar method for adding many characters at once
    }
    

    And then change the command line runner as follows:

    @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));
                
    
                // Trying to assign foreign keys
                avengers.addCharacter(ironMan);
                avengers.addCharacter(thor);
                teamRepository.save(avengers);
            };
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search