java - 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 ?我已经阅读了一些关于这类案例的文章,但我仍然没有理解它们。
解决方案
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
使用自己的 repo 或通过从列表中添加和删除它们来明确更新条目,因为CascadeType.ALL
已设置,如图所示。使用CascadeType.ALL
for spring-data-jpa 的问题在于,即使您预取连接表实体,spring-data-jpa 仍然会再次执行此操作。尝试通过CascadeType.ALL
新实体更新关系是有问题的。
如果CascadeType
没有lawyers
orClients
列表(应该是 Set),则不是关系的所有者,因此添加到它们不会在持久性方面完成任何事情,并且仅用于查询结果。
在阅读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 (?, ?)
推荐阅读
- azure-sql-database - 使用 Dataverse (CDS) 导入/导出数据
- javascript - 以角度转换数组对象
- django - 如何更新文件字段django
- google-app-engine - 使用 Google Cloud App Engine 阻止基于 UserAgent 的请求
- python - 如何在 Python 的 Data Gathering 中显示不同的标题?
- gradle - gradle“包不存在”
- r - R十进制逗号而不是ggplot scales::percent中的小数点
- c# - CefSharp如何像真正的浏览器C#一样清除当前的chrome web浏览器控制内容
- python - 在 python 类中返回数据帧的最佳实践
- python - 重塑以字典为值的熊猫数据框