英文:
Saving entities with Hibernate 5.1 and Spring 5.2.9 fails to save entity (spring-boot 2.3.4, spring-boot-devtools)
问题
[Edit: 在问题中修复了 Spring 版本为 5.2.9。v2.3.4 是聚合的 Maven 依赖。]
我已经阅读了大量的帖子,试图弄清楚这个问题。似乎存在着巨大的兼容性问题,但我无法弄清楚要更改成哪个版本。我通常不使用 Java,但在教学时不得不使用,所以希望有人可以忽略我不懂 Java 的技能,并给我一些建议。🤷♂️😊
如果我通过反汇编的代码进行步进,我会发现我的单元测试(集成测试)在尝试反映我的 ID 属性时做了与使用 Spring 运行时不同的操作。
[Edit] 这是 SQL 日志:
```plaintext
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: 使用 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:org.hiof.chatroom.core.ChatMessage,在属性的 getter 方法中发生了异常:id
2020-10-07 02:28:59.438 ERROR 47504 --- [nio-8081-exec-6] o.a.c.c.C.[.[.[/].[dispatcherServlet] : 对 servlet [dispatcherServlet] 执行 Servlet.service(),在路径为 [] 的上下文中抛出异常 [请求处理失败;嵌套异常是 org.hibernate.PropertyAccessException:调用 org.hiof.chatroom.core.ChatMessage.id 的 getter 时发生了 IllegalArgumentException],根本原因是
java.lang.IllegalArgumentException:对象不是声明类的实例
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_172]
……(省略其他堆栈跟踪)……
这是一个非常简单的带有 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
。但是我不知道该怎么做。
现在已经很晚了,我希望明天可以为一些学生演示这个,所以如果我离开这篇帖子有点不符合 SO 的标准,我深感抱歉。
[Edit]
存储库在此处发布:https://github.com/lars-erik/hiof-sweat2020-chatroom-demo
[Edit after 2 days]
我设法找到了在 Web 项目中运行时抛出的位置。对于像我这样的 .NET 开发者来说,这似乎是我的核心“程序集”(模块)的两个版本加载到了“域”中,并且从“错误模块的元数据”中读取了 ID 属性。然而,我不太清楚,但事情似乎是这样的。在 getIdentifier
方法中,所有者和 idGetter 都指向同一个类 org.hiof.chatroom.core.ChatMessage
。然而,invoke
坚持说实例不是 id getter 的声明类。以下是跟踪:
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$$Lambda$541)
fireEventOnEachListener:102, EventListenerGroupImpl (
英文:
[Edit: fixed spring version in q to 5.2.9. v2.3.4 is the aggregated maven dep.]
I've gone through heaps of posts to try to figure this out. Seems like there's a huge compatibility issue, but I can't figure out which versions to change to. I don't do Java normally, but have to when teaching, so hoping someone will ignore my non-existant Java-skills and bless me with some advice. 🤷♂️🙏
If I step through disassembled code, I can see that my unit tests (integrated) does something else when trying to reflect my ID property than what is done when ran with Spring.
[Edit] Here's the SQL log:
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_
Here's the exception and the top of the trace:
[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]
It's a super simple DB setup with SQL Lite.
The only entity is mapped as such:
<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>
And the entity:
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;
}
}
Here's a test that goes through with no different infrastructure and config than (not) having Spring running the application:
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);
}
}
I'm fairly sure it has to do with the StandardServiceRegistryBuilder
involved in setting up the session factory in my UOW. No idea what to do tho.
It's way too late and I was hoping to demo this for some students tomorrow, so I apologize if I left this post a bit off the prefered standards of SO.
[Edit]
Repo is published here: https://github.com/lars-erik/hiof-sweat2020-chatroom-demo
[Edit after 2 days]
I managed to pin down the place it throws when ran in the web project. For a dotnet developer like me, this smells like two versions of my core "assembly" (module) is loaded into the "domain" and the ID property is read from the "wrong module's metadata". No idea tho, but that's how it seems. In the getIdentifier
method, both owner and idGetter points to the same class, org.hiof.chatroom.core.ChatMessage
. Yet the invoke
insists the instance is not the id getter's declaring class. Here's the trace:
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$$Lambda$541)
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)
[Edit oct. 11.]
Debugging currently shows two versions of the ChatMessage class when ran 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"
The owner's class' classloader is "RestartClassLoader", while the declaring one is "Launcher$AppClassLoader".
答案1
得分: 3
这是由 spring-boot-devtools
引起的。它创建了一个名为 RestartClassLoader
的类加载器,以加速应用程序的重启。然而,它会干扰 Hibernate 的元数据反射,导致在进行 id 反射时出现相同类型的两个实例。
因此,这在某种程度上与以下内容有些重复,以及其他一些内容:
https://stackoverflow.com/questions/57848521/classes-loaded-by-different-classloaders-in-spring-boot-and-logback
英文:
This was caused by spring-boot-devtools
. It creates a classloader called RestartClassLoader
to speed up restarts. However, it messes with Hibernate's metadata reflection giving two instances of the same type when we get to id reflection.
So this ends up being somewhat of a dupe of this and a few others:
https://stackoverflow.com/questions/57848521/classes-loaded-by-different-classloaders-in-spring-boot-and-logback
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论