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.