java - Spring REST API 列表始终为空
问题描述
因此,我想在 Intellij(使用 Gradle)中使用 Spring 创建一个 REST API 来管理国家/地区。每个国家/地区都有多个区域(作为列表),每个区域都有多个位置。我正在使用 Hibernate,并且我有一个名为 DatabaseLoader 的类,它可以在数据库中预加载条目。它预加载国家/地区,但它们的列表是空的。谁能解释我做错了什么?
这是国家级:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "Countries")
public class Country {
private @Id @GeneratedValue Long id;
private String name;
@Autowired
@OneToMany(mappedBy="country")
private List<Region> regions = new ArrayList<>();
Country (String name, List<Region> regions) {
this.name = name;
this.regions = regions;
}
Country(String name) {
this.name = name;
}
Country() {}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Region> getRegions() {
return regions;
}
public void setRegions(List<Region> regions) {
this.regions = regions;
}
@Bean
@Autowired
public void addRegion(Region region) {
regions.add(region);
}
@Bean
@Autowired
public void listRegions() {
System.out.println(name);
System.out.println("xxxx");
for (Region region : regions) {
region.listLocations();
}
}
}
和区域类:
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "Regions")
public class Region {
private @Id @GeneratedValue Long id;
private String name;
@ManyToOne
@JoinColumn(name = "country_id")
private Country country;
@OneToMany(mappedBy="region")
private List<Location> locations = new ArrayList<>();
public Country getCountry () {
return country;
}
public void setCountry (Country country) {
this.country = country;
}
public Region(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<Location> getLocations() {
return locations;
}
public void setLocations(List<Location> locations) {
this.locations = locations;
}
@Bean
public void addLocation(Location location) {
this.locations.add(location);
}
@Bean
@Autowired
public void listLocations() {
System.out.println(this.name);
System.out.println("xxx");
for(int i =0; i < locations.size(); i++) {
System.out.print(locations.get(i).getId() + " ");
locations.get(i).showAvailableSports();
}
}
}
这是数据库加载器:
package com.example.demo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class DatabaseLoader {
private static final Logger log = LoggerFactory.getLogger(DatabaseLoader.class);
@Bean
CommandLineRunner initDatabase(CountryRepo repo) {
return args -> {
Region lyon = new Region("Lyon");
List<Region> regions = new ArrayList<>();
Country France = new Country("France");
France.addRegion(lyon);
log.info("Preloading " + repo.save(France) + " " + France.getRegions().get(1).getName());
};
}
}
在输出中,它正确显示了区域的名称,但是当使用 Postman 进行 GET 请求时,列表为空。
解决方案
由于您有两个不同的实体并且您在这里只保存一个(父级),因此您还需要将您的持久效果级联到子级,如下所示
@OneToMany(mappedBy="country", cascade = {CascadeType.PERSIST})
private List<Region> regions = new ArrayList<>();
这将指示 JPA 提供者(默认休眠)在父实体(保存国家/地区)时也保存子实体。
Cascade.PERSIST
是一种级联类型,您可以在更新/删除父实体 ie 等时产生类似的效果Cascade.MERGE
,Cascade.REMOVE
如果您想有选择地选择然后添加其中一些,否则使用Cascade.ALL
,但要注意这将适用于所有效果,所以请阅读有关的文档它。
您在日志中看到的name
区域由您设置,尝试打开休眠查询日志并查看查询是否被触发到子表。
编辑
简短回答:让你的两个实体保持同步
public void addRegion(Region region) {
regions.add(region);
region.setCountry(this);
}
TL;博士
在下面的评论之后,我看到了您的代码的另一个问题,而在写这个答案时我错过了它。
问题是您的实体之间存在双向关系,当您拥有这种关系时,您必须在对一个或另一个执行操作时使它们保持同步。
如果您要检查您的日志(如果您不知道,请在此答案的底部找到参考),然后 JPA(休眠)也会向您的数据库发出查询以获取区域实体。您还可以查询您的数据库并进行检查。
但是您会发现外键country_id
为空,因此您的两个表之间的关系未建立,当您查询国家/地区时,hibernate 将发出第二个查询以获取相关区域(如果您的获取策略是 LAZY)
select regions0_.country_id as country_3_3_0_, regions0_.id as id1_3_0_, regions0_.id as id1_3_1_, regions0_.country_id as country_3_3_1_, regions0_.name as name2_3_1_ from region regions0_ where regions0_.country_id=?
由于country_id
in 中的字段region
将为空,因此您不会收到带有国家 ID 的区域。
因此,为了保持双向关系同步,您可以做两件事:
- 要么在两个实体上调用 setter。
- 或者,在
addRegion
您所在国家/地区实体的函数中调用区域设置器。
两个实体上的调用设置器
您addRegion
在国家实体中具有功能,该实体正在将区域添加到其List<Region>
public void addRegion(Region region) {
regions.add(region);
}
并且您setCountry
在区域实体中
public void setCountry (Country country) {
this.country = country;
}
您需要在两个实体上的命令行运行程序中调用这两种方法,以便休眠可以在表中为您应用该外键。
Region lyon = new Region();
lyon.setName("Lyon");
Country france = new Country();
france.setName("France");
france.addRegion(lyon);
lyon.setCountry(france);
//other code below it to save
在 addRegion 函数中调用区域设置器
早期方法的问题是客户端必须知道它需要在两个实体上调用 setter。
要解决此问题,您可以在客户端内部函数中隐藏这两个操作addRegion
,如下所示
public void addRegion(Region region) {
regions.add(region);
region.setCountry(this);
}
这将更新country_id
区域表中的字段,您必须在获取请求中获取包含区域的国家/地区。
这些是它在应用程序运行时触发的查询(您的可能有更多字段)
insert into country (name) values (?)
insert into region (country_id, name) values (?, ?)
如果您想知道我是如何打印此 SQL 的,那么不要担心它很容易,您必须在开发中使用它来检查查询。
您可以简单地将这两个属性添加到您的application.properties
spring.datasource.jpa.show-sql=true
logging.level.org.hibernate.SQL=debug
注意:如果您返回实体对象作为响应,您必须@JsonIgnore
在您的类getCountry()
内部注释Region
,否则它将通过 stackoverlow 错误,因为杰克逊试图递归序列化您的Country
对象及其所有属性。
另外,为什么你有这些@Bean
注释在你的实体类之上getters
或之上?setters
要么你误解了@Bean
注释,要么你试图做一些你不应该做的事情。
推荐阅读
- python-3.x - 从 pandas xlsxwriter 打开 Excel (XLSX) 文件时出错
- c - 这里的“tempPtr”不是多余的吗?(链接列表)
- java - 无法在 Spring Boot Maven 项目中集成外部 jar
- flutter - 如何在 Flutter 中制作具有浮雕效果的按钮
- c++ - 使用 mem_fn 代替 mem_fun
- django - Heroku 应用程序在使用新依赖项推送更改后崩溃
- javascript - 以编程方式替换整个文档的“innerHTML”不会触发加载 JS?
- html - 我想将复选框标签向左移动
- javascript - async 标签是否用 Promise 包装了 JavaScript 函数?
- postgresql - SSH RDS 的 DNS ODBC 驱动程序配置