public interface Repository<T,K extends Serializable> { Class<T> clazz(); default T create(T toBePersisted){ return inTransaction(em ->{ em.persist(toBePersisted); return toBePersisted; }); } default boolean delete(K toBeDeletedId){ ... } default T update(T toBeUpdated){ ... } default Optional<T> get(K id){ ... } default List<T> getAll(){ ... } }
public class StudentRepository implements Repository<Student,Long> { @Override public Class<Student> getClazz() { return Student.class; } ... }
On peut éviter de passer explicitement l'Id pour la suppression.
public interface WithId<K> { K getId(); }
public interface Repository<T extends WithId<K>, K extends Serializable> { ... default boolean delete(T toBeDeleted){ Objects.requireNonNull(toBeDeleted.getId()); return inTransaction(em ->{ var obj = em.find(getClazz(),toBeDeleted.getId()); if (obj==null) { return false;} em.remove(obj); return true; }); } }
Si l'on veut faire les choses proprement, il faut pouvoir distinguer les objets qui n'ont jamais eu de clé en BD (transients) des autres.
public class Student { @Id @GeneratedValue private Long id; private String firstName; private String lastName; ... }
Transient :
.id == null
En bonus, on peut faire une implémentation assez satisfaisante (cf. cours précédent) de equals
et hashcode
.
Les objets transients (i.e., sans id) sont considérés comme tous différents. Les autres objets sont comparés par leur id.
@Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Student)) return false; Student student = (Student) o; if (id == null) return false; return id.equals(student.id); } @Override public int hashCode() { return id != null ? id.hashCode() : super.hashCode(); }
public class Student implements WithId<Long> { @Id @GeneratedValue private Long id; private String firstName; private String lastName; @Embedded private Address address; @OneToOne(cascade = CascadeType.ALL,fetch = FetchType.LAZY) @JoinColumn(name = "phoneNumber_id") private PhoneNumber phoneNumber; @ManyToOne(fetch = FetchType.LAZY) // pq pas de cascade PERSIST ? private University university; @ManyToMany private Set<Lecture> lectures; @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "student_id") @OrderBy("date") private List<Remark> remarks; ... }
public void removeRemark(Student student,Remark remark){ Objects.requireNonNull(student.getId()); Objects.requireNonNull(remark.getId()); inTransaction(em -> { var q="SELECT s FROM Student s LEFT JOIN FETCH s.remarks WHERE s.id = :id"; var query=em.createQuery(q,Student.class); query.setParameter("id",student.getId()); var studentPersistent = query.getSingleResult(); if (studentPersistent == null){ throw new IllegalStateException("No student with id "+student.getId()); } var remarkPersistent = em.find(Remark.class,remark.getId()); if (remarkPersistent==null){ throw new IllegalStateException("No remark with id" +remark.getId()); } em.remove(remarkPersistent); studentPersistent.getRemarks().remove(remarkPersistent); }); }
Sans la ligne surlignée, il ne se passe rien! Pourquoi ?
Objects.requireNonNull(student.getId()); Objects.requireNonNull(remark.getId()); inTransaction(em -> { var remarkPersistent = em.find(Remark.class,remark.getId()); ... em.remove(remarkPersistent); });
Elle ne marche que parce que l'EntityManager
est vide et parce qu'on à fait un join column.
public boolean deleteWrong(Lecture lecture) { ... return inTransaction(em -> { var lectureToBeDeleted = em.find(Lecture.class, lecture.getId()); ... var q = "SELECT s FROM Student s LEFT JOIN FETCH s.lectures l WHERE l.id = :id"; var students = em.createQuery(q,Student.class) .setParameter("id", lectureToBeDeleted.getId()) .getResultList(); for (var student : students){ student.getLectures().remove(lectureToBeDeleted); } em.remove(lectureToBeDeleted); return true; }); }
Supprime tous les cours des Student de lecture.
La version sans le FETCH est correcte mais fait un select par étudiant qui suit le cours.
public boolean delete(Lecture lecture) { Objects.requireNonNull(lecture.getId()); return inTransaction(em -> { var lectureToBeDeleted = em.find(Lecture.class, lecture.getId()); ... var q = "SELECT s FROM Student s LEFT JOIN FETCH s.lectures WHERE :lecture MEMBER OF s.lectures"; var students = em.createQuery(q,Student.class) .setParameter("lecture", lectureToBeDeleted) .getResultList(); for (var student : students){ student.getLectures().remove(lectureToBeDeleted); } em.remove(lectureToBeDeleted); return true; }); }
La norme JPA impose de toujours maintenir le graphe des objets dans un état cohérent.