【问题标题】:Spring JPA REST One to ManySpring JPA REST 一对多
【发布时间】:2016-04-07 14:45:34
【问题描述】:

我想通过将地址列表添加到Person 实体来扩展示例Accessing JPA Data with REST。所以,我添加了一个列表addresses@OneToMany 注释:

@Entity
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    private String firstName;
    private String lastName;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Address> addresses = new ArrayList<>();

   // get and set methods...
}

Address 类是一个非常简单的类:

@Entity
public class Address {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String street;
    private String number;
    // get and set methods...
}

最后我添加了AddressRepository接口:

public interface AddressRepository extends PagingAndSortingRepository<Address, Long> {}

然后我尝试用一​​些地址发布一个人:

curl -i -X POST -H "Content-Type:application/json" -d '{  "firstName" : "Frodo",  "lastName" : "Baggins", "addresses": [{"street": "somewhere", "number": 1},{"street": "anywhere", "number": 0}]}' http://localhost:8080/people

我得到的错误是:

Could not read document: Failed to convert from type [java.net.URI] to type [ws.model.Address] for value 'street';
nested exception is java.lang.IllegalArgumentException: Cannot resolve URI street. Is it local or remote? Only local URIs are resolvable. (through reference chain: ws.model.Person[\"addresses\"]->java.util.ArrayList[1]);
nested exception is com.fasterxml.jackson.databind.JsonMappingException: Failed to convert from type [java.net.URI] to type [ws.model.Address] for value 'street'; nested exception is java.lang.IllegalArgumentException: Cannot resolve URI street. Is it local or remote? Only local URIs are resolvable. (through reference chain: ws.model.Person[\"addresses\"]->java.util.ArrayList[1])

创建一对多和多对多关系并将json对象发布到它们的正确方法是什么?

【问题讨论】:

  • 您已经向我们展示了您的 ORM 的实体类,但您没有向我们展示任何为 REST 注释的内容。
  • 查看此答案,建议使用自定义转换器stackoverflow.com/questions/24781516/…
  • @scottb 我在教程中使用@RepositoryRestResource 注释,用于两个存储库(人员、地址)。这将为实体创建公共 REST 端点。
  • 我相信它与spring-dataspring-data-jpa 而不是普通的spring 和jpa 更相关。重新标记

标签: java rest spring-data-jpa spring-data-rest


【解决方案1】:

您应该先发布这两个地址,然后在您的个人 POST 中使用它们返回的 URL(例如 http://localhost:8080/addresses/1http://localhost:8080/addresses/2):

curl -i -X POST -H "Content-Type:application/json" -d '{  "firstName" : "Frodo",  "lastName" : "Baggins", "addresses": ["http://localhost:8080/addresses/1","http://localhost:8080/addresses/2"]}' http://localhost:8080/people

如果您想先保存此人,然后添加其地址,您可以这样做:

curl -i -X POST -H "Content-Type:application/json" -d '{  "firstName" : "Frodo",  "lastName" : "Baggins"}' http://localhost:8080/people
curl -i -X POST -H "Content-Type:application/json" -d '{"street": "somewhere", "number": 1}' http://localhost:8080/addresses
curl -i -X POST -H "Content-Type:application/json" -d '{"street": "anywhere", "number": 0}' http://localhost:8080/addresses
curl -i -X PATCH -H "Content-Type: text/uri-list" -d "http://localhost:8080/addresses/1
http://localhost:8080/addresses/2" http://localhost:8080/people/1/addresses

【讨论】:

  • 这行得通(它也适用于多对多关系,只需将注释更改为@ManyToMany),但我觉得很奇怪。你能解释一下为什么会这样吗?在应用程序中,我会先添加一个人,然后再添加地址,而不是相反。
  • 这种方法的另一件事是支持的 HTTP 方法是 GETPOST (docs.spring.io/spring-data/rest/docs/current/reference/html/…)。所以,如果我想为现有的人添加地址,我不能。
  • @ArisF。我认为使用资源及其 url(为您构建 HATEOAS architecture)符合 Spring Data REST 的精神。一种先保存此人然后将其地址分配给他的方法是在答案中(我刚刚对其进行了编辑)
  • 我收到一个错误"message": "Illegal character in path at index 33: http://localhost:8080/addresses/1 http://localhost:8080/addresses/2"。它位于第一个地址 URI 的末尾。我尝试用逗号分隔,但消息是一样的。
  • 是的,在 text/uri-list 媒体类型的 URI 之间,您需要一个换行符。编辑了答案,我可能对 Stackoverflow 的格式化程序犯了一些错误。
【解决方案2】:

我设法通过不导出引用的存储库来解决此问题。这是在界面顶部添加注释。在你的例子中,它会是这样的:

@RepositoryRestResource(exported = false)
public interface AddressRepository extends CrudRepository<Address, Long> {
}

这部分解决了问题,因为 Spring Data 仍不会为您传播外键。但是,它将保留您的人员和地址(不提及所属人员)。然后,如果我们再次调用 API 来更新这些丢失的外键,您将能够通过 API 获取一个人及其所有链接地址 - 正如@Francesco Pitzalis 提到的那样

希望对你有帮助。只是最后一点。我仍在为此努力,因为我认为 Hibernate 无法为我们传播外键是荒谬的(以及基本和必要的)。应该是有可能的。


已编辑:确实有可能。下面的实现能够持久化一个实体及其子代,将外键传播给它们,用于基于 Spring Data(Rest - 因为我们正在公开存储库)、Hibernate 5.0.12Final 和 MySQL 以及存储引擎 InnoDB(不在内存中)的架构数据库)。

@Entity
public class Producto implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String nombre;
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = "producto_id")
    private List<Formato> listaFormatos;
    //Constructor, getters and setters
}

https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/JoinColumn.html - 这很关键。

@Entity
public class Formato implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Integer cantidad;
    private String unidadMedida;
    @ManyToOne
    private Producto producto;
    //Constructor, getters and setters
}

@RepositoryRestResource
public interface ProductoRepository extends CrudRepository<Producto, Long> {
}

@RepositoryRestResource
public interface FormatoRepository extends CrudRepository<Formato, Long> {
}

spring.datasource.url=jdbc:mysql://localhost:3306/(database name)
spring.datasource.username=(username)
spring.datasource.password=(password)
spring.jpa.show-sql=true

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

这是非常重要的。您需要知道 Hibernate 在哪里运行 SQL 语句才能正确设置方言。对我来说,我的表的存储引擎是 InnoDB。下一个链接有所帮助。 What mysql driver do I use with spring/hibernate?

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

我唯一无法解释的是,现在,我可以导出“子”存储库,它仍然可以正常工作。有什么想法吗,伙计们?

【讨论】:

  • +1 用于暴露=false 存储库的解决方法/修复。但是,如果您还需要访问未公开存储库的某些实体,则不能使用它。我也尝试了@JoinColumn 的方式,但这对我不起作用。我得到了与作者相同的Failed to convert from type [java.net.URI] to type Exception
【解决方案3】:

你的休息服务不应该接受一个人而不是一个地址吗?

public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {}

或者,也许您正在尝试制作两种不同的休息服务,我不明白。你应该只有一个休息服务接受一个人,其中有地址条目。

【讨论】:

  • 你是对的。我在代码中有它。我在问题中跳过了它,因为教程中提到了它。
猜你喜欢
  • 1970-01-01
  • 2021-10-19
  • 1970-01-01
  • 2023-01-16
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
  • 2019-12-09
  • 1970-01-01
相关资源
最近更新 更多