使用 Hibernate 5.1 和 Spring 5.2.9 保存实体无法保存实体(spring-boot 2.3.4,spring-boot-devtools)
Saving entities with Hibernate 5.1 and Spring 5.2.9 fails to save entity (spring-boot 2.3.4, spring-boot-devtools)
[编辑:将 q 中的 spring 版本固定为 5.2.9。 v2.3.4 是聚合的 maven 部门。]
我已经研究了很多 post 来试图弄清楚这个问题。似乎存在巨大的兼容性问题,但我无法弄清楚要更改为哪个版本。我平时不做Java,但在教学时必须做,所以希望有人会忽略我不存在的Java-技能并祝福我一些建议。 ♂️
如果我单步执行反汇编代码,我可以看到我的单元测试(集成)在尝试反映我的 ID 属性 时做了其他事情,而不是 运行 和 [=67 时所做的事情=].
[编辑] 这是 SQL 日志:
Hibernate: create table ChatMessages (Id varchar(255) not null, UserName varchar(50), Message varchar(512), primary key (Id))
2020-10-07 10:01:15.142 INFO 43304 --- [nio-8081-exec-8] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select chatmessag0_.Id as Id1_0_, chatmessag0_.UserName as UserName2_0_, chatmessag0_.Message as Message3_0_ from ChatMessages chatmessag0_
这是异常和跟踪的顶部:
[nio-8081-exec-6] o.h.p.access.spi.GetterMethodImpl : HHH000122: IllegalArgumentException in class: org.hiof.chatroom.core.ChatMessage, getter method of property: id
2020-10-07 02:28:59.438 ERROR 47504 --- [nio-8081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of org.hiof.chatroom.core.ChatMessage.id] with root cause
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_172]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_172]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_172]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_172]
at org.hibernate.property.access.spi.GetterMethodImpl.get(GetterMethodImpl.java:41) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:223) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.getIdentifier(AbstractEntityPersister.java:4633) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.id.Assigned.generate(Assigned.java:32) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:105) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:682) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:674) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:669) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hiof.chatroom.database.ChatMessageRepository.add(ChatMessageRepository.java:21) ~[classes/:na]
这是一个超级简单的数据库设置 SQL Lite。
唯一的实体是这样映射的:
<class name="org.hiof.chatroom.core.ChatMessage" table="ChatMessages">
<id name="id" column="Id">
<generator class="assigned"/>
</id>
<property name="user" column="UserName" length="50"/>
<property name="message" column="Message" length="512"/>
</class>
实体:
public class ChatMessage {
private String id;
private String user;
private String message;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
这是一个测试,其基础架构和配置与(不)具有 Spring 运行 应用程序没有不同:
public class When_persisting_chat_messages {
@Test
public void stores_message() throws Exception {
DatabaseManager.ensureDatabase("./db/chat-test.db");
UnitOfWork uow = new UnitOfWork();
ChatMessageRepository repo = new ChatMessageRepository(uow);
ChatMessage msg = new ChatMessage();
String id = UUID.randomUUID().toString();
msg.setId(id);
repo.add(msg);
uow.saveChanges();
uow.close();
uow = new UnitOfWork();
repo = new ChatMessageRepository(uow);
msg = repo.get(id);
Assertions.assertNotNull(msg);
}
}
我相当确定这与在我的 UOW 中设置会话工厂所涉及的 StandardServiceRegistryBuilder
有关。不知道该怎么办。
太晚了,我希望明天能为一些学生演示这个,所以如果我让这个 post 有点偏离 SO 的首选标准,我深表歉意。
[编辑]
回购发布在这里:https://github.com/lars-erik/hiof-sweat2020-chatroom-demo
[2 天后编辑]
我设法确定了它在 Web 项目中 运行 时抛出的位置。对于像我这样的 dotnet 开发人员,这听起来像是我的核心“程序集”(模块)的两个版本被加载到“域”中,并且 ID 属性 是从“错误的模块的元数据”中读取的。不知道,但看起来就是这样。在getIdentifier
方法中,owner和idGetter都指向同一个class、org.hiof.chatroom.core.ChatMessage
。然而 invoke
坚持实例不是 id getter 声明的 class。这是踪迹:
get:42, GetterMethodImpl (org.hibernate.property.access.spi)
getIdentifier:230, AbstractEntityTuplizer (org.hibernate.tuple.entity)
getIdentifier:5155, AbstractEntityPersister (org.hibernate.persister.entity)
generate:31, Assigned (org.hibernate.id)
saveWithGeneratedId:115, AbstractSaveEventListener (org.hibernate.event.internal)
saveWithGeneratedOrRequestedId:194, DefaultSaveOrUpdateEventListener (org.hibernate.event.internal)
saveWithGeneratedOrRequestedId:38, DefaultSaveEventListener (org.hibernate.event.internal)
entityIsTransient:179, DefaultSaveOrUpdateEventListener (org.hibernate.event.internal)
performSaveOrUpdate:32, DefaultSaveEventListener (org.hibernate.event.internal)
onSaveOrUpdate:75, DefaultSaveOrUpdateEventListener (org.hibernate.event.internal)
accept:-1, 1659093435 (org.hibernate.internal.SessionImpl$$Lambda1)
fireEventOnEachListener:102, EventListenerGroupImpl (org.hibernate.event.service.internal)
fireSave:636, SessionImpl (org.hibernate.internal)
save:629, SessionImpl (org.hibernate.internal)
save:624, SessionImpl (org.hibernate.internal)
[编辑十月。 11.]
调试当前显示两个版本的 ChatMessage class when 运行 with spring。 :/
owner.getClass() = {Class@9587} "class org.hiof.chatroom.core.ChatMessage"
owner.getClass() == getterMethod.getDeclaringClass() = false
org.hiof.chatroom.core.ChatMessage.class = {Class@8446} "class org.hiof.chatroom.core.ChatMessage"
getterMethod.getDeclaringClass() = {Class@8446} "class org.hiof.chatroom.core.ChatMessage"
所有者的 class' classloader 是“RestartClassLoader”,而声明者是“Launcher$AppClassLoader”。
这是由 spring-boot-devtools
引起的。它创建一个名为 RestartClassLoader
的类加载器来加速重启。但是,当我们进行 id 反射时,它会干扰 Hibernate 的元数据反射,从而给出相同类型的两个实例。
所以这最终有点像这个和其他一些的骗局:
[编辑:将 q 中的 spring 版本固定为 5.2.9。 v2.3.4 是聚合的 maven 部门。]
我已经研究了很多 post 来试图弄清楚这个问题。似乎存在巨大的兼容性问题,但我无法弄清楚要更改为哪个版本。我平时不做Java,但在教学时必须做,所以希望有人会忽略我不存在的Java-技能并祝福我一些建议。 ♂️
如果我单步执行反汇编代码,我可以看到我的单元测试(集成)在尝试反映我的 ID 属性 时做了其他事情,而不是 运行 和 [=67 时所做的事情=].
[编辑] 这是 SQL 日志:
Hibernate: create table ChatMessages (Id varchar(255) not null, UserName varchar(50), Message varchar(512), primary key (Id))
2020-10-07 10:01:15.142 INFO 43304 --- [nio-8081-exec-8] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select chatmessag0_.Id as Id1_0_, chatmessag0_.UserName as UserName2_0_, chatmessag0_.Message as Message3_0_ from ChatMessages chatmessag0_
这是异常和跟踪的顶部:
[nio-8081-exec-6] o.h.p.access.spi.GetterMethodImpl : HHH000122: IllegalArgumentException in class: org.hiof.chatroom.core.ChatMessage, getter method of property: id
2020-10-07 02:28:59.438 ERROR 47504 --- [nio-8081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.hibernate.PropertyAccessException: IllegalArgumentException occurred calling getter of org.hiof.chatroom.core.ChatMessage.id] with root cause
java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_172]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_172]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_172]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_172]
at org.hibernate.property.access.spi.GetterMethodImpl.get(GetterMethodImpl.java:41) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.tuple.entity.AbstractEntityTuplizer.getIdentifier(AbstractEntityTuplizer.java:223) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.persister.entity.AbstractEntityPersister.getIdentifier(AbstractEntityPersister.java:4633) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.id.Assigned.generate(Assigned.java:32) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:105) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:682) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:674) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:669) ~[hibernate-core-5.1.0.Final.jar:5.1.0.Final]
at org.hiof.chatroom.database.ChatMessageRepository.add(ChatMessageRepository.java:21) ~[classes/:na]
这是一个超级简单的数据库设置 SQL Lite。
唯一的实体是这样映射的:
<class name="org.hiof.chatroom.core.ChatMessage" table="ChatMessages">
<id name="id" column="Id">
<generator class="assigned"/>
</id>
<property name="user" column="UserName" length="50"/>
<property name="message" column="Message" length="512"/>
</class>
实体:
public class ChatMessage {
private String id;
private String user;
private String message;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
这是一个测试,其基础架构和配置与(不)具有 Spring 运行 应用程序没有不同:
public class When_persisting_chat_messages {
@Test
public void stores_message() throws Exception {
DatabaseManager.ensureDatabase("./db/chat-test.db");
UnitOfWork uow = new UnitOfWork();
ChatMessageRepository repo = new ChatMessageRepository(uow);
ChatMessage msg = new ChatMessage();
String id = UUID.randomUUID().toString();
msg.setId(id);
repo.add(msg);
uow.saveChanges();
uow.close();
uow = new UnitOfWork();
repo = new ChatMessageRepository(uow);
msg = repo.get(id);
Assertions.assertNotNull(msg);
}
}
我相当确定这与在我的 UOW 中设置会话工厂所涉及的 StandardServiceRegistryBuilder
有关。不知道该怎么办。
太晚了,我希望明天能为一些学生演示这个,所以如果我让这个 post 有点偏离 SO 的首选标准,我深表歉意。
[编辑]
回购发布在这里:https://github.com/lars-erik/hiof-sweat2020-chatroom-demo
[2 天后编辑]
我设法确定了它在 Web 项目中 运行 时抛出的位置。对于像我这样的 dotnet 开发人员,这听起来像是我的核心“程序集”(模块)的两个版本被加载到“域”中,并且 ID 属性 是从“错误的模块的元数据”中读取的。不知道,但看起来就是这样。在getIdentifier
方法中,owner和idGetter都指向同一个class、org.hiof.chatroom.core.ChatMessage
。然而 invoke
坚持实例不是 id getter 声明的 class。这是踪迹:
get:42, GetterMethodImpl (org.hibernate.property.access.spi)
getIdentifier:230, AbstractEntityTuplizer (org.hibernate.tuple.entity)
getIdentifier:5155, AbstractEntityPersister (org.hibernate.persister.entity)
generate:31, Assigned (org.hibernate.id)
saveWithGeneratedId:115, AbstractSaveEventListener (org.hibernate.event.internal)
saveWithGeneratedOrRequestedId:194, DefaultSaveOrUpdateEventListener (org.hibernate.event.internal)
saveWithGeneratedOrRequestedId:38, DefaultSaveEventListener (org.hibernate.event.internal)
entityIsTransient:179, DefaultSaveOrUpdateEventListener (org.hibernate.event.internal)
performSaveOrUpdate:32, DefaultSaveEventListener (org.hibernate.event.internal)
onSaveOrUpdate:75, DefaultSaveOrUpdateEventListener (org.hibernate.event.internal)
accept:-1, 1659093435 (org.hibernate.internal.SessionImpl$$Lambda1)
fireEventOnEachListener:102, EventListenerGroupImpl (org.hibernate.event.service.internal)
fireSave:636, SessionImpl (org.hibernate.internal)
save:629, SessionImpl (org.hibernate.internal)
save:624, SessionImpl (org.hibernate.internal)
[编辑十月。 11.] 调试当前显示两个版本的 ChatMessage class when 运行 with spring。 :/
owner.getClass() = {Class@9587} "class org.hiof.chatroom.core.ChatMessage"
owner.getClass() == getterMethod.getDeclaringClass() = false
org.hiof.chatroom.core.ChatMessage.class = {Class@8446} "class org.hiof.chatroom.core.ChatMessage"
getterMethod.getDeclaringClass() = {Class@8446} "class org.hiof.chatroom.core.ChatMessage"
所有者的 class' classloader 是“RestartClassLoader”,而声明者是“Launcher$AppClassLoader”。
这是由 spring-boot-devtools
引起的。它创建一个名为 RestartClassLoader
的类加载器来加速重启。但是,当我们进行 id 反射时,它会干扰 Hibernate 的元数据反射,从而给出相同类型的两个实例。
所以这最终有点像这个和其他一些的骗局: