加载spring-boot和spring-data-jpa时,Hibernate无法加载JPA 2.1 Converter

我有一个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 { @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 getRoles() { HashSet result = new HashSet(); account.getRoles().forEach(t -> result.add(t.getDisplayNumber())); return Collections.unmodifiableSet(result); } public Set 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文件)时,我得到一个令人讨厌的exception,归结为:

 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.(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 

转换的autoApply不起作用。 我试着将转换器注释到类和属性本身。 但是没有使用转换器。 但是当我通过hibernate特定的注释hibernate投诉添加了hibernate UUID类型时,它不能为同一属性提供转换器和hibernate类型定义。 所以hibernate读取转换器配置。

使用envers时,JPA 2.1转换器不起作用。 但我不在我的软件中使用envers。

我希望那里有人知道我做错了什么……

安迪威尔金森给出了正确的答案。 阅读规范有很多次。

JPA 2.1转换器不适用于@Id注释的属性。

谢谢安迪。

另一个选择是将转换逻辑嵌入替代的getter / setter中,如下所示:

 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忽略此getter,因此它认为没有单独的UUID属性。 它不够优雅,但它适用于我在UUID为PK的类上使用JPA。 您确实通过setId(String)方法运行其他代码设置错误值的风险,但它似乎是唯一的解决方法。 这种方法有可能受到保护/私有吗?

虽然普通的Java代码能够根据不同的参数类型区分具有相同名称的setter,但如果您没有以不同的方式命名它们,JPA会抱怨。

令人讨厌的是,JPA不支持转换器在Ids上,或者它不遵循JAXB约定,不需要转换器用于具有标准转换方法的类(即toString / fromString,intValue / parseInt等)。