Reputation: 79
@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 Class
@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);
}
}
When I'm making a new Appointment on my class Lawyer I'm trying to propagate de data from Lawyer to Client, but I can only reach it to Appointment. From Appointment to Client, I can't propagate it.... I get this error:
Caused by: java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing
How do I propagate from Appointment to Client ? I already read some articles about this types of cases, but still I havent understood them.
Upvotes: 0
Views: 578
Reputation: 11551
The spring-data-jpa is a layer on top of JPA. Each entity has its own repository and you have to deal with that.
@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;
And to use it, as show above:
@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);
}
Note I don't explicitly set the AppointmentId
id's. These are handled by the persistence layer (hibernate in this case).
Note also that you can update Appointment
entries either explicity with its own repo or by adding and removing them from the list since CascadeType.ALL
is set, as shown. The problem with using the CascadeType.ALL
for spring-data-jpa is that even though you prefetch the join table entities spring-data-jpa will do it again anyway. Trying to update the relationship through the CascadeType.ALL
for new entities is problematic.
Without the CascadeType
neither the lawyers
or Clients
lists (which should be Sets) are the owners of the relationship so adding to them wouldn't accomplish anything in terms of persistence and would be for query results only.
When reading the Appointment
relationships you need to specifically fetch them since you don't have FetchType.EAGER
. The problem with FetchType.EAGER
is the overhead if you don't want the joins and also if you put it on both Client
and Lawyer
then you will create a recursive fetch that gets all Clients
and lawyers
for any query.
@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);
Finally, always check the logs. Creating an association requires spring-data-jpa (and I think JPA) to read the existing table to see if the relationship is new or updated. This happens whether you create and save a Appointment
yourself or even if you prefetched the list. JPA has a separate merge and I think you can use that more efficiently.
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 (?, ?)
Upvotes: 1
Reputation:
You save the Lowyer
, therefore you need to cascade the Lowyer -> Appointment and the Appointment -> Client relations.
Therefore you have to cascade the relation Appointment -> Client also.
@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;
}
Upvotes: 1