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 (?, ?)