Typ Optional
jest jednym z ciekawszych dodatków do Javy w ostatnich latach, jednak gdy chcemy użyć go jako typ pola dla encji (obiekt, którego stan przechowywany jest w bazie danych) to czeka nas nieprzyjemna niespodzianka zaserwowana przez Hibernate.
Problem z typami opcjonalnymi
Mając klasę
@Entity public class Book { @Id private Integer id; private String title; private Optional<String> publisher; public Book() { } public Book(Integer id, String title, Optional<String> publisher) { this.id = id; this.title = title; this.publisher = publisher; } //...gettery }
Dostaniemy następujący wyjątek już przy starcie aplikacji:
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Optional
Dzieje się tak, ponieważ Hibernate nie jest w stanie zmapować typu Optional
na znany mu typ wspierany przez baze danych.
Rozwiązanie
Na szczęście nie wszystko stracone. Mianowicie możemy skorzystać z faktu, że nasze zmienne i tak są prywatne, więc ukryte przed całym światem, a dostęp do nich mamy tylko poprzez gettery.
Wystarczy drobna zmiana w kodzie:
@Entity public class Book { @Id private Integer id; private String title; private String publisher; public Book() { } public Book(Integer id, String title, String publisher) { this.id = id; this.title = title; this.publisher = publisher; } public Optional<String> getPublisher() { return Optional.ofNullable(publisher); } }
Zamieniliśmy typ stanu z Optional
na String
oraz standardowy getter – teraz zwraca on typ Optional
, który to jest tworzony przy wywoływaniu gettera. W ten oto sposób zadowalamy wymagania Hibernate, bo nie ma on problemu ze zmapowaniem na typ tekstowy w dowolnej bazie danych, oraz wymagania biznesowe by publisher
był dostępny jako typ opcjonalny.
Jedyną niedogodnością takiego rozwiązania jest to, że jeśli chcemy wewnątrz klasy korzystać z opcjonalnego publisher to musimy albo korzystać z gettera (preferowane), albo sami tworzyć Optional.ofNullable(...)
.
Rozwiązanie ma jeszcze jedną zaletę. Przy takiej definicji klasy możemy nieco rozszerzyć zwracanie pustego Optional
również do sytuacji, gdy mamy pusty tekst („”) jako publisher:
@Entity public class Book { @Id private Integer id; private String title; private String publisher; public Book() { } public Book(Integer id, String title, String publisher) { this.id = id; this.title = title; this.publisher = publisher; } public Optional<String> getPublisher() { if( publisher != null || publisher.isEmpty()) { return Optional.empty(); } else { return Optional.ofNullable(publisher); } } }
Jeśli chcesz dowiedzieć się więcej na temat typu Optional
polecam nasz poprzedni wpis, a w nagraniu poniżej możesz zobaczyć program demonstrujący zachowanie Optional wraz z Hibernate.
Bardzo nam pomożesz udostępniając ten artykuł na swoim facebooku, twitterze czy innym medium społecznościowym, które preferujesz 🙂
Cześć,
Pierwszy raz na Waszym blogu i taki błąd u Was złapałem 🙂
Macie:
if( publisher != null || publisher.isEmpty()) {
return Optional.empty();
} else {
return Optional.ofNullable(publisher);
}
Zamiast
if( publisher == null || publisher.isEmpty()) {
return Optional.empty();
} else {
…..
jeszcze mogłoby być zamiast „==” publisher.equals(null)