Spring boot REST CRUD - как опубликовать объект с однозначным отношением?

У меня действительно простая модель предметной области: «Предупреждение» имеет один «Тип» и один «Статус».

Это моя схема:

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. Я пытаюсь указать, что хочу связать с этим новым предупреждением существующие объекты «Статус» и «Тип», передав их соответствующие идентификаторы:

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}'
  • Как и в первом вопросе, как я могу обновить оповещение, связав новые существующие объекты «Статус» и «Тип»?

Спасибо!

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
819
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я думаю, что нет готового способа добиться этого с помощью одного запроса POST. Подход, который, как я вижу, используется большую часть времени, - это первоначальный запрос на создание оповещения и последующие запросы на привязку статуса и типа.

Вы можете посмотреть, как Spring Data Rest подходит к проблеме здесь:

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

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

Я не самый большой поклонник Spring Data Rest, так как он заставляет некоторые вещи (например, ненависть) проникнуть вам в глотку. , но вы можете легко реализовать тот же подход вручную.

Вы можете возразить, что иметь отдельные вызовы для установки статуса и типа оповещения - это излишне, поскольку оба они фактически являются частью оповещения, и я могу с этим согласиться. Поэтому, если вы не возражаете немного отклониться от жесткости того, что люди в основном называют REST API (но больше похожи на интерфейсы CRUD, раскрывающие вашу модель данных), имеет смысл использовать AlertDto (со статусом и идентификаторами типа) в вашем предупреждении. создание конечной точки, получите статус и тип с этими идентификаторами и создайте объект Alert, который вы в конечном итоге сохраните.

Сказав все вышесказанное, я бы не стал использовать таблицы для статуса и типа, если бы у них было только имя. У меня были бы эти имена в самом предупреждении и вообще никаких отношений. Да, он может занимать больше места в базе данных, но дисковое пространство в настоящее время не является проблемой, и я предполагаю, что статус и тип обычно короткие.

Я признаю, что я особенно предвзято отношусь к этому шаблону таблицы поиска id-name, потому что у нас есть десятки таких шаблонов в одном из наших работающих проектов, и они ничего не делают, кроме генерирования большого количества бесполезного кода и усложнения схемы БД.

Спасибо, Педро. Я согласен с вами.

Tiago Melo 23.07.2018 14:25

Другие вопросы по теме