web-dev-qa-db-ja.com

spring-bootおよびspring-data-jpaをロードすると、HibernateはJPA 2.1Converterのロードに失敗します

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を介して統合テストを実行すると、データベースは非常に正常に機能します。しかし、アプリケーション(3番目の.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

変換の自動適用は機能しません。コンバーターにクラスと属性自体に注釈を付けようとしました。ただし、コンバータは使用しません。しかし、Hibernate固有のアノテーションを介してHibernate UUIDタイプを追加したとき、同じ属性のコンバーターとHibernateタイプ定義を持つことができないというHibernateの苦情がありました。したがって、休止状態はコンバーター構成を読み取ります。

エンバーを使用する場合、JPA2.1コンバーターは機能しません。しかし、私は自分のソフトウェアでenversを使用していません。

私が間違っていることを知っている誰かがそこにいることを願っています...

16
klenkes74

Andy Wilkinsonが正解しました。 仕様を読むことは多くの場合役に立ちます。

JPA 2.1コンバーターは、@Idアノテーション付き属性には適用されません。

アンディありがとう。

10
klenkes74

別のオプションは、次のように、変換ロジックを代替のゲッター/セッターに埋め込むことです。

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プロパティがあるとは見なされません。エレガントではありませんが、PKとしてUUIDを持つクラスでJPAを使用することでうまくいきました。 setId(String)メソッドを介して他のコードが不正な値を設定するリスクを冒しますが、それが唯一の回避策のようです。この方法を保護/非公開にすることは可能かもしれませんか?

通常のJavaコードは、異なる引数タイプに基づいて同じ名前のセッターを区別できますが、異なる名前を付けないと、JPAは文句を言います。

JPAがID上のコンバーターをサポートしていないこと、または標準の変換メソッド(toString/fromString、intValue/parseIntなど)を持つクラスにコンバーターを必要としないというJAXB規則に準拠していないことは厄介です。

0
Tristan Nixon