Spring 引导 REST CRUD - 如何 POST 具有一对一关系的实体?
Spring boot REST CRUD - how to POST an entitiy with a one-to-one relationship?
我有一个非常简单的域模型:一个 'Alert' 有一个 'Type' 和一个 'Status'.
这是我的架构:
create table `price_alert_status` (
`id` bigint(20) not null,
`status_name` varchar(64) not null,
primary key (`id`),
unique key (`status_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_status` values (0, 'INACTIVE');
insert into `price_alert_status` values (1, 'ACTIVE');
create table `price_alert_type` (
`id` bigint(20) not null,
`type_name` varchar(64) not null,
primary key (`id`),
unique key (`type_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_type` values (0, 'TYPE_0');
insert into `price_alert_type` values (1, 'TYPE_1');
create table `price_alert` (
`id` bigint(20) not null auto_increment,
`user_id` bigint(20) not null,
`price` double not null,
`price_alert_status_id` bigint(20) not null,
`price_alert_type_id` bigint(20) not null,
`creation_date` datetime not null,
`cancelation_date` datetime null,
`send_periodic_email` tinyint(1) not null,
`price_reached_notifications` tinyint(4) default '0',
`approximate_price_notifications` tinyint(4) null,
`notify` tinyint(1) not null default '1',
primary key (`id`),
constraint `FK_ALERT_TO_ALERT_STATUS` foreign key (`price_alert_status_id`) references `price_alert_status` (`id`),
constraint `FK_ALERT_TO_ALERT_TYPE` foreign key (`price_alert_type_id`) references `price_alert_type` (`id`)
) engine=InnoDB default charset=utf8;
现在,我将展示相应的实体 类:
Alert.java:
// imports omitted
@Entity
@Table(name = "price_alert")
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"creationDate"},
allowGetters = true)
public class Alert implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private double price;
@OneToOne
@JoinColumn(name = "price_alert_status_id", nullable = false)
private Status status;
@OneToOne
@JoinColumn(name = "price_alert_type_id", nullable = false)
private Type type;
@Column(nullable = false, updatable = false)
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date creationDate;
@Column(nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date cancelationDate;
private boolean sendPeriodicEmail;
@Column(nullable = true)
private byte priceReachedNotifications;
@Column(nullable = true)
private byte approximatePriceNotifications;
private boolean notify;
// getters and setters omitted
}
Status.java:
//imports omitted
@Entity
@Table(name = "price_alert_status")
public class Status implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
@Column(name = "status_name")
@NotBlank
private String name;
//getters and setters omitted
}
Type.java:
//imports omitted
@Entity
@Table(name = "price_alert_type")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
@Column(name = "type_name")
@NotBlank
private String name;
//getters and setters omitted
}
存储库:
AlertRepository.java:
//imports omitted
@Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
}
StatusRepository.java:
//imports omitted
@Repository
public interface StatusRepository extends JpaRepository<Status, Long> {
}
TypeRepository.java:
//imports omitted
@Repository
public interface TypeRepository extends JpaRepository<Type, Long> {
}
现在,主控制器:
AlertController.java:
@RestController
@RequestMapping("/api")
public class AlertController {
@Autowired
AlertRepository alertRepository;
@Autowired
StatusRepository statusRepository;
@Autowired
TypeRepository typeRepository;
@GetMapping("/alerts")
public List<Alert> getAllAlerts() {
return alertRepository.findAll();
}
@PostMapping("/alert")
public Alert createAlert(@Valid @RequestBody Alert alert) {
return alertRepository.save(alert);
}
@GetMapping("/alert/{id}")
public Alert getAlertById(@PathVariable(value = "id") Long alertId) {
return alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
}
@PutMapping("/alert/{id}")
public Alert updateAlert(@PathVariable(value = "id") Long alertId,
@Valid @RequestBody Alert alertDetails) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alert.setApproximatePriceNotifications(alertDetails.getApproximatePriceNotifications());
alert.setCancelationDate(alertDetails.getCancelationDate());
alert.setNotify(alertDetails.isNotify());
alert.setPrice(alertDetails.getPrice());
alert.setPriceReachedNotifications(alertDetails.getPriceReachedNotifications());
alert.setSendPeriodicEmail(alertDetails.isSendPeriodicEmail());
alert.setUserId(alertDetails.getUserId());
// TODO: how to update Status and Type?
Alert updatedAlert = alertRepository.save(alert);
return updatedAlert;
}
@DeleteMapping("/alert/{id}")
public ResponseEntity<?> deleteAlert(@PathVariable(value = "id") Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alertRepository.delete(alert);
return ResponseEntity.ok().build();
}
}
所以,我有两个问题:
- 如何通过 POST 创建提醒并关联现有状态和类型?
例如,这是我的 cURL。我试图表明我想将 'Status' 和 'Type' 现有对象关联到这个新警报,传递它们各自的 ID:
curl -H "Content-Type: application/json" -v -X POST localhost:8080/api/alert -d '{"userId": "1", "price":"20.0", "status": {"id": 0}, "type": {"id": 0}, "sendPeriodicEmail":false,"notify":true}'
- 与第一个问题一样,我如何更新警报,关联新的现有 'Status' 和 'Type' 对象?
谢谢!
我认为没有现成的方法可以通过单个 POST 请求来实现这一点。我看到大部分时间使用的方法是发出创建警报的初始请求,以及随后关联状态和类型的请求。
您可以在此处查看 Spring Data Rest 如何解决该问题:
https://reflectoring.io/relations-with-spring-data-rest/
不过,我不是 Spring Data Rest 的忠实粉丝,因为它会迫使一些东西(比如 hateoas)进入你的喉咙
,但您可以轻松地手动实施相同的方法。
您可能会争辩说,单独调用来设置警报的状态和类型是矫枉过正,实际上两者都是警报的一部分,我可能真的同意。因此,如果您不介意稍微偏离人们通常所说的 REST API 的严格性(但更像是公开您的数据模型的 CRUD 接口),那么在您的警报中使用 AlertDto(具有状态和类型 ID)可能是有意义的创建端点,使用这些 ID 检索状态和类型,并创建您最终将存储的 Alert 对象。
综上所述,如果状态和类型只有名称,我会避免使用 table。我会在警报本身中使用这些名称,并且根本没有任何关系。是的,它可能会在数据库上占用更多 space,但现在磁盘 space 几乎不是问题,我猜状态和类型通常是短字符串。
我承认我特别反对这种 id-name 查找模式 table 因为我们在工作的一个项目中有几十个这样的模式,它们除了生成大量无用的代码并使数据库架构。
我有一个非常简单的域模型:一个 'Alert' 有一个 'Type' 和一个 'Status'.
这是我的架构:
create table `price_alert_status` (
`id` bigint(20) not null,
`status_name` varchar(64) not null,
primary key (`id`),
unique key (`status_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_status` values (0, 'INACTIVE');
insert into `price_alert_status` values (1, 'ACTIVE');
create table `price_alert_type` (
`id` bigint(20) not null,
`type_name` varchar(64) not null,
primary key (`id`),
unique key (`type_name`)
) engine=InnoDB default charset=utf8;
insert into `price_alert_type` values (0, 'TYPE_0');
insert into `price_alert_type` values (1, 'TYPE_1');
create table `price_alert` (
`id` bigint(20) not null auto_increment,
`user_id` bigint(20) not null,
`price` double not null,
`price_alert_status_id` bigint(20) not null,
`price_alert_type_id` bigint(20) not null,
`creation_date` datetime not null,
`cancelation_date` datetime null,
`send_periodic_email` tinyint(1) not null,
`price_reached_notifications` tinyint(4) default '0',
`approximate_price_notifications` tinyint(4) null,
`notify` tinyint(1) not null default '1',
primary key (`id`),
constraint `FK_ALERT_TO_ALERT_STATUS` foreign key (`price_alert_status_id`) references `price_alert_status` (`id`),
constraint `FK_ALERT_TO_ALERT_TYPE` foreign key (`price_alert_type_id`) references `price_alert_type` (`id`)
) engine=InnoDB default charset=utf8;
现在,我将展示相应的实体 类:
Alert.java:
// imports omitted
@Entity
@Table(name = "price_alert")
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = {"creationDate"},
allowGetters = true)
public class Alert implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private double price;
@OneToOne
@JoinColumn(name = "price_alert_status_id", nullable = false)
private Status status;
@OneToOne
@JoinColumn(name = "price_alert_type_id", nullable = false)
private Type type;
@Column(nullable = false, updatable = false)
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private Date creationDate;
@Column(nullable = true)
@Temporal(TemporalType.TIMESTAMP)
private Date cancelationDate;
private boolean sendPeriodicEmail;
@Column(nullable = true)
private byte priceReachedNotifications;
@Column(nullable = true)
private byte approximatePriceNotifications;
private boolean notify;
// getters and setters omitted
}
Status.java:
//imports omitted
@Entity
@Table(name = "price_alert_status")
public class Status implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
@Column(name = "status_name")
@NotBlank
private String name;
//getters and setters omitted
}
Type.java:
//imports omitted
@Entity
@Table(name = "price_alert_type")
public class Type implements Serializable {
private static final long serialVersionUID = 1L;
@Id
private Long id;
@Column(name = "type_name")
@NotBlank
private String name;
//getters and setters omitted
}
存储库:
AlertRepository.java:
//imports omitted
@Repository
public interface AlertRepository extends JpaRepository<Alert, Long> {
}
StatusRepository.java:
//imports omitted
@Repository
public interface StatusRepository extends JpaRepository<Status, Long> {
}
TypeRepository.java:
//imports omitted
@Repository
public interface TypeRepository extends JpaRepository<Type, Long> {
}
现在,主控制器:
AlertController.java:
@RestController
@RequestMapping("/api")
public class AlertController {
@Autowired
AlertRepository alertRepository;
@Autowired
StatusRepository statusRepository;
@Autowired
TypeRepository typeRepository;
@GetMapping("/alerts")
public List<Alert> getAllAlerts() {
return alertRepository.findAll();
}
@PostMapping("/alert")
public Alert createAlert(@Valid @RequestBody Alert alert) {
return alertRepository.save(alert);
}
@GetMapping("/alert/{id}")
public Alert getAlertById(@PathVariable(value = "id") Long alertId) {
return alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
}
@PutMapping("/alert/{id}")
public Alert updateAlert(@PathVariable(value = "id") Long alertId,
@Valid @RequestBody Alert alertDetails) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alert.setApproximatePriceNotifications(alertDetails.getApproximatePriceNotifications());
alert.setCancelationDate(alertDetails.getCancelationDate());
alert.setNotify(alertDetails.isNotify());
alert.setPrice(alertDetails.getPrice());
alert.setPriceReachedNotifications(alertDetails.getPriceReachedNotifications());
alert.setSendPeriodicEmail(alertDetails.isSendPeriodicEmail());
alert.setUserId(alertDetails.getUserId());
// TODO: how to update Status and Type?
Alert updatedAlert = alertRepository.save(alert);
return updatedAlert;
}
@DeleteMapping("/alert/{id}")
public ResponseEntity<?> deleteAlert(@PathVariable(value = "id") Long alertId) {
Alert alert = alertRepository.findById(alertId)
.orElseThrow(() -> new ResourceNotFoundException("Alert", "id", alertId));
alertRepository.delete(alert);
return ResponseEntity.ok().build();
}
}
所以,我有两个问题:
- 如何通过 POST 创建提醒并关联现有状态和类型?
例如,这是我的 cURL。我试图表明我想将 'Status' 和 'Type' 现有对象关联到这个新警报,传递它们各自的 ID:
curl -H "Content-Type: application/json" -v -X POST localhost:8080/api/alert -d '{"userId": "1", "price":"20.0", "status": {"id": 0}, "type": {"id": 0}, "sendPeriodicEmail":false,"notify":true}'
- 与第一个问题一样,我如何更新警报,关联新的现有 'Status' 和 'Type' 对象?
谢谢!
我认为没有现成的方法可以通过单个 POST 请求来实现这一点。我看到大部分时间使用的方法是发出创建警报的初始请求,以及随后关联状态和类型的请求。
您可以在此处查看 Spring Data Rest 如何解决该问题:
https://reflectoring.io/relations-with-spring-data-rest/
不过,我不是 Spring Data Rest 的忠实粉丝,因为它会迫使一些东西(比如 hateoas)进入你的喉咙 ,但您可以轻松地手动实施相同的方法。
您可能会争辩说,单独调用来设置警报的状态和类型是矫枉过正,实际上两者都是警报的一部分,我可能真的同意。因此,如果您不介意稍微偏离人们通常所说的 REST API 的严格性(但更像是公开您的数据模型的 CRUD 接口),那么在您的警报中使用 AlertDto(具有状态和类型 ID)可能是有意义的创建端点,使用这些 ID 检索状态和类型,并创建您最终将存储的 Alert 对象。
综上所述,如果状态和类型只有名称,我会避免使用 table。我会在警报本身中使用这些名称,并且根本没有任何关系。是的,它可能会在数据库上占用更多 space,但现在磁盘 space 几乎不是问题,我猜状态和类型通常是短字符串。
我承认我特别反对这种 id-name 查找模式 table 因为我们在工作的一个项目中有几十个这样的模式,它们除了生成大量无用的代码并使数据库架构。