Как реализовать циклическую зависимость в JPA с классом в середине - PullRequest
0 голосов
/ 04 января 2019

У меня следующая проблема в Java / Spring.

Я попытался максимально упростить мою проблему с более конкретным случаем. Подводя итог, у нас есть класс Country, и каждая страна связана со многими City. Каждый город связан с другим дорогой промежуточного класса.

Я реализовал это таким образом (см. Ниже), но это меня не устраивает, потому что у нас есть избыточность в объекте Road, связанном со страной его городами и атрибутом страны (например, давайте предположим, что мы не t заботится о дубликатах, т.е. Дорога Париж-Лион и Дорога Лион-Париж).

Еще одна точность, у нас нет перекрестных стран. Все дороги и города под одной страной принадлежат одной стране.

public class Country implements Serializable {
    ...

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "country")
    private List<City> cities;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "country")
    private List<Road> roads;
}

public class City implements Serializable {
    ...

    @ManyToOne(optional = false)
    @JoinColumn(name = "countryId", referencedColumnName = "id")
    private Country country;
}

public class Road implements Serializable {
    // Unicity by the corresponding 3 items countryId, cityFromId and cityToId
    ...

    @ManyToOne(optional = false)
    @JoinColumn(name = "countryId", referencedColumnName = "id")
    private Country country;


    @ManyToOne(optional = false)
    @JoinColumn(name = "cityFromId", referencedColumnName = "id")
    private City cityFrom;


    @ManyToOne(optional = false)
    @JoinColumn(name = "cityToId", referencedColumnName = "id")
    private City cityTo;
}

Чтобы избежать этой повторяющейся ссылки, мы могли бы представить себе удаление ссылки на класс Country в классе Road, но это подразумевало бы добавление ссылок @OneToMany или Road в классе City. Тогда мы будем на циклической ссылке из-за того, что Дорога связывает 2 города (см. Ниже).

public class Country implements Serializable {
    ...

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

public class City implements Serializable {
    ...

    @ManyToOne(optional = false)
    @JoinColumn(name = "countryId", referencedColumnName = "id")
    private Country country;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "cityFrom")
    private List<Road> cityFromRoad;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "cityTo")
    private List<Road> cityToRoad;
}

public class Road implements Serializable {
    // Unicity by the corresponding 3 items countryId, cityFromId and cityToId
    ...


    @ManyToOne(optional = false)
    @JoinColumn(name = "cityFromId", referencedColumnName = "id")
    private City cityFrom;


    @ManyToOne(optional = false)
    @JoinColumn(name = "cityToId", referencedColumnName = "id")
    private City cityTo;
}

Не могли бы вы помочь мне найти лучший способ сделать мою модель?

Большое спасибо!

1 Ответ

0 голосов
/ 06 января 2019

Не думаю, что у вас есть хорошее представление о реальной модели. Дорога должна быть отдельным объектом, а не просто связью между двумя городами. Например, дорога может проходить через многие города. Также в городе может быть много дорог. Что вы описываете, так это то, что города могут быть связаны со многими городами.

enter image description here

Но что вы хотите описать, так это то, что между дорогами и городами существует ManyToMany.

enter image description here

Простая модель JPA с однонаправленным отображением будет выглядеть следующим образом.

@Entity
public class Country {
    @Id
    private String name;

@Entity
public class City {
    @Id
    private String name;    
    @ManyToOne
    private Country country;
    @ManyToMany
    private Set<Road> roads;

@Entity
public class Road {
    @Id
    private String name;    

Чтобы сохранить карту, вы создаете графство, добавляете города, добавляете дороги и описываете, какие дороги существуют для каждого города.

tx.begin();
Country france = new Country("France");
City paris = new City("Paris");
paris.setCountry(france);
City nice = new City("Nice");
nice.setCountry(france);
City normandy = new City("Normandy");
normandy.setCountry(france);
Road r1 = new Road("R1");
Road r2 = new Road("R2");
Set<Road> roads = new HashSet<>();
roads.add(r1);
roads.add(r2);
paris.setRoads(roads);
roads = new HashSet<>();
roads.add(r2);
normandy.setRoads(roads);
roads = new HashSet<>();
roads.add(r1);
nice.setRoads(roads);

em.persist(france);
em.persist(paris);
em.persist(nice);
em.persist(normandy);
em.persist(r1);
em.persist(r2);

tx.commit();

Чтобы получить список дорог для города, вы выбираете город с его списком дорог. Чтобы увидеть, с какими городами дорога соединяется, выберите города, в которых она указана в списке дорог.

TypedQuery<City> roadsForCityQuery = em.createQuery("select c from City c join fetch c.roads join fetch c.country where c = :city", City.class);

City normandyRoads = roadsForCityQuery.setParameter("city", normandy).getSingleResult();    
System.out.println("normandyRoads = " + normandyRoads);

City parisRoads = roadsForCityQuery.setParameter("city", paris).getSingleResult();
System.out.println("parisRoads = " + parisRoads);

TypedQuery<City> citiesForRoadQuery = em.createQuery("select distinct c from City c join fetch c.roads road join fetch c.country where road = :road", City.class);

List<City> r1Cities = citiesForRoadQuery.setParameter("road", r1).getResultList();  
System.out.println("r1Cities = " + r1Cities);

List<City> r2Cities = citiesForRoadQuery.setParameter("road", r2).getResultList();  
System.out.println("r2Cities = " + r2Cities);

Это дает следующие результаты:

normandyRoads = City(name=Normandy, roads=[Road(name=R2)])
parisRoads = City(name=Paris, roads=[Road(name=R1), Road(name=R2)])
r1Cities = [City(name=Nice, roads=[Road(name=R1)]), City(name=Paris, roads=[Road(name=R1), Road(name=R2)])]
r2Cities = [City(name=Normandy, roads=[Road(name=R2)]), City(name=Paris, roads=[Road(name=R1), Road(name=R2)])]
...