简体   繁体   中英

Spring boot REST CRUD - how to POST an entitiy with a one-to-one relationship?

I have a really simple domain model: An 'Alert' has one 'Type' and one 'Status'.

This is my schema:

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;

Now, I'm going to show the respective entity classes:

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
}

Repositories:

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> {

}

Now, the main controller:

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();
    }
}

So, I have two questions:

  • How can I create an alert, via POST, and associate existing status and type?

For example, this would be my cURL. I'm trying to indicate that I want to associate to this new alert the 'Status' and 'Type' existing objects, passing their respective IDs:

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}'
  • Like the first question, how can I update an Alert, associating new existing 'Status' and 'Type' objects?

Thanks!

I think there is no out-of-the-box way to achieve this with a single POST request. The approach I see used most of the time is making an initial request to create the Alert, and subsequent requests to associate Status and Type.

You could take a look at how Spring Data Rest approaches the problem here:

https://reflectoring.io/relations-with-spring-data-rest/

https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources.association-resource

I'm not the biggest fan of Spring Data Rest though, since it forces some things (like hateoas) down your throat ,but you can easily implement the same approach manually.

You could argue that it's overkill to have separate calls to set the status and type of an alert, being both actually part of the alert, and I may agree actually. So if you don't mind slightly deviating from the rigidity of what people mostly call REST APIs (but are more like CRUD interfaces exposing your data model), it could make sense to take an AlertDto (with status and type ids) in your alert creation endpoint, retrieve status and type with these ids and create the Alert object you will eventually store.

Having said all of the above, I would avoid having tables for Status and Type if all they have is a name. I would have these names in the Alert itself and no relationships at all. Yes it may occupy more space on the database, but disk space is hardly a problem nowadays, and I'm guessing status and type are usually short strings.

I admit I am specially biased against this id-name lookup table pattern because we have dozens of these in one of our projects at work and they do nothing but generate a lot of useless code and complicate the DB schema.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM