Загрузка Spring: запрос POST к объекту с отношением ManyToMany

#java #spring-boot #many-to-many

#java #spring-boot #многие ко многим

Вопрос:

Я работаю над базой данных для добавления групп, музыкантов, инструментов и т. Д.

У меня есть таблица «группа» и таблица «музыкант». У них есть отношения ManyToMany (в одной группе может быть много музыкантов, музыкант может быть во многих группах), с дополнительной таблицей BandMusician, которая имеет встроенный идентификатор BandMusicianId . Я сделал это так, потому что хочу, чтобы отношения между группами и музыкантами содержали и другую информацию, например, год, когда музыкант присоединился к группе.

 @Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Band {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String genre;
    private int year;

    @OneToOne(mappedBy = "band")
    private Website website;

    @OneToMany(mappedBy = "band")
    private List<Album> albuns;

    @OneToMany(mappedBy = "band")
    private List<BandMusician> musicians;

}


@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonDeserialize(using = MusicianJsonDeserializer.class)
public class Musician {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @JsonFormat(pattern = "dd-MM-yyyy")
    @JsonProperty("DoB")
    @Column(name = "date_of_birth")
    private LocalDate DoB;

    @ManyToMany
    @JoinTable(
            name = "musician_instruments",
            joinColumns = @JoinColumn(name = "musician_id"),
            inverseJoinColumns = @JoinColumn(name = "instrument_id")
    )   
    private List<Instrument> instruments = new ArrayList<>();

    @OneToMany(mappedBy = "musician")
    private List<BandMusician> bands;

    public void addInstrument(Instrument instrument) {
        this.instruments.add(instrument);
    }

}


@Embeddable
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BandMusiciansId implements Serializable{
    
    private static final long serialVersionUID = 1L;

    @Column(name = "band_id")
    private Long bandId;

    @Column(name = "musician_id")
    private Long musicianId;
}


@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BandMusician {

    @EmbeddedId
    private BandMusiciansId id = new BandMusiciansId();

    @ManyToOne
    @MapsId("bandId")
    @JoinColumn(name = "band_id")
    private Band band;

    @ManyToOne
    @MapsId("musicianId")
    @JoinColumn(name = "musician_id")
    private Musician musician;

    private String role;
    private int joined;
}
 

Когда я получаю запрос POST на «/ musician», я могу сохранить музыканта. Я использую Jackson для десериализации такого запроса:

 {
    "name": "John the Ripper",
    "DoB": "03-12-1965",
    "instruments": "voice, guitar",
    "bands": "Band1, Band2"
}
 

С помощью Jackson я могу получить каждую группу, выполнить поиск в BandRepository и создать BandMusician.

ПРОБЛЕМА: когда я получаю запрос, чтобы создать BandMusician, я должен создать BandMusiciansId, и для этого мне нужны идентификатор группы и идентификатор музыканта. Но я создаю музыканта прямо сейчас, поэтому у меня нет musicianId. Он создается автоматически, когда я сохраняю музыканта.

Класс MusicianJsonDeserializer

 public class MusicianJsonDeserializer extends JsonDeserializer<Musician>{

private final InstrumentRepository instrumentRepository;
private final BandRepository bandRepository;

@Autowired
public MusicianJsonDeserializer(
        InstrumentRepository instrumentRepository,
        BandRepository bandRepository
) {
    this.instrumentRepository = instrumentRepository;
    this.bandRepository = bandRepository;
}

@Override
public Musician deserialize(JsonParser p, DeserializationContext ctxt) 
        throws IOException, JacksonException {
    
    ObjectCodec codec = p.getCodec();
    JsonNode root = codec.readTree(p);
    
    Musician musician = new Musician();
    musician.setName(root.get("name").asText());
    
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
    musician.setDoB(LocalDate.parse(root.get("DoB").asText(), formatter));
    
    if (root.get("instruments") != null) {
        String instrumentList = root.get("instruments").asText();
        String[] instrumentArray = instrumentList.split(", ");
        List<Instrument> musicianInstrumentList = new ArrayList<>();
        
        for (String instrument : instrumentArray) {
            Instrument instrumentFound = 
                    instrumentRepository.findByName(instrument)
                    .orElseThrow(RuntimeException::new);
            // TODO custom exception
            musicianInstrumentList.add(instrumentFound);
        }
        
        musician.setInstruments(musicianInstrumentList);
    }
    
    if (root.get("bands") != null) {
        // TODO Stuck here!
 

Что я думал сделать: в моем MusicianService после сохранения музыканта я могу создать BandMusician и отношения. Я думаю, что делать это на уровне сервиса было бы плохим выбором.

РЕДАКТИРОВАТЬ: чтобы упростить понимание, я создал проект только с соответствующими частями этого и отправил на github (https://github.com/ricardorosa-dev/gettinghelp ). Опять же, я хочу иметь возможность отправлять сообщение в «/ musician», которое будет перехвачено MusicianJsonDeserializer, и каким-то образом создавать BandMusicianId и BandMusician для каждой группы, отправленной в теле запроса.

Ответ №1:

У меня есть сущности Band и Musician и множество взаимосвязей между ними с таблицей ассоциаций BandMusician.

Я хотел создать сущность Musician и отношения (BandMusician) в одном запросе.

Насколько я могу судить, это невозможно, потому что для создания записи в таблице ассоциаций (BandMusician) мне нужно было бы, чтобы музыкант (который я создаю в этом запросе) уже был создан.

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

Очевидным решением было создать только музыканта с этим запросом, а затем отправить другой запрос для создания соединения между группой и музыкантом.

Я также пытался создать много записей в таблице BandMusician одним запросом, что также было невозможно, потому что таблица JsonDeserializer, похоже, не принимает List<> в качестве возвращаемого типа. Я пытался избежать большого количества запросов на создание записей отношений (например, для музыканта, который состоит из пяти групп), но, похоже, лучше сохранить все ясным и простым.

Теперь я сохраняю одно отношение музыкант-группа для каждого запроса:

 {
    "musician": "Awesome musician",
    "band": "Awesome band",
    "role": "guitar",
    "joined": 2003
}