Java Hibernate获取具有层次结构的双向关系关联

Java Hibernate获取具有层次结构的双向关系关联,java,hibernate,jpa,spring-data-jpa,Java,Hibernate,Jpa,Spring Data Jpa,假设我有3个模型,如下所示: @Entity @Data class Continent { @Id private Long id; private String name; ... } @Entity @Data class Country { @Id private Long id; @ManyToOne(fetch = FetchType.LAZY) private Continent continent;

假设我有3个模型,如下所示:

@Entity
@Data
class Continent {
    @Id
    private Long id;

    private String name;

    ...
}

@Entity
@Data    
class Country {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Continent continent;

    private String name;

    ...
}

@Entity
@Data
class City {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    private String name;

    ...
}
@Entity
@Data
class Continent {
    @Id
    private Long id;

    private String name;

    ...
    
    @OneToMany(mappedBy = "continent", fetch = FetchType.EAGER)
    private List<Country> countries;
}

@Entity
@Data    
class Country {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Continent continent;

    private String name;

    ...
    
    @OneToMany(mappedBy = "country", fetch = FetchType.EAGER)
    private List<City> cities;
}

@Entity
@Data
class City {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    private String name;

    ...
}
@Service
public class ContinentService
{
    @Transactional(readOnly = true)
    public Optional<Continent> getContinent(Long continentId)
    {
        countryRepository.findCountry(continentId);
        return continentRepository.findContinent(continentId);
    }
}
我想通过它的主键
id
获取大陆。但我也想要属于一个大陆的所有国家和属于一个国家的所有城市

目前我正在进行3个单独的调用,首先查询大陆表,然后按FK大陆id查询国家表,最后按FK国家id查询城市表

我想在一次通话中查询所有内容。因此,我添加了双向的OneToMany关系,并将其与急切抓取联系在一起,如下所示:

@Entity
@Data
class Continent {
    @Id
    private Long id;

    private String name;

    ...
}

@Entity
@Data    
class Country {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Continent continent;

    private String name;

    ...
}

@Entity
@Data
class City {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    private String name;

    ...
}
@Entity
@Data
class Continent {
    @Id
    private Long id;

    private String name;

    ...
    
    @OneToMany(mappedBy = "continent", fetch = FetchType.EAGER)
    private List<Country> countries;
}

@Entity
@Data    
class Country {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Continent continent;

    private String name;

    ...
    
    @OneToMany(mappedBy = "country", fetch = FetchType.EAGER)
    private List<City> cities;
}

@Entity
@Data
class City {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    private String name;

    ...
}
@Service
public class ContinentService
{
    @Transactional(readOnly = true)
    public Optional<Continent> getContinent(Long continentId)
    {
        countryRepository.findCountry(continentId);
        return continentRepository.findContinent(continentId);
    }
}
但是对于像大陆->国家->城市这样的层次结构,我也希望填充城市字段,我无法设计JPQL查询。你知道我该怎么做吗


编辑:未来的读者可能对阅读感兴趣

您可以使用例如
@EntityGraph
,但我认为这是一个完美的使用案例

我创建了这个库,以便在JPA模型和自定义接口或抽象类定义的模型之间进行简单的映射,类似于类固醇上的Spring数据投影。其思想是以您喜欢的方式定义目标结构(域模型),并通过JPQL表达式将属性(getter)映射到实体模型

在Blaze持久性实体视图中,您的用例的DTO模型可能如下所示:

@EntityView(Continent.class)
public interface ContinentDto {
    @IdMapping
    Long getId();
    String getName();
    Set<CountryDto> getCountries();

    @EntityView(Country.class)
    interface CountryDto {
        @IdMapping
        Long getId();
        String getName();
        Set<CityDto> getCities();
    }
    @EntityView(City.class)
    interface CityDto {
        @IdMapping
        Long getId();
        String getName();
    }
}

我提出了以下方法

首先,我将所有关联标记为懒惰:

大陆模式

@OneToMany(fetch = FetchType.LAZY)
private List<Country> countries = new ArrayList<>();
@OneToMany(fetch=FetchType.LAZY)
私有列表国家=新的ArrayList();
国家模式

@OneToMany(fetch = FetchType.LAZY)
private List<City> cities = new ArrayList<>();
@OneToMany(fetch=FetchType.LAZY)
私有列表城市=新的ArrayList();
接下来,我创建了存储库并添加了JPQL查询,以加入所需的关联

public interface ContinentRepository extends JpaRepository<Continent, Long>
{
    @Transactional(readOnly = true)
    @Query(value = "SELECT u FROM Continent u JOIN FETCH u.countries WHERE u.id=?1")
    public Optional<Continent> findContinent(Long id);    
}

public interface CountryRepository extends JpaRepository<Country, Long>
{
    @Transactional(readOnly = true)
    @Query(value = "SELECT u FROM Country u JOIN FETCH u.cities WHERE u.continent.id=?1")
    public List<Continent> findCountry(Long continentId);    
}
public接口扩展了JpaRepository
{
@事务(只读=真)
@查询(value=“从u.id=?1”的大陆u连接获取u.countries中选择u”)
公共可选findContinent(长id);
}
公共接口CountryRepository扩展了JpaRepository
{
@事务(只读=真)
@查询(value=“从国家/地区选择u加入获取u.Continental.id=?1的u.cities”)
公共列表查找国家(长ID);
}
接下来,在我的服务类中,当我想在大陆上加载已初始化的所有国家和城市字段时,我执行如下调用:

@Entity
@Data
class Continent {
    @Id
    private Long id;

    private String name;

    ...
}

@Entity
@Data    
class Country {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Continent continent;

    private String name;

    ...
}

@Entity
@Data
class City {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    private String name;

    ...
}
@Entity
@Data
class Continent {
    @Id
    private Long id;

    private String name;

    ...
    
    @OneToMany(mappedBy = "continent", fetch = FetchType.EAGER)
    private List<Country> countries;
}

@Entity
@Data    
class Country {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Continent continent;

    private String name;

    ...
    
    @OneToMany(mappedBy = "country", fetch = FetchType.EAGER)
    private List<City> cities;
}

@Entity
@Data
class City {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    private Country country;

    private String name;

    ...
}
@Service
public class ContinentService
{
    @Transactional(readOnly = true)
    public Optional<Continent> getContinent(Long continentId)
    {
        countryRepository.findCountry(continentId);
        return continentRepository.findContinent(continentId);
    }
}
@服务
公共服务
{
@事务(只读=真)
公共可选GetContinental(长continentId)
{
countryRepository.findCountry(大陆ID);
返回continentRepository.findContinent(continentId);
}
}

基本上,第一次调用会加载由
大陆ID
过滤的所有国家,并在第一次调用中初始化城市,然后在第二次调用中加载大陆上的所有国家。因此,所有关联都加载到当前的持久性上下文中。

可以这样尝试:从中选择u从大陆中选择u加入获取u.countries c加入获取c.cities WHERE u.id=?1这不会以您想要的方式检索数据吗?不,它不起作用。我已经试过了。它给出:
方法查询的验证失败
对不起,我的错,我在查询中键入了一些东西,应该是这样的:从大陆选择u u JOIN FETCH u.countries c JOIN FETCH c.cities WHERE u.id=?1基本上,你的想法是使用JOIN FETCH来获取所需的嵌套实体。你能尝试一下并让我知道吗?我想,这个答案验证了没有简单的JPA解决这个问题的简单方法。如中所述,我尝试使用NamedEntityGraph和子图,但它导致了MultipleBagFetchException