Spring 数据 JPA 单向 OneToOne 映射不持久
Spring Data JPA Unidirectional OneToOne Mapping does not persist
我有一个 Client 数据类型(代表一个客户),它有一个 Person 类型的委托人和一个 Contact 类型的联系人(联系方式)。
客户端数据类型(带有映射)是:
@Entity
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Access(AccessType.PROPERTY)
private Long id;
@Column(unique = true, updatable = false)
private String nk;
@Min(10000000000L)
private Long abn;
private String name;
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "person_id")
private Person principal;
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "contact_id")
private Contact contact;
public Client() {
this.nk = UUID.randomUUID().toString();
}
// remaining constructors, builders and accessors omitted for brevity
}
以联系人为例(人员结构相同)
@Entity
public class Contact {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String phone;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String mobile;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String fax;
@OneToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
@JoinColumn(name = "address_id")
@JsonInclude(JsonInclude.Include.NON_NULL)
private Address address;
private String email;
public Contact() {
}
// remaining constructors, builders and accessors omitted for brevity
}
地址数据类型也是单向映射。我可以显示 Person 和 Address,但我很确定他们不会理解任何新内容。
不幸的是,通过 owner 和 operator 字段,Client 是与 Aircraft 的 ManyToOne 关系的主体的两倍。我说很不幸,因为它使我的问题复杂化。飞机数据类型如下:
@Entity
public class Aircraft {
@Id
private String registration;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "casa_code")
private Casa casa;
private String manufacturer;
private String model;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-d")
private LocalDate manufacture;
@ManyToOne(cascade={CascadeType.PERSIST}, fetch = FetchType.EAGER)
private Client owner;
@ManyToOne(cascade={CascadeType.PERSIST}, fetch = FetchType.EAGER)
private Client operator;
private String base;
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name = "airframe_id")
private Airframe airframe;
@OneToMany(cascade={CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinColumn(name = "ac_registration")
private Set<Prop> props;
@OneToMany(cascade={CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinColumn(name = "ac_registration")
private Set<Engine> engines;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-d")
private LocalDate lastUpdated;
public Aircraft() {
}
// remaining constructors, builders and accessors omitted for brevity
}
负责保存的控制器方法:
@RestController
@RequestMapping("/api")
public class AircraftController {
private static final Logger LOG = LoggerFactory.getLogger(AircraftController.class);
private AircraftService aircraftService;
@Autowired
public AircraftController(AircraftService aircraftService) {
this.aircraftService = aircraftService;
}
@Secured("ROLE_ADMIN")
@PostMapping(value="/maintainers/{mid}/aircrafts", produces = "application/json")
@ResponseStatus(value = HttpStatus.CREATED)
Response<Aircraft> create(@PathVariable("mid") Long mid, @RequestBody Aircraft request) {
return Response.of(aircraftService.create(request));
}
}
服务方式:
public interface AircraftService {
Aircraft create(Aircraft aircraft);
// other interface methods omitted for brevity
@Service
class Default implements AircraftService {
private static final Logger LOG = LoggerFactory.getLogger(AircraftService.class);
@Autowired
AircraftRepository aircraftRepository;
@Transactional
public Aircraft create(Aircraft aircraft) {
LOG.debug("creating new Aircraft with {}", aircraft);
if (aircraft.getOwner() != null && aircraft.getOwner().getNk() == null) {
aircraft.getOwner().setNk(UUID.randomUUID().toString());
}
if (aircraft.getOperator() != null && aircraft.getOperator().getNk() == null) {
aircraft.getOperator().setNk(UUID.randomUUID().toString());
}
return aircraftRepository.save(aircraft);
}
}
}
最后是存储库:
@Repository
public interface AircraftRepository extends JpaRepository<Aircraft, String> {
}
当我提供以下内容时 JSON:
{
"registration":"VH-ZZZ",
"casa":null,
"manufacturer":"PITTS AVIATION ENTERPRISES",
"model":"S-2B",
"manufacture":"1983-01-2",
"owner":{
"id":null,
"nk":"84f5f053-82dd-4563-8158-e804b3003f3b",
"abn":null,
"name":"xxxxx, xxxxx xxxxxx",
"principal":null,
"contact":{
"id":null,
"phone":null,
"address":{
"id":null,
"line3":"PO Box xxx",
"suburb":"KARAMA",
"postcode":"0813",
"state":"NT",
"country":"Australia"
},
"email":null
}
},
"operator":{
"id":null,
"nk":"edfd3e41-664c-4832-acc5-1c04d9c673a3",
"abn":null,
"name":"xxxxx, xxxxx xxxxxx",
"principal":null,
"contact":{
"id":null,
"phone":null,
"address":{
"id":null,
"line3":"PO Box xxx",
"suburb":"KARAMA",
"postcode":"0813",
"state":"NT",
"country":"Australia"
},
"email":null
}
},
"base":null,
"airframe":{
"id":null,
"acRegistration":null,
"serialNumber":"5005",
"hours":null
},
"props":[
{
"id":null,
"acRegistration":"VH-ZZZ",
"engineNumber":1,
"make":"HARTZELL PROPELLERS",
"model":"HC-C2YR-4CF/FC8477A-4",
"casa":null,
"serialNumber":null,
"hours":null
}
],
"engines":[
{
"id":null,
"acRegistration":"VH-ZZZ",
"engineNumber":1,
"make":"TEXTRON LYCOMING",
"model":"AEIO-540",
"casa":null,
"serialNumber":null,
"hours":null
}
],
"lastUpdated":"2018-12-16"
}
本次测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@EnableJpaRepositories({ "au.com.avmaint.api" })
@AutoConfigureMockMvc
public class AircraftControllerFunctionalTest {
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@TestConfiguration
static class ServiceImplTestContextConfiguration {
@Bean
public CasaFixtures casaFixtures() {
return new CasaFixtures.Default();
}
@Bean
public ModelFixtures modelFixtures() {
return new ModelFixtures.Default();
}
@Bean
public MaintainerFixtures maintainerFixtures() {
return new MaintainerFixtures.Default();
}
@Bean
public AircraftFixtures aircraftFixtures() {
return new AircraftFixtures.Default();
}
}
@Autowired
private WebApplicationContext context;
@Autowired
private MockMvc mvc;
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private ModelFixtures modelFixtures;
@Autowired
private MaintainerFixtures maintainerFixtures;
@Autowired
private AircraftFixtures aircraftFixtures;
Maintainer franks;
@Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream()
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
franks = maintainerFixtures.createFranksMaintainer();
}
@After
public void tearDown() {
maintainerFixtures.removeFranks(franks);
aircraftFixtures.killAircraft(aircrafts);
UserAndRoleFixtures.killAllUsers(userService, roleService);
}
@Test
public void doCreate() throws Exception {
File file = ResourceUtils.getFile("classpath:json/request/vh-zzz.json");
String json = new String(Files.readAllBytes(file.toPath()));
mvc.perform((post("/api/maintainers/{mid}/aircrafts", franks.getId())
.header(AUTHORIZATION_HEADER, "Bearer " + ModelFixtures.ROOT_JWT_TOKEN))
.content(json)
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.payload").isNotEmpty())
.andExpect(jsonPath("$.payload.registration").value("VH-ZZZ"))
.andExpect(jsonPath("$.payload.manufacturer").value("PITTS AVIATION ENTERPRISES"))
.andExpect(jsonPath("$.payload.model").value("S-2B"))
.andExpect(jsonPath("$.payload.manufacture").value("1983-01-2"))
.andExpect(jsonPath("$.payload.owner.id").isNotEmpty())
.andExpect(jsonPath("$.payload.owner.nk").isNotEmpty())
.andExpect(jsonPath("$.payload.owner.contact").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.id").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.nk").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.contact").isNotEmpty())
;
}
}
我发现 Aircraft 中的两个 ManyToOne 映射被持久化了,但是 OneToOne 映射没有。基本上这两个期望都失败了:
.andExpect(jsonPath("$.payload.owner.contact").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.contact").isNotEmpty())
我尝试了一些级联选项,例如 ALL、MERGE 等,看起来我的示例与其他示例非常相似。我意识到这有点不寻常,因为 Address、Person 和 Contact 不包含对其 parents 的引用,但我认为这是单向关系的要点。有谁知道如何坚持这些?
更新 - 在 AircraftService 的创建方法中,我尝试分别保存所有者和操作员:
@Transactional
public Aircraft create(Aircraft aircraft) {
if (aircraft.getOwner() != null && aircraft.getOwner().getNk() == null) {
aircraft.getOwner().setNk(UUID.randomUUID().toString());
} else if (aircraft.getOwner() != null) {
if (aircraft.getOwner().getPrincipal() != null) {
LOG.debug("saving principal");
Person person = personRepository.save(aircraft.getOwner().getPrincipal());
aircraft.getOwner().setPrincipal(person);
}
if (aircraft.getOwner().getContact() != null) {
Contact contact = contactRepository.save(aircraft.getOwner().getContact());
aircraft.getOwner().setContact(contact);
}
}
if (aircraft.getOperator() != null && aircraft.getOperator().getNk() == null) {
aircraft.getOperator().setNk(UUID.randomUUID().toString());
if (aircraft.getOperator().getPrincipal() != null) {
Person person = personRepository.save(aircraft.getOperator().getPrincipal());
aircraft.getOperator().setPrincipal(person);
}
if (aircraft.getOperator().getContact() != null) {
Contact contact = contactRepository.save(aircraft.getOperator().getContact());
aircraft.getOperator().setContact(contact);
}
}
return aircraftRepository.save(aircraft);
}
我这样做是基于 JPA 不知道如何处理对尚不存在的 objects 的引用。
但没有变化。
除了保存联系人和个人 ID 之外,此更改对保存存储库没有任何影响,它仍然无法 link 客户端到联系人或个人。我是否必须在单独的事务中保存联系人和人员,然后保存客户?
这让我快要死了:这是怎么回事?
问题是我需要 CascadeType.MERGE 飞机实体:
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private Client owner;
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private Client operator;
本质上,当 JSON 被输入到创建操作时,它似乎与 MERGE 操作没有区别(与 PERSIST 不同,MERGE 操作创建并置于管理之下),因此 MERGE 操作是传递给客户端,而不是 PERSIST(如我所料)。这就是为什么没有保留 Person 和 Contact 的原因。我仍然不明白为什么 JSON 输入被视为合并 - 它没有意义。
我有一个 Client 数据类型(代表一个客户),它有一个 Person 类型的委托人和一个 Contact 类型的联系人(联系方式)。
客户端数据类型(带有映射)是:
@Entity
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Access(AccessType.PROPERTY)
private Long id;
@Column(unique = true, updatable = false)
private String nk;
@Min(10000000000L)
private Long abn;
private String name;
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "person_id")
private Person principal;
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
@JoinColumn(name = "contact_id")
private Contact contact;
public Client() {
this.nk = UUID.randomUUID().toString();
}
// remaining constructors, builders and accessors omitted for brevity
}
以联系人为例(人员结构相同)
@Entity
public class Contact {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String phone;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String mobile;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String fax;
@OneToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
@JoinColumn(name = "address_id")
@JsonInclude(JsonInclude.Include.NON_NULL)
private Address address;
private String email;
public Contact() {
}
// remaining constructors, builders and accessors omitted for brevity
}
地址数据类型也是单向映射。我可以显示 Person 和 Address,但我很确定他们不会理解任何新内容。
不幸的是,通过 owner 和 operator 字段,Client 是与 Aircraft 的 ManyToOne 关系的主体的两倍。我说很不幸,因为它使我的问题复杂化。飞机数据类型如下:
@Entity
public class Aircraft {
@Id
private String registration;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "casa_code")
private Casa casa;
private String manufacturer;
private String model;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-d")
private LocalDate manufacture;
@ManyToOne(cascade={CascadeType.PERSIST}, fetch = FetchType.EAGER)
private Client owner;
@ManyToOne(cascade={CascadeType.PERSIST}, fetch = FetchType.EAGER)
private Client operator;
private String base;
@OneToOne(cascade={CascadeType.ALL})
@JoinColumn(name = "airframe_id")
private Airframe airframe;
@OneToMany(cascade={CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinColumn(name = "ac_registration")
private Set<Prop> props;
@OneToMany(cascade={CascadeType.ALL}, fetch = FetchType.EAGER)
@JoinColumn(name = "ac_registration")
private Set<Engine> engines;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-d")
private LocalDate lastUpdated;
public Aircraft() {
}
// remaining constructors, builders and accessors omitted for brevity
}
负责保存的控制器方法:
@RestController
@RequestMapping("/api")
public class AircraftController {
private static final Logger LOG = LoggerFactory.getLogger(AircraftController.class);
private AircraftService aircraftService;
@Autowired
public AircraftController(AircraftService aircraftService) {
this.aircraftService = aircraftService;
}
@Secured("ROLE_ADMIN")
@PostMapping(value="/maintainers/{mid}/aircrafts", produces = "application/json")
@ResponseStatus(value = HttpStatus.CREATED)
Response<Aircraft> create(@PathVariable("mid") Long mid, @RequestBody Aircraft request) {
return Response.of(aircraftService.create(request));
}
}
服务方式:
public interface AircraftService {
Aircraft create(Aircraft aircraft);
// other interface methods omitted for brevity
@Service
class Default implements AircraftService {
private static final Logger LOG = LoggerFactory.getLogger(AircraftService.class);
@Autowired
AircraftRepository aircraftRepository;
@Transactional
public Aircraft create(Aircraft aircraft) {
LOG.debug("creating new Aircraft with {}", aircraft);
if (aircraft.getOwner() != null && aircraft.getOwner().getNk() == null) {
aircraft.getOwner().setNk(UUID.randomUUID().toString());
}
if (aircraft.getOperator() != null && aircraft.getOperator().getNk() == null) {
aircraft.getOperator().setNk(UUID.randomUUID().toString());
}
return aircraftRepository.save(aircraft);
}
}
}
最后是存储库:
@Repository
public interface AircraftRepository extends JpaRepository<Aircraft, String> {
}
当我提供以下内容时 JSON:
{
"registration":"VH-ZZZ",
"casa":null,
"manufacturer":"PITTS AVIATION ENTERPRISES",
"model":"S-2B",
"manufacture":"1983-01-2",
"owner":{
"id":null,
"nk":"84f5f053-82dd-4563-8158-e804b3003f3b",
"abn":null,
"name":"xxxxx, xxxxx xxxxxx",
"principal":null,
"contact":{
"id":null,
"phone":null,
"address":{
"id":null,
"line3":"PO Box xxx",
"suburb":"KARAMA",
"postcode":"0813",
"state":"NT",
"country":"Australia"
},
"email":null
}
},
"operator":{
"id":null,
"nk":"edfd3e41-664c-4832-acc5-1c04d9c673a3",
"abn":null,
"name":"xxxxx, xxxxx xxxxxx",
"principal":null,
"contact":{
"id":null,
"phone":null,
"address":{
"id":null,
"line3":"PO Box xxx",
"suburb":"KARAMA",
"postcode":"0813",
"state":"NT",
"country":"Australia"
},
"email":null
}
},
"base":null,
"airframe":{
"id":null,
"acRegistration":null,
"serialNumber":"5005",
"hours":null
},
"props":[
{
"id":null,
"acRegistration":"VH-ZZZ",
"engineNumber":1,
"make":"HARTZELL PROPELLERS",
"model":"HC-C2YR-4CF/FC8477A-4",
"casa":null,
"serialNumber":null,
"hours":null
}
],
"engines":[
{
"id":null,
"acRegistration":"VH-ZZZ",
"engineNumber":1,
"make":"TEXTRON LYCOMING",
"model":"AEIO-540",
"casa":null,
"serialNumber":null,
"hours":null
}
],
"lastUpdated":"2018-12-16"
}
本次测试:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("embedded")
@EnableJpaRepositories({ "au.com.avmaint.api" })
@AutoConfigureMockMvc
public class AircraftControllerFunctionalTest {
private MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
MediaType.APPLICATION_JSON.getSubtype(),
Charset.forName("utf8"));
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@TestConfiguration
static class ServiceImplTestContextConfiguration {
@Bean
public CasaFixtures casaFixtures() {
return new CasaFixtures.Default();
}
@Bean
public ModelFixtures modelFixtures() {
return new ModelFixtures.Default();
}
@Bean
public MaintainerFixtures maintainerFixtures() {
return new MaintainerFixtures.Default();
}
@Bean
public AircraftFixtures aircraftFixtures() {
return new AircraftFixtures.Default();
}
}
@Autowired
private WebApplicationContext context;
@Autowired
private MockMvc mvc;
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private ModelFixtures modelFixtures;
@Autowired
private MaintainerFixtures maintainerFixtures;
@Autowired
private AircraftFixtures aircraftFixtures;
Maintainer franks;
@Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream()
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
}
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
franks = maintainerFixtures.createFranksMaintainer();
}
@After
public void tearDown() {
maintainerFixtures.removeFranks(franks);
aircraftFixtures.killAircraft(aircrafts);
UserAndRoleFixtures.killAllUsers(userService, roleService);
}
@Test
public void doCreate() throws Exception {
File file = ResourceUtils.getFile("classpath:json/request/vh-zzz.json");
String json = new String(Files.readAllBytes(file.toPath()));
mvc.perform((post("/api/maintainers/{mid}/aircrafts", franks.getId())
.header(AUTHORIZATION_HEADER, "Bearer " + ModelFixtures.ROOT_JWT_TOKEN))
.content(json)
.contentType(contentType))
.andDo(print())
.andExpect(status().isCreated())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.payload").isNotEmpty())
.andExpect(jsonPath("$.payload.registration").value("VH-ZZZ"))
.andExpect(jsonPath("$.payload.manufacturer").value("PITTS AVIATION ENTERPRISES"))
.andExpect(jsonPath("$.payload.model").value("S-2B"))
.andExpect(jsonPath("$.payload.manufacture").value("1983-01-2"))
.andExpect(jsonPath("$.payload.owner.id").isNotEmpty())
.andExpect(jsonPath("$.payload.owner.nk").isNotEmpty())
.andExpect(jsonPath("$.payload.owner.contact").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.id").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.nk").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.contact").isNotEmpty())
;
}
}
我发现 Aircraft 中的两个 ManyToOne 映射被持久化了,但是 OneToOne 映射没有。基本上这两个期望都失败了:
.andExpect(jsonPath("$.payload.owner.contact").isNotEmpty())
.andExpect(jsonPath("$.payload.operator.contact").isNotEmpty())
我尝试了一些级联选项,例如 ALL、MERGE 等,看起来我的示例与其他示例非常相似。我意识到这有点不寻常,因为 Address、Person 和 Contact 不包含对其 parents 的引用,但我认为这是单向关系的要点。有谁知道如何坚持这些?
更新 - 在 AircraftService 的创建方法中,我尝试分别保存所有者和操作员:
@Transactional
public Aircraft create(Aircraft aircraft) {
if (aircraft.getOwner() != null && aircraft.getOwner().getNk() == null) {
aircraft.getOwner().setNk(UUID.randomUUID().toString());
} else if (aircraft.getOwner() != null) {
if (aircraft.getOwner().getPrincipal() != null) {
LOG.debug("saving principal");
Person person = personRepository.save(aircraft.getOwner().getPrincipal());
aircraft.getOwner().setPrincipal(person);
}
if (aircraft.getOwner().getContact() != null) {
Contact contact = contactRepository.save(aircraft.getOwner().getContact());
aircraft.getOwner().setContact(contact);
}
}
if (aircraft.getOperator() != null && aircraft.getOperator().getNk() == null) {
aircraft.getOperator().setNk(UUID.randomUUID().toString());
if (aircraft.getOperator().getPrincipal() != null) {
Person person = personRepository.save(aircraft.getOperator().getPrincipal());
aircraft.getOperator().setPrincipal(person);
}
if (aircraft.getOperator().getContact() != null) {
Contact contact = contactRepository.save(aircraft.getOperator().getContact());
aircraft.getOperator().setContact(contact);
}
}
return aircraftRepository.save(aircraft);
}
我这样做是基于 JPA 不知道如何处理对尚不存在的 objects 的引用。
但没有变化。
除了保存联系人和个人 ID 之外,此更改对保存存储库没有任何影响,它仍然无法 link 客户端到联系人或个人。我是否必须在单独的事务中保存联系人和人员,然后保存客户?
这让我快要死了:这是怎么回事?
问题是我需要 CascadeType.MERGE 飞机实体:
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private Client owner;
@ManyToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.EAGER)
private Client operator;
本质上,当 JSON 被输入到创建操作时,它似乎与 MERGE 操作没有区别(与 PERSIST 不同,MERGE 操作创建并置于管理之下),因此 MERGE 操作是传递给客户端,而不是 PERSIST(如我所料)。这就是为什么没有保留 Person 和 Contact 的原因。我仍然不明白为什么 JSON 输入被视为合并 - 它没有意义。