代码之家  ›  专栏  ›  技术社区  ›  parsecer

Hibernate一对一对应两列(Entity类中两个相同类型的字段)

  •  0
  • parsecer  · 技术社区  · 5 年前

    假设我有一个Noughts and Crosses游戏(井字游戏):

    游戏 :

    id    crosses_player_id    noughts_player_id...
    

    玩家 :

    id    alignment    game_id
    

    由于只有2名玩家,我认为没有必要创建单独的表,所以我这样做:

    @Entity
    @Table(name = "games")
    public class Game {
        @Id @GeneratedValue
        private int id;
    
        @OneToOne(mappedBy = "game")
        @JoinColumn(name = "crosses_player_id")
        private Player crossesPlayer;
        @OneToOne(mappedBy = "game")
        @JoinColumn(name = "noughts_player_id")
        private Player noughtsPlayer;
    
        private List<Move> moves = new ArrayList<>();
        private List<Field> fields = new ArrayList<>();
    ...
    
    @Entity
    @Table(name = "players")
    public class Player {
        @ManyToOne
        @JoinColumn(name="user_id", nullable=false)
        private User user;
    
        private enum Alignment {
            NOUGHTS, CROSSES
        };
        private Alignment alignment;
    
        @OneToOne(mappedBy = ?)
        private Game game;
    ...
    

    但是我不知道该放什么进去 @OneToOne(mappedBy = ?) 在里面 Player 类。

    我找到的最接近的答案是:

    https://stackoverflow.com/a/13944195/4759176

    class TeamPlay {
        @ManyToOne
        Team homeTeam;
    
        @ManyToOne
        Team awayTeam;
    }
    
    class Team {
        @OneToMany(mappedBy="homeTeam")
        Set<TeamPlay> homeTeamPlays;
    
        @OneToMany(mappedBy="awayTeam")
        Set<TeamPlay> awayTeamPlays;
    }
    

    然而,它看起来像 Team 类可以有很多可能的游戏,而在我的例子中,一个玩家只有一个游戏。玩家基本上是用户的id、游戏的id和联盟(零或叉),游戏结束后,同一玩家再也不会从任何新游戏中被引用。也许我应该这样做:

    玩家 :

    @OneToOne(mappedBy = "crossesPlayer")
    private Game crossesGame;
    
    @OneToOne(mappedBy = "noughtsPlayer")
    private Game noughtsGame;
    

    但是,玩家可以 只有 要么是零,要么是传中,所以其中一个字段总是空的?

    0 回复  |  直到 5 年前
        1
  •  3
  •   Carlitos Way    5 年前

    @解析器。。。对我来说,你的问题在于对数据模型的误解;引用您的话:

    玩家基本上是用户的id、游戏的id和联盟(零或叉),游戏结束后,同一玩家再也不会从任何新游戏中被引用

    这意味着“游戏”是一个强实体,不应该有对玩家的引用(即外键)(即“游戏”表不应该包含以下列:crosses_player_id,noughts_player_id)。。。这也意味着“玩家”是一个弱实体,只存在于“游戏”的上下文中(所以,这里可以有一个外来的游戏列)。。。

    假设所有这些,你的模型和映射都会变得更清晰,例如:

    @Entity
    @Table(name = "players")
    public class Player {
        /**
         * Player's ID.
         */
        @Id
        private int id;
    
        /**
         * Game to which this Player belongs.
         */
        @ManyToOne(optional = false)
        @JoinColumn(name = "game_id", nullable = false, updatable = false)
        private Game game;
    
        /**
         * Player's alignment in the game.
         */
        @Enumerated(EnumType.STRING)
        private Alignment alignment;
    }
    
    @Entity
    @Table(name = "games")
    public class Game {
        /**
         * Game's Id.
         */
        @Id
        private int id;
    
        /**
         * Players in this game.
         */
        @OneToMany(mappedBy = "game", cascade = PERSIST)
        @MapKey(name = "alignment")
        private Map<Alignment, Player> players = new HashMap<>();
    
        ...
    
        public List<Player> getPlayers() {
            return Arrays.asList( this.players.values() );
        }
    
        public Player getCrossesPlayer() {
            return this.players.get(Alignment.CROSSES);
        }
    
        public Player getNoughtesPlayer() {
            return this.players.get(Alignment.NOUGHTES);
        }
    }
    
        2
  •  0
  •   theMahaloRecords    5 年前
     @ManyToOne(optional=false) 
     @JoinColumn(name="game_id")
     private Game game;
    

    玩家游戏中的OneToOne已经在这些实体之间创建了你想要的关系。使用游戏主键上的ManyToOne With Joining,您将能够看到玩家正在参加什么游戏(从数据上的玩家视图)。 根据该数据逻辑,最多两名玩家可以在数据层上参与游戏。然而,你仍然可以创建一个玩家,给他一个他不玩的游戏。

    Player player1ThatDoesentPlay // = new Player(...); 
    player1ThatDoesentPlay.setGame(thatGame) // with thatGame could already have the participating players
    

    我建议你创建一个类似的方法

    Game checkandsetGame(Game game){
    if (game.crossesplayer == null | game.noughtsplayer == null && game != null){
    return setGame(game);
        }
    // else {probably want to throw an error here, that indicates that the player couldn't be assigned to a game}
    }
    

    一般来说,我建议你过度思考你的数据模式,也许可以像@Carlitos Way建议的那样做

        3
  •  0
  •   cghislai    5 年前

    在你的模型中,你希望你的游戏和你的玩家之间有1-1的关系。但是两个玩家将共享一个游戏,因此单个游戏id可以从玩家表中多次引用,但单个玩家只能从游戏表中引用一次。

    你犯的一个错误是使用 mappedBy 属性,用于关系的非拥有方。它告诉JPA'我是从另一个表中的那个外键引用的。请帮我把它拿来,并在这里参考。请注意,你必须保持关系的双方,也就是说,当你打电话的时候 game.setPlayer(player) ,你必须确保你打电话 player.setGame(game) 也。还要注意,虽然它可能适用于@OneToOne关系,但这是不可扩展的,你最好避免使用它。

    你的游戏玩家关系是由他们的对齐来定义的。事实上,你的玩家实体似乎定义了用户和游戏之间的关系。

    我为您的用例提供的解决方案是使用 GamePosition 该表将允许您强制执行单个玩家只是单个游戏的一部分。

    @Entity
    @Table(name = "players")
    public class Player {
        @ManyToOne
        @JoinColumn(name="user_id", nullable=false)
        private User user;    
    }
    
    
    
    @Entity
    @Table(name = "game")
    public class Game {
        @Id @GeneratedValue
        private int id;
    }
    
    
    @Entity
    @Table(name = "game_position")
    public class GamePosition {
    
        @OneToOne
        @JoinColumn(name="player_id", unique=true)
        // Making this column unique will prevent a single player to be part of two games
        private Player player;
    
        @ManyToOne
        @JoinColumn(name="game_id")
        private Game game;
    
        @Enumarated
        private Alignement alignement;
    
    }
    

    然后,如果你想要双向关系,你可以使用mappedBy属性。 在玩家表中:

      @OneToOne(mappedBy="player")
      private GamePosition gamePosition;
    

    或者在游戏桌上:

      @OneToMany(mappedBy="game")
      private List<GamePosition> gamePositions;