Spring Data JPA-多双向@ManyToOne传播数据

时间:2018-12-03 18:38:32

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

Diagram

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

BootStrap类

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

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

由以下原因引起:java.lang.IllegalStateException:org.hibernate.TransientPropertyValueException:对象引用了未保存的临时实例-在刷新之前保存该临时实例

我如何从约会传播到客户? 我已经阅读了一些有关此类案例的文章,但我仍然不理解它们。

2 个答案:

答案 0 :(得分:0)

您保存了Lowyer,因此需要将 Lowyer->约会 Appointment->客户关系进行级联。

因此,您还必须级联关系约会->客户

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

答案 1 :(得分:0)

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。这些由持久层(在这种情况下为休眠)处理。

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

没有CascadeTypelawyers列表(应为Set)都不是该关系的所有者,因此,在持久性和仅用于查询结果。

在阅读Clients关系时,由于没有Appointment,因此需要专门获取它们。 FetchType.EAGER的问题是开销,如果您不希望联接,并且同时将其放在FetchType.EAGERClient上,那么您将创建一个递归获取来获取所有{{ 1}}和Lawyer进行任何查询。

Clients

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

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