derjust / spring-data-dynamodb

This module deals with enhanced support for a data access layer built on AWS DynamoDB.
https://derjust.github.io/spring-data-dynamodb/
Apache License 2.0
403 stars 141 forks source link

GSI Queries can fail with NPE when using an overriding DynamoDBMapperConfig #263

Open kjl-dev opened 5 years ago

kjl-dev commented 5 years ago

Querying a Global Secondary Index will fail if using an overriding DynamoDBMapperConfig that doesnt specify a ConversionSchema and TypeConverterFactory.

Expected Behavior

Querying a Global Secondary Index will not fail if using an overriding DynamoDBMapperConfig that doesnt specify a ConversionSchema and TypeConverterFactory (perhaps by using reasonable defaults).

OR

An appropriate exception is thrown indicating an invalid configuration.

Actual Behavior

Querying a Global Secondary Index of a table when using a custom DynamoDBMapperConfig that does not specify a ConversionSchema and TypeConverterFactory fails with a NullPointerException. Searches on the regular table itself execute correctly.

Steps to Reproduce the Problem

  1. Create the DynamoDB beans with a custom DynamoDBMapperConfig
  @Primary
  public DynamoDBMapperConfig dynamoDBMapperConfig() {

    String fullPrefix = tablePrefix + TABLE_DELIMITER + environment + TABLE_DELIMITER;
    TableNameOverride tableNameOverride = TableNameOverride.withTableNamePrefix(fullPrefix);

    return DynamoDBMapperConfig.builder()
        .withTableNameOverride(tableNameOverride)
        .build();
  }

  @Bean
  @Primary
  public DynamoDBMapper dynamoDBMapper(AmazonDynamoDB amazonDynamoDB, DynamoDBMapperConfig config,
      DynamoDBMapper mapper) {
    return new DynamoDBMapper(amazonDynamoDB, config);
  }

  @Bean
  public AmazonDynamoDB amazonDynamoDB() {
    return AmazonDynamoDBClientBuilder
        .standard()
        .withCredentials(amazonAWSCredentialsProvider())
        .withRegion(Regions.fromName(awsRegion))
        .build();
  } 
  1. Create the domain object that specifies the Global Secondary Index
@Data
@DynamoDBTable(tableName = "shopper")
public class Shopper{

  @DynamoDBHashKey(attributeName = "id")
  @DynamoDBAutoGeneratedKey
  private String id;

  @DynamoDBAttribute(attributeName = "address-hash" )
  @DynamoDBIndexHashKey(globalSecondaryIndexName = "idx-address-hash")
  private String addressHash;

  @DynamoDBTypeConvertedJson
  @DynamoDBAttribute(attributeName = "address")
  private Address address;

}
  1. Create the Repository Bean with the custom find method
@Repository
@EnableScan
public interface ShopperRepository extends CrudRepository<Shopper, String> {

  Shopper findByAddressHash(String addressHash);
}
  1. Invoke the method during runtime
Shopper result = repository.findByAddressHash(addressHash);
  1. Observe the resulting stack trace
java.lang.NullPointerException: null
    at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[na:1.8.0_66]
    at java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:964) ~[na:1.8.0_66]
    at com.amazonaws.services.dynamodbv2.datamodeling.StandardModelFactories$StandardModelFactory.getTableFactory(StandardModelFactories.java:82) ~[aws-java-sdk-dynamodb-1.11.613.jar:na]
    at com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper.getTableModel(DynamoDBMapper.java:410) ~[aws-java-sdk-dynamodb-1.11.613.jar:na]
    at org.socialsignin.spring.data.dynamodb.core.DynamoDBTemplate.getTableModel(DynamoDBTemplate.java:223) ~[spring-data-dynamodb-5.1.0.jar:5.1.0]
    at org.socialsignin.spring.data.dynamodb.repository.query.AbstractDynamoDBQueryCreator.create(AbstractDynamoDBQueryCreator.java:69) ~[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.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:95) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:81) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    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$SingleEntityExecution.execute(AbstractDynamoDBQuery.java:282) ~[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.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:605) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.lambda$invoke$3(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:595) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:59) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61) ~[spring-data-commons-2.1.10.RELEASE.jar:2.1.10.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at com.sun.proxy.$Proxy78.findByAddressHash(Unknown Source) ~[na:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_66]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_66]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_66]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_66]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) ~[spring-tx-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212) ~[spring-aop-5.1.9.RELEASE.jar:5.1.9.RELEASE]
    at com.sun.proxy.$Proxy78.findByAddressHash(Unknown Source) ~[na:na]
...

Specifications

Additional Information

  1. I have enabled spring.main.allow-bean-definition-overriding
  2. I was able to work around this issue by setting the ConversionSchema and TypeConverterFactory on my DynamoDBMapperConfig:
  @Bean
  @Primary
  public DynamoDBMapperConfig dynamoDBMapperConfig() {

    String fullPrefix = tablePrefix + TABLE_DELIMITER + environment + TABLE_DELIMITER;
    TableNameOverride tableNameOverride = TableNameOverride.withTableNamePrefix(fullPrefix);

    return DynamoDBMapperConfig.builder()
        .withTableNameOverride(tableNameOverride)
        .withConversionSchema(ConversionSchemas.V2)
        .withTypeConverterFactory(DynamoDBTypeConverterFactory.standard())
        .build();
  }
Aoriseth commented 5 years ago

I had a similar problem, adding .withTypeConverterFactory(DynamoDBTypeConverterFactory.standard()) seems to have solved the issue.

harikiranmca commented 4 years ago

@kjl-dev I tried both ways suggested above but still getting null pointer exception. Can you post your complete config?

ramkumar-mn commented 4 years ago

The error is because the table name is not getting overridden while querying. Meanwhile it is already taken care for other requests. So I was able to work around over that issue by registering a custom AWS Request handler.

    public RequestHandler2 requestHandler2() {
        return new RequestHandler2() {
            @Override
            public AmazonWebServiceRequest beforeExecution(AmazonWebServiceRequest request) {
                if (request instanceof QueryRequest)
                    ((QueryRequest) request).setTableName(tableName);
                return super.beforeExecution(request);
            }
        };
    }
CyberDracula commented 4 years ago

@kjl-dev I tried both ways suggested above but still getting null pointer exception. Can you post your complete config?

you also need to add @EnableDynamoDBRepositories(dynamoDBMapperConfigRef = "dynamoDBMapperConfig") at AwsConfiguration class level.

kaisermario commented 4 years ago

@CyberDracula had the same problem. Your hint was my solution :) Thank you!