-
Notifications
You must be signed in to change notification settings - Fork 678
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 support for Kotlin Value Classes #1947
Comments
Mark Paluch commented I switched this ticket from a bug report to a new feature because inline classes weren't there when we built support for Kotlin. Let's see whether and how we can support this type of classes |
Ben Madore commented Just as another data point, ran into this same issue today, (should have searched here first instead of google!). Would love if this worked |
Mark Paluch commented Classes, that use Kotlin inline classes in a constructor receive a synthetic The newly introduced Kotlin behavior has two effects:
Basically, if |
Ben Madore commented I wonder if it's worth opening up a ticket against kotlin to provide this as feedback on their Inline Class implementation which is still "experimental" and thus should be possible to change? I can open up a ticket there referencing this - though i suppose the Spring team might have more direct channels of communication there? |
Ben Madore commented Opened https://youtrack.jetbrains.com/issue/KT-36320 with a link back to this issue |
gaerfield commented Possible intermediate workaround: https://stackoverflow.com/a/60652012/5029667 |
NB: I'm not using I'm seeing something similar to this on a brand new, greenfield project using Kotlin The offending
(fyi, Any known workarounds here? |
Answered my own question, thanks to #2215 (comment). I was using unsigned types. Changing them to |
A few things come together with inline classes: Synthetic constructors that do not follow any known scheme and property accessors with rewritten names. I think one could provide bean introspectors/bean descriptor factories to address specifics of how Kotlin classes using inline/unsigned are compiled by Kotlin. For the constructor issue, it's difficult to find a reasonable approach. |
@mp911de Sorry for the delay. Inline class constructors are not supposed to be invoked directly, and that's by design. There's a public static method |
The issue is that the entire instantiation mechanism in Spring Data relies solely on constructors. We discover a persistence constructor, invoke it via reflection and have code to generate bytecode to call constructors via bytecode. Switching to factory methods requires a significant rewrite and, what's more important, we need to adopt a different concept. |
This also affects Caused by: java.lang.IllegalStateException: Required property null not found for class org.example.DbEntity
at org.springframework.data.mapping.PersistentEntity.getRequiredPersistentProperty(PersistentEntity.java:190)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter$RowParameterValueProvider.getParameterValue(MappingR2dbcConverter.java:731)
at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:53)
at org.springframework.data.relational.core.conversion.BasicRelationalConverter$ConvertingParameterValueProvider.getParameterValue(BasicRelationalConverter.java:293)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:313)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:285)
at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:102)
at org.springframework.data.relational.core.conversion.BasicRelationalConverter.createInstance(BasicRelationalConverter.java:149)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.createInstance(MappingR2dbcConverter.java:330)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:127)
at org.springframework.data.r2dbc.convert.MappingR2dbcConverter.read(MappingR2dbcConverter.java:122)
at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:46)
at org.springframework.data.r2dbc.convert.EntityRowMapper.apply(EntityRowMapper.java:29)
at io.r2dbc.postgresql.PostgresqlResult.lambda$map$2(PostgresqlResult.java:123)
is it required to create a bug there as well? |
Hi, i am user using spring data with kotlin. |
As mentioned the original constructor is still present but marked
So, since the newly created ctor is KFunction<T> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(type.getType()));
Constructor<T> javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor);
if(javaConstructor.isSynthetic()) {
Class<?>[] parameterTypes = javaConstructor.getParameterTypes();
if(parameterTypes.length > 0 && parameterTypes[parameterTypes.length-1] == DefaultConstructorMarker.class) {
try {
Class[] classes = Arrays.stream(parameterTypes).limit(javaConstructor.getParameters().length - 1).toArray(Class[]::new);
Constructor<T> declaredConstructor = type.getType().getDeclaredConstructor(classes);
return Discoverers.buildPreferredConstructor(declaredConstructor, type, entity);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
} |
I will ask a feedback from Kotlin team on that. |
@christophstrobl Probably not based on @udalov comment above. @elizarov provided more insights
|
@sdeleuze, @elizarov this is true for the Using the types from the original sample mentioned here. inline class AccountId(val id: String)
data class AccountNameCheck(val accountId: AccountId, val accountName: String, val id: String) For the
That's fine so far. The problem is with the
Also the above has no Comparing the above to an class AccountId(val id: String)
data class AccountNameCheck(val accountId: AccountId, val accountName: String, val id: String)
|
I see. I've misidentified your current problem. I believe we have a corresponding issue. Let me find it and see if we can raise its priority. I'll get back to you. |
Let me explain the logic behind the behavior for constructors accepting inline classes. An inline class may encapsulate arbitrary code and checks in its constructor. For example, the aforementined
The corresponding constructor logic gets compiled into
If the constructor of This constructor is marked as synthetic so that it is not accessible from Java sources. It has an additional parameter, so that it does not conflict with any signature of any constructor that a Kotlin class can potentially define. It is perfectly available via Java reflection. All you have to do is to call this synthetic constructor, passing |
Thank you @elizarov for the detailed explanation! Is there a way to map the constructor parameter names from the koltin type to the java? Let me explain what we're doing right now. KFunction<?> primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(AccountNameCheck.class));
Constructor<?> javaConstructor = ReflectJvmMapping.getJavaConstructor(primaryConstructor); which returns So in order to access the original parameter names we go ahead and obtain the koltin function via So maybe the way we try to resolve parameter names needs an update to cover the mismatch, and either assign the java parameter name |
christophstrobl You can safely ignore the |
Wyatt Smith opened DATACMNS-1517 and commented
Here is an example with the bug: https://github.com/wyattjsmith1/SpringDataBug
When this runs, there is an
IndexOutOfBoundsException
. This is caused by kotlin's synthetic constructor. I believe the issues is that synthetic constructors aren't filtered before.buildPreferredConstructor
atorg.springframework.data.mapping.model.PreferredConstructorDiscoverer.Discoverers#discover
, but there is probably a better solution to this.Changing AccountId in the data class to a
String
causes the application to work properly. Admittedly, inline classes are still experimental for Kotlin, but this might be worth investigatingIssue Links:
7 votes, 10 watchers
The text was updated successfully, but these errors were encountered: