Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add native support for @Convert on JPA entities #29771

Closed
NicklasWallgren opened this issue Jan 4, 2023 · 7 comments
Closed

Add native support for @Convert on JPA entities #29771

NicklasWallgren opened this issue Jan 4, 2023 · 7 comments
Assignees
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Milestone

Comments

@NicklasWallgren
Copy link

NicklasWallgren commented Jan 4, 2023

I've run into an issue with the jakarta.persistence.Convert annotation with GraalVM and Spring Native, see the stacktrace below.

I'm a bit unsure if this issue should be reported here, with the Hibernate project or elsewhere .

Could not resolve matching constructor on bean class [.....EmailConverter] (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1751) ~[demo:6.0.3]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:599) ~[demo:6.0.3]
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:521) ~[demo:6.0.3]
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[demo:6.0.3]
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[demo:6.0.3]
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[demo:6.0.3]

The Converter is implemented as shown below and is applied via @Convert(converter = ....).
The issue does not present itself if I remove the @Convert and instead auto apply it via @Converter(autoApply = true).

@Column(name = "email", columnDefinition = "varchar(255) not null", nullable = false)
@Convert(converter = EmailConverter.class)
@NaturalId
private Email email;
public final class EmailConverter implements AttributeConverter<Email, String> {
    public EmailConverter() {}

    @Override
    public String convertToDatabaseColumn(final Email attribute) {
        return attribute.getValue();
    }

    @Override
    public Email convertToEntityAttribute(final String dbData) {
        return new Email(dbData);
    }

}

You can reproduce the issue with https://github.com/NicklasWallgren/spring-boot-native-issue

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 4, 2023
@wilkinsona
Copy link
Member

Thanks for the sample. Unfortunately, it doesn't compile:

./gradlew nativeCompile  
Starting a Gradle Daemon, 3 incompatible Daemons could not be reused, use --status for details

> Task :compileJava FAILED
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:10: error: cannot find symbol
public final class Email extends ValueObject<String> {
                                 ^
  symbol: class ValueObject
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:30: error: cannot find symbol
        if (value != null) {
            ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:31: error: cannot find symbol
            return value;
                   ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:37: error: method does not override or implement a method from a supertype
    @Override
    ^
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:40: error: cannot find symbol
            throw ValidationErrorException.of(ValidationError.of("invalid email address", value, "email"));
                                                                                          ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:50: error: cannot find symbol
        if (value == null) {
            ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:54: error: cannot find symbol
        final int splitPosition = value.lastIndexOf('@');
                                  ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:59: error: cannot find symbol
        final String localPart = value.substring(0, splitPosition);
                                 ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/Email.java:60: error: cannot find symbol
        final String domainPart = value.substring(splitPosition + 1);
                                  ^
  symbol:   variable value
  location: class Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/EmailConverter.java:19: error: cannot find symbol
        return attribute.getValue();
                        ^
  symbol:   method getValue()
  location: variable attribute of type Email
/Users/awilkinson/dev/temp/spring-boot-native-issue/src/main/java/com/example/demo/EmailConverter.java:26: error: constructor Email in class Email cannot be applied to given types;
        return new Email(dbData);
               ^
  required: String
  found:    String
  reason: Email(String) has private access in Email
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
11 errors

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':compileJava'.
> Compilation failed; see the compiler error output for details.

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 13s
1 actionable task: 1 executed

@wilkinsona wilkinsona added the status: waiting-for-feedback We need additional information before we can continue label Jan 5, 2023
@NicklasWallgren
Copy link
Author

NicklasWallgren commented Jan 5, 2023

@wilkinsona Sorry, I forgot to push the latest changes. It should compile now.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 5, 2023
@wilkinsona
Copy link
Member

Running the app with debug logging enabled shows that both SpringBeanContainer and Hibernate itself fail to create an instance of EmailConverter:

2023-01-05T13:14:14.868Z DEBUG 50100 --- [       Thread-1] o.s.orm.hibernate5.SpringBeanContainer   : Falling back to Hibernate's default producer after bean creation failure for class com.example.demo.EmailConverter: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.example.demo.EmailConverter': Could not resolve matching constructor on bean class [com.example.demo.EmailConverter] (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities)
2023-01-05T13:14:14.868Z DEBUG 50100 --- [       Thread-1] o.s.orm.hibernate5.SpringBeanContainer   : Fallback producer failed for class com.example.demo.EmailConverter: org.hibernate.InstantiationException: Could not instantiate managed bean directly : com.example.demo.EmailConverter

This is due to a lack of reflection hints which means that the converter class cannot be created reflectively in a native image. You could work around the problem by using @ImportRuntimeHints to import a RuntimeHintsRegistrar implementation that registers EmailConverter allowing invocation of its public constructors. We can also look into doing this automatically.

@wilkinsona wilkinsona removed the status: feedback-provided Feedback has been provided label Jan 5, 2023
@NicklasWallgren
Copy link
Author

@wilkinsona Thanks for the clarification. I will look in to RuntimeHintsRegistrar.

@bclozel bclozel transferred this issue from spring-projects/spring-boot Jan 5, 2023
@wilkinsona
Copy link
Member

wilkinsona commented Jan 5, 2023

As discussed with @sdeleuze, PersistenceManagedTypesBeanRegistrationAotProcessor looks like a logical place for us to deal with this. We've transferred this to the Framework repository (thanks, Brian!) for their further consideration.

@sdeleuze sdeleuze self-assigned this Jan 5, 2023
@sdeleuze sdeleuze added in: data Issues in data modules (jdbc, orm, oxm, tx) theme: aot An issue related to Ahead-of-time processing and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 5, 2023
@sdeleuze sdeleuze added this to the 6.0.4 milestone Jan 5, 2023
@wilkinsona
Copy link
Member

@NicklasWallgren Your sample app starts when this change is applied to its main class:

diff --git a/src/main/java/com/example/demo/DemoApplication.java b/src/main/java/com/example/demo/DemoApplication.java
index 64b538a..76b9b00 100644
--- a/src/main/java/com/example/demo/DemoApplication.java
+++ b/src/main/java/com/example/demo/DemoApplication.java
@@ -1,13 +1,29 @@
 package com.example.demo;
 
+import org.springframework.aot.hint.MemberCategory;
+import org.springframework.aot.hint.RuntimeHints;
+import org.springframework.aot.hint.RuntimeHintsRegistrar;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ImportRuntimeHints;
+
+import com.example.demo.DemoApplication.ConverterRuntimeHints;
 
 @SpringBootApplication
+@ImportRuntimeHints(ConverterRuntimeHints.class)
 public class DemoApplication {
 
        public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
        }
 
+       static class ConverterRuntimeHints implements RuntimeHintsRegistrar {
+
+               @Override
+               public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+                       hints.reflection().registerType(EmailConverter.class, MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS);
+               }
+
+       }
+
 }

@NicklasWallgren
Copy link
Author

@wilkinsona Great, thanks for the help!

@sdeleuze sdeleuze changed the title [Native] jakarta.persistence.Convert - Could not resolve matching constructor on bean class, Add native support for @Convert on JPA entities Jan 6, 2023
@sdeleuze sdeleuze added the type: enhancement A general enhancement label Jan 6, 2023
mdeinum pushed a commit to mdeinum/spring-framework that referenced this issue Jun 29, 2023
This commit infers the reflection hints required for converters
when they are specified with the @convert annotation at class or
field level.

It also refines the hints generated for @converter in order
to just generate the construct hint, not the public method one
which should be not needed.

Closes spring-projectsgh-29771
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) theme: aot An issue related to Ahead-of-time processing type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

4 participants