【问题标题】:Spring Data JPA - Multi Bidirectional @ManyToOne Propagation DataSpring Data JPA - 多双向@ManyToOne 传播数据
【发布时间】:2019-05-05 02:34:18
【问题描述】:

    @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);
    }
}

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

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

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

【问题讨论】:

  • 能贴出create-persist代码吗?
  • @EugenCovaci 我添加了有关我的代码的更多信息。我不明白你的第二个问题,但我的目标是创建一个单一的存储库(LawyerRepoI),允许我一次存储所有传播的数据
  • 什么是m1m2
  • misstyped...l1 和 l2 将律师及其相关信息保存在我的存储库中
  • 方法addAppointmentAppointment中声明但在Lawyer中使用?!?

标签: java spring spring-boot spring-data-jpa


【解决方案1】:

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);
}

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

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

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

读取Appointment 关系时,您需要专门获取它们,因为您没有FetchType.EAGERFetchType.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 (?, ?)

【讨论】:

    【解决方案2】:

    您保存Lowyer,因此您需要级联 Lowyer -> AppointmentAppointment -> Client 关系。

    因此,您还必须级联关系 Appointment -> Client

            @Entity
            @Getter
            @Setter
            @NoArgsConstructor
            @ToString
            public class Appointment extends ID{
    
                @EqualsAndHashCode.Exclude
                @ToString.Exclude
                @ManyToOne(cascade = CascadeType.ALL)
                private Client client;
    
                @EqualsAndHashCode.Exclude
                @ToString.Exclude
                @ManyToOne
                private Lawyer lawyer;
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-04
      • 1970-01-01
      • 1970-01-01
      • 2021-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多