首页 > 解决方案 > Spring Data JPA - 多双向@ManyToOne 传播数据

问题描述

图表

    @Entity
    @Getter
    @Setter
    @NoArgsConstructor
    @ToString 
    public class Lawyer extends ID{
        @EqualsAndHashCode.Exclude
        @ToString.Exclude
        @JsonIgnore
        @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "lawyer")
        private Set<Appointment> appointments = new HashSet<>();

public void addAppointment(Client client, LocalDateTime data) {
            Appointment app = new Appointment (client,this,data);
            this.consultas.add(app);
            app.getClient().getAppointments().add(app);
        }

       }

        @Entity
        @Getter
        @Setter
        @NoArgsConstructor
        @ToString
        public class Appointment extends ID{

            @EqualsAndHashCode.Exclude
            @ToString.Exclude
            @ManyToOne
            private Client client;

            @EqualsAndHashCode.Exclude
            @ToString.Exclude
            @ManyToOne
            private Lawyer lawyer;
        }



        @Entity
        @Getter
        @Setter
        @NoArgsConstructor
        @ToString
        public class Client extends ID{

            @EqualsAndHashCode.Exclude
            @ToString.Exclude
            @JsonIgnore
            @OneToMany
            private Set<Appointment> appointments = new HashSet<>();
        }

        @MappedSuperclass
        @Getter
        @Setter
        @NoArgsConstructor
        public class ID{
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private Long id;
        }

引导类

@Component
public class Bootstrap implements ApplicationListener<ContextRefreshedEvent> {
    private LaywerRepoI LaywerService;

    public Bootstrap(LaywerRepoI LaywerService) {
        this.LaywerService = LaywerService;
    }

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        Client c1 = new Client("Lukz", LocalDate.of(1971, 11, 26));
        Client c2 = new Client("Adrian", LocalDate.of(1956, 01, 28));
        Client c3 = new Client("Danny", LocalDate.of(1936, 1, 11));

        Laywer l1 = new Laywer("Morgan", LocalDate.of(1941, 1, 1));
        Laywer l2 = new Laywer("Ana", LocalDate.of(1931, 10, 1));

        l1.addAppointment(c1,LocalDateTime.of(2018, 11, 22,18, 25));
        l1.addAppointment(c1,LocalDateTime.of(2018, 11, 22, 10, 15));

        LawyerService.save(l1);
        LawyerService.save(l2);
    }
}

当我在我的班级律师上进行新的约会时,我试图将数据从律师传播到客户,但我只能将其传递给 Appointment。从约会到客户,我无法传播它....我收到此错误:

引起:java.lang.IllegalStateException:org.hibernate.TransientPropertyValueException:对象引用了一个未保存的瞬态实例 - 在刷新之前保存瞬态实例

如何从 Appointment 传播到 Client ?我已经阅读了一些关于这类案例的文章,但我仍然没有理解它们。

标签: javaspringspring-bootspring-data-jpa

解决方案


spring-data-jpa 是 JPA 之上的一个层。每个实体都有自己的存储库,您必须处理它。

@Entity
public class Lawyer  {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "client", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Appointment> appointments;

@Entity
public class Client {
    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "lawyer", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Appointment> appointments;

@Entity
public class Appointment {
    @EmbeddedId
    private AppointmentId id = new AppointmentId();

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("lawyerId")
    private Lawyer lawyer;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("clientId")
    private Client client;

    public Appointment() {}
    public Appointment(Lawyer lawyer, Client client) {
        this.lawyer = lawyer;
        this.client = client;
    }

@SuppressWarnings("serial")
@Embeddable
public class AppointmentId implements Serializable {
    private Long lawyerId;
    private Long clientId;

并使用它,如上所示:

@Transactional
private void update() {
    System.out.println("Step 1");
    Client client1 = new Client();
    Lawyer lawyer1 = new Lawyer();
    Appointment apt1 = new Appointment(lawyer1, client1);
    clientRepo.save(client1);
    lawyerRepo.save(lawyer1);
    appointmentRepo.save(apt1);

    System.out.println("Step 2");
    Client client2 = new Client();
    Lawyer lawyer2 = new Lawyer();
    Appointment apt2 = new Appointment(lawyer2, client2);
    lawyerRepo.save(lawyer2);
    clientRepo.save(client2);
    appointmentRepo.save(apt2);

    System.out.println("Step 3");
    client2 = clientRepo.getOneWithLawyers(2L);
    client2.getAppointments().add(new Appointment(lawyer1, client2));
    clientRepo.save(client2);

    System.out.println("Step 4 -- better");
    Appointment apt3 = new Appointment(lawyer2, client1);
    appointmentRepo.save(apt3);
}

请注意,我没有明确设置AppointmentIdid。这些由持久层处理(在本例中为休眠)。

另请注意,您可以Appointment使用自己的 repo 或通过从列表中添加和删除它们来明确更新条目,因为CascadeType.ALL已设置,如图所示。使用CascadeType.ALLfor spring-data-jpa 的问题在于,即使您预取连接表实体,spring-data-jpa 仍然会再次执行此操作。尝试通过CascadeType.ALL新实体更新关系是有问题的。

如果CascadeType没有lawyersorClients列表(应该是 Set),则不是关系的所有者,因此添加到它们不会在持久性方面完成任何事情,并且仅用于查询结果。

在阅读Appointment关系时,您需要专门获取它们,因为您没有FetchType.EAGER. 问题FetchType.EAGER在于,如果您不想要连接,并且如果您将其放在两者上ClientLawyer那么您将创建一个递归提取来获取所有查询Clientslawyers任何查询。

@Query("select c from Client c left outer join fetch c.lawyers ls left outer join fetch ls.lawyer where t.id = :id")
Client getOneWithLawyers(@Param("id") Long id);

最后,始终检查日志。创建关联需要 spring-data-jpa(我认为是 JPA)来读取现有表以查看关系是新的还是更新的。无论您是自己创建和保存,Appointment还是即使您预取了列表,都会发生这种情况。JPA 有一个单独的合并,我认为您可以更有效地使用它。

create table appointment (client_id bigint not null, lawyer_id bigint not null, primary key (client_id, lawyer_id))
create table client (id bigint generated by default as identity, primary key (id))
alter table appointment add constraint FK3gbqcfd3mnwwcit63lybpqcf8 foreign key (client_id) references client
create table lawyer (id bigint generated by default as identity, primary key (id))
alter table appointment add constraint FKc8o8ake38y74iqk2jqpc2sfid foreign key (lawyer_id) references lawyer

insert into client (id) values (null)
insert into lawyer (id) values (null)
select appointmen0_.client_id as client_i1_0_0_, appointmen0_.lawyer_id as lawyer_i2_0_0_ from appointment appointmen0_ where appointmen0_.client_id=? and appointmen0_.lawyer_id=?
insert into appointment (client_id, lawyer_id) values (?, ?)

insert into lawyer (id) values (null)
insert into client (id) values (null)
select appointmen0_.client_id as client_i1_0_0_, appointmen0_.lawyer_id as lawyer_i2_0_0_ from appointment appointmen0_ where appointmen0_.client_id=? and appointmen0_.lawyer_id=?
insert into appointment (client_id, lawyer_id) values (?, ?)

select client0_.id as id1_1_0_, appointmen1_.client_id as client_i1_0_1_, appointmen1_.lawyer_id as lawyer_i2_0_1_, lawyer2_.id as id1_2_2_, appointmen1_.lawyer_id as lawyer_i2_0_0__, appointmen1_.client_id as client_i1_0_0__ from client client0_ left outer join appointment appointmen1_ on client0_.id=appointmen1_.lawyer_id left outer join lawyer lawyer2_ on appointmen1_.lawyer_id=lawyer2_.id where client0_.id=?
select client0_.id as id1_1_1_, appointmen1_.lawyer_id as lawyer_i2_0_3_, appointmen1_.client_id as client_i1_0_3_, appointmen1_.client_id as client_i1_0_0_, appointmen1_.lawyer_id as lawyer_i2_0_0_ from client client0_ left outer join appointment appointmen1_ on client0_.id=appointmen1_.lawyer_id where client0_.id=?
select appointmen0_.client_id as client_i1_0_0_, appointmen0_.lawyer_id as lawyer_i2_0_0_ from appointment appointmen0_ where appointmen0_.client_id=? and appointmen0_.lawyer_id=?
insert into appointment (client_id, lawyer_id) values (?, ?)

select appointmen0_.client_id as client_i1_0_0_, appointmen0_.lawyer_id as lawyer_i2_0_0_ from appointment appointmen0_ where appointmen0_.client_id=? and appointmen0_.lawyer_id=?
insert into appointment (client_id, lawyer_id) values (?, ?)

推荐阅读