TweetRepository.java
package com.pontsuyo.anyiine.domain.repository;
import com.pontsuyo.anyiine.domain.model.Tweet;
import java.util.List;
import org.socialsignin.spring.data.dynamodb.repository.EnableScan;
import org.springframework.data.repository.CrudRepository;
@EnableScan
public interface TweetRepository extends CrudRepository<Tweet, Long> {
@Override
List<Tweet> findAll();
List<Tweet> findAllByType(String type); // <-This is no good
}
This is the Repository class I created to use DynamoDB as the DB for Spring applications. But ** this causes the application to fail to launch. ** ** So, as a countermeasure, I will talk about making a separate repository class.
In Spring, access to DB is the responsibility of Repository, but if you create a class that inherits the interface of Repository prepared by various libraries, it is often not necessary to prepare the method yourself.
Spring-data-dynamodb is convenient when you also select DynamoDB as the DB. However, it seems that ** data acquisition by query specification (narrowing down by fields other than hash key, range key, etc.) is not implemented **. For this reason, When I start the Spring application, I get an error and it fails to start.
The error when starting the application was as follows.
Bottom of stack trace
Caused by: java.lang.IllegalStateException: You have defined query method in the repository but you don't have any query lookup strategy defined. The infrastructure apparently does not support query methods!
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:553) ~[spring-data-commons-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:332) ~[spring-data-commons-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:297) ~[spring-data-commons-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.data.util.Lazy.getNullable(Lazy.java:212) ~[spring-data-commons-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.data.util.Lazy.get(Lazy.java:94) ~[spring-data-commons-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:300) ~[spring-data-commons-2.2.7.RELEASE.jar:2.2.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
... 44 common frames omitted
As you can see from a closer look, this error is being thrown by spring. If you roughly translate the error sentence, "I have a query method defined in this repository, but no query lookup strategy is defined. Infrastructure does not explicitly support the query method!" It will be.
If it is completed with the library made by Spring, the implementation of repository is also made by Spring, and it should be guaranteed that the query method is supported, but the spring-data-dynamodb used this time does not support it. It seems that it is not guaranteed.
The first solution that comes to mind is to tweak the query method guarantee settings, as it's because it's not guaranteed, but it seems that this library doesn't really support the query method. (If you check [GitHub issue corresponding to this issue), it will be removed after being included in the TODO of the update to v5.0.3. You can see how it happened. It seems to be difficult ...)
So, although it is forcible, I decided to implement it myself. (Maybe there are other workarounds.)
Define a class equivalent to repository by yourself using DynamoDBMapper.
Using DynamoDBMapper provided by AWS, implement a query method that reproduces the query search of DynamoDB. It is shown with an example in the official AWS documentation, so please refer to it. (In the implementation of this link, model is also implemented in the class equivalent to repository, but I implement model as a separate class.) https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBMapper.QueryScanExample.html
Here is the final product. (What is the class name in such a case ...) Data in DynamoDB is defined elsewhere as a Tweet class. Imagine that the DB has some sort of Tweet information. This time it's okay if you just know that the Tweet class has a field named ** type **.
TweetRepositoryPlugin.java
package com.pontsuyo.anyiine.domain.repository;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBScanExpression;
import com.amazonaws.services.dynamodbv2.model.AttributeValue;
import com.pontsuyo.anyiine.domain.model.Tweet;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Repository;
/**
*Spring used to access Dynamo DB-data-In dynamodb
*Implementation of unsupported methods (such as scan with query specification)
*
* issue: https://github.com/derjust/spring-data-dynamodb/issues/114
*/
@Repository
public class TweetRepositoryPlugin {
private final DynamoDBMapper mapper;
public TweetRepositoryPlugin(DynamoDBMapper mapper) {
this.mapper = mapper;
}
public List<Tweet> findAllByType(String type) {
DynamoDBScanExpression scanExpression =
new DynamoDBScanExpression()
// "type"Is like a reserved word in DynamoDB, so
//Put a placeholder in the query string and replace it later.
.withFilterExpression("#t = :val1")
.withExpressionAttributeNames(
Map.of("#t", "type")
)
.withExpressionAttributeValues(
Map.of(":val1", new AttributeValue().withS(type))
);
return mapper.scan(Tweet.class, scanExpression);
}
}
[About withExpressionAttributeNames](https://qiita.com/ponponpopon/items/9b723d6210a26d512a9f#%E6%B3%A8-withexpressionattributenames%E3%81%AB%E3%81%A4%E3%81%84%E3%81% A6)
The instance mapper of DynamoDBMapper class is injected by constructor, but this is defined in the Config class prepared separately and registered as Bean.
DynamoDBConfig.java
package com.pontsuyo.anyiine.config;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
import org.socialsignin.spring.data.dynamodb.repository.config.EnableDynamoDBRepositories;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableDynamoDBRepositories(basePackages = "com.pontsuyo.anyiine.domain.repository")
public class DynamoDBConfig {
/**
*AWS DynamoDB settings
* @see com.amazonaws.auth.DefaultAWSCredentialsProviderChain
* @return
*/
@Bean
public AmazonDynamoDB amazonDynamoDB() {
return AmazonDynamoDBClientBuilder.standard()
.withCredentials(DefaultAWSCredentialsProviderChain.getInstance())
.withRegion(Regions.AP_NORTHEAST_1)
.build();
}
@Bean
public DynamoDBMapper dynamoDBMapper(){
return new DynamoDBMapper(amazonDynamoDB());
}
}
For the time being, it is a Service class that calls the method defined this time. You should have already injected some repository, so simply add the element you want to inject.
TweetService.java
package com.pontsuyo.anyiine.domain.service;
import com.pontsuyo.anyiine.controller.model.DestroyRequestParameter;
import com.pontsuyo.anyiine.controller.model.UpdateRequestParameter;
import com.pontsuyo.anyiine.domain.model.Tweet;
import com.pontsuyo.anyiine.domain.repository.TweetRepository;
import com.pontsuyo.anyiine.domain.repository.TweetRepositoryPlugin;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import twitter4j.Status;
import twitter4j.Twitter;
import twitter4j.TwitterException;
@Slf4j
@Service
public class TweetService {
private final TweetRepository tweetRepository;
private final TweetRepositoryPlugin tweetRepositoryPlugin; // <-This time added repository
private final Twitter twitter;
public TweetService(TweetRepository tweetRepository, TweetRepositoryPlugin tweetRepositoryPlugin, Twitter twitter) {
this.tweetRepository = tweetRepository;
this.tweetRepositoryPlugin = tweetRepositoryPlugin; // <-This time added repository
this.twitter = twitter;
}
//Below, various method definitions.
That's it. Official Explanation explains how to implement various patterns of queries. If the explanation in this article is not enough, please refer to it.
If anyone knows other solutions, please let me know.
I've included comments in the code, but I'm using **. WithExpressionAttributeNames () ** to replace the field names. The reason for doing this is that the field ** type ** included in the search query I wanted to specify this time seems to have been a reserved word in DynamoDB, and the following error was initially thrown.
Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: type
You can avoid this error by replacing the previous field name.
reference: https://note.kiriukun.com/entry/20190212-attribute-name-is-a-reserved-keyword-in-dynamodb
Recommended Posts