thesystem
thesystem

Reputation: 604

Spring Boot with hibernate: Creating Composite Key and Many-to-many relations

I am trying to create a very simple Spring Boot application for storing sports data.

It has two entities: player and tournament. However, I want to store how each player placed in each tournament.

For this, I created the following ER Diagram. The junction table PlayerPlacement_map contains a relationship-attribute placement for storing how the players place in a tournament: enter image description here

I have followed this guide on how to map the relationship between Players and Tournament, with Id from those two tables as a composite key in the junction table called PlayerPlacementMap: https://www.baeldung.com/jpa-many-to-many

That has given me the following classes:

Player.java (Tournament.java follows a similar pattern - left out for brevity)

@Entity
@Table(name = "players")
public class Player {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id", updatable = false, nullable = false)
    private long id;
                 

    @Column(name = "Name")
    private String name;

    @ManyToMany
    @JoinTable(
        name = "PlayerTournament_map",
        joinColumns = @JoinColumn(name = "Player_Id"),
        inverseJoinColumns = @JoinColumn(name = "Tournament_Id"))
    Set<Tournament> attendedTournaments;

    @OneToMany(mappedBy = "player")
    Set<PlayerPlacement> placements;

    //getters, constructors - left out for brevity
}

PlayerPlacementKey.java (making an embeddable composite key-class)

@Embeddable
public class PlayerPlacementKey implements Serializable {

    @Column(name = "Player_Id")
    long playerId;

    @Column(name = "Tournament_Id")
    long tournamentId;

    //getters, setters, constructors, equals, hashcode - left out for brevity
}

PlayerPlacement.java (junction table)

@Entity
@Table(name = "PlayerPlacement_map")
public class PlayerPlacement {

    @EmbeddedId
    PlayerPlacementKey id;

    @ManyToOne
    @MapsId("PlayerId")
    @JoinColumn(name = "Player_Id")
    Player player;

    @ManyToOne
    @MapsId("TournamentId")
    @JoinColumn(name = "Tournament_Id")
    Tournament tournament;

    int placement;

    //constructors - left out for brevity
}

I have repositories for player, tournament and playerplacement. Player and tournament repositories are working fine on their own, and I am able to perform CRUD operations through a @RestController against a MSSQL database.

This is the repository for playerplacement:

@Repository
public interface PlayerPlacementRepository extends JpaRepository<PlayerPlacement, PlayerPlacementKey> 
{}

For the junction table, I have made a PlayerPlacementController:

@RestController
public class PlayerPlacementController {

    @Autowired
    private PlayerPlacementRepository playerPlacementRepository;

    @PostMapping("/playerplacement")
    public PlayerPlacement addPlayerPlacement(@RequestBody PlayerPlacement playerPlacement) {
    return playerPlacementRepository.save(playerPlacement);
    }
}

Finally, here comes the problem: When I call the /"playerplacement" endpoint, I receive the following error:

org.hibernate.id.IdentifierGenerationException: null id generated for:class com.testproject.learning.model.PlayerPlacement

I think I have migrated the database just fine, but just to be safe, here is my migration for the junction table:

CREATE TABLE PlayerPlacement_map (

PlayerId Bigint NOT NULL,
TournamentId Bigint NOT NULL,
Placement int

CONSTRAINT PK_PlayerPlacement NOT NULL IDENTITY(1,1) PRIMARY KEY
(
    PlayerId,
    TournamentId
)

FOREIGN KEY (PlayerId) REFERENCES Players (Id),
FOREIGN KEY (TournamentId) REFERENCES Tournaments (Id)
);

I haven't been able to figure out what I am doing wrong. I appreciate all help and pointers that I receive - thanks in advance.

EDIT: Progress has been made with help of user Alex V., but I hit a new problem as of right now.

I make the following JSON call:

{
    "player": 
    {
        "name":"John Winther"
    },
    "tournament": 
    {
        "name":"Spring Boot Cup 2020"
    },
    "placement":3
}

I don't set the id (composite key) myself - I guess it should be handled by something of the framework? Anyways, when I make this call in debug mode, I get the following debug variables set in the endpoint:

enter image description here

However, it results in the following error:

java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "o" is null, which probably refers to my equals-method in PlayerPlacementKey.java (?):

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof PlayerPlacementKey)) return false;
    PlayerPlacementKey that = (PlayerPlacementKey) o;
    return playerId == that.playerId &&
            tournamentId == that.tournamentId;
}

Hope anybody can push me in the right direction one more time.

Upvotes: 3

Views: 2345

Answers (1)

Oleksii Valuiskyi
Oleksii Valuiskyi

Reputation: 2841

Your entity has playerId field annotated as @Column(name = "Player_Id"), but database table contains PlayerId column instead of Player_Id. The same situation with tournamentId, player, tournament fields.

@MapsId("TournamentId") has to contain @EmbededId class field name tournamentId instead of TournamentId. It means the field is mapped by embeded id field tournamentId.

@ManyToOne
@MapsId("tournamentId")
@JoinColumn(name = "tournamentId")
Tournament tournament;

The same problem here @MapsId("PlayerId") .

Upvotes: 1

Related Questions