Сохранение встроенного класса Kotlin в MongoDB с данными Spring

Я пытаюсь сохранить встроенный класс Kotlin (v1.3.61) в MongoDB, используя Spring Data MongoDB (2.2.3-RELEASE), но пока безуспешно. Это настройка:

inline class UserId(@NotBlank val id: String) 

а также

@Document(collection = "data")
class Data(
  @Field("uid")
  val userId: UserId
)

Spring выдает следующее исключение при создании bean-компонентов:

Вызвано: java.lang.ArrayIndexOutOfBoundsException: 1 в org.springframework.data.mapping.model.PreferredConstructorDiscoverer$Discoverers.buildPreferredConstructor(PreferredConstructorDiscoverer.java:221) ~[spring-data.-commons-.3.RELEASE] в org.springframework.data.mapping.model.PreferredConstructorDiscoverer$Discoverers.access$200(PreferredConstructorDiscoverer.java:89) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mapping.model.PreferredConstructorDiscoverer$Discoverers$2.lambda$discover$0(PreferredConstructorDiscoverer.java:161) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) ~[na:1.8.0_171] в java.util.Spliterators$ArraySpliterator.tryAdvance(Spliterators.java:958) ~[na:1.8.0_171] в java.util.stream.ReferencePipeline.forEachWithCancel(ReferencePipeline.java:126) ~[na:1.8.0_171] в java.util.stream.AbstractPipeline.copyIntoWithCancel(AbstractPipeline.java:498) ~[na:1.8.0_171] в java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485) ~[na:1.8.0_171] в java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_171] в java.util.stream.FindOps$FindOp.evaluateSequential(FindOps.java:152) ~[na:1.8.0_171] в java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_171] в java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:464) ~[na:1.8.0_171] в org.springframework.data.mapping.model.PreferredConstructorDiscoverer$Discoverers$2.discover(PreferredConstructorDiscoverer.java:164) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mapping.model.PreferredConstructorDiscoverer.discover(PreferredConstructorDiscoverer.java:77) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mapping.model.BasicPersistentEntity.(BasicPersistentEntity.java:105) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity.(BasicMongoPersistentEntity.java:74) ~[spring-data-mongodb-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mongodb.core.mapping.MongoMappingContext.createPersistentEntity(MongoMappingContext.java:91) ~[spring-data-mongodb-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mongodb.core.mapping.MongoMappingContext.createPersistentEntity(MongoMappingContext.java:39) ~[spring-data-mongodb-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java) ~ [spring-data-commons-2.2.3.RELEASE.jar: 2.2.3.RELEASE] в org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:323) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_171] в org.springframework.data.mapping.context.AbstractMappingContext.initialize(AbstractMappingContext.java:452) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.data.mapping.context.AbstractMappingContext.afterPropertiesSet(AbstractMappingContext.:444) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE] в org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java: beans. -5.2.2.RELEASE.jar: 5.2.2.RELEASE] в org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean (AbstractAutowireCapableBeanFactory.java:1792.java:1792.arpring-beansE-5.2.2). 5.2.2.RELEASE]... 151 общий кадр пропущен java:1792) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE] ... 151 общий фрейм опущен java:1792) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE] ... 151 общий фрейм опущен

Трассировка стека довольно неясна, но исключение происходит в PreferredConstructorDiscoverer и userId является частью конструктора, поэтому проблема может быть там.

Следующим шагом была проверка байтового кода Data класс:

// ================net/test/Data.class
================= // class version 50.0 (50) // access flags 0x31 
public final class net/test/Data {


  @Lorg/springframework/data/mongodb/core/mapping/Document;(collection="data")

  // access flags 0x12   private final Ljava/lang/String; userId   @Lorg/springframework/data/mongodb/core/mapping/Field;(value="uid")   @Lorg/jetbrains/annotations/NotNull;() // invisible

  // access flags 0x11   public final getUserId()Ljava/lang/String;   @Lorg/jetbrains/annotations/NotNull;() // invisible    L0
    LINENUMBER 10 L0
    ALOAD 0
    GETFIELD net/test/Data.userId : Ljava/lang/String;
    ARETURN    L1
    LOCALVARIABLE this Lnet/test/Data; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x2   private <init>(Ljava/lang/String;)V    L0
    LINENUMBER 7 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ALOAD 0
    ALOAD 1
    PUTFIELD net/test/Data.userId : Ljava/lang/String;
    RETURN    L1
    LOCALVARIABLE this Lnet/test/Data; L0 L1 0
    LOCALVARIABLE userId Ljava/lang/String; L0 L1 1
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1001   public synthetic <init>(Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
    // annotable parameter count: 2 (visible)
    // annotable parameter count: 2 (invisible)    L0
    LINENUMBER 7 L0
    ALOAD 0
    ALOAD 1
    INVOKESPECIAL net/test/Data.<init> (Ljava/lang/String;)V
    RETURN    L1
    LOCALVARIABLE this Lnet/test/Data; L0 L1 0
    LOCALVARIABLE userId Ljava/lang/String; L0 L1 1
    LOCALVARIABLE $constructor_marker Lkotlin/jvm/internal/DefaultConstructorMarker; L0 L1 2
    MAXSTACK = 2
    MAXLOCALS = 3

  @Lkotlin/Metadata;(mv={1, 1, 16}, bv={1, 0, 3}, k=1, d1={"\u0000\u0012\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0018\u0002\n\u0002\u0008\u0005\u0008\u0007\u0018\u00002\u00020\u0001B\u0010\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u00f8\u0001\u0000\u00a2\u0006\u0002\u0010\u0004R\u001b\u0010\u0002\u001a\u00020\u00038\u0006X\u0087\u0004\u00f8\u0001\u0000\u00a2\u0006\n\n\u0002\u0010\u0007\u001a\u0004\u0008\u0005\u0010\u0006\u0082\u0002\u0004\n\u0002\u0008\u0019\u00a8\u0006\u0008"}, d2={"Lnet/test/Data;", "", "userId", "Lnet/test/UserId;", "(Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V", "getUserId", "()Ljava/lang/String;", "Ljava/lang/String;", "core"})   // compiled from: Data.kt }


// ================META-INF/core.kotlin_module =================            

и это соответствующая строка 221 PreferredConstructorDiscoverer из источника Spring:

String name = parameterNames == null ? null : parameterNames[i];

Я полный новичок в понимании байтового кода, но тот или другой может сразу увидеть проблему.

Еще я пробовал реализовать два пользовательских преобразователя типа org.springframework.core.convert.converter.Converter<UserId,String>наоборот. Однако создание экземпляра компонента происходит до того, как может произойти какое-либо преобразование, поэтому это кажется несущественным.

Я знаю, что встроенные классы все еще являются экспериментальными в Kotlin 1.3, но, возможно, проблема в другом. У кого-нибудь есть идея?

Кстати, при смене типа с UserId к String все работает нормально.

2 ответа

Я использую обходной путь, вводя частное поле.

inline class UserId(@NotBlank val id: String = UUID.randomUUID().toString())

@Document(collection = "data")
data class Data(
  @Transient val userId: UserId = UserId()
) {
  @Id private val idData = userId.id
}

Но при таком подходе вам следует перейти от расширения CRUD-интерфейсов к самостоятельной реализации основных операций (иначе ID-класс не имеет смысла):

// introduce some extension functions for easier use with mongo-template
suspend inline fun <T> ReactiveMongoTemplate.saveAndAwait(entity: T) : T =
      save(entity).awaitSingle()
suspend inline fun <reified T> ReactiveMongoTemplate.findById(id: Any) : T = 
      findById(id, T::class.java).awaitSingle()

@ExperimentalCoroutinesApi
@Repository
internal class DataRepository(
        private val template: ReactiveMongoTemplate
) {
    suspend fun save(order: Order) : Order = template.saveAndAwait(order)
    suspend fun findById(orderId: OrderId): Order = template.findById(orderId.id)
}

Стоит ли накладных расходов? Pro:

  • Набранные идентификаторы
  • в Mongo столбец id остается примитивным

Минусы:

  • Репозитории становятся более сложными

Если DATACMNS-1517 будет исправлен когда-нибудь в будущем, адаптация должна быть относительно простой (в основном реализация репозиториев удаляется в пользу расширенных интерфейсов).

Вы видели, как данные Spring JPA выдают исключение java.lang.ArrayIndexOutOfBoundsException: X [Kotlin]? Похоже, для этой проблемы нет подходящего решения. Вот проблема для Spring Data https://jira.spring.io/browse/DATACMNS-1517.

Другие вопросы по тегам