【发布时间】:2022-11-12 02:31:41
【问题描述】:
假设我们有以下实体:
用户实体
@Getter
@Setter
@SuperBuilder(toBuilder = true)
@ToString
@RequiredArgsConstructor
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "referred_by", referencedColumnName = "id")
@ToString.Exclude
private User referredBy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "city_id")
@ToString.Exclude
private City city;
private String name;
// equals and hashcode implementation omitted for brevity
}
城市实体
@Getter
@Setter
@ToString
@SuperBuilder(toBuilder = true)
@RequiredArgsConstructor
@Entity
@Table(name = "cities")
public class City {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
private String name;
// equals and hashcode implementation omitted for brevity
}
以及以下存储库:
用户存储库
@Repository
public interface UserRepository extends PagingAndSortingRepository<User, Long>, JpaSpecificationExecutor<User> {
@Query(
"select u from User u " +
"left join fetch u.referredBy " +
"left join fetch u.city " +
"where u.id = :id"
)
Optional<User> loadDetailsById(@Param("id") Long id);
}
城市资料库
@Repository
public interface CityRepository extends JpaRepository<City, Long> {
}
所以问题是:当我在UserRepository 中使用loadDetailsById 方法时,我希望我能得到类似这样的结果:
User(
id=2,
name=John,
city=City(id=1, name=Seattle),
referredBy=User(id=1, name=Joe, city=HIBERNATE_PROXY, referredBy=HIBERNATE_PROXY)
)
考虑到事实User(name=John) 有引用者 User(name=Joe)。通过我现在的查询,我得到了类似的结果:
User(
id=2,
name=John,
city=City(id=1, name=Seattle),
referredBy=User(
id=1,
name=Joe,
city=City(id=2, name=New York),
referredBy=User(id=3, ... and so on and so forth)
)
)
所以问题是:JPQL join fetch 是否有可能急切地获取实体及其所有子代,并保持实体子代的子关联延迟加载(事实上,实体的一个子代是同一个实体)?
或者我应该改用EntityGraph 吗?
测试作为参考
@DataJpaTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JoinFetchMweApplicationTests {
private @Autowired UserRepository userRepository;
private @Autowired CityRepository cityRepository;
@BeforeAll
void setup() {
City city = City.builder().id(1L).name("Seattle").build();
City city2 = City.builder().id(2L).name("New York").build();
cityRepository.saveAll(Arrays.asList(city, city2));
User referrer = User.builder()
.id(1L)
.name("Joe")
.city(city2)
.build();
User referral = User.builder()
.id(2L)
.name("John")
.city(city)
.referredBy(referrer)
.build();
userRepository.saveAll(Arrays.asList(referrer, referral));
}
// Passed
@Test
void whenFindById_itReturnsUser() {
Optional<User> userOptional = userRepository.findById(2L);
assertThat(userOptional).isPresent();
User user = userOptional.get();
assertThat(user.getName()).isEqualTo("John");
System.out.println(user);
assertThat(user.getCity()).isInstanceOf(HibernateProxy.class);
assertThat(user.getReferredBy()).isInstanceOf(HibernateProxy.class);
}
// Failed
@Test
void whenLoadDetailsById_itLoadsEagerly() {
Optional<User> userOptional = userRepository.loadDetailsById(2L);
assertThat(userOptional).isPresent();
User user = userOptional.get();
assertThat(user.getName()).isEqualTo("John");
System.out.println(user);
assertThat(user.getCity()).isNotInstanceOf(HibernateProxy.class);
assertThat(user.getReferredBy()).isNotInstanceOf(HibernateProxy.class);
assertThat(user.getReferredBy().getCity()).isInstanceOf(HibernateProxy.class); // Fails here: getCity is NOT instance of HibernateProxy
assertThat(user.getReferredBy().getReferredBy()).isInstanceOf(HibernateProxy.class); // Fails here: getReferredBy is NOT instance of HibernateProxy
}
}
以及为loadDetailsById 生成的 SQL
select
user0_.id as id1_1_0_,
user1_.id as id1_1_1_,
city2_.id as id1_0_2_,
user0_.city_id as city_id3_1_0_,
user0_.name as name2_1_0_,
user0_.referred_by as referred4_1_0_,
user1_.city_id as city_id3_1_1_,
user1_.name as name2_1_1_,
user1_.referred_by as referred4_1_1_,
city2_.name as name2_0_2_
from users user0_
left outer join
users user1_ on user0_.referred_by=user1_.id
left outer join
cities city2_ on user0_.city_id=city2_.id
where user0_.id=?
【问题讨论】:
-
请修剪您的代码,以便更容易找到您的问题。请按照以下指南创建minimal reproducible example。
标签: java hibernate jpa spring-data-jpa jpql