Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
<version.lib.h2>2.2.220</version.lib.h2>
<version.lib.hamcrest>1.3</version.lib.hamcrest>
<version.lib.handlebars>4.4.0</version.lib.handlebars>
<version.lib.hibernate.family>6.3</version.lib.hibernate.family>
<version.lib.hibernate>${version.lib.hibernate.family}.1.Final</version.lib.hibernate>
<version.lib.hibernate.family>6.6</version.lib.hibernate.family>
<version.lib.hibernate>${version.lib.hibernate.family}.29.Final</version.lib.hibernate>
<version.lib.hibernate-validator>8.0.2.Final</version.lib.hibernate-validator>
<version.lib.hikaricp>5.0.1</version.lib.hikaricp>
<version.lib.hystrix>1.5.18</version.lib.hystrix>
Expand Down
5 changes: 5 additions & 0 deletions integrations/cdi/hibernate-cdi/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@
<artifactId>svm</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<scope>provided</scope>
</dependency>

<!-- Compile-scoped dependencies. -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.integrations.cdi.hibernate;

import java.util.Optional;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;

@TargetClass(className = "org.hibernate.bytecode.enhance.internal.bytebuddy.ByteBuddyEnhancementContext")
@SuppressWarnings("checkstyle:StaticVariableName")
final class ByteBuddyEnhancementContext {

@Alias
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)
private static ElementMatcher.Junction<MethodDescription> IS_GETTER;

@Substitute
Optional<MethodDescription> resolveGetter(FieldDescription fieldDescription) {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.integrations.cdi.hibernate;

import java.util.Collection;
import java.util.function.BiFunction;

import com.oracle.svm.core.annotate.Alias;
import com.oracle.svm.core.annotate.RecomputeFieldValue;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;


@TargetClass(className = "org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper")
@SuppressWarnings("checkstyle:StaticVariableName")
final class ByteBuddyProxyHelper {

@Alias
@RecomputeFieldValue(kind = RecomputeFieldValue.Kind.FromAlias)
private static TypeDescription OBJECT;

@Substitute
private BiFunction<ByteBuddy, NamingStrategy, DynamicType.Builder<?>> proxyBuilder(TypeDefinition persistentClass,
Collection<? extends TypeDefinition> interfaces) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
* Copyright (c) 2024, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,11 +16,13 @@

package io.helidon.integrations.cdi.hibernate;

import java.util.Map;
import java.util.function.Predicate;

import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import org.hibernate.bytecode.spi.BytecodeProvider;
import org.hibernate.service.spi.ServiceRegistryImplementor;

/**
* In native image, we force the usage of the no-op bytecode provider so no bytecode
Expand All @@ -34,17 +36,23 @@ private BytecodeProviderInitiator() {
}

@Substitute
public static BytecodeProvider buildBytecodeProvider(String providerName) {
public static BytecodeProvider buildDefaultBytecodeProvider() {
return new org.hibernate.bytecode.internal.none.BytecodeProviderImpl();
}

@Substitute
public BytecodeProvider initiateService(Map<String, Object> configurationValues, ServiceRegistryImplementor registry) {
return buildDefaultBytecodeProvider();
}

static class SubstituteOnlyIfPresent implements Predicate<String> {

@Override
public boolean test(String type) {
try {
Class<?> clazz = Class.forName(type, false, getClass().getClassLoader());
clazz.getDeclaredMethod("buildBytecodeProvider", String.class);
clazz.getDeclaredMethod("buildDefaultBytecodeProvider");
clazz.getDeclaredMethod("initiateService", Map.class, ServiceRegistryImplementor.class);
return true;
} catch (ClassNotFoundException | NoClassDefFoundError | NoSuchMethodException ex) {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
requires transitive jakarta.inject;
requires jakarta.persistence;
requires transitive jakarta.transaction;
requires transitive net.bytebuddy;
requires org.graalvm.nativeimage;
requires transitive org.hibernate.orm.core;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright (c) 2024 Oracle and/or its affiliates.
# Copyright (c) 2024, 2025 Oracle and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -14,4 +14,6 @@
# limitations under the License.
#

Args=-H:ReflectionConfigurationResources=${.}/reflect-config-additional.json
Args=-H:ReflectionConfigurationResources=${.}/reflect-config-additional.json \
-H:ClassInitialization=org.hibernate.bytecode.enhance.internal.bytebuddy.ByteBuddyEnhancementContext:run_time \
-H:ClassInitialization=org.hibernate.proxy.pojo.bytebuddy.ByteBuddyProxyHelper:run_time
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concern would be that this is going to fail at runtime. Just hiding / postponing problems.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use static enhancement, so bytebuddy related things should not be executed. This args allows me to replace the static fields that are triggering reflection.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand - but the compiler is usually right, it's running a formal proof. So if it's including it, this would suggest that there are some corner cases in which this code is necessary.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding --trace-class-initialization only shows the stack traces I printed here. It starts with ByteBuddyEnhancementContext and ByteBuddyProxyHelper. We cannot know more about why are that static fields loaded.

We already have a lot of GraalVM configuration related to Hibernate here and some other methods substitutions here. I was not part of this, but it seems this integration was not easy.

Original file line number Diff line number Diff line change
Expand Up @@ -2147,5 +2147,13 @@
]
}
]
},
{
"name": "org.hibernate.event.spi.PostUpsertEventListener[]",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jbescos such metadata should be unnecessary if you use the Hibernate provided GraalVM module?
-https://github.com/hibernate/hibernate-orm/blame/main/hibernate-graalvm/src/main/java/org/hibernate/graalvm/internal/StaticClassLists.java#L68

I created that module to simplify such integrations by other frameworks, please let us know if it's not useful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file we tell GraalVM what classes should be available for reflection at runtime. I added that because GraalVM told me to do so.

We don't have org.hibernate.orm:hibernate-graalvm as dependency, but I have added it and the test fails, with and without this configuration. It compiles well, but in runtime I am having some ClassNotFoundExceptions.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this file we tell GraalVM what classes should be available for reflection at runtime. I added that because GraalVM told me to do so.

I understand, but it makes more sense to have the rules which are specific to a library to be maintained by the library - otherwise you'll need to make changes for each new Hibernate release.
So for that reason I had created the hibernate-graalvm module.

We don't have org.hibernate.orm:hibernate-graalvm as dependency, but I have added it and the test fails, with and without this configuration.

Such features need to be activated

"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allDeclaredFields": true,
"unsafeAllocated": true
}
]
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023 Oracle and/or its affiliates.
* Copyright (c) 2019, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -292,9 +292,9 @@ void testJpaTransactionScopedSynchronizedEntityManager()
assertThat(transactionScopedContext.isActive(), is(true));

// Remove the Author we successfully committed before. We
// have to merge because author1 became detached a few lines
// above.
author1 = em.merge(author1);
// have to find because author1 became detached a few lines
// above and it is not allowed to attach it again with merge.
author1 = em.find(Author.class, Integer.valueOf(1));
assertThat(author1, notNullValue());
assertThat(em.contains(author1), is(true));
em.remove(author1);
Expand All @@ -313,13 +313,15 @@ void testJpaTransactionScopedSynchronizedEntityManager()
// tables.
assertTableRowCount(dataSource, "AUTHOR", 0);

// Start a new transaction, merge our detached author1, and
// Start a new transaction, persist our detached author1, and
// commit. This will bump the author's ID and put a row in
// the database.
tm.begin();
assertThat(em.isJoinedToTransaction(), is(true));
assertThat(transactionScopedContext.isActive(), is(true));
author1 = em.merge(author1);
author1 = new Author("Abraham Lincoln");
em.persist(author1);
em.merge(author1);
tm.commit();
assertThat(em.isJoinedToTransaction(), is(false));
assertThat(em.contains(author1), is(false));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2023 Oracle and/or its affiliates.
* Copyright (c) 2019, 2025 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -276,6 +276,8 @@ void testRollbackScenarios()
assertThat(tm.getStatus(), is(Status.STATUS_ACTIVE));
assertThat(em.isJoinedToTransaction(), is(true));
assertThat(em.contains(author), is(false));
author = new Author("John Kennedy");
em.persist(author);
author = em.merge(author);
em.remove(author);
tm.commit();
Expand Down