繁体   English   中英

Spring 启动 JPA OneToMany 关系 - 如果不存在则创建相关 object

[英]Spring boot JPA OneToMany relationship - create related object if not exist

假设我在PersonJob之间有 oneToMany 关系。 每个人只有一份工作,而一份工作有很多人。

我有一个 controller 调用服务,该服务调用将执行查询的存储库。

他们来了:

@RestController
@CrossOrigin()
@RequestMapping(path = "api/person")
public class PersonController {
    private final PersonService personService;
    @Autowired
    public PersonController(PersonService personService) {
        this.personService = personService;
    }

    @PostMapping
    public Person storePerson(@RequestBody Person person) {
        return this.personService.storePerson(person);
    }
    //...more code is also here
}

@Service
public class PersonService {
    private final PersonRepository personRepository;
    @Autowired
    public PersonService(PersonRepository personRepository, CountryRepository countryRepository,
            JobRepository jobRepository, RoleRepository roleRepository, HairColorRepository hairColorRepository) {
        this.personRepository = personRepository;
    }

    public Person storePerson(Person person) {
        return this.personRepository.save(person);
    }
    //...more code is also here
}

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {

}

现在模型和我定义它们之间关系的方式。 我可以通过两种方式对此进行编码。

情景 1:

@Entity
@Table(name = "people")
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = Job.class)
    @JoinColumn(name = "job_id")
    private Job job;
    
    // ...getters and setters, constructors, toString(), etc are here
}

@Entity
@Table(name = "jobs")
public class Job {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @OneToMany(mappedBy = "job", orphanRemoval = true, cascade = CascadeType.ALL)
    private Set<Person> persons;
    // ...getters and setters, constructors, toString(), etc are here
}

我使用 postman 将记录插入此数据库。 我发送一个POST请求,这是正文:

第一个 Json

{
    "name": "James",
    "job": {
        "id": null,
        "name": "Doctor"
    }
}

这很完美,因为它创建了这个人,它还创建了一个在数据库中不存在的新工作,并且还创建了两者之间的关系。 但是根据第二个请求,我想重用该作业。 所以我提出这个要求:

第二个Json

{
    "name": "David",
    "job": {
        "id": 1,
        "name": "Doctor"
    }
}

在这里我得到一个例外:

{
    "timestamp": "2022-08-05T11:20:41.037+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "detached entity passed to persist: ir.arm.archiver.job.Job; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: ir.arm.archiver.job.Job",
    "path": "/api/person"
}

Senario2

如果我稍微更改关系注释中的Cascade值,我会得到完全相反的结果。 如果在Person.java中,我将private Job job字段的注释更改为使用Cascade.MERGE ,如下所示:

    @ManyToOne(cascade = CascadeType.MERGE, fetch = FetchType.EAGER, targetEntity = Job.class)
    @JoinColumn(name = "job_id")
    private Job job;

然后,当我通过第一个 Json时,这一次,我得到一个异常:

{
    "timestamp": "2022-08-05T11:36:17.854+00:00",
    "status": 500,
    "error": "Internal Server Error",
    "message": "org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : ir.arm.archiver.person.Person.job -> ir.arm.archiver.job.Job; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : ir.arm.archiver.person.Person.job -> ir.arm.archiver.job.Job",
    "path": "/api/person"
}

但是,如果我自己在数据库中创建工作记录,然后使用第二个 Json执行请求,它将起作用,并创建与现有工作记录有关系的人。

现在我的问题是:

我怎样才能将两者结合起来? 我希望 JPA 两者都做。 有什么办法可以同时传递 jsons 和 jpa 自动创建作业,如果Id是 null,如果它有Id则获取并重用它?

我找到了解决办法。 移除 Person Entity 的 Job 成员变量的cascade属性。 JPA 将两者结合起来。

更新人员实体:

@Entity
@Table(name = "people")
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = Job.class)
    @JoinColumn(name = "job_id")
    private Job job;
    
    // ...getters and setters, constructors, toString(), etc are here
}

Output(在数据库人员表中):

在此处输入图像描述

工作表:

在此处输入图像描述

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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