7.1 Tworzenie własnego konwertera typów

Czasami pojawiają się sytuacje, gdy chcesz przechowywać dość złożony typ danych w jednej kolumnie tabeli. Jeśli Hibernate wie, jak przekonwertować go na ciąg znaków (i z powrotem), wszystko jest w porządku. Jeśli nie, będziesz musiał napisać własny konwerter danych.

Załóżmy, że ktoś zdecydował się zapisać rok urodzenia użytkownika w bazie danych jako YY.MM.DD, na przykład: 98.12.15. Musisz także przekonwertować go na zwykłą datę: 15/12/1998. Następnie musisz napisać własny konwerter.

Aby to zrobić, musisz zaimplementować interfejs AttributeConverter<EntityType, DbType>.


@Converter(autoApply = true)
public class DateConverter implements AttributeConverter<java.time.LocalDate, String> {
 
    public String convertToDatabaseColumn(java.time.LocalDate date) {
    	return date.format("YY.MM.DD");
    }
 
    public java.time.LocalDate convertToEntityAttribute(String dbData) {
    	String[] data = dbData.split(".");
    	return LocalDate.of(data[2], data[1], "19"+data[0]);
    }
}

I oczywiście ten konwerter można dodać do dowolnego pola (pod warunkiem, że typy są zgodne):


@Entity
@Table(name="user")
class User {
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_date")
   @Convert(converter = DateConverter.class)
   public java.time.LocalDate date;
}

Konwertery bardzo często muszą być używane, jeśli nie zaprojektowałeś bazy danych. Dane mogą być w „dziwnych formatach”. Daty mogą być przechowywane jako łańcuchy znaków, wartości logiczne jako CHAR z wartościami Y i N i tym podobne.

7.2 Tworzenie własnego typu danych

Pamiętasz tabelę z listą typów znanych Hibernacji? Mówię o typach, które są określone wraz z adnotacją @Type. Możesz napisać własny typ danych, który może być używany w taki sam sposób, jak inne wbudowane typy w Hibernate.

Na przykład chcemy mieć typ LocalTime, który będzie przechowywany w bazie danych nie jako TIME, ale jako VARCHAR. I np. mamy dostęp do takiej bazy danych i nie wolno nam zmieniać typów danych w jej kolumnach. Następnie możemy napisać własny typ Hibernate. Nazwijmy to LocalTimeString.

Najpierw potrzebujemy małej klasy, która opisze nasz nowy typ:

public class LocalTimeStringType extends AbstractSingleColumnStandardBasicType<<LocalTime> {

    public static final LocalTimeStringType  INSTANCE = new LocalTimeStringType ();

    public LocalTimeStringType () {
    	super(VarcharTypeDescriptor.INSTANCE, LocalTimeStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
    	return "LocalTimeString";
    }
}

Jest to coś typu Enum, które składa się z jednej wartości. Zbiór takich pojedynczych enamów to wszystkie typy znane Hibernacji.

Potrzebujemy również klasy - analogu konwertera, który będzie zawierał dwie metody - wrap()oraz unwrap()do konwersji wartości typu LocalTime na String.

Tak to będzie wyglądać bez implementacji metod:

public class LocalTimeStringJavaDescriptor extends AbstractTypeDescriptor<LocalTime> {

    public static final LocalTimeStringJavaDescriptor INSTANCE =  new  LocalTimeStringJavaDescriptor();

    public LocalTimeStringJavaDescriptor() {
    	super(LocalTime.class, ImmutableMutabilityPlan.INSTANCE);
    }

    public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    }

    public <X> LocalTime wrap(X value, WrapperOptions options) {

    }

}

Teraz napiszmy implementację metod:

public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    if (value == null)
    	return null;

    if (String.class.isAssignableFrom(type))
    	return (X) LocalTimeType.FORMATTER.format(value);

    throw unknownUnwrap(type);
}

I druga metoda:

@Override
public <X> LocalTime wrap(X value, WrapperOptions options) {
    if (value == null)
    	return null;

    if(String.class.isInstance(value))
    	return LocalTime.from(LocalTimeType.FORMATTER.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

Gotowy. Możesz użyć tej klasy do przechowywania czasu jako ciągu znaków:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "com.codegym.hibernate.customtypes.LocalTimeStringType")  
   public java.time.LocalTime time;
}

7.3 Rejestracja typu

Możesz także zarejestrować swój typ danych podczas konfiguracji Hibernate. To jest trochę niebanalne.


ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
    .applySettings(getProperties()).build();
                                                                                                                                                              	                                        	 
    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources
  	.addAnnotatedClass(User.class)
  	.getMetadataBuilder()
  	.applyBasicType(LocalTimeStringType.INSTANCE)
  	.build();
                                                                                                                                                              	                                        	 
    SessionFactory factory =  metadata.buildSessionFactory();

Najpierw musisz zdobyć MetadataSources, pobrać z niego MetadataBuilder i użyć go do dodania swojej klasy. Jest to możliwe przez hibernate.cfg.xml, ale też trochę uciążliwe.

Ale po rejestracji możesz napisać tak:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "LocalTimeString")  
   public java.time.LocalTime time;
}