Praca z Optional w Hibernate

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.


Jeśli chcesz prześledzić rozwiązanie krok po kroku w formie wideo, to zapraszamy na nasz kanał na YouTube.


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 🙂


Podziel się tym wpisem:

Dodaj komentarz