【问题标题】:Spring-data 2.1 get "UnsupportedOperationException: No accessor to set property" with kotlinSpring-data 2.1 使用 kotlin 获取“UnsupportedOperationException: No accessor to set property”
【发布时间】:2019-01-20 06:51:47
【问题描述】:

环境: 春季启动 2.1.2.RELEASE, 弹簧数据2.1.4.RELEASE, 科特林 1.2.x ~ 1.3.x, MongoDB。

我定义了一个模型类,如:

@Document
class MeAccount : Setting() {

    lateinit var id: String

    val accountEntries = listOf<BankAccount>()
 }

当我试图从 mongodb 读取这个模型时,我得到了异常堆栈跟踪:

java.lang.UnsupportedOperationException: No accessor to set property private final java.util.List com.xxx.MeCustodianAccount.accountEntries!
    at com.xxx.MeCustodianAccount_Accessor_fs514j.setProperty(Unknown Source)
    at org.springframework.data.mapping.model.ConvertingPropertyAccessor.setProperty(ConvertingPropertyAccessor.java:61)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readProperties(MappingMongoConverter.java:378)

请注意,该代码适用于 spring-boot 1.5.x 和 spring-data 1.x。

我能做些什么来解决这个问题? 下面似乎有很多类似的异常报告:

Spring boot 2.1.0 security change with kotlin data class?

https://github.com/arangodb/spring-data/issues/123

https://github.com/spring-projects/spring-boot/issues/15698

[已解决] 回退到 Spring-boot 2.0.x 和 spring-data-commons 2.0.x 后工作。 将在未来的升级计划中排除 2.1。

【问题讨论】:

  • val 属性没有设置器。将其更改为var
  • @Strelok 这是完全不能接受的,val 是我在 kotlin 中令人钦佩的特性。该代码适用于 spring-data 1.12.x。
  • Welp,对不起,但这是事实。主体中的 Vals 需要在 init 块、构造函数或内联中​​初始化。他们没有二传手
  • 您是否考虑过将其设为data 类并将属性移动到构造函数中?正如你现在所拥有的,没有任何东西(包括 Spring Data MongoDB)可以设置 accountEntries 的值。
  • 正如我所说,'val' 是我们最想要的 kotlin 功能,而代码失败是因为 spring-data 2.1.x 破坏了兼容性。 @Mark B 该代码在以前的 spring-data 版本中运行良好。

标签: mongodb kotlin spring-data spring-data-mongodb


【解决方案1】:

2.1 中的 Spring 数据。改变了它处理实体中最终字段的方式。它不再使用反射来覆盖字段的不变性,这通常是好的。有几种方法可以解决这个问题。

他们在这里描述:https://jira.spring.io/browse/DATACMNS-1374?focusedCommentId=182289&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-182289

这是 Spring 小伙伴们推荐的:

  1. 添加一个@PersistenceConstructor 来构造设置不可变字段的实体。
  2. 添加 wither 方法 (MyEntity withXxx(...)) 以创建包含更改的属性值的新实例。
  3. 或者:使用 Kotlin 的数据类功能。这基本上和枯萎方法一样。

所以你应该像这样工作:

@Document
data class MeAccount(val id: String, val accountEntries: List<Price>) : Setting()

【讨论】:

  • 仅添加 @PersistenceConstructor 会导致我出错。使用 Lombok 的 @With(第 2 条)有效。
【解决方案2】:

只是为了记录,因为我没有找到关于 apache cassandra 的 spring 数据的相同问题,并且文档建议仅使用 @Value 来避免 DTO 上 @Data 的不可变变化的样板,但是按照该示例 spring 抛出 UnsupportedOperationException,以解决这个 dto 必须有 lombok 实验性 @Wither 才能工作。

存储库

<T> List<T> findByActive(Class<T> class);

DTO

@Value
public class PersonDTO {

    private UUID id;
    private Boolean active;
}

测试 - 调用

    List<PersonDTO> personList  = personService.findByActive(PersonDTO.class);

g

java.lang.UnsupportedOperationException: No accessor to set property private final java.lang.Boolean com.x.dto.PersonDTO.active!
    at com.x.dto.PersonDTO_Accessor_y82uew.setProperty(Unknown Source)
    at org.springframework.data.cassandra.repository.query.DtoInstantiatingConverter.lambda$convert$0(DtoInstantiatingConverter.java:107)
    at org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:369)
    at org.springframework.data.cassandra.repository.query.DtoInstantiatingConverter.convert(DtoInstantiatingConverter.java:101)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.lambda$and$0(ResultProcessor.java:210)
    at org.springframework.data.repository.query.ResultProcessor$ChainingConverter.convert(ResultProcessor.java:224)
    at org.springframework.data.repository.query.ResultProcessor.processResult(ResultProcessor.java:152)
    at org.springframework.data.cassandra.repository.query.CassandraQueryExecution$ResultProcessingConverter.convert(CassandraQueryExecution.java:267)
    at org.springframework.data.cassandra.repository.query.CassandraQueryExecution$ResultProcessingExecution.execute(CassandraQueryExecution.java:232)
    at org.springframework.data.cassandra.repository.query.AbstractCassandraQuery.execute(AbstractCassandraQuery.java:103)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    at com.sun.proxy.$Proxy145.findByActive(Unknown Source)
    at com.x.service.impl.PersonServiceImpl.findByActive(PersonServiceImpl.java:22)
    at com.x.PersonRepositoryTest.findByActive(PersonRepositoryTest.java:78)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

Spring Data for Apache Cassandra - Reference Documentation

避免投影 DTO 的样板代码 使用 Project Lombok 简化 DTO 的代码,它提供了一个 @Value 注解(不要与 Spring 的 @Value 注解混淆) 如前面的接口示例所示)。如果您使用 Project Lombok 的 @Value 注释,前面显示的示例 DTO 将成为 以下:

@Value
@Wither //added - not included in documentation
class NamesOnly {
    String firstname, lastname;
}

动态投影到目前为止,我们使用投影类型作为 集合的返回类型或元素类型。但是,您可能想要 选择要在调用时使用的类型(这使得它 动态的)。要应用动态投影,请使用查询方法,例如 如下例所示:

示例 86. 使用动态投影参数的存储库

interface PersonRepository extends Repository<Person, UUID> {

  <T> Collection<T> findByLastname(String lastname, Class<T> type);
}

【讨论】:

    猜你喜欢
    • 2018-04-28
    • 2018-04-13
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 2015-07-16
    • 1970-01-01
    • 2018-08-22
    • 2014-09-09
    相关资源
    最近更新 更多