전체적인 흐름
- S3 티어 선택 후 S3 버킷 생성
- 액세스 권한을 얻기 위한 IAM 생성
- 스프링 빈에 AWS S3 등록
- Image API 구현 및 테스트
S3 티어 선택 및 S3 버킷 생성
S3 티어(스토리지 클래스)
어떤 종류의 데이터를 관리하는지, 얼마나 자주 그 데이터에 접근해야하는지에 따라 분류, 사용자는 그에 맞는 티어를 선택
- Standard
- 가장 보편적으로 사용되는 스토리지 타입
- IA
- 자주 접근되지는 않으나 접근시 빠른 접근, standard보다 비용은 저렴하나 데이터를 불러올때마다 추가 비용
- One Zone IA
- IA와 같지만 하나의 AZ에만 데이터 저장 → 가용영역 문제 생길경우 데이터가 날라갈수도 있음
- Intelligent Tiering
- 머신러능을 통한 자동으로 파일의 티어를 변경하는 서비스 ex. 접근 많으면 스탠다드, 빈도 낮으면 IA
- Glacier
- 거의 접근하지 않을 데이터, 데이터 접근시 분~시간 소요 ex. 쓸모 없는 법적 데이터
- Glacier Instant Retrieval
- 밀리초 단위의 검색시간, 분기당 한번 액세스에 적합 한마디로 빠르게 데이터를 가져와야하는데 자주 액세스하지않은 데이터에 적합
- Glacier Flexible Retrieval, Glacier Deep Archive
- 더 깊어지는 빙하(glacier)검색 시간 상당히 소요
클래스 간 전환
Amazon S3 수명 주기를 사용하여 객체 전환 - Amazon Simple Storage Service
복원된 객체에 대한 복사 작업은 Amazon S3 콘솔에서 S3 Glacier Flexible Retrieval 또는 S3 Glacier Deep Archive 스토리지 클래스에 있는 객체에 대해 지원되지 않습니다. 이러한 유형의 복사 작업에는 AWS Comman
docs.aws.amazon.com
쉽게 말하면 standard → standard-IA는 가능하지만 반대로 standard-IA → standard는 불가능하다

S3 수명주기
- 의의
- 무한으로 증가되는 S3 버킷 용량을 주기적으로 정리하는데 목적, 각 버킷별로 수명주기 생성 가능
- 사용하지 않을 예정
- 문제 자체가 수명이 있는것이 아니고 쓰이지 않는 문제(2016년 이전 기출문제 등)가 존재하면 직접 지우는게 맞다고 판단
S3 버킷 생성 및 권한 설정
- 버킷 생성 및 정책 변환
- 버킷 생성
- 버킷 정책 변경을 위해서 퍼블릭 액세스 비활성화
- 버킷 정책 추가
- 버킷 정책 편집 - 정책 생성기를 통해 아래 3가지 체크 및 principal: *(전체 적용) 입력
- GetObject
- PutObject
- DeleteObject
- 정책 생성기를 통해 생성된 코드를 아래 버킷 정책 페이지로 돌아와서 붙여준다
- 버킷 정책 편집 - 정책 생성기를 통해 아래 3가지 체크 및 principal: *(전체 적용) 입력
{
"Version": "2012-10-17",
"Id": "",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
],
"Resource": ""
}
]
}


IAM 사용자 추가
- IAM 사용자 생성 클릭 및 이름 설정
- 권한 설정에서 AmazonS3FullAccess 추가

액세스 생성 및 키, 비번 별도로 저장
코드
S3 관련 Gradle 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
S3 Config
- 인터페이스인 AmazonsS3 빈 등록
@Configuration
public class S3Config {
@Value("${aws.s3.region}")
private String region;
@Value("${aws.s3.accessKeyId}")
private String accessKeyId;
@Value("${aws.s3.secretKey}")
private String secretKey;
@Bean
public AmazonS3 s3Client() {
AWSCredentials credentials = new BasicAWSCredentials(accessKeyId, secretKey);
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region).build();
}
}
QuestionsController
- 이미지 추가, 삭제 API구현
//이미지 관련 API
@PostMapping("image")
public List<String> saveImages(@ModelAttribute ImageSaveDto imageSaveDto) {
return questionsService.saveImages(imageSaveDto);
}
@DeleteMapping("image/delete")
public ResponseEntity<String> deleteImages(){
questionsService.deleteImages();
return ResponseEntity.ok("DeleteAll");
}
QuestionsServiceImpl
- ImageService로 책임 전가
//Image관련 Service로직은 ImageService에 위임 - 추후 문제 데이터 추가시에 내용도 추가됨에 따라 QuestionsService 로직이 담당하는 부분이 많을 것으로 예상 -> 분산필요
@Override
public List<String> saveImages(ImageSaveDto imageSaveDto){
return imageService.saveImagesInS3(imageSaveDto);
}
@Override
public void deleteImages(){
imageService.deleteImagesInFolder();
}
ImageServiceImpl
@Service
@RequiredArgsConstructor
public class ImageServiceImpl implements ImageService{
private static String bucketName = "examlab-image";
private static String folderName = "test_images/"; //저장 폴더명 임시 부여
private final AmazonS3 amazonS3;
//이미지 저장 로직
@Transactional
public List<String> saveImagesInS3(ImageSaveDto saveDto) {
List<String> resultList = new ArrayList<>();
for (int i = 0; i < saveDto.getImages().size(); i++) {
String value = saveImage(saveDto.getImages().get(i));
resultList.add(value);
}
return resultList;
}
@Transactional
public String saveImage(MultipartFile multipartFile) {
String originalName = multipartFile.getOriginalFilename();
String accessUrl = ""; //반환 URL저장
String filename = folderName+originalName;
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
objectMetadata.setContentLength(multipartFile.getInputStream().available());
amazonS3.putObject(bucketName, filename, multipartFile.getInputStream(), objectMetadata);
accessUrl = amazonS3.getUrl(bucketName, filename).toString();
} catch(IOException e) {
}
return accessUrl;
}
//이미지 삭제 로직
public void deleteImagesInFolder() {
// 폴더 내 객체 리스트를 가져옴
List<S3ObjectSummary> objectSummaries = amazonS3.listObjects(bucketName, folderName).getObjectSummaries();
// 각 객체를 삭제
for (S3ObjectSummary objectSummary : objectSummaries) {
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, objectSummary.getKey()));
}
}

테스트
테스트를 위해서 스프링 실행 후 Postman에 이미지 4개 첨부 및 실행을 해본다. 그러면 다음과 같이 저장된 이미지의 url과 함께
해당 S3버킷 폴더에 이미지가 저장된것을 확인 할 수 있다


출처
'스프링' 카테고리의 다른 글
[AI] 이미지 생성시 DALLE에서 Stable Diffusion으로 AI모델 변경 with SpringAI (2) | 2025.02.02 |
---|---|
[Spring] Validation 종류 및 적용 (0) | 2024.03.15 |
[스프링] 스프링 3.1이후에 디버그 로그가 안뜰경우, 로그레벨 조정 방법 (0) | 2024.01.29 |
[스프링] 혼자 구현하는 웹 서비스1: 프로젝트 세팅 (0) | 2024.01.08 |
Google Drive API를 활용한 이미지 업로드 및 Url반환(스프링 서비스단계 구현) (0) | 2023.08.26 |
전체적인 흐름
- S3 티어 선택 후 S3 버킷 생성
- 액세스 권한을 얻기 위한 IAM 생성
- 스프링 빈에 AWS S3 등록
- Image API 구현 및 테스트
S3 티어 선택 및 S3 버킷 생성
S3 티어(스토리지 클래스)
어떤 종류의 데이터를 관리하는지, 얼마나 자주 그 데이터에 접근해야하는지에 따라 분류, 사용자는 그에 맞는 티어를 선택
- Standard
- 가장 보편적으로 사용되는 스토리지 타입
- IA
- 자주 접근되지는 않으나 접근시 빠른 접근, standard보다 비용은 저렴하나 데이터를 불러올때마다 추가 비용
- One Zone IA
- IA와 같지만 하나의 AZ에만 데이터 저장 → 가용영역 문제 생길경우 데이터가 날라갈수도 있음
- Intelligent Tiering
- 머신러능을 통한 자동으로 파일의 티어를 변경하는 서비스 ex. 접근 많으면 스탠다드, 빈도 낮으면 IA
- Glacier
- 거의 접근하지 않을 데이터, 데이터 접근시 분~시간 소요 ex. 쓸모 없는 법적 데이터
- Glacier Instant Retrieval
- 밀리초 단위의 검색시간, 분기당 한번 액세스에 적합 한마디로 빠르게 데이터를 가져와야하는데 자주 액세스하지않은 데이터에 적합
- Glacier Flexible Retrieval, Glacier Deep Archive
- 더 깊어지는 빙하(glacier)검색 시간 상당히 소요
클래스 간 전환
Amazon S3 수명 주기를 사용하여 객체 전환 - Amazon Simple Storage Service
복원된 객체에 대한 복사 작업은 Amazon S3 콘솔에서 S3 Glacier Flexible Retrieval 또는 S3 Glacier Deep Archive 스토리지 클래스에 있는 객체에 대해 지원되지 않습니다. 이러한 유형의 복사 작업에는 AWS Comman
docs.aws.amazon.com
쉽게 말하면 standard → standard-IA는 가능하지만 반대로 standard-IA → standard는 불가능하다

S3 수명주기
- 의의
- 무한으로 증가되는 S3 버킷 용량을 주기적으로 정리하는데 목적, 각 버킷별로 수명주기 생성 가능
- 사용하지 않을 예정
- 문제 자체가 수명이 있는것이 아니고 쓰이지 않는 문제(2016년 이전 기출문제 등)가 존재하면 직접 지우는게 맞다고 판단
S3 버킷 생성 및 권한 설정
- 버킷 생성 및 정책 변환
- 버킷 생성
- 버킷 정책 변경을 위해서 퍼블릭 액세스 비활성화
- 버킷 정책 추가
- 버킷 정책 편집 - 정책 생성기를 통해 아래 3가지 체크 및 principal: *(전체 적용) 입력
- GetObject
- PutObject
- DeleteObject
- 정책 생성기를 통해 생성된 코드를 아래 버킷 정책 페이지로 돌아와서 붙여준다
- 버킷 정책 편집 - 정책 생성기를 통해 아래 3가지 체크 및 principal: *(전체 적용) 입력
{
"Version": "2012-10-17",
"Id": "",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
],
"Resource": ""
}
]
}


IAM 사용자 추가
- IAM 사용자 생성 클릭 및 이름 설정
- 권한 설정에서 AmazonS3FullAccess 추가

액세스 생성 및 키, 비번 별도로 저장
코드
S3 관련 Gradle 추가
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
S3 Config
- 인터페이스인 AmazonsS3 빈 등록
@Configuration
public class S3Config {
@Value("${aws.s3.region}")
private String region;
@Value("${aws.s3.accessKeyId}")
private String accessKeyId;
@Value("${aws.s3.secretKey}")
private String secretKey;
@Bean
public AmazonS3 s3Client() {
AWSCredentials credentials = new BasicAWSCredentials(accessKeyId, secretKey);
return AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.withRegion(region).build();
}
}
QuestionsController
- 이미지 추가, 삭제 API구현
//이미지 관련 API
@PostMapping("image")
public List<String> saveImages(@ModelAttribute ImageSaveDto imageSaveDto) {
return questionsService.saveImages(imageSaveDto);
}
@DeleteMapping("image/delete")
public ResponseEntity<String> deleteImages(){
questionsService.deleteImages();
return ResponseEntity.ok("DeleteAll");
}
QuestionsServiceImpl
- ImageService로 책임 전가
//Image관련 Service로직은 ImageService에 위임 - 추후 문제 데이터 추가시에 내용도 추가됨에 따라 QuestionsService 로직이 담당하는 부분이 많을 것으로 예상 -> 분산필요
@Override
public List<String> saveImages(ImageSaveDto imageSaveDto){
return imageService.saveImagesInS3(imageSaveDto);
}
@Override
public void deleteImages(){
imageService.deleteImagesInFolder();
}
ImageServiceImpl
@Service
@RequiredArgsConstructor
public class ImageServiceImpl implements ImageService{
private static String bucketName = "examlab-image";
private static String folderName = "test_images/"; //저장 폴더명 임시 부여
private final AmazonS3 amazonS3;
//이미지 저장 로직
@Transactional
public List<String> saveImagesInS3(ImageSaveDto saveDto) {
List<String> resultList = new ArrayList<>();
for (int i = 0; i < saveDto.getImages().size(); i++) {
String value = saveImage(saveDto.getImages().get(i));
resultList.add(value);
}
return resultList;
}
@Transactional
public String saveImage(MultipartFile multipartFile) {
String originalName = multipartFile.getOriginalFilename();
String accessUrl = ""; //반환 URL저장
String filename = folderName+originalName;
try {
ObjectMetadata objectMetadata = new ObjectMetadata();
objectMetadata.setContentType(multipartFile.getContentType());
objectMetadata.setContentLength(multipartFile.getInputStream().available());
amazonS3.putObject(bucketName, filename, multipartFile.getInputStream(), objectMetadata);
accessUrl = amazonS3.getUrl(bucketName, filename).toString();
} catch(IOException e) {
}
return accessUrl;
}
//이미지 삭제 로직
public void deleteImagesInFolder() {
// 폴더 내 객체 리스트를 가져옴
List<S3ObjectSummary> objectSummaries = amazonS3.listObjects(bucketName, folderName).getObjectSummaries();
// 각 객체를 삭제
for (S3ObjectSummary objectSummary : objectSummaries) {
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, objectSummary.getKey()));
}
}

테스트
테스트를 위해서 스프링 실행 후 Postman에 이미지 4개 첨부 및 실행을 해본다. 그러면 다음과 같이 저장된 이미지의 url과 함께
해당 S3버킷 폴더에 이미지가 저장된것을 확인 할 수 있다


출처
'스프링' 카테고리의 다른 글
[AI] 이미지 생성시 DALLE에서 Stable Diffusion으로 AI모델 변경 with SpringAI (2) | 2025.02.02 |
---|---|
[Spring] Validation 종류 및 적용 (0) | 2024.03.15 |
[스프링] 스프링 3.1이후에 디버그 로그가 안뜰경우, 로그레벨 조정 방법 (0) | 2024.01.29 |
[스프링] 혼자 구현하는 웹 서비스1: 프로젝트 세팅 (0) | 2024.01.08 |
Google Drive API를 활용한 이미지 업로드 및 Url반환(스프링 서비스단계 구현) (0) | 2023.08.26 |