Spring Data JPA - Многонаправленные данные распространения @ManyToOne - PullRequest
0 голосов
/ 03 декабря 2018

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 голосов
/ 03 декабря 2018

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.Они обрабатываются постоянным слоем (в данном случае спящим).

Также обратите внимание, что вы можете обновлять записи Appointment либо explicity с его собственным репо, либо добавляя и удаляя их из списка, так как CascadeType.ALL установлен, как показано.Проблема с использованием CascadeType.ALL для spring-data-jpa заключается в том, что даже если вы предварительно выберете сущности таблицы соединений, spring-data-jpa все равно сделает это снова.Попытка обновить отношение через CascadeType.ALL для новых сущностей проблематично.

Без CascadeType ни списки lawyers, ни Clients (которые должны быть наборами) не являются владельцами отношения, поэтомудобавление к ним ничего не даст в плане сохранения и будет только для результатов запроса.

При чтении отношений Appointment вам необходимо специально их извлечь, поскольку у вас нет FetchType.EAGER.Проблема с FetchType.EAGER заключается в накладных расходах, если вы не хотите объединений, а также если вы поместите их в Client и Lawyer, тогда вы создадите рекурсивную выборку, которая получает все Clients и lawyers длялюбой запрос.

@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 (?, ?)
0 голосов
/ 03 декабря 2018

Вы сохраняете Lowyer, поэтому вам необходимо каскадировать отношения Lowyer -> Назначение и Назначение -> Клиент .

Поэтому вам необходимокаскадное отношение Назначение -> Клиент также.

        @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;
        }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...