Spring JPA REST 一对多
Spring JPA REST One to Many
我想通过向 Person
实体添加地址列表来扩展示例 Accessing JPA Data with REST。所以,我添加了一个带有 @OneToMany
注释的列表 addresses
:
@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
class是一个很简单的:
@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> {}
然后我试图POST一个人有一些地址:
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])
创建一对多和多对多关系以及 post json 对象的正确方法是什么?
你的休息服务不应该接受一个人而不是一个地址吗?
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {}
或者,也许您正在尝试制作两种不同的休息服务,我不明白。你应该只有一个 rest 服务接受一个有地址条目的人。
您应该先 POST 这两个地址,然后在您的 Person POST:
中使用返回的 URL(例如 http://localhost:8080/addresses/1 and http://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
我通过不导出引用的存储库设法解决了这个问题。这是在界面顶部添加注释。在你的例子中,它会是这样的:
@RepositoryRestResource(exported = false)
public interface AddressRepository extends CrudRepository<Address, Long> {
}
这部分解决了问题,因为 Spring 数据仍不会为您传播外键。但是,它将保留您的个人和地址(不引用属于的人)。然后,如果我们再次调用 API 来更新这些丢失的外键,您将能够通过 API 找到一个人及其所有 linked 地址 - 正如@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。下一个 link 有所帮助。
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>
我唯一无法解释的是,现在,我可以导出 "child" 存储库并且它仍然可以正常工作。有什么想法吗,伙计们?
我想通过向 Person
实体添加地址列表来扩展示例 Accessing JPA Data with REST。所以,我添加了一个带有 @OneToMany
注释的列表 addresses
:
@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
class是一个很简单的:
@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> {}
然后我试图POST一个人有一些地址:
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])
创建一对多和多对多关系以及 post json 对象的正确方法是什么?
你的休息服务不应该接受一个人而不是一个地址吗?
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {}
或者,也许您正在尝试制作两种不同的休息服务,我不明白。你应该只有一个 rest 服务接受一个有地址条目的人。
您应该先 POST 这两个地址,然后在您的 Person POST:
中使用返回的 URL(例如 http://localhost:8080/addresses/1 and http://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
我通过不导出引用的存储库设法解决了这个问题。这是在界面顶部添加注释。在你的例子中,它会是这样的:
@RepositoryRestResource(exported = false)
public interface AddressRepository extends CrudRepository<Address, Long> {
}
这部分解决了问题,因为 Spring 数据仍不会为您传播外键。但是,它将保留您的个人和地址(不引用属于的人)。然后,如果我们再次调用 API 来更新这些丢失的外键,您将能够通过 API 找到一个人及其所有 linked 地址 - 正如@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。下一个 link 有所帮助。 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>
我唯一无法解释的是,现在,我可以导出 "child" 存储库并且它仍然可以正常工作。有什么想法吗,伙计们?