Spring+H2桌面游戏ORM映射问题:findById关联对象为null
Spring Data JPA ORM映射问题解决
问题背景
使用Spring + H2开发桌面游戏,核心实体类包括Cards、Player、Tabletop,关联关系如下:
Tabletop包含两个卡片集合(通过BaseCards、FieldCards)和一个Player列表Player包含两个卡片集合(通过PlayerHandCards、PlayerBinCards)
数据可正常保存到数据库,但调用findById查询Tabletop时,所有关联对象均为null。
现有实体映射代码
@Entity public class Tabletop{ @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @OneToOne(mappedBy = "tabletop") private BaseCards baseCards; @OneToOne(mappedBy = "tabletop") private FieldCards fieldCards; @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER) @JoinColumn(name = "tabletop_id") private List<Player> players; } @Entity public class Player { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @OneToOne(mappedBy = "player") private PlayerHandCards handCards; @OneToOne(mappedBy = "player") private PlayerBinCards binCards; } @Entity public class PlayerHandCards { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @OneToMany( cascade = CascadeType.ALL) @JoinColumn(name="player_hand_id") List<Cards> Cards; @OneToOne @JoinColumn(name = "player_id") private Player player; } @Entity public class PlayerBinCards { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @OneToMany( cascade = CascadeType.ALL) @JoinColumn(name="player_bin_id") List<Cards> cards; @OneToOne @JoinColumn(name = "player_id") private Player player; } @Entity public class BaseCards { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @OneToMany( cascade = CascadeType.ALL) @JoinColumn(name="base_cards_id") List<Cards> cards; @OneToOne @JoinColumn(name = "Tabletop_id") private Tabletop tabletop; } @Entity public class FieldCards { @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; @OneToMany( cascade = CascadeType.ALL) @JoinColumn(name="field_cards_id") List<Cards> cards; @OneToOne @JoinColumn(name = "Tabletop_id") private Tabletop tabletop; } @Entity public class Cards{ @Id @GeneratedValue(strategy= GenerationType.AUTO) private long id; private String color; private Integer value; }
问题原因分析
- 懒加载默认策略:除
players列表设置了FetchType.EAGER,其他@OneToOne关联默认使用FetchType.LAZY。如果查询Session已关闭(如Service层返回实体后),懒加载对象无法初始化,会返回null。 - 双向关联未维护:
Tabletop与BaseCards/FieldCards、Player与PlayerHandCards/PlayerBinCards是双向关联,但保存时仅设置了外键拥有方的关联(如baseCards.setTabletop(tabletop)),未设置另一方关联,导致Hibernate无法识别关系。 - 外键大小写不一致:
BaseCards和FieldCards中@JoinColumn(name = "Tabletop_id")使用大写开头,H2数据库默认对标识符大小写敏感,可能导致外键关联失败。 - 字段命名不规范:
PlayerHandCards中的List<Cards> Cards不符合Java驼峰规范,可能导致ORM映射识别异常。
修复方案
1. 统一外键字段命名
将所有@JoinColumn的name改为小写,避免数据库大小写问题:
// BaseCards类中 @OneToOne @JoinColumn(name = "tabletop_id") private Tabletop tabletop; // FieldCards类中 @OneToOne @JoinColumn(name = "tabletop_id") private Tabletop tabletop;
2. 调整关联加载策略
对需要立即加载的@OneToOne关联添加fetch = FetchType.EAGER:
// Tabletop类中 @OneToOne(mappedBy = "tabletop", fetch = FetchType.EAGER) private BaseCards baseCards; @OneToOne(mappedBy = "tabletop", fetch = FetchType.EAGER) private FieldCards fieldCards; // Player类中 @OneToOne(mappedBy = "player", fetch = FetchType.EAGER) private PlayerHandCards handCards; @OneToOne(mappedBy = "player", fetch = FetchType.EAGER) private PlayerBinCards binCards;
3. 维护双向关联关系
保存实体时必须同时设置关联两端:
// 示例:创建Tabletop和BaseCards Tabletop tabletop = new Tabletop(); BaseCards baseCards = new BaseCards(); baseCards.setTabletop(tabletop); tabletop.setBaseCards(baseCards); // 同理处理Player与卡片容器类 playerHandCards.setPlayer(player); player.setHandCards(playerHandCards);
4. 规范实体字段命名
修正不符合Java规范的字段:
// PlayerHandCards类中 @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name="player_hand_id") private List<Cards> cards; // 改为小写开头,添加private修饰符
5. 自定义查询强制关联加载(可选)
若不想全局使用EAGER加载,可在Repository中定义JPQL查询,通过JOIN FETCH显式加载所有关联:
public interface TabletopRepository extends JpaRepository<Tabletop, Long> { @Query("SELECT t FROM Tabletop t " + "JOIN FETCH t.baseCards bc JOIN FETCH bc.cards " + "JOIN FETCH t.fieldCards fc JOIN FETCH fc.cards " + "JOIN FETCH t.players p " + "JOIN FETCH p.handCards phc JOIN FETCH phc.cards " + "JOIN FETCH p.binCards pbc JOIN FETCH pbc.cards " + "WHERE t.id = :id") Tabletop findByIdWithAllAssociations(@Param("id") long id); }
内容的提问来源于stack exchange,提问作者Paulo Henrique
相关产品推荐
相关产品推荐

