-
Notifications
You must be signed in to change notification settings - Fork 0
DynamoDB 쿼리 및 스캔 작업 정리
Lucy Oh edited this page Aug 15, 2023
·
6 revisions
Written by Jisoo Oh
KeyConditionExpression 작동 확인하기
- PK가 동일하고 SK가 USER#로 시작하는 데이터를 전부 가져오기:
.withKeyConditionExpression("#PK = :val1 and begins_with(#SK, :val2)")
public User findUserByUserHandle(String userHandle) throws BaseException {
HashMap<String, String> ean = new HashMap<>(); // attribute names
ean.put("#PK", "PartitionKey");
ean.put("#SK", "SortKey");
Map<String, AttributeValue> eav = new HashMap<>(); // attribute value
eav.put(":val1", new AttributeValue().withS(userHandle));
eav.put(":val2", new AttributeValue().withS("USER#"));
DynamoDBQueryExpression<User> query = new DynamoDBQueryExpression<User>()
.withKeyConditionExpression("#PK = :val1 and begins_with(#SK, :val2)")
.withExpressionAttributeValues(eav)
.withExpressionAttributeNames(ean);
List<User> users = mapper.query(User.class, query);
if (users.isEmpty()) { // 결과값이 비어있다면 - 예외 처리
throw new BaseException(INVALID_USER_HANDLE);
}
return users.get(0);
}
최신 순 정렬하기
- SK(allowedDate)를 기준으로 게시글을 최신 순 정렬(내림차순 - desc)한 값을 가져오기 (기본은 오래된 순으로 정렬됨.):
.withScanIndexForward(false); // desc
public List<Tag> findTagsByUserHandle(String userHandle) throws BaseException {
HashMap<String, String> ean = new HashMap<>();
ean.put("#PK", "PartitionKey");
ean.put("#SK", "SortKey");
Map<String, AttributeValue> eav = new HashMap<>();
eav.put(":val1", new AttributeValue().withS(userHandle));
eav.put(":val2", new AttributeValue().withS("TAG#"));
DynamoDBQueryExpression<Tag> query = new DynamoDBQueryExpression<Tag>()
.withKeyConditionExpression("#PK = :val1 and begins_with(#SK, :val2)")
.withExpressionAttributeValues(eav)
.withExpressionAttributeNames(ean)
.withScanIndexForward(false); // desc
List<Tag> tags = mapper.query(Tag.class, query);
return tags;
}
KeyCondition으로 나온 결과에 필터 적용하기
- status가
PUBLIC
인 데이터만 가져오기:
.withFilterExpression("#STATUS = :val3") // filter - get only public publish
public List<Publish> findOnlyPublicPublishWithUserHandle(String userHandle) throws BaseException {
// TODO: 이후 페이징 필요
HashMap<String, String> ean = new HashMap<>();
ean.put("#PK", "PartitionKey");
ean.put("#SK", "SortKey");
ean.put("#STATUS", "status");
Map<String, AttributeValue> eav = new HashMap<>();
eav.put(":val1", new AttributeValue().withS(userHandle));
eav.put(":val2", new AttributeValue().withS("PUBLISH#"));
eav.put(":val3", new AttributeValue().withS(Status.PUBLIC.toString()));
DynamoDBQueryExpression<Publish> query = new DynamoDBQueryExpression<Publish>()
.withKeyConditionExpression("#PK = :val1 and begins_with(#SK, :val2)")
.withFilterExpression("#STATUS = :val3") // filter - get only public publish
.withExpressionAttributeValues(eav)
.withExpressionAttributeNames(ean)
.withScanIndexForward(false); // desc
List<Publish> publishes = mapper.query(Publish.class, query);
return publishes;
}
Java의 List에 add, remove 메서드가 있지만, 쿼리가 더 속도가 빠를 것이라는 생각에 도전해보았습니다.
- 팔로우 중인 상태면 팔로우 취소하기
- 반대 상태라면? 팔로우하기
- 먼저 기존에 List로 구현되어있던 팔로우/팔로잉 목록을 HashSet으로 변경해주었습니다.
- 일단 UserHandle이 겹쳐서는 안되기 때문에 Set을 사용하는 편이 로직에 맞았고, String Set이 구현에 더 편리합니다.
- isFollow라는 현재 팔로우 상태를 알아보는 Boolean 값을 받았습니다.
- 이는 List의 contains를 사용해주어도 되고, 저는 별도의 쿼리를 사용하였습니다.
- isFollow() 쿼리 로직은 위의 설명이 충분히 나와있으니 설명은 생략하겠습니다~~
- isFollow를 통해 현재 action ADD 또는 DELETE를 결정해주고, UpdateItemRequest를 작성할 수 있습니다.
- 작성법은 그냥 query와 매우 유사하며, 특히 withAttributeUpdates에 들어가는 HashMap에 해당 action을 추가해주면 됩니다.
-
awsDynamoDB.updateItem(updateItemRequest)
를 통해 쿼리를 전송할 수 있으며, 오류 처리도 해주었습니다.
public String followOrCancelByIsFollow(String followedUserHandle, String followingUserHandle, Boolean isFollow) throws BaseException {
String result = "성공적으로 팔로우하였습니다."; // add
AttributeAction action = ADD;
if (isFollow) {
result = "성공적으로 팔로우를 취소하였습니다."; // delete
action = DELETE;
}
// add or delete follower
// TODO: User SK가 "USER#" 이 아니라 다른것으로 바뀐다면 바꿔야 함.
HashMap<String, AttributeValue> followerItemKey = new HashMap<>();
followerItemKey.put("PartitionKey", new AttributeValue().withS(followedUserHandle));
followerItemKey.put("SortKey", new AttributeValue().withS("USER#"));
HashMap<String, AttributeValueUpdate> followerUpdateValue = new HashMap<>();
followerUpdateValue.put("followerUserHandles", new AttributeValueUpdate()
.withValue(new AttributeValue().withSS(followingUserHandle))
.withAction(action));
UpdateItemRequest addFollower = new UpdateItemRequest()
.withKey(followerItemKey)
.withTableName("pochakdatabase")
.withAttributeUpdates(followerUpdateValue);
// add or delete following
HashMap<String, AttributeValue> followingItemKey = new HashMap<>();
followingItemKey.put("PartitionKey", new AttributeValue().withS(followingUserHandle));
followingItemKey.put("SortKey", new AttributeValue().withS("USER#"));
HashMap<String, AttributeValueUpdate> followingUpdateValues = new HashMap<>();
followingUpdateValues.put("followingUserHandles", new AttributeValueUpdate()
.withValue(new AttributeValue().withSS(followedUserHandle))
.withAction(action));
UpdateItemRequest addFollowing = new UpdateItemRequest()
.withKey(followingItemKey)
.withTableName("pochakdatabase")
.withAttributeUpdates(followingUpdateValues);
try {
amazonDynamoDB.updateItem(addFollower);
amazonDynamoDB.updateItem(addFollowing);
return result;
} catch (ResourceNotFoundException e) {
throw new BaseException(RESOURCE_NOT_FOUND);
} catch (AmazonDynamoDBException e) {
throw new BaseException(DATABASE_ERROR);
}
}
Spring Data JPA와 마찬가지로 쿼리 메소드를 사용할 수 있습니다.
- User를 찾을 때 PK인 Handle과 SK의 prefix인 "USER#" 를 사용하여
#PK = :val1 and begins_with(#SK, :val2)
쿼리를 날리고자 할 때, 쿼리 메소드를 사용하여 구현해보기
- 쿼리 메소드 사용은 JPA와 사용이 동일합니다. 자세한 사용방법은 공식문서를 참고하세요!
@EnableScan
public interface UserCrudRepository extends DynamoDBCrudRepository<User, UserId> {
Optional<User> findUserByHandleAndUserSKStartingWith(String handle, String prefix);
}
- 여기서 만약 이렇게 작성하면, PK인 UserId만 가지고 쿼리를 날리게 됩니다. 이 경우 DB 설계를 고려해보았을 때 User가 아닌, Tag, Publish 등이 함께 찾아질 수 있으므로 유의합니다.
- 참고로 최신순 정렬 (내림차순 정렬)을 하고 싶은 경우,
ScanIndexForward
를 false로 주어야 하므로, 수동 쿼리를 작성해야합니다.
- 참고로 최신순 정렬 (내림차순 정렬)을 하고 싶은 경우,
@EnableScan
public interface UserCrudRepository extends DynamoDBCrudRepository<User, UserId> {
Optional<User> findUserByHandle(String handle);
}
- 결과 로깅
// PK로 "dayeon"을 주었을 때
"{"TableName":"pochakdatabase","ConsistentRead":true,"KeyConditions":{"PartitionKey":{"AttributeValueList":[{"S":"dayeon"}],"ComparisonOperator":"EQ"}},"ScanIndexForward":true}"
// 결과 - 다음과 같이 USER 뿐만 아니라 TAG도 찾아짐.
"{"Count":3,"Items":
[
// TAG
{"PartitionKey":{"S":"dayeon"},"postPK":{"S":"POST#test124"},"SortKey":{"S":"TAG#2023-08-10T00:12:35.451Z"},"postImg":{"S":"https://~~"},"status":{"S":"PUBLIC"}},
// TAG
{"PartitionKey":{"S":"dayeon"},"postPK":{"S":"POST#test123"},"SortKey":{"S":"TAG#2023-08-10T00:16:35.451Z"},"postImg":{"S":"https://~~"},"status":{"S":"PUBLIC"}},{"createdDate":{"S":"2023-08-12T06:15:17.847Z"},"status":{"S":"PRIVATE"},
// USER - 원래 찾고자 했던 데이터
"PartitionKey":{"S":"dayeon"},"followerUserHandles":{"SS":["jisoo"]},"message":{"S":"[0xed][0x95][0x9c] [0xec][0xa4][0x84] [0xec][0x86][0x8c][0xea][0xb0][0x9c] 111"},"lastModifiedDate":{"S":"2023-08-13T15:03:19.531Z"},"email":{"S":"[email protected]"},"SortKey":{"S":"USER#"},"name":{"S":"testUser1"},"profileImage":{"S":"https://11~~"}}],
// 결과 - 3개 (찾고자 하는 데이터 외에 다른 데이터가 섞임)
"ScannedCount":3}"