7.1 Създаване на ваш собствен конвертор на типове

Понякога възникват ситуации, когато искате да съхраните доста сложен тип данни в една колона на table. Ако Hibernate знае How да го преобразува в низ (и обратно), тогава всичко е наред. Ако не, тогава ще трябва да напишете свой собствен конвертор на данни.

Да приемем, че някой реши да съхрани годината на раждане на потребителя в базата данни като YY.MM.DDнапример: 98.12.15. Трябва също да го преобразувате в обикновена дата: 15/12/1998. След това трябва да напишете свой собствен конвертор.

За да направите това, трябва да внедрите интерфейс 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]);
    }
}

И, разбира се, този конвертор може да се добави към всяко поле (при condition че типовете съвпадат):


@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;
}

Много често трябва да се използват конвертори, ако не сте проектирали базата данни. Данните там може да са в "странни формати". Датите могат да се съхраняват като низове, булевите като CHAR със стойности Y и N и други подобни.

7.2 Създаване на наш собствен тип данни

Помните ли tableта със списъка с типове, които са известни на Hibernate? Говоря за типове, които са посочени заедно с анотацията @Type. Можете да напишете свой собствен тип данни, който може да се използва по същия начин като другите вградени типове в Hibernate.

Например искаме да имаме типа LocalTime, който ще се съхранява в базата данни не като TIME, а като VARCHAR. И например имаме достъп до такава база данни и не ни е позволено да променяме типовете данни в нейните колони. След това можем да напишем наш собствен тип Hibernate. Нека го наречем LocalTimeString.

Първо имаме нужда от малък клас, който ще опише нашия нов тип:

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";
    }
}

Това е нещо от тип Enum, което се състои от една стойност. Наборът от такива единични енами е всички видове, известни на Hibernate.

Също така се нуждаем от клас - аналог на конвертора, който ще съдържа два метода - wrap()и unwrap()за преобразуване на стойности от типа LocalTime в String.

Ето How ще изглежда без прилагане на методите:

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) {

    }

}

Сега нека напишем изпълнението на методите:

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);
}

И вторият метод:

@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());
}

Готов. Можете да използвате този клас, за да съхранявате времето като низ:


@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 Регистриране на вашия тип

Можете също да регистрирате своя тип данни по време на конфигурацията на Hibernate. Това е малко нетривиално.


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();

Първо ще трябва да получите MetadataSources, да получите MetadataBuilder от него и да го използвате, за да добавите своя клас. Възможно е чрез hibernate.cfg.xml, но и малко тромаво.

Но след регистрация можете да пишете така:


@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;
}