Spring 数据 AWS DynamoDB 存储库查找方式包含抛出 ClassCastException

Spring Data AWS DynamoDB Repository Find By Contains throws ClassCastException

我有一个名为组 Table 的 AWS DynamoDB Table。它包含两个字段 ID 和 Members。成员字段是一个字符串集。我想搜索组 Table 和 return 成员列表包含指定字符串的所有组。

当我尝试使用 Spring 数据的 CRUD 存储库 FindByMembersContains 时,它 return 是一个 ClassCastException,指出 java.lang.String 无法转换为 java.util.Collection。

我也尝试过使用字符串列表而不是字符串集并获得相同的结果。

我正在使用 AWS Java SDK DynamoDB 版本 1.12.192 和 Spring Data DynamoDB 版本 5.1.0。 我使用存储库编写这些条目或通过 ID 读取没有问题。

Group.java

@DynamoDBTable(tableName = "GroupTable")
public class Group {

    String id;
    Set<String> members;

    @DynamoDBHashKey
    @DynamoDBAutoGeneratedKey
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @DynamoDBAttribute
    public Set<String> getMembers() {
        return members;
    }

    public void setMembers(Set<String> members) {
        this.members = members;
    }

}

GroupRepository.java

@EnableScan
public interface GroupRepository extends CrudRepository<Group, String> {

    List<Group> findByMembersContaining(String member);

}

GroupController.java

@RestController
@RequestMapping("/group")
public class GroupController {

    private final GroupRepository groupRepository;

    public GroupController(GroupRepository groupRepository) {
        this.groupRepository = groupRepository;
    }

    @GetMapping("/test")
    public List<Group> getTestGroups() {
        String testMemberId = "SomeMemberId";
        return groupRepository.findByMembersContaining(testMemberId);
    }

}

DynamoDBConfig.java

@Configuration
@EnableDynamoDBRepositories(basePackages = "com.example.dynamodbtest.repository")
public class DynamoDBConfig {

    @Value("${amazon.dynamodb.endpoint}")
    private String amazonDynamoDBEndpoint;

    @Value("${amazon.aws.accesskey}")
    private String amazonAWSAccessKey;

    @Value("${amazon.aws.secretkey}")
    private String amazonAWSSecretKey;

    private final ApplicationContext context;

    public DynamoDBConfig(ApplicationContext context) {
        this.context = context;
    }

    @Bean
    public AmazonDynamoDB amazonDynamoDB() {
        AmazonDynamoDB amazonDynamoDB = new AmazonDynamoDBClient(amazonAWSCredentials());
        if (!ObjectUtils.isEmpty(amazonDynamoDBEndpoint)) {
            amazonDynamoDB.setEndpoint(amazonDynamoDBEndpoint);
        }
        return amazonDynamoDB;
    }

    @Bean
    public AWSCredentials amazonAWSCredentials() {
        return new BasicAWSCredentials(amazonAWSAccessKey, amazonAWSSecretKey);
    }

    @Bean(name = "mvcHandlerMappingIntrospectorCustom")
    public HandlerMappingIntrospector mvcHandlerMappingIntrospectorCustom() {
        HandlerMappingIntrospector handlerMappingIntrospector = new HandlerMappingIntrospector();
        handlerMappingIntrospector.setApplicationContext(context);
        return handlerMappingIntrospector;
    }

}

堆栈跟踪

java.lang.ClassCastException: java.lang.String cannot be cast to java.util.Collection
    at com.amazonaws.services.dynamodbv2.datamodeling.StandardTypeConverters$Vector$ToSet.convert(StandardTypeConverters.java:449) ~[aws-java-sdk-dynamodb-1.12.192.jar:na]
    at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$DelegateConverter.convert(DynamoDBTypeConverter.java:104) ~[aws-java-sdk-dynamodb-1.12.192.jar:na]
    at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$NullSafeConverter.convert(DynamoDBTypeConverter.java:123) ~[aws-java-sdk-dynamodb-1.12.192.jar:na]
    at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTypeConverter$ExtendedConverter.convert(DynamoDBTypeConverter.java:83) ~[aws-java-sdk-dynamodb-1.12.192.jar:na]
    at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.convert(DynamoDBMapperFieldModel.java:138) ~[aws-java-sdk-dynamodb-1.12.192.jar:na]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCriteria.getPropertyAttributeValue(AbstractDynamoDBQueryCriteria.java:499) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCriteria.createSingleValueCondition(AbstractDynamoDBQueryCriteria.java:648) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCriteria.withSingleValueCriteria(AbstractDynamoDBQueryCriteria.java:427) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCreator.addCriteria(AbstractDynamoDBQueryCreator.java:103) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCreator.create(AbstractDynamoDBQueryCreator.java:74) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCreator.create(AbstractDynamoDBQueryCreator.java:42) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:119) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:95) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:81) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.socialsignin.spring.data.dynamodb.repository.query.PartTreeDynamoDBQuery.doCreateQuery(PartTreeDynamoDBQuery.java:56) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQuery.doCreateQueryWithPermissions(AbstractDynamoDBQuery.java:81) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQuery$CollectionExecution.execute(AbstractDynamoDBQuery.java:100) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQuery.execute(AbstractDynamoDBQuery.java:311) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:159) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138) ~[spring-data-commons-2.6.3.jar:2.6.3]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.18.jar:5.3.18]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.18.jar:5.3.18]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.18.jar:5.3.18]
    at com.sun.proxy.$Proxy69.findByMembersContaining(Unknown Source) ~[na:na]
    at com.example.dynamodbtest.controller.GroupController.getTestGroups(GroupController.java:24) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.18.jar:5.3.18]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:655) ~[tomcat-embed-core-9.0.60.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.18.jar:5.3.18]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.60.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.18.jar:5.3.18]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.18.jar:5.3.18]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.18.jar:5.3.18]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.18.jar:5.3.18]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:889) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1743) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.60.jar:9.0.60]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]

AWS DynamoDB 对象 Group Object Showing String Set

“我正在使用 AWS Java SDK DynamoDB 版本 1.12.192”。

您应该使用 AWS SDK for Java V2 并将 Java classes 映射到 DynamoDB table使用 增强客户端 。这是最佳实践,而不是使用旧的 V1 API。请参阅此文档主题:

Using the DynamoDB Enhanced Client in the AWS SDK for Java 2.x

假设我们有一个 Work table,其键名为 id。下图显示了 table.

要映射到此 table,您可以创建一个使用 @DynamoDbBean 注释的 class,如下所示:

 import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
        
            @DynamoDbBean
             public class Work {
        
               private String id;
               private String date;
               private String description ;
               private String guide;
               private String username ;
               private String status  ;
               private String archive   ;
        
               @DynamoDbPartitionKey
               public String getId() {
                 return this.id;
               }
        
               public void setId(String id) {
                this.id = id;
               }
        
               @DynamoDbSortKey
               public String getName() {
                return this.username;
                }
        
               public void setArchive(String archive) {
                this.archive = archive;
                }
        
               public String getArchive() {
                 return this.archive;
                }
        
               public void setStatus(String status) {
                 this.status = status;
               }
        
               public String getStatus() {
                return this.status;
               }
        
              public void setUsername(String username) {
               this.username = username;
              }
        
              public String getUsername() {
               return this.username;
              }
        
              public void setGuide(String guide) {
                this.guide = guide;
            }
        
              public String getGuide() {
                return this.guide;
              }
        
              public String getDate() {
               return this.date;
               }
        
              public void setDate(String date) {
               this.date = date;
              }
        
              public String getDescription() {
               return description;
              }
        
             public void setDescription(String description) {
              this.description = description;
               }
              }

要使用增强客户端执行 CRUD 操作,请创建一个 DynamoDbTable 对象。

// Create a DynamoDbEnhancedClient.
 DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
                .dynamoDbClient(getClient())
                .build();

 // Create a DynamoDbTable object.
 DynamoDbTable<Work> workTable = enhancedClient.table("Work", TableSchema.fromBean(Work.class));

现在您可以执行CRUD 操作了。要将记录放入 table,您可以创建一个 Work 对象并设置数据。然后调用DynamoDbTable对象putItem方法

// Populate the table.
 Work record = new Work();
 record.setUsername(item.getName());
 record.setId(myGuid);
 record.setDescription(item.getDescription());
 record.setDate(now()) ;
 record.setStatus(item.getStatus());
 record.setArchive("Open");
 record.setGuide(item.getGuide());

 // Put the customer data into a DynamoDB table.
  workTable.putItem(record);

Amazon DynamoDB API V2(包括增强客户端)也可以在 Spring BOOT 应用程序中使用。要了解如何构建示例 Spring 引导 Web 应用程序,请参阅此 AWS tutorial

切换到增强型客户端后,我能够执行 'Scan' 和 'Filter Expression' 到 return 我想要的结果。

private DynamoDbClient getClient() {
    Region region = Region.US_EAST_2;
    return DynamoDbClient.builder()
            .region(region)
            .build();
}

private DynamoDbEnhancedClient getEnhancedClient() {
    return DynamoDbEnhancedClient.builder()
            .dynamoDbClient(getClient())
            .build();
}

public List<Group> scanGroupsByMember(String memberId) {

    DynamoDbTable<Group> groupTable = getEnhancedClient().table("GroupTable", TableSchema.fromClass(Group.class));

    AttributeValue attributeValue = AttributeValue.builder()
            .s(memberId)
            .build();

    Map<String, AttributeValue> expressionValues = new HashMap<>();
    expressionValues.put(":value", attributeValue);

    Expression expression = Expression.builder()
            .expression("contains(members, :value)")
            .expressionValues(expressionValues)
            .build();

    ScanEnhancedRequest scanEnhancedRequest = ScanEnhancedRequest.builder()
            .filterExpression(expression)
            .build();

    return groupTable.scan(scanEnhancedRequest).items().stream().collect(Collectors.toList());

}