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

ckx4rj1h  于 2021-07-06  发布在  Java
关注(0)|答案(2)|浏览(301)

假设我有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;

    ...
}

我想用它的主键来取大陆 id . 但我也希望所有属于一个大陆的国家和所有属于一个国家的城市。
目前我正在进行3个单独的调用,首先查询大陆表,然后按fk大陆id查询国家表,最后按fk国家id查询城市表。
我想在一次通话中查询所有内容。因此,我添加了双向onetomany关系和急切抓取,如下所示:

@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;

    ...
}

一切正常。我在hibernate生成的查询中看到两个左外连接。
现在的问题是,我不想每次都急切地获取所有相关的关联。一种选择是将所有onetomany关联都更改为lazy和have join fetch 在jpql查询中。
因此,为了获取一个大陆的所有国家,我可以使用如下jpql查询

SELECT u FROM Continent u JOIN FETCH u.countries WHERE u.id=?1

但是对于像continent->country->city这样的层次结构,我也希望填充city字段,我无法设计jpql查询。你知道我该怎么做吗?
编辑:未来的读者可能对阅读jpa感兴趣:如何为3个级别定义@namedentitygraph?

brgchamk

brgchamk1#

你可以用例如。 @EntityGraph ,但我认为这是blaze持久性实体视图的完美用例。
我创建了这个库,以便在jpa模型和自定义接口或抽象类定义的模型之间进行简单的Map,比如spring数据在steroids上的投影。其思想是以您喜欢的方式定义目标结构(域模型),并通过jpql表达式将属性(getter)Map到实体模型。
对于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();
    }
}

查询是将实体视图应用于查询的问题,最简单的就是按id进行查询。 ContinentDto a = entityViewManager.find(entityManager, ContinentDto.class, id); spring数据集成允许您像spring数据投影一样使用它:https://persistence.blazebit.com/documentation/entity-view/manual/en_us/index.html#spring-数据特征

ContinentDto findById(Long id);
vshtjzan

vshtjzan2#

我想出了以下方法。
首先,我将所有关联标记为懒惰:
大陆模式

@OneToMany(fetch = FetchType.LAZY)
private List<Country> countries = new ArrayList<>();

国家模式

@OneToMany(fetch = FetchType.LAZY)
private List<City> cities = new 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);    
}

接下来,在我的服务类中,当我想在大陆上加载初始化的所有国家和城市字段时,我执行如下调用:

@Service
public class ContinentService
{
    @Transactional(readOnly = true)
    public Optional<Continent> getContinent(Long continentId)
    {
        countryRepository.findCountry(continentId);
        return continentRepository.findContinent(continentId);
    }
}

基本上,这首先会加载所有经过筛选的国家 continentId 在第一次调用中初始化城市,然后在第二次调用中加载整个大陆的所有国家。因此,所有关联都加载到当前持久性上下文中。

相关问题