Spring 引导:POST 请求具有多对多关系的实体
Spring Boot: POST request to entity with ManyToMany relationship
我正在开发用于添加乐队、音乐家、乐器等的数据库
我有一个table'band'和一个table'musician'。他们有一个 ManyToMany 关系(一个乐队可以有很多音乐家,一个音乐家可以在很多乐队中),还有一个额外的 table BandMusician,它有一个 embeddedId 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;
}
当我收到“/musician”的 POST 请求时,我可以保存一位音乐家。我正在使用 Jackson 反序列化这样的请求:
{
"name": "John the Ripper",
"DoB": "03-12-1965",
"instruments": "voice, guitar",
"bands": "Band1, Band2"
}
有了 Jackson,我可以获得每个乐队,用 BandRepository 搜索并创建一个 BandMusician。
问题:当我收到请求时,为了创建 BandMusician,我必须创建 BandMusiciansId,为此我需要 bandId 和 MusicianId。但我现在正在创建音乐家,所以我没有 musicianId。它是在我保存音乐家时自动创建的。
MusicianJsonDeserializer class
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 ).
同样,我想要的是能够将 POST 发送到“/musician”,这将被 MusicianJsonDeserializer 捕获,并以某种方式为请求正文中发送的每个乐队创建 BandMusicianId 和 BandMusician。
我有实体 Band 和 Musician 以及它们之间的多对多关系与关联 table BandMusician。
我想要的是在同一请求中创建实体 Musician 和关系 (BandMusician)。
据我所知,这是不可能的,因为要在协会 table(BandMusician)中创建唱片,我必须有音乐家(我在这个请求中创建) 已经创建。
我尝试了一切只是为了看看它是否可能并且无法做到。但即使有可能,这也是一种非常糟糕的做法,因为它会使 class 耦合得太紧。
明确的解决方案是使用此请求仅创建 Musician,然后发送另一个请求以创建 Band 和 Musician 之间的连接。
我还尝试通过一个请求在 BandMusician table 中创建许多条目,这也是不可能的,因为 JsonDeserializer table 似乎不接受 List<> 作为 return 类型。我试图避免提出很多请求来创建关系条目(例如,对于一个在五个乐队中的音乐家),但似乎最好让事情保持清晰和简单。
我现在根据请求保存一个音乐家-乐队关系:
{
"musician": "Awesome musician",
"band": "Awesome band",
"role": "guitar",
"joined": 2003
}
我正在开发用于添加乐队、音乐家、乐器等的数据库
我有一个table'band'和一个table'musician'。他们有一个 ManyToMany 关系(一个乐队可以有很多音乐家,一个音乐家可以在很多乐队中),还有一个额外的 table BandMusician,它有一个 embeddedId 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;
}
当我收到“/musician”的 POST 请求时,我可以保存一位音乐家。我正在使用 Jackson 反序列化这样的请求:
{
"name": "John the Ripper",
"DoB": "03-12-1965",
"instruments": "voice, guitar",
"bands": "Band1, Band2"
}
有了 Jackson,我可以获得每个乐队,用 BandRepository 搜索并创建一个 BandMusician。
问题:当我收到请求时,为了创建 BandMusician,我必须创建 BandMusiciansId,为此我需要 bandId 和 MusicianId。但我现在正在创建音乐家,所以我没有 musicianId。它是在我保存音乐家时自动创建的。
MusicianJsonDeserializer class
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 ). 同样,我想要的是能够将 POST 发送到“/musician”,这将被 MusicianJsonDeserializer 捕获,并以某种方式为请求正文中发送的每个乐队创建 BandMusicianId 和 BandMusician。
我有实体 Band 和 Musician 以及它们之间的多对多关系与关联 table BandMusician。
我想要的是在同一请求中创建实体 Musician 和关系 (BandMusician)。
据我所知,这是不可能的,因为要在协会 table(BandMusician)中创建唱片,我必须有音乐家(我在这个请求中创建) 已经创建。
我尝试了一切只是为了看看它是否可能并且无法做到。但即使有可能,这也是一种非常糟糕的做法,因为它会使 class 耦合得太紧。
明确的解决方案是使用此请求仅创建 Musician,然后发送另一个请求以创建 Band 和 Musician 之间的连接。
我还尝试通过一个请求在 BandMusician table 中创建许多条目,这也是不可能的,因为 JsonDeserializer table 似乎不接受 List<> 作为 return 类型。我试图避免提出很多请求来创建关系条目(例如,对于一个在五个乐队中的音乐家),但似乎最好让事情保持清晰和简单。
我现在根据请求保存一个音乐家-乐队关系:
{
"musician": "Awesome musician",
"band": "Awesome band",
"role": "guitar",
"joined": 2003
}