Cześć. Napiszę teraz na co natknęłam się korzystając z UUID, Version i Spring Data
Co to jest UUID?
UUID to taki naprawdę unikalny identyfikator -we wszechświecie i nie musimy przejmować się, że np. dwie osobne maszyny piszące do jednej tabelki wygenerują ten sam UUID. Możemy też np. scalić ze sobą dwie te same tabelki z różnych baz danych nie martwiąc się o naruszenie unikalności Primary Key.
UUID jest unikalny dla obiektu w kontekście całego świata a nie tylko tabeli. Brzmi fajnie prawda? Ja np. teraz wygeneruje sobie UUID:8a686cc0-4e65-4f6c-b932-b8b2ed209f4b
<- o taki mi się wygenerował i mogę się założyć, że nikt inny na świecie takiego nie ma -wpisałam go nawet w google i zero wyników. Więc widać, że to działa.
UUID stosowany jest już przy klastrowaniu, w oznaczaniu partycji w Linuxie a jedna z najlepszych baz relacyjnych -czyli PostgreSQL, posiada już nawet taki typ jak UUID: https://www.postgresql.org/docs/9.1/static/datatype-uuid.html
O zaletach stosowania UUID w bazach danych, encjach możecie sobie poczytać w internecie a w kontekście np. equals i hashCode posłuchać tutaj:
.
Jak można tworzyć UUID w encji?
Można naprzykład stworzyć sobie klasę abstrakcyjną SimpleEntity, po której dziedziczą wszystkie nasze encje. Klasa ta nadaje UUIDa w momencie tworzenia obiektu a nie przy save. Jest to dość istotna własność, ale o tym potem. Przykładowa implementacja może wyglądać tak:
import lombok.Getter;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.UUID;
@Getter
@ToString
@MappedSuperclass
public abstract class SimpleEntity implements Serializable {
@Id
@Column(updatable = false, nullable = false, unique = true)
private final UUID id = UUID.randomUUID();
@Version
private Integer version;
private LocalDateTime creationDate;
private LocalDateTime modificationDate;
@PrePersist
private void prePersist() {
modificationDate = creationDate = LocalDateTime.now();
}
@PreUpdate
private void preUpdate() {
modificationDate = LocalDateTime.now();
}
@Override
public boolean equals(Object that) {
return this == that || that instanceof SimpleEntity
&& Objects.equals(id, ((SimpleEntity) that).id);
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
}
Repozytorium dla encji
Korzystając z Spring Data również możemy stworzyć sobie własny interfejs dla tych encji z UUIDem , po którym będą dziedziczyć
repozytoria.
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.NoRepositoryBean;
import java.util.UUID;
@NoRepositoryBean
public interface SimpleRepo<T> extends JpaRepository<T, UUID> {
}
Na co trzeba uważać w kontekście korzystania z Spring Data?
W Springu metoda save
przy wyborze czy użyć persist czy merge patrzy na ID i skoro ID za każdym razem jest już ustawione to używa niepotrzebnie i niewydajnie merge.
Metoda do zapisu w SimpleJpaRepository wygląda tak:
/*
* (non-Javadoc)
* @see org.springframework.data.repository.CrudRepository#save(java.lang.Object)
*/
@Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
W przypadku gdy zadeklarowaliśmy pole version
w encji metoda, która sprawdza czy encja jest nowa wygląda tak:
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.AbstractEntityInformation#isNew(java.lang.Object)
*/
@Override
public boolean isNew(T entity) {
if (versionAttribute == null || versionAttribute.getJavaType().isPrimitive()) {
return super.isNew(entity); // TUTAJ WCHODZI DO STANDARDOWEGO SPRAWDZENIA PO TYM CZY ID() == null
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
Object versionValue = wrapper.getPropertyValue(versionAttribute.getName());
return versionValue == null;
}
Więc, jeżeli ten UUID mamy przypisany na etapie tworzenia obiektu, przed wywołaniem save
Spring myśli, że ma updateować i wywołuje za każdym razem merge, bo ID != null. Można to chyba obejść przeciążając metodę save
w naszym interfejsie (nwm czy tak się da).
Drugim sposobem patrząc na ten kod jest zadeklarowanie pola version - w tym przypadku Spring Data używa tego kodu, który wkleiłam wyżej.
Tylko uwaga pole version nie może być prymitywem (int
). I to tyle już mi się nie chce pisać, ale trzeba na to uważać i to jest straszne jak duże znaczenie w tym przypadku mają takie szczegóły. Nie jestem też pewna co do tego, że to jest prawidłowe obejście, bo zmienią kiedyś implementacje Springa i znowu przestanie to działać, więc polecam dopisać sobie na to test jednostkowy.