我有一个固定深度为4的分层数据结构。为了更好地理解,我们假设以下(只是一个例子):
因此,各级之间始终存在1-N关系。
一个非常重要的用例 (给定一个国家的ID)是一次加载一个国家/地区的整个“内容”,对性能的影响最小数据库。
在第一个天真的方法中,我在Java中创建了4个授权类,其中实体“Country”包含“State”类型的列表,实体“State”包含“County”类型的列表,依此类推。 .. 但是JPA之后创建的当然不是4个表,而是7个(4个实体+ 3个用于1-N之间的连接)。我不知道这是否是一个很好的解决方案,因为很多人都在加入。
我还试图将子类型映射到它们的父类型(一个城市属于一个县,一个县属于一个州,一个州属于一个国家)。这导致4个表,但是从应用程序的角度来看,一次检索所有数据变得更加困难。如果我没错,我需要4个不同的请求,而不是一个。
我怎么能解决这个问题?有没有办法将简单的表格布局(有四个表,而不是七个)与易于使用的实体类(父类型应该知道它的子节点)相结合? 如果没有,你怎么会意识到这一点?
我正在使用JPA和Hibernate以及PostgreSQL。
答案 0 :(得分:3)
您可以使用@JoinColumn
注释而不是我怀疑您正在使用的@JoinTable
注释来避免使用3个额外的映射表。
所以例如,
<强> COUNTRY 强>
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="country")
private List<State> stateList;
<强> STATE 强>
@ManyToOne
@JoinColumn(name="country_id")
private Country country
数据库表如下:
<强>国家强>
country_id => primary key
<强>国家强>
state_id => primary key
country_id => foriegn key
这样就可以避免所有4个实体之间的映射表。
答案 1 :(得分:2)
您已经有了解决方案:四个表是可行的方式,具有双向关系(在每个关系的非拥有方使用mappedBy
属性)。如果关系为EAGER
- 获取,则会自动加载所有实体。如果您想使用LAZY
抓取,您可以尝试使用命名查询来加载加载了所有关系的实体:
SELECT DISTINCT c FROM Country c LEFT JOIN FETCH c.states s LEFT JOIN FETCH s.counties co...
答案 2 :(得分:2)
使用双向映射非常简单。浏览该链接
How to delete Child or Parent objects from Relationship?
进行如下修改
国家/地区实体:
------
@OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<States > states;
@OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Counties> counties;
@OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Cities> cities;
-------
setters & getters
国家实体:
-----
@ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="countryId")
private Country country ;
-----
县实体:
--------
@ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="countryId")
private Country country ;
-------
城市实体:
@ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn(name="countryId")
private Country country ;
---------
编译完所有实体后进行插入。只有4将使用Country对象id创建和读取您的数据。
答案 3 :(得分:2)
您可以使用JPQL轻松实现此目的:
SELECT DISTINCT country
FROM Country country
JOIN FETCH country.states states
JOIN FETCH states.counties counties
JOIN FETCH counties.cities cities
WHERE country.id = :countryId
在fetchType = FetchType.EAGER
/ @OneToMany
上使用@ManyToOne
(相信默认情况下已经EAGER
)将获得类似的结果。
答案 4 :(得分:1)
您是否尝试使用第二种方法明确地声明关系的获取类型以急切(默认为懒惰,这就是您必须进行四次查询的原因)。
E.g。
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@JoinColumn ...
private ...;
见这里:http://www.concretepage.com/hibernate/fetch_hibernate_annotation
答案 5 :(得分:1)
以下是您的实体的外观:(如果您愿意,也可以使用EAGER Loading代替LAZY)
@Id
private Integer id;
@OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
@JoinColumn(name="COUNTRY_ID")
private List<State> stateList;
此表具有COUNTRY_ID,即国家/地区的外键
@Id
private Integer id;
@OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
@JoinColumn(name="STATE_ID")
private List<County> countyList;
@Column(name="COUNTRY_ID")
private Integer countryId;
此表具有STATE_ID,即状态的外键
@Id
private Integer id;
@OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
@JoinColumn(name="COUNTY_ID")
private List<City> cityList;
@Column(name="STATE_ID")
private Integer stateId;
此表的COUNTY_ID是县的外键
@Id
private Integer id;
@Column(name="COUNTY_ID")
private Integer countyId;
您的JPQL将是:
Select o from Country o where o.id=10
这将选择国家实体以及下面的所有映射。
Country
Holding List of States
Each States Holding List of Counties
Each Counties Holding LIst of Cities
答案 6 :(得分:1)
对于像你这样的要求,我建议使用树状结构来维护分层位置数据。实施起来比较容易。维护并且更具可扩展性可扩展的。
为了实现树,您需要有2个表LOCATION_NODE
(位置ID,位置名称,位置类型[国家,州,县,城市])&amp; LOCATION_REL
(关系ID,父ID,子ID)。以下是树构思的基本实现。
public class LocationRel<T> {
private LocationNode<T> root;
public LocationRel(T rootData) {
root = new LocationNode<T>();
root.data = rootData;
root.children = new ArrayList<LocationNode<T>>();
}
public static class LocationNode<T> {
private T data;
private LocationNode<T> parent;
private List<LocationNode<T>> children;
}
}
这是树的基本构建块。您可能需要添加添加,删除,遍历和构造函数的方法。但是,一旦实现,您可以自由添加任何新的位置类型,更改层次结构,添加节点,删除节点等与您的分层数据。
开箱即用。
Shishir
答案 7 :(得分:1)
如果您需要性能,我建议您对表进行反规范化并创建具有以下属性(列)的4个实体:
(映射很明显)
然后,您将能够构建一个简单的SQL查询。 如果需要性能,则首选命名查询,因为它们是在初始化时编译的。 例如。按国家/地区选择所有城市:“SELECT id,name FROM city WHERE country_id =?”
您甚至可能不使用@ManyToOne声明实体之间的引用,而只是声明一个简单的@Columns。 API调用很可能会接受ID(countryId,stateId),因此您最好将这些ID作为参数传递给DAO。最有可能的是,您通过sql脚本填充了一次位置表,并且不应修改数据。创建外键以保证数据完整性。
你真的需要记忆中的树状结构吗?如果是这样,手动创建,它不是很复杂。
答案 8 :(得分:0)
在线搜索,我发现了一些关于JPQL的链接,我认为这可能有所帮助。
反正,
JPQL
是实现此目的的最佳方法之一,请尝试此查询
SELECT DISTINCT country FROM Country country JOIN FETCH country.states states JOIN FETCH states.counties counties JOIN FETCH counties.cities cities WHERE country.id = :countryId
答案 9 :(得分:0)
如果您的关系仅指向其父级,那么有用的解决方案如下:
有记录:
@Entity
public class Country
{
@Id
private Long id;
}
@Entity
public class State
{
@Id
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "country_id", referencedColumnName = "id", nullable = false)
Country country;
}
@Entity
public class County
{
@Id
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "state_id", referencedColumnName = "id", nullable = false)
State state;
}
@Entity
public class City
{
@Id
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "county_id", referencedColumnName = "id", nullable = false)
County county;
}
您可以通过以下方式获取一个国家/地区的所有城市:
public interface CityRepository extends JpaRepository<City, Long>
{
List<City> findByCounty(County county); // county is a direct field of City
@Query("SELECT c FROM City c WHERE c.county.state.country = ?1")
List<City> findByCountry(Country country);
}