hypersistence-utils: PostgreSQLEnumType error "Could not set value of type" with Hibernate 6 and Quarkus Panache

We are using hypersistence-utils together with Quarkus, Kotlin, Hibernate 6 and Postgresql 15.

After migrating from quarkus 2 and hibernate 5 we now face an enum issue we hadn’t had before.

Could not set value of type [DemoEnum] : DemoEntity.demoEnum (setter)

at org.hibernate.property.access.spi.SetterFieldImpl.set(SetterFieldImpl.java:86) at org.hibernate.property.access.spi.EnhancedSetterImpl.set(EnhancedSetterImpl.java:40) at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:4155) at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeEntityInstance(AbstractEntityInitializer.java:844) at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeEntity(AbstractEntityInitializer.java:804) at org.hibernate.sql.results.graph.entity.AbstractEntityInitializer.initializeInstance(AbstractEntityInitializer.java:790)

We also converted the project into Java and get the same issue.

Java: https://github.com/neukunft/pg-issue-java/tree/main Kotlin: https://github.com/neukunft/pg-issue

Any suggestions highly appreceated.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 36 (14 by maintainers)

Commits related to this issue

Most upvoted comments

FWIW forks, here is the test:

package io.hypersistence.utils.hibernate.util;

import static junit.framework.TestCase.assertEquals;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import org.junit.Test;

public class ReflectionUtilsClassLoaderTest {

    @Test
    public void testGetClass() {
        byte[] appClassBytes = getClassBytes(AppClass.class);

        HashMap<String, byte[]> customClasses = new HashMap<>();
        customClasses.put(AppClass.class.getName(), appClassBytes);
        CustomClassLoader cl1 = new CustomClassLoader(this.getClass().getClassLoader(), customClasses);
        CustomClassLoader cl2 = new CustomClassLoader(this.getClass().getClassLoader(), customClasses);

        tryLoadClass(AppClass.class, cl1);
        tryLoadClass(AppClass.class, cl2);

        withNewTCCL(cl1, () -> {
            Class<?> c = ReflectionUtils.getClassOrNull(AppClass.class.getName());
            assertEquals(c.getClassLoader(), cl1);
        });
        withNewTCCL(cl2, () -> {
            Class<?> c = ReflectionUtils.getClassOrNull(AppClass.class.getName());
            assertEquals(c.getClassLoader(), cl2); // this will FAIL
        });
    }

    private byte[] getClassBytes(Class<?> clazz) {
        try {
            return this.getClass().getClassLoader().getResourceAsStream(clazz.getName().replace('.', '/') + ".class").readAllBytes();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private void tryLoadClass(Class<AppClass> clazz, CustomClassLoader classLoader) {
        try {
            Class<?> aClass = classLoader.loadClass(clazz.getName());
            assertEquals(aClass.getClassLoader(), classLoader); // ensure that our classloader loaded the class instead of its parent
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private void withNewTCCL(ClassLoader cl, Runnable runnable) {
        ClassLoader old =
                Thread.currentThread().getContextClassLoader();

        try {
            Thread.currentThread().setContextClassLoader(cl);
            runnable.run();
        } finally {
            Thread.currentThread().setContextClassLoader(old); // restore the previous TCCL
        }
    }

    public static class CustomClassLoader extends ClassLoader {

        private final Map<String, byte[]> customClasses;

        private static final AtomicInteger incr = new AtomicInteger(1);
        public CustomClassLoader(ClassLoader parent, Map<String, byte[]> customClasses) {
            super(parent);
            this.customClasses = customClasses;
        }

        @Override
        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> ex = findLoadedClass(name);
            if (ex != null) {
                return ex;
            }
            if (customClasses.containsKey(name)) {
                return findClass(name);
            }
            return super.loadClass(name, resolve);
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] bytes = customClasses.get(name);
            if (bytes == null) {
                throw new ClassNotFoundException();
            }
            return defineClass(name, bytes, 0, bytes.length);
        }

        @Override
        public String toString() {
            return "CustomClassLoader" + incr.getAndIncrement();
        }
    }

    public static class AppClass {

    }
}

The outcome of running this test is:

junit.framework.AssertionFailedError: 
Expected :CustomClassLoader1
Actual   :CustomClassLoader2

P.S. I thought the test worked on Java 8 but it doesn’t because readAllBytes() was only added in 9. Oh well, it can be easily updated 😃

Fixed.

After inspecting the ClassUtils in Spring, I noticed that they only cache the most common Java classes while leaving the rest uncached.

Since the ones we load are very specific, the number of classes loaded via Reflection will not be very high, so the cache will probably have very little impact, even on Java 8.

@neukunft The artifact was in staging mode in Nexus and I released it now. It should be available in a couple of hours in Maven Central.

👌

That’s highly debatable (apart for the questionable performance benefit, you’re opening yourself up to potential ClassLoader leaks).

Anyway, I demonstrated the problem is clearly with the library (and of course it’s a problem that can easily happen to anyone) so up to you folks how and if you want to handle it.

Providing a test case would be the first step to validate the problem.