Подтвердить что ты не робот

Hibernate не загружает конвертер JPA 2.1 при загрузке spring -boot и spring -data-jpa

У меня есть пользовательский конвертер для UUID, чтобы передать его в строку вместо двоичного:

package de.kaiserpfalzEdv.commons.jee.db;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.util.UUID;

@Converter(autoApply = true)
public class UUIDJPAConverter implements AttributeConverter<UUID, String> {
    @Override
    public String convertToDatabaseColumn(UUID attribute) {
        return attribute.toString();
    }

    @Override
    public UUID convertToEntityAttribute(String dbData) {
        return UUID.fromString(dbData);
    }
}

Преобразователи (у меня есть некоторые другие особенности для обработки времени и даты) находятся в файле .jar библиотеки.

Тогда у меня есть сущности в файле .jar. Как этот:

package de.kaiserpfalzEdv.office.core.security;

import de.kaiserpfalzEdv.commons.jee.db.OffsetDateTimeJPAConverter;
import de.kaiserpfalzEdv.commons.jee.db.UUIDJPAConverter;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

@Entity
@Table(
        name = "tickets"
)
public class SecurityTicket implements Serializable {
    private final static ZoneId TIMEZONE = ZoneId.of("UTC");
    private final static long DEFAULT_TTL = 600L;
    private final static long DEFAULT_RENEWAL = 600L;

    @Id @NotNull
    @Column(name = "id_", length=50, nullable = false, updatable = false, unique = true)
    @Convert(converter = UUIDJPAConverter.class)
    private UUID id;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "account_id_", nullable = false, updatable = false, unique = true)
    private Account account;

    @Convert(converter = OffsetDateTimeJPAConverter.class)
    @Column(name = "created_", nullable = false, updatable = false)
    private OffsetDateTime created;

    @Convert(converter = OffsetDateTimeJPAConverter.class)
    @Column(name = "validity_", nullable = false, updatable = false)
    private OffsetDateTime validity;


    @Deprecated
    public SecurityTicket() {
    }


    public SecurityTicket(@NotNull final Account account) {
        id = UUID.randomUUID();
        this.account = account;
        created = OffsetDateTime.now(TIMEZONE);
        validity = created.plusSeconds(DEFAULT_TTL);
    }


    public void renew() {
        validity = OffsetDateTime.now(TIMEZONE).plusSeconds(DEFAULT_RENEWAL);
    }

    public boolean isValid() {
        OffsetDateTime now = OffsetDateTime.now(TIMEZONE);

        System.out.println(validity.toString() + " is hopefully after " + now.toString());

        return validity.isAfter(now);
    }

    public UUID getId() {
        return id;
    }

    public OffsetDateTime getValidity() {
        return validity;
    }

    public String getAccountName() {
        return account.getAccountName();
    }

    public String getDisplayName() {
        return account.getDisplayName();
    }

    public Set<String> getRoles() {
        HashSet<String> result = new HashSet<>();

        account.getRoles().forEach(t -> result.add(t.getDisplayNumber()));

        return Collections.unmodifiableSet(result);
    }

    public Set<String> getEntitlements() {
        return Collections.unmodifiableSet(new HashSet<>());
    }


    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (obj.getClass() != getClass()) {
            return false;
        }
        SecurityTicket rhs = (SecurityTicket) obj;
        return new EqualsBuilder()
                .append(this.id, rhs.id)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder()
                .append(id)
                .toHashCode();
    }


    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
                .append("id", id)

                .append("account", account)
                .append("validity", validity)
                .toString();
    }
}

При выполнении интеграционных тестов через maven и testng база данных работает очень хорошо. Но когда я запускаю приложение (третий файл .jar), я получаю неприятное исключение, которое сводится к:

Caused by: org.hibernate.HibernateException: Wrong column type in kpoffice.tickets for column id_. Found: varchar, expected: binary(50)
        at org.hibernate.mapping.Table.validateColumns(Table.java:372)
        at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1338)
        at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:175)
        at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:525)
        at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1859)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:852)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:845)
        at org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl.withTccl(ClassLoaderServiceImpl.java:398)
        at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:844)
        at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
        at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:343)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:318)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1625)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1562)
        ... 120 more

Автоматическое преобразование не работает. Я попытался аннотировать конвертер к классу и самому атрибуту. Но конвертер не используется. Но когда я добавил тип UUID спящего режима через жалобную запись с гибернацией, которая не может иметь конвертер и определение типа гибернации для того же атрибута. Таким образом, hibernate считывает конфигурацию преобразователя.

При использовании Envers конвертер JPA 2.1 не работает. Но я не использую envers в своем программном обеспечении.

Я надеюсь, что есть кто-то, кто знает, что я делаю неправильно...

4b9b3361

Ответ 2

Другой вариант - встроить логику преобразования в альтернативные getters/seters, например:

public class SecurityTicket implements Serializable
{
...
private UUID id;

@Transient
public UUID getUUID()
{
    return id;
}

@Id @NotNull
@Column(name = "id_", length=50, nullable = false, updatable = false, unique = true)
public String getID()
{
    return id.toString();
}

public void setUUID( UUID id )
{
    this.id = id;
}

public void setID( String id )
{
    this.id = UUID.fromString( id );
}

...

Аннотация @Transient сообщит JPA игнорировать этот геттер, поэтому он не считает, что существует отдельное свойство UUID. Это неэлегантно, но для меня это работало с использованием JPA на классах с UUID как PK. Вы рискуете другим кодом, устанавливающим неправильные значения с помощью метода setId (String), но это кажется единственным обходным решением. Возможно, этот способ может быть защищен/закрыт?

В то время как обычный Java-код сможет отличать сеттеры с тем же именем на основе разных типов аргументов, JPA будет жаловаться, если вы не называете их по-разному.

Это раздражает то, что JPA не поддерживает конвертеры на идентификаторах или что он не следует за соглашением JAXB, которое не требует конвертеров для классов со стандартными методами преобразования (то есть toString/fromString, intValue/parseInt и т.д.).