Spring Hibernate:在使用组合主键存储n:m 表格值时出现 EntityExistsException 错误。

huangapple go评论75阅读模式
英文:

Spring Hibernate: EntityExistsException when storing n:m table value with combiend Priamry Key

问题

所以这是我第二篇帖子。这次我致力于我的一个热情项目,结果比我预期的要复杂得多,我再次需要一些帮助。

我有两个实体:Gamestate(游戏状态)和User(用户)。

用户应该能够加入多个游戏(/gamestates)。游戏(/gamestates)应该有多个人加入。因此,它表示为N:M关系。

根据谁何时加入以及何时加入,他们应该拥有不同的角色,在应用程序中赋予他们不同的权限。这意味着我需要一个带有自定义字段的N:M关系,因此我不得不自己建模关系表。这就是我到目前为止的进展。

抽象模型:

@EqualsAndHashCode
@Getter
@Setter
@ToString
public abstract class AbstractModel {
    @Id
    @GeneratedValue
    protected Long id;

    @NotNull
    protected String identifier;
}

用户(User)

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class User extends AbstractModel{

    private String nickName;
    private UserRole role;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = true)
    private LoginInformation loginInformation;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "gameState")
    private List<UserGameState> userGameStates = new ArrayList<>();

    //DTO Constructor
    public User(UserDTO userDTO){
        this.identifier = Optional.ofNullable(userDTO.getIdentifier())
                .orElse(UUID.randomUUID().toString());
        this.nickName = userDTO.getNickName() == null ? "": userDTO.getNickName();
        this.role = UserRole.valueOf(userDTO.getRole());

        this.loginInformation = null;
        if(userDTO.getLoginInformation() != null) {
            setLoginInformation(new LoginInformation(userDTO.getLoginInformation()));
        } else {
            setLoginInformation(new LoginInformation());
        }

        (userDTO.getUserGameStates() == null ? new ArrayList<GameStateDTO>() : userDTO.getUserGameStates())
                .stream()
                .map(x -> new UserGameState((UserGameStateDTO) x))
                .forEach(this::addUserGameState);
    }

游戏状态(GameState)

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class GameState extends AbstractModel{
    private String name;
    private String description;
    private String image;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = "user")
    private List<UserGameState> userGameStates = new ArrayList<>();

    //DTO Constructor
    public GameState(GameStateDTO gameStateDTO){
        this.identifier = Optional.ofNullable(gameStateDTO.getIdentifier())
                .orElse(UUID.randomUUID().toString());
        this.name = gameStateDTO.getName() == null ? "": gameStateDTO.getName();
        this.description = gameStateDTO.getDescription() == null ? "": gameStateDTO.getDescription();
        this.image = gameStateDTO.getImage() == null ? "": gameStateDTO.getImage();

        (gameStateDTO.getUserGameStates() == null ? new ArrayList<UserDTO>() : gameStateDTO.getUserGameStates())
                .stream()
                .map(x -> new UserGameState((UserGameStateDTO) x))
                .forEach(this::addUserGameState);
    }
    //----------------------1:1 Relationship Methods----------------------
    //----------------------1:N Relationship Methods----------------------
    public void addUserGameState(UserGameState userGameState) {
        if (userGameStates.contains(userGameState)) {
            return;
        }
        userGameStates.add(userGameState);
        userGameState.setGameState(this);
    }

    public void removeUserGameState(UserGameState userGameState) {
        if (!userGameStates.contains(userGameState)) {
            return;
        }
        userGameState.setGameState(null);
        userGameStates.remove(userGameState);
    }
    //----------------------N:1 Relationship Methods----------------------
    //----------------------N:M Relationship Methods----------------------
}

UserGameState(自定义的N:M表)

@Getter
@Setter
@Entity
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class UserGameState{

    @EmbeddedId
    private User_GameState_PK id;

    @ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
    @MapsId("user_id")
    @JoinColumn(name = "USER_ID", insertable = false, updatable = false)
    private User user;

    @ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
    @MapsId("gameState_id")
    @JoinColumn(name = "GAMESTATE_ID", insertable = false, updatable = false)
    private GameState gameState;

    //add Role later

    public UserGameState(User u, GameState gs) {
        // create primary key
        this.id = new User_GameState_PK(u.getId(), gs.getId());

        // initialize attributes
        setUser(u);
        setGameState(gs);

    }


    public UserGameState(UserGameStateDTO userGameStateDTO){
        //this.id =
        this.user = null;
        this.gameState = null;
    }



    //----------------------1:1 Relationship Methods----------------------
    //----------------------1:N Relationship Methods----------------------
    //----------------------N:1 Relationship Methods----------------------
    public void setUser(User user) {
        if (Objects.equals(this.user, user)) {
            return;
        }

        User oldUser = this.user;
        this.user = user;

        if (oldUser != null) {
            oldUser.removeUserGameState(this);
        }

        if (user != null) {
            user.addUserGameState(this);
        }
    }

    public void setGameState(GameState gameState) {
        if (Objects.equals(this.gameState, gameState)) {
            return;
        }

        GameState oldGameState = this.gameState;
        this.gameState = gameState;

        if (oldGameState != null) {
            oldGameState.removeUserGameState(this);
        }

        if (oldGameState != null) {
            oldGameState.addUserGameState(this);
        }
    }
    //----------------------N:M Relationship Methods----------------------
}

User_GameState_PK(组合键)

@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class User_GameState_PK implements Serializable {

    @Column(name = "USER_ID")
    private Long user_id;

    @Column(name = "GAMESTATE_ID")
    private Long gameState_id;

    public User_GameState_PK(long user_id, long gameState_id){
        this.user_id = user_id;
        this.gameState_id = gameState_id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass())
            return false;



<details>
<summary>英文:</summary>

So my second post. This time i worked on a passion project of mine, which turned out to be far more complicated than I expected and again I need some help.

I have two enitites: Gamestate and User.

Users are supposed to be able to join multiple Games(/gamestates). Games(/gamestates) are supposed to have muliple people join them. So therefore it is represented as a N:M Relation.

Depending on who joins and when they join they are supposed to have different roles, giving them different rights in the app. Which means I needed an N:M Relation with custom fields and therefore I had to model the relation table myself. That&#39;s as far as I have come.

Abstract Model:

```@MappedSuperclass
@EqualsAndHashCode
@Getter
@Setter
@ToString
public abstract class AbstractModel {
    @Id
    @GeneratedValue
    protected Long id;

    @NotNull
    protected String identifier;
}

User

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class User extends AbstractModel{

    private String nickName;
    private UserRole role;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = &quot;user&quot;, orphanRemoval = true)
    private LoginInformation loginInformation;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = &quot;gameState&quot;)
    private List&lt;UserGameState&gt; userGameStates = new ArrayList&lt;&gt;();

    //DTO Constructor
    public User(UserDTO userDTO){
        this.identifier = Optional.ofNullable(userDTO.getIdentifier())
                .orElse(UUID.randomUUID().toString());
        this.nickName = userDTO.getNickName() == null ? &quot;&quot;: userDTO.getNickName();
        this.role = UserRole.valueOf(userDTO.getRole());

        this.loginInformation = null;
        if(userDTO.getLoginInformation() != null) {
            setLoginInformation(new LoginInformation(userDTO.getLoginInformation()));
        } else {
            setLoginInformation(new LoginInformation());
        }

        (userDTO.getUserGameStates() == null ? new ArrayList&lt;GameStateDTO&gt;() : userDTO.getUserGameStates())
                .stream()
                .map(x -&gt; new UserGameState((UserGameStateDTO) x))
                .forEach(this::addUserGameState);
    }

GameState

@Getter
@Setter
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class GameState extends AbstractModel{
    private String name;
    private String description;
    private String image;

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY, mappedBy = &quot;user&quot;)
    private List&lt;UserGameState&gt; userGameStates = new ArrayList&lt;&gt;();

    //DTO Constructor
    public GameState(GameStateDTO gameStateDTO){
        this.identifier = Optional.ofNullable(gameStateDTO.getIdentifier())
                .orElse(UUID.randomUUID().toString());
        this.name = gameStateDTO.getName() == null ? &quot;&quot;: gameStateDTO.getName();
        this.description = gameStateDTO.getDescription() == null ? &quot;&quot;: gameStateDTO.getDescription();
        this.image = gameStateDTO.getImage() == null ? &quot;&quot;: gameStateDTO.getImage();

        (gameStateDTO.getUserGameStates() == null ? new ArrayList&lt;UserDTO&gt;() : gameStateDTO.getUserGameStates())
                .stream()
                .map(x -&gt; new UserGameState((UserGameStateDTO) x))
                .forEach(this::addUserGameState);
    }
    //----------------------1:1 Relationship Methods----------------------
    //----------------------1:N Relationship Methods----------------------
    public void addUserGameState(UserGameState userGameState) {
        if (userGameStates.contains(userGameState)) {
            return;
        }
        userGameStates.add(userGameState);
        userGameState.setGameState(this);
    }

    public void removeUserGameState(UserGameState userGameState) {
        if (!userGameStates.contains(userGameState)) {
            return;
        }
        userGameState.setGameState(null);
        userGameStates.remove(userGameState);
    }
    //----------------------N:1 Relationship Methods----------------------
    //----------------------N:M Relationship Methods----------------------
}

UserGameSatet (Custom N:M Table)

@Getter
@Setter
@Entity
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class UserGameState{

    @EmbeddedId
    private User_GameState_PK id;

    @ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
    @MapsId(&quot;user_id&quot;)
    @JoinColumn(name = &quot;USER_ID&quot;, insertable = false, updatable = false)
    private User user;

    @ManyToOne(cascade = {CascadeType.PERSIST}, fetch = FetchType.LAZY)
    @MapsId(&quot;gameState_id&quot;)
    @JoinColumn(name = &quot;GAMESTATE_ID&quot;, insertable = false, updatable = false)
    private GameState gameState;

    //add Role later

    public UserGameState(User u, GameState gs) {
        // create primary key
        this.id = new User_GameState_PK(u.getId(), gs.getId());

        // initialize attributes
        setUser(u);
        setGameState(gs);

    }


    public UserGameState(UserGameStateDTO userGameStateDTO){
        //this.id =
        this.user = null;
        this.gameState = null;
    }



    //----------------------1:1 Relationship Methods----------------------
    //----------------------1:N Relationship Methods----------------------
    //----------------------N:1 Relationship Methods----------------------
    public void setUser(User user) {
        if (Objects.equals(this.user, user)) {
            return;
        }

        User oldUser = this.user;
        this.user = user;

        if (oldUser != null) {
            oldUser.removeUserGameState(this);
        }

        if (user != null) {
            user.addUserGameState(this);
        }
    }

    public void setGameState(GameState gameState) {
        if (Objects.equals(this.gameState, gameState)) {
            return;
        }

        GameState oldGameState = this.gameState;
        this.gameState = gameState;

        if (oldGameState != null) {
            oldGameState.removeUserGameState(this);
        }

        if (oldGameState != null) {
            oldGameState.addUserGameState(this);
        }
    }
    //----------------------N:M Relationship Methods----------------------
}

User_GameState_PK (Combined Key)

@Embeddable
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class User_GameState_PK implements Serializable {

    @Column(name = &quot;USER_ID&quot;)
    private Long user_id;

    @Column(name = &quot;GAMESTATE_ID&quot;)
    private Long gameState_id;

    public User_GameState_PK(long user_id, long gameState_id){
        this.user_id = user_id;
        this.gameState_id = gameState_id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;

        if (o == null || getClass() != o.getClass())
            return false;

        User_GameState_PK that = (User_GameState_PK) o;
        return Objects.equals(user_id, that.user_id) &amp;&amp;
                Objects.equals(gameState_id, that.gameState_id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(user_id, gameState_id);
    }
}

The method saving the Connection in my Service

(both GameState and User are already instantiated, and the method gets the identifier of both objects, retrieving them from the database and adding the relation between them.)

    public Optional&lt;GameStateDTO&gt; addUserToGameState(String identifierGS, String identifierU) {
        GameState gameState = gameStateRepo.findByIdentifier(identifierGS)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;GameState ID has no according GameState.&quot;));
        User user = userRepo.findByIdentifier(identifierU)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;User ID has no according User.&quot;));

        //Custom N:M Connection Part
        UserGameState connection = new UserGameState(user, gameState);
        userGameStateRepo.save(connection);

        return Optional.of(gameState)
                .map(m -&gt; convertModelIntoDTO(m));
    }

I managed to set the N:M table up, together with its combined key. I tested it with simple CRUD Routes, and they worked.

Next I tried to set up some routes so that people could actually join a game(/gamestate) at which point it throws the following exception upon saving.

javax.persistence.EntityExistsException: A different object with the same identifier value was already associated with the session : [com.Astralis.backend.model.UserGameState#User_GameState_PK(user_id=1, gameState_id=7)]

After reading through some posts on stackoverflow I tried out changing the Cascadetype to .MERGE, which resulted in this exception.

javax.persistence.EntityNotFoundException: ...

Really I am lost here, it feels like if I use .PERSIST, Hibernate complaines that it copies itself while saving the Relation. While if I change it to .MERGE, it complaines that the value isn't already present in the first place.

I am more than thankfull for any breadcrumb bringing me closer to a solution, as this turned out to be a gigantic roadblock for the project, and I have tried out everything that I can think of.

答案1

得分: 0

在经过几天的搜索后,我成功解决了这个问题。

为此,我首先根据指南中的数据结构和我的项目的服务/控制器结构重新制作了指南中的项目。测试它是否能够工作,因为它确实工作了,所以我开始将模型彼此进行比较,并尝试了所有不同的可能性,以找出到底是什么导致了问题。

使用的指南是这个:https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/

我有六个副本和粘贴(类似)的错误,导致Hibernate错误地将表列相互关联。这些错误是:

  1. 在User类中:
...

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(
            cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY,
            mappedBy = "user",// 从gameState更改为user
            orphanRemoval = true
    )
    private List<UserGameState> userGameStates = new ArrayList<>();

...
  1. 在GameState类中相反的部分:
...

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY,
            mappedBy = "gameState",// 从user更改为gameState
            orphanRemoval = true)
    private List<UserGameState> userGameStates = new ArrayList<>();

...

3和4. JoinColumn注解是不必要的,似乎我将多个指南合并到一个项目中。这随后导致了更多的问题:

...

    @ManyToOne(
            cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY)
    @MapsId("user_id")
    //@JoinColumn(name = "USER_ID", insertable = false, updatable = false) //这一行已删除
    private User user;

    @ManyToOne(
            cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY)
    @MapsId("gameState_id")
    //@JoinColumn(name = "GAMESTATE_ID", insertable = false, updatable = false) //这一行已删除
    private GameState gameState;

...

5和6. UserGameState类中的“连续性保持者”方法中有两个次要的复制粘贴错误:

...

    public void setGameState(GameState gameState) {
        if (Objects.equals(this.gameState, gameState)) {
            return;
        }

        GameState oldGameState = this.gameState;
        this.gameState = gameState;

        if (oldGameState != null) {
            oldGameState.removeUserGameState(this);
        }

        //我复制了前面的if块,并用add...替换了remove...,但我没有将oldGameState更改为gameState。
        //这没有引发任何错误,实际上它仍然正确创建了关系,但我相当确定它会在以后引发问题。
        if (gameState != null) {
            gameState.addUserGameState(this);
        }
    }

...

现在它是如何工作的:
与以前一样,当调用带有连接的GameState和User的标识符的路由时,将调用服务“addUserToGameState”,并获取具有给定标识符的模型。

...
    public Optional<GameStateDTO> addUserToGameState(String identifierGS, String identifierU) {
        GameState gameState = gameStateRepo.findByIdentifier(identifierGS)
                .orElseThrow(() -> new IllegalArgumentException("GameState ID has no according GameState."));
        User user = userRepo.findByIdentifier(identifierU)
                .orElseThrow(() -> new IllegalArgumentException("User ID has no according User."));

        //自定义的N:M连接部分
        UserGameState connection = new UserGameState(user, gameState);

        return Optional.of(gameState)
                .map(m -> convertModelIntoDTO(m));
    }
...

然后调用UserGameState构造函数,该构造函数设置并创建了组合键,并调用了相关User/GameState字段的setter方法。

...
    public UserGameState(User u, GameState gs) {
        // 创建主键
        this.id = new User_GameState_PK(u.getId(), gs.getId());

        // 初始化属性
        setUser(u);
        setGameState(gs);

    }
...

我以一种方式编写了setter方法,它们同时检查添加的模型是否存在关系一致性问题,并根据它们是否被新编辑或替换来调整其字段。

...
    public void setUser(User user) {
        if (Objects.equals(this.user, user)) {
            return;
        }

        User oldUser = this.user;
        this.user = user;

        if (oldUser != null) {
            oldUser.removeUserGameState(this);
        }

        if (user != null) {
            user.addUserGameState(this);
        }
    }

    public void setGameState(GameState gameState) {
        if (Objects.equals(this.gameState, gameState)) {
            return;
        }

        GameState oldGameState = this.gameState;
        this.gameState = gameState;

        if (oldGameState != null) {
            oldGameState.removeUserGameState(this);
        }

        if (gameState != null) {//复制粘贴错误
            gameState.addUserGameState(this);
        }
    }
...
英文:

So after a few more days of searching I managed to solve it.

For this I first remade a guide's project in with the data structure from the guide and the service/controller structure of my project. Testing if it would work, and as it did I just started comparing the models with each other and tried all different possibilities out, to find out what is actually causing the issues.

The used guide is this one: https://vladmihalcea.com/the-best-way-to-map-a-many-to-many-association-with-extra-columns-when-using-jpa-and-hibernate/

I had six Copy&Paste (kinda) mistakes that caused Hibernate to falsely associate table columns with each other. These were:

  1. in User:
...

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(
            cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY,
            mappedBy = &quot;user&quot;,// changed from gameState to user
            orphanRemoval = true
    )
    private List&lt;UserGameState&gt; userGameStates = new ArrayList&lt;&gt;();

...
  1. in GameState the reverse:
...

    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @OneToMany(cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY,
            mappedBy = &quot;gameState&quot;,// changed from user to gameState
            orphanRemoval = true)
    private List&lt;UserGameState&gt; userGameStates = new ArrayList&lt;&gt;();

...

3&4. The JoinColumn Annotations were unnecessary, seemingly I combiend multiple guides into one project. This caused then even more issues:

...

    @ManyToOne(
            cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY)
    @MapsId(&quot;user_id&quot;)
    //@JoinColumn(name = &quot;USER_ID&quot;, insertable = false, updatable = false) //this one removed
    private User user;

    @ManyToOne(
            cascade = {CascadeType.PERSIST},
            fetch = FetchType.LAZY)
    @MapsId(&quot;gameState_id&quot;)
    //@JoinColumn(name = &quot;GAMESTATE_ID&quot;, insertable = false, updatable = false) //this one removed
    private GameState gameState;

...

5&6. Two minor copy&paste mistakes, in the "continuity keeper" methods in UserGameState:

...


    public void setGameState(GameState gameState) {
        if (Objects.equals(this.gameState, gameState)) {
            return;
        }

        GameState oldGameState = this.gameState;
        this.gameState = gameState;

        if (oldGameState != null) {
            oldGameState.removeUserGameState(this);
        }

        //I copied the previous if block, and replaced the remove... with add...
        //But I didn&#39;t change the oldGameState to gameState.
        //This didn&#39;t throw any errors, and actually it still created the relations         properly, but I am pretty sure it would cause issues further down the line.
        if (gameState != null) {
            gameState.addUserGameState(this);
        }
    }
...

So how does this work now:
As before, when the route with the Identifiers for the connected GameState and User is called, the service "addUserToGameState" is called, getting the models with the given Identifiers.

...
    public Optional&lt;GameStateDTO&gt; addUserToGameState(String identifierGS, String identifierU) {
        GameState gameState = gameStateRepo.findByIdentifier(identifierGS)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;GameState ID has no according GameState.&quot;));
        User user = userRepo.findByIdentifier(identifierU)
                .orElseThrow(() -&gt; new IllegalArgumentException(&quot;User ID has no according User.&quot;));

        //Custom N:M Connection Part
        UserGameState connection = new UserGameState(user, gameState);

        return Optional.of(gameState)
                .map(m -&gt; convertModelIntoDTO(m));
    }
...

After that the UserGameState cosntructer is called, which sets and creates the combined key and calls the setter methods for the related User/GameState fields.

...
    public UserGameState(User u, GameState gs) {
        // create primary key
        this.id = new User_GameState_PK(u.getId(), gs.getId());

        // initialize attributes
        setUser(u);
        setGameState(gs);

    }
...

I wrote the setters in a way, that they at the same time, check the added models for relationship consistency issues, and adjust their fields according to if they are newly edited or replaced.

...
    public void setUser(User user) {
        if (Objects.equals(this.user, user)) {
            return;
        }

        User oldUser = this.user;
        this.user = user;

        if (oldUser != null) {
            oldUser.removeUserGameState(this);
        }

        if (user != null) {
            user.addUserGameState(this);
        }
    }

    public void setGameState(GameState gameState) {
        if (Objects.equals(this.gameState, gameState)) {
            return;
        }

        GameState oldGameState = this.gameState;
        this.gameState = gameState;

        if (oldGameState != null) {
            oldGameState.removeUserGameState(this);
        }

        if (gameState != null) {//copy paste error
            gameState.addUserGameState(this);
        }
    }
...

huangapple
  • 本文由 发表于 2020年9月24日 21:56:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/64048031.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定