使用Hibernate 5.1和Spring 5.2.9保存实体失败(spring-boot 2.3.4,spring-boot-devtools)

huangapple go评论82阅读模式
英文:

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:

&lt;class name=&quot;org.hiof.chatroom.core.ChatMessage&quot; table=&quot;ChatMessages&quot;&gt;
    &lt;id name=&quot;id&quot; column=&quot;Id&quot;&gt;
        &lt;generator class=&quot;assigned&quot;/&gt;
    &lt;/id&gt;
    &lt;property name=&quot;user&quot; column=&quot;UserName&quot; length=&quot;50&quot;/&gt;
    &lt;property name=&quot;message&quot; column=&quot;Message&quot; length=&quot;512&quot;/&gt;
&lt;/class&gt;

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(&quot;./db/chat-test.db&quot;);
        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} &quot;class org.hiof.chatroom.core.ChatMessage&quot;
owner.getClass() == getterMethod.getDeclaringClass() = false
org.hiof.chatroom.core.ChatMessage.class = {Class@8446} &quot;class org.hiof.chatroom.core.ChatMessage&quot;
getterMethod.getDeclaringClass() = {Class@8446} &quot;class org.hiof.chatroom.core.ChatMessage&quot;

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

huangapple
  • 本文由 发表于 2020年10月7日 08:37:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/64235608.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定