我一直在咨询许多方法/帖子/堆栈溢出问题,以便在运行Kotlin / SpringBoot应用程序时处理以下错误(完整堆栈跟踪):
2020-04-22 18:33:56.823 ERROR 46345 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: No Dialect mapping for JDBC type: 2118910070 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1803) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1108) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) at app.ApplicationKt.main(Application.kt:13) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: No Dialect mapping for JDBC type: 2118910070 at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:403) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:378) at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1862) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1799) ... 21 common frames omitted Caused by: org.hibernate.MappingException: No Dialect mapping for JDBC type: 2118910070 at org.hibernate.dialect.TypeNames.get(TypeNames.java:71) at org.hibernate.dialect.TypeNames.get(TypeNames.java:103) at org.hibernate.dialect.Dialect.getTypeName(Dialect.java:369) at org.hibernate.mapping.Column.getSqlType(Column.java:238) at org.hibernate.tool.schema.internal.AbstractSchemaValidator.validateColumnType(AbstractSchemaValidator.java:156) at org.hibernate.tool.schema.internal.AbstractSchemaValidator.validateTable(AbstractSchemaValidator.java:143) at org.hibernate.tool.schema.internal.GroupedSchemaValidatorImpl.validateTables(GroupedSchemaValidatorImpl.java:42) at org.hibernate.tool.schema.internal.AbstractSchemaValidator.performValidation(AbstractSchemaValidator.java:89) at org.hibernate.tool.schema.internal.AbstractSchemaValidator.doValidation(AbstractSchemaValidator.java:68) at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:192) at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:73) at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:320) at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:462) at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1249) at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:391) ... 25 common frames omitted
问题在于使用Hibernate映射PostgreSQL的JSONB数据类型。
我广泛尝试和调试的2种方法如下:
我在这两个方面都进行了很多尝试,但是没有运气,而且我很想知道我要去哪里错了,我一直想念什么。
方法1
我的实体:
@Entity @TypeDef(name = "JsonUserType", typeClass = JsonUserType::class) @Table(name = "entity") data class MyEntity( @Column(nullable = false) val id: UUID, @Column(nullable = false) @Enumerated(value = EnumType.STRING) @Column(nullable = false) val type: Type, @Type(type = "JsonUserType") @Column(columnDefinition = "jsonb") @Basic(fetch = FetchType.LAZY) var event_data: Event ) : SomeEntity<UUID>(), SomeOtherStuff { override fun getName(): String { return id } } enum class Type(val value: String) { TYPE1("Type1"), TYPE2("Type2") }
我的PoJO:
data class Event( val someContent: String, val someBoolean: Boolean ) : Serializable { //equals, hashcode etc are omitted }
我的自定义hibernate方言:
class CustomPostgreSQLDialect : PostgreSQL95Dialect { constructor() : super() { this.registerColumnType(Types.JAVA_OBJECT, "jsonb") } }
我的自定义类型(抽象类)
abstract class JsonDataUserType : UserType { override fun sqlTypes(): IntArray? { return intArrayOf(Types.JAVA_OBJECT) } override fun equals(value1: Any?, value2: Any?): Boolean { return value1 == value2 } override fun hashCode(value1: Any?): Int { return value1!!.hashCode() } override fun assemble(value1: Serializable?, value2: Any?): Any { return deepCopy(value1) } override fun disassemble(value1: Any?): Serializable { return deepCopy(value1) as Serializable } override fun deepCopy(p0: Any?): Any { return try { val bos = ByteArrayOutputStream() val oos = ObjectOutputStream(bos) oos.writeObject(p0) oos.flush() oos.close() bos.close() val bais = ByteArrayInputStream(bos.toByteArray()) ObjectInputStream(bais).readObject() } catch (ex: ClassNotFoundException) { throw HibernateException(ex) } catch (ex: IOException) { throw HibernateException(ex) } } override fun replace(p0: Any?, p1: Any?, p2: Any?): Any { return deepCopy(p0) } override fun nullSafeSet(p0: PreparedStatement?, p1: Any?, p2: Int, p3: SharedSessionContractImplementor?) { if (p1 == null) { p0?.setNull(p2, Types.OTHER) return } try { val mapper = ObjectMapper() val w = StringWriter() mapper.writeValue(w, p1) w.flush() p0?.setObject(p2, w.toString(), Types.OTHER) } catch (ex: java.lang.Exception) { throw RuntimeException("Failed to convert Jsonb to String: " + ex.message, ex) } } override fun nullSafeGet(p0: ResultSet?, p1: Array<out String>?, p2: SharedSessionContractImplementor?, p3: Any?): Any { val cellContent = p0?.getString(p1?.get(0)) return try { val mapper = ObjectMapper() mapper.readValue(cellContent?.toByteArray(charset("UTF-8")), returnedClass()) } catch (ex: Exception) { throw RuntimeException("Failed to convert String to Jsonb: " + ex.message, ex) } } override fun isMutable(): Boolean { return true } }
我的具体课程:
class JsonType : JsonDataUserType() { override fun returnedClass(): Class<Event> { return Event::class.java } }
我的application.yml jpahibernate属性
jpa.properties.database.database-platform: org.hibernate.dialect.PostgreSQL95Dialect jpa.properties.hibernate.dialect: org.myapp.util.CustomPostgreSQLDialect
方法2
Hibernate属性与PoJo类完全相同,不包含自定义映射器。
实体
@Entity @TypeDef( name = "jsonb", typeClass = JsonBinaryType::class ) @Table(name = "entity") data class MyEntity( @Column(nullable = false) val id: UUID, @Column(nullable = false) @Enumerated(value = EnumType.STRING) @Column(nullable = false) val type: Type, @Type(type = "jsonb") @Column(columnDefinition = "jsonb") @Basic(fetch = FetchType.LAZY) var event_data: Event ) : SomeEntity<UUID>(), SomeOtherStuff { override fun getName(): String { return id } } enum class Type(val value: String) { TYPE1("Type1"), TYPE2("Type2") }
自定义方言(使用hibernate类型):
class CustomPostgreSQLDialect : PostgreSQL95Dialect { constructor() : super() { this.registerHibernateType(Types.OTHER, JsonNodeBinaryType::class.java.name) this.registerHibernateType(Types.OTHER, JsonStringType::class.java.name) this.registerHibernateType(Types.OTHER, JsonBinaryType::class.java.name) this.registerHibernateType(Types.OTHER, JsonNodeBinaryType::class.java.name) this.registerHibernateType(Types.OTHER, JsonNodeStringType::class.java.name) } }
请注意,我也尝试仅使用:
this.registerHibernateType(Types.OTHER, "jsonb")
以及所有这些都存在于我的实体或其扩展的基础实体中(对此没有更改):
@TypeDefs({ @TypeDef(name = "string-array", typeClass = StringArrayType.class), @TypeDef(name = "int-array", typeClass = IntArrayType.class), @TypeDef(name = "json", typeClass = JsonStringType.class), @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class), @TypeDef(name = "jsonb-node", typeClass = JsonNodeBinaryType.class), @TypeDef(name = "json-node", typeClass = JsonNodeStringType.class), })
在这两种方法中我都明显做错了吗?我无法使其正常工作,并且不确定是否以任何方式相关, JDBC类型的No Dialect映射 之后的数值始终是不同的。我添加此代码是因为我看到某些ID与某些错误类别相关。
你能帮我吗?
谢谢
编辑: 我想提供有关jpa,postgres和hibernate版本的更多信息。我目前正在与以下人员合作:
其中是否存在任何特定的版本控制问题?
编辑2 我一直试图成功使用hibernate类型(如上所述的方法2)。根据Postgres版本(10),我进行了以下更改:
class CustomPostgreSQLDialect : PostgreSQL10Dialect { constructor() : super() { this.registerHibernateType(Types.OTHER, StringArrayType::class.java.name) this.registerHibernateType(Types.OTHER, IntArrayType::class.java.name) this.registerHibernateType(Types.OTHER, JsonStringType::class.java.name) this.registerHibernateType(Types.OTHER, JsonBinaryType::class.java.name) this.registerHibernateType(Types.OTHER, JsonNodeBinaryType::class.java.name) this.registerHibernateType(Types.OTHER, JsonNodeStringType::class.java.name) } }
然后在我的实体中
@TypeDefs({ @TypeDef(name = "string-array", typeClass = StringArrayType.class), @TypeDef(name = "int-array", typeClass = IntArrayType.class), @TypeDef(name = "json", typeClass = JsonStringType.class), @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) })
和
@Type(type = "jsonb") @Column(columnDefinition = "jsonb") @Basic(fetch = FetchType.LAZY) var event_data: Event
然后,我已经在 TypeNames 中调试了 get 方法,其中错误来自: __
public String get(final int typeCode) throws MappingException { final Integer integer = Integer.valueOf( typeCode ); final String result = defaults.get( integer ); if ( result == null ) { throw new MappingException( "No Dialect mapping for JDBC type: " + typeCode ); } return result; }
这就是我得到的:
defaults = {HashMap@12093} size = 27 {Integer@12124} -1 -> "text" {Integer@12126} 1 -> "char(1)" {Integer@12128} -2 -> "bytea" {Integer@12130} 2 -> "numeric($p, $s)" {Integer@12132} -3 -> "bytea" {Integer@12133} -4 -> "bytea" {Integer@12134} 4 -> "int4" {Integer@12136} -5 -> "int8" {Integer@12138} -6 -> "int2" {Integer@12140} 5 -> "int2" {Integer@12141} -7 -> "bool" {Integer@12143} 6 -> "float4" {Integer@12145} 7 -> "real" {Integer@12147} 8 -> "float8" {Integer@12149} -9 -> "nvarchar($l)" {Integer@12151} 12 -> "varchar($l)" {Integer@12153} -15 -> "nchar($l)" {Integer@12155} -16 -> "nvarchar($l)" {Integer@12156} 16 -> "boolean" {Integer@12158} 2000 -> "json" {Integer@12160} 2004 -> "oid" {Integer@12162} 2005 -> "text" {Integer@12163} 1111 -> "uuid" {Integer@12165} 91 -> "date" {Integer@12167} 2011 -> "nclob" {Integer@12169} 92 -> "time" {Integer@12171} 93 -> "timestamp"
找不到 jsonb ,当我调试自定义方言时,得到以下信息:
{Integer@10846} 1111 -> "com.vladmihalcea.hibernate.type.json.JsonStringType" key = {Integer@10846} 1111 value = "com.vladmihalcea.hibernate.type.json.JsonStringType"
这是为什么?为什么我没有得到jsonb类型?
我在请求请求中提出我的解决方案
这个想法是将Entity更改为:
import com.example.demo.pojo.SamplePojo import com.vladmihalcea.hibernate.type.json.JsonBinaryType import com.vladmihalcea.hibernate.type.json.JsonStringType import org.hibernate.annotations.Type import org.hibernate.annotations.TypeDef import org.hibernate.annotations.TypeDefs import javax.persistence.* @Entity @Table(name = "tests") @TypeDefs( TypeDef(name = "json", typeClass = JsonStringType::class), TypeDef(name = "jsonb", typeClass = JsonBinaryType::class) ) data class SampleEntity ( @Id @GeneratedValue val id: Long?, val name: String?, @Type(type = "jsonb") @Column(columnDefinition = "jsonb") var data: Map<String, Any>? ) { /** * Dependently on use-case this can be done differently: * https://stackoverflow.com/questions/37873995/how-to-create-empty-constructor-for-data-class-in-kotlin-android */ constructor(): this(null, null, null) }
Map<String, Any>
由于我们可以完全控制业务逻辑中的POJO,因此唯一缺少的部分是将POJO转换为Map并将Map转换为POJO
SamplePojo实现
data class SamplePojo( val payload: String, val flag: Boolean ) { constructor(map: Map<String, Any>) : this(map["payload"] as String, map["flag"] as Boolean) fun toMap() : Map<String, Any> { return mapOf("payload" to payload, "flag" to flag) } }
这是一种解决方法,但是它允许我们使用任何深度级别的结构。
PS我注意到您使用Serializer并重新定义了equals, toString, hashCode。如果使用,则不需要此data class。
Serializer
equals, toString, hashCode
data class
更新:
如果您需要比以上更灵活的结构Map<String,Any>,则可以使用JsonNode。代码示例
Map<String,Any>
JsonNode
实体:
import com.fasterxml.jackson.databind.JsonNode import com.vladmihalcea.hibernate.type.json.JsonBinaryType import com.vladmihalcea.hibernate.type.json.JsonStringType import org.hibernate.annotations.Type import org.hibernate.annotations.TypeDef import org.hibernate.annotations.TypeDefs import javax.persistence.* @Entity @Table(name = "tests") @TypeDefs( TypeDef(name = "json", typeClass = JsonStringType::class), TypeDef(name = "jsonb", typeClass = JsonBinaryType::class) ) data class SampleJsonNodeEntity ( @Id @GeneratedValue val id: Long?, val name: String?, @Type(type = "jsonb") @Column(columnDefinition = "jsonb") var data: JsonNode? ) { /** * Dependently on use-case this can be done differently: * https://stackoverflow.com/questions/37873995/how-to-create-empty-constructor-for-data-class-in-kotlin-android */ constructor(): this(null, null, null) }
更改存储库中的实体:
import com.example.demo.entity.SampleJsonNodeEntity import org.springframework.data.jpa.repository.JpaRepository interface SampleJsonNodeRepository: JpaRepository<SampleJsonNodeEntity, Long> { }
测试两种方法:
import com.example.demo.DbTestInitializer import com.example.demo.entity.SampleJsonNodeEntity import com.example.demo.entity.SampleMapEntity import com.example.demo.pojo.SamplePojo import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import junit.framework.Assert.assertEquals import junit.framework.Assert.assertNotNull import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.context.SpringBootTest import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.junit4.SpringRunner @RunWith(SpringRunner::class) @SpringBootTest @ContextConfiguration(initializers = [DbTestInitializer::class]) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class SampleRepositoryTest { @Autowired lateinit var sampleMapRepository: SampleMapRepository @Autowired lateinit var sampleJsonNodeRepository: SampleJsonNodeRepository lateinit var dto: SamplePojo lateinit var mapEntity: SampleMapEntity lateinit var jsonNodeEntity: SampleJsonNodeEntity @Before fun setUp() { dto = SamplePojo("Test", true) mapEntity = SampleMapEntity(null, "POJO1", dto.toMap() ) jsonNodeEntity = SampleJsonNodeEntity(null, "POJO2", jacksonObjectMapper().valueToTree(dto) ) } @Test fun createMapPojo() { val id = sampleMapRepository.save(mapEntity).id!! assertNotNull(sampleMapRepository.getOne(id)) assertEquals(sampleMapRepository.getOne(id).data?.let { SamplePojo(it) }, dto) } @Test fun createJsonNodePojo() { val id = sampleJsonNodeRepository.save(jsonNodeEntity).id!! assertNotNull(sampleJsonNodeRepository.getOne(id)) assertEquals(jacksonObjectMapper().treeToValue(sampleJsonNodeRepository.getOne(id).data, SamplePojo::class.java), dto) } }