12 Октября 2019

Hibernate Envers HHH-9042 как исправить

В предыдущей статье рассказывал про баг HHH-9042 с которым пришлось столкнуться.

В этой статье я расскажу как у меня получилось выйти из ситуации.

Не секрет, что с появлением JPA многие вещи, которые раньше делали с использованием Hibernate стало делать сложнее. Spring всячески прячет от нас реализацию EntityProvider и добраться что до сессии, что до SessionFactory не всегда поулчается красиво.

Для приведения стандартных типов Hibernate использует аннотацию @Type. В пакете org.hibernate.type лежат стандартные для Hibernate типы данных.

Для типа данных, который помещается в одной колонке достаточно org.hibernate.type.AbstractSingleColumnStandardBasicType.

Реализацию LocalDateType я взял из 5 версии Hibernate, она легко ищется как наследник org.hibernate.type.Type

public class LocalDateType
    extends AbstractSingleColumnStandardBasicType<LocalDate>
    implements LiteralType<LocalDate> {

    public static final LocalDateType INSTANCE = new LocalDateType();

    public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern( "yyyy-MM-dd", Locale.ENGLISH );


    public LocalDateType() {
        super( DateTypeDescriptor.INSTANCE, LocalDateJavaDescriptor.INSTANCE );
    }

    @Override
    public String objectToSQLString(LocalDate value, Dialect dialect) throws Exception {
        return "{d '" + FORMATTER.format( value ) + "'}";
    }

    @Override
    protected boolean registerUnderJavaType() {
        return true;
    }

    @Override
    public String getName() {
        return LocalDate.class.getSimpleName();
    }
}

Дескриптор взял оттуда же hibernate-orm

Дальше начинается интересный момент с подключением. Чтобы Hibernate начал узнавать Custom Type нужно или воспользоваться аннотацией @Type или достать где-нибудь при инициализации org.hibernate.cfg.Configuration и вызвать у него configuration.registerTypeOverride(LocalDateType.INSTANCE);

Аннотация @Type не поможет. Envers для управления ревизиями использует свои entity-объекты, поэтому на @Type на поле класса @Card результата не даст, я пробовал.

Остается класс конфигурации. И вот тут проблема, так как JPA для инициализации Hibernate используют класс HibernateJpaAutoConfiguration, выключать ее и использовать LocalSessionFactoryBean себе в итоге дороже, осбенно если у вас разные профили и диалекты и ваша конфигурация и так вас устраивает.

Я нашел рещение в механизме Service Provider

Сделал реализацию класса org.hibernate.integrator.spi.Integrator

public class CustomUserTypesIntegrator implements Integrator {

    @Override
    public void integrate(Configuration configuration, SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
        configuration.registerTypeOverride(LocalDateType.INSTANCE);
    }

    @Override
    public void integrate(MetadataImplementor metadataImplementor, SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {
    }

    @Override
    public void disintegrate(SessionFactoryImplementor sessionFactoryImplementor, SessionFactoryServiceRegistry sessionFactoryServiceRegistry) {

    }

}

Создал файл в ресурсах (src/main/resources) с названием org.hibernate.integrator.spi.Integrator, содержимое файла:

ru.inetwork.config.hibernate.CustomUserTypesIntegrator

После такой реализации Hibernate начал воспринимать LocalDate как базовый тип и конверторы для него больше не нужны, а Envers стартует без проблем.

Если конверторы удалять не хочется можно только в аудируемом классе дописать аннотацию @Convert(disableConversion = true) тогда и конвертор можно оставить.