Entity Graph实体层失效:按钱包所有者ID查CryptoCount出现多查询问题
实体链与实体类代码
实体链:User -> CryptoWallet -> List<CryptoCount>
1. User类代码
public class User { @Id @Column(name = "id") @GenericGenerator(name = "uuidFromIp" , strategy = "org.hibernate.id.uuid.CustomVersionOneStrategy") @GeneratedValue(generator = "uuid_gen_strategy") private UUID id; @Column(name = "user_name") private String userName; @Column(name = "email") private String email; @OneToOne(cascade = CascadeType.ALL) @JoinColumn(name = "crypto_wallet_id") private CryptoWallet cryptoWallet; }
2. CryptoWallet类代码
public class CryptoWallet { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "balance") private BigDecimal balance; @OneToMany(mappedBy = "cryptoWallet", cascade = CascadeType.ALL) private List<CryptoCount> cryptoCounts; @OneToOne(mappedBy = "cryptoWallet") private User walletOwner; }
3. CryptoCount类代码
public class CryptoCount { @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "crypto_id") private CryptoCurrency cryptoCurrency; @Column(name = "count") private BigDecimal countOfCrypto; @ManyToOne @JoinColumn(name = "crypto_wallet_id") private CryptoWallet cryptoWallet; }
需求与两种Repository实现
需求:通过一次数据库查询,根据钱包所有者ID加载所有CryptoCount数据。
方法一(正常,仅一次查询)
@EntityGraph(attributePaths = {"cryptoCurrency","cryptoWallet","cryptoWallet.walletOwner"}) List<CryptoCount> findAllByCryptoWalletWalletOwnerId(UUID id);
方法二(出现两次查询)
先在实体类定义NamedEntityGraph:
- CryptoCount类上添加:
@NamedEntityGraph(name = "CryptoCountWithCurrencyAndWallet",attributeNodes = { @NamedAttributeNode(value = "cryptoCurrency"), @NamedAttributeNode(value = "cryptoWallet") })
- CryptoWallet类上添加:
@NamedEntityGraph(name = "WalletWithCryptoCountsAndWalletOwner", attributeNodes = { @NamedAttributeNode(value = "cryptoCounts"), @NamedAttributeNode(value = "walletOwner",subgraph = "UserWithWallet") }, subgraphs = { @NamedSubgraph(name = "UserWithWallet", attributeNodes = @NamedAttributeNode("cryptoWallet")) })
- User类上添加:
@NamedEntityGraph(name = "UserWithWallet",attributeNodes = { @NamedAttributeNode("cryptoWallet") },subgraphs = { @NamedSubgraph(name = "userWithWallet", attributeNodes = {@NamedAttributeNode("cryptoWallet")}) })
然后Repository方法:
@EntityGraph(value = "CryptoCountWithCurrencyAndWallet") List<CryptoCount> findAllByCryptoWalletWalletOwnerId(UUID id);
问题:为什么方法二会执行两次数据库查询?
核心问题在于你定义的CryptoCountWithCurrencyAndWallet这个NamedEntityGraph没有包含cryptoWallet.walletOwner的关联加载,而方法一中的attributePaths明确指定了这个嵌套关联。
当使用方法二的NamedEntityGraph时,Hibernate只会加载CryptoCount关联的cryptoCurrency和cryptoWallet,但不会自动加载cryptoWallet关联的walletOwner。而你的查询条件依赖walletOwner的ID,Hibernate为了匹配这个条件,在加载完CryptoCount和cryptoWallet后,会额外发起一次查询去获取walletOwner数据,因此出现了两次查询。
修复方案
修改CryptoCount类上的NamedEntityGraph,通过子图包含cryptoWallet.walletOwner的关联:
@NamedEntityGraph(name = "CryptoCountWithCurrencyAndWalletAndOwner", attributeNodes = { @NamedAttributeNode(value = "cryptoCurrency"), @NamedAttributeNode(value = "cryptoWallet", subgraph = "WalletWithOwner") }, subgraphs = { @NamedSubgraph(name = "WalletWithOwner", attributeNodes = @NamedAttributeNode("walletOwner")) })
然后在Repository中使用这个新的NamedEntityGraph:
@EntityGraph(value = "CryptoCountWithCurrencyAndWalletAndOwner") List<CryptoCount> findAllByCryptoWalletWalletOwnerId(UUID id);
这样Hibernate就会在一次查询中通过左连接加载CryptoCount、cryptoCurrency、cryptoWallet以及walletOwner,避免额外的查询。
另外,你之前在User和CryptoWallet上定义的NamedEntityGraph与当前需求无关,无需使用——当前是从CryptoCount出发加载关联,不需要反向加载的图结构。
内容的提问来源于stack exchange,提问作者Smetana Po Aktsii

