요구사항 분석   

 

 진행할 웹 애플리케이션의 요구사항은 다음과 같다. 

 

 


 

    프로젝트에 Spring Data JPA 적용하기   

 

 먼저, build.gradle에 의존성을 추가로 등록해야 한다. 

 

dependencies {
    //compile('org.springframework.boot:spring-boot-starter-web')
    //compile('org.projectlombok:lombok')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('com.h2database:h2')
    //testCompile('org.springframework.boot:spring-boot-starter-test')
}

 

 

1. spring-boot-start-data-jpa

  • 스프링부트용 Spring Data JPA 추상화 라이브러리이며 자동으로 라이브러리들의 버전을 관리해준다. 

2. h2

  • 인메모리 관계형 데이터베이스로 별도의 설치 없이 프로젝트 의존성으로만 관리할 수 있다.
  • 메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화된다. 보통 테스트 용도로 많이 사용한다.

 

 

 build.gradle에 의존성을 추가했다면 domain 패키지를 하나 추가한다. 이 패키지는 게시글, 댓글, 회원 등 각각의 요구사항인 도메인을 담을 패키지이다. 그 후에 posts 패키지와 Posts 클래스를 만든다. 그리고 다음 코드를 추가한다.

 

 

package com.daldalhada.springboot.domain.posts;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Getter
@NoArgsConstructor
@Entity
public class Posts {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }
}

 

 Posts 클래스는 실제 DB의 테이블과 매칭될 클래스이며 Entity 클래스라고도 한다. RDB와 달리, JPA에서는 쿼리를 날리기보다는 Entity 클래스의 수정을 통해 이루어진다. 

 

 

1. @Entity

  • 테이블과 매칭될 클래스이며 기본값으로 클래스의 카멜케이스 이름을 언더스코어()_로 테이블 이름을 매칭한다. 
  • UsersManager.java ▶ Users_manager table

2. @Id

  • 해당 테이블의 기본키(PK) 필드를 나타낸다.
  • 웬만하면 Long(RDB에서 BigInt) 타입의 auto_increment를 추천한다.

3. @GeneratedValue

  • 기본키의 생성 규칙을 나타낸다.
  • [참고] 스프링부트 2.0에서는 GenerationType.IDENTITY 옵션을 추가해야 auto_increment 옵션을 사용할 수 있다.

4. @Column

  • 테이블의 칼럼을 나타낸다.(굳이 선언하지 않더라도 해당 클래스의 필드는 모두 칼럼이 된다.)
  • 굳이 사용하는 이유는 기본값 외에 추가로 변경이 필요한 옵션이 있으면 사용한다.
  • 예) 문자열의 경우 VARCHAR(255)가 디폴트값이지만 사이즈를 500으로 늘리거나(Length=500), 타입을 변경하고 싶을 때(columnDefinition = "TEXT")

 

5. @NoArgsConstructor

  • 기본 생성자를 자동으로 추가해준다.(public Posts() { } 와 같은 효과)

6. @Getter

  • 클래스 내 모든 필드의 Getter 메소드를 자동으로 생성해준다.

7. @Builder

  • 클래스의 빌더 패턴 클래스를 생성해준다.
  • 생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함된다.

 

 서비스 초기 구축 단계에선 테이블 설계(여기서는 Entity)가 빈번하게 변경되는데 5~7번에 해당하는 롬복 어노테이션은 코드 변경량을 최소화시켜 주기에 적극적으로 사용하는 것이 좋다.

 

 


 

    데이터베이스에 값을 추가하는 방법   

 

 위의 Posts 클래스는 Getter 메소드는 설정했지만 Setter 메소드가 없다. Entity 클래스에서는 절대로 Setter 메소드를 만들지 않아야 하는 이유가 있는데, 해당 클래스의 인스턴스 값들이 언제 어디서 변해야 하는지 코드상으로 명확하게 구분할 수 없기 때문이다. 따라서, 해당 필드 값 변경이 필요하면 그 목적과 의도를 나타낼 수 있는 메소드를 추가해야만 한다. 

 

 주문 취소 메소드를 예로 들면

 

// 잘못된 사용 예

public class Order{
	public void setStatus(boolean status){
    		this.status = status;
    }
}

public void cancel(){
	order.setstatus(false);
}
// 올바른 사용 예

public class Order{
	public void orderCancel(){
    		this.status = false;
    }
}

public void cancel(){
	order.orderCalncel();
}

 

 

 그러면 Setter가 없는 상황에서 값을 데이터베이스에 어떻게 삽입(Insert)해야 할까? 기본적인 구조는 생성자를 통해 값을 채운 후 DB에 삽입하고 값 변경이 필요하면 정의한 public 메소드를 호출하여 변경한다. 

 

 여기서는 생성자 대신에 @Builder를 통해 제공되는 빌더 클래스를 이용한다. 생성자나 빌더나 생성 시점에 값을 채워주는 역할은 똑같지만 생성자의 경우에는 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 없다. 

 

 예를 들어 다음과 같은 생성자가 있다면 개발자가 new User(c, b, a)처럼 매개 변수의 위치를 변경해도 코드를 실행하기 전까지 문제를 찾을 수 없다. 즉, a에 c값이, c에 a값이 들어간다.

 

public User(String a, String b, String c){
    this.a = a;
    this.b = b
    this.c = c;
}

 

 하지만, 빌더를 사용하면 어느 필드에 어떤 값을 채워야 할지 명확하게 인지할 수 있다. 

 

User.builder()
    .a(a)
    .b(b)
    .c(c)
    .build();

 

 Posts 클래스 생성이 끝났으면 Posts 클래스로 데이터베이스에 접근하게 해줄 JPARepository를 생성한다. 보통 MyBatis 등에서 DAO라고 불리는 DB Layer 접근자이다. JPA에선 Repository라고 불리며 인터페이스로 생성한다. 인터페이스 생성 후 JpaRepository<Entity Class, PK Type>를 상속하면 기본적인 CRUD 메소드가 자동으로 생성된다. 

 

 주의할점은 Entity 클래스와 Repository는 같은 폴더 위치에 있어야 한다는 점이다. 다른 위치에 있다면 제대로 수행할 수가 없다. 

 

 

package com.daldalhada.springboot.domain.posts;

import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Posts, Long> {

}

 


    테스트 코드 작성하기   

 

 위의 작성된 코드들을 테스트 코드로 기능을 검증해봐야 하므로 test 디렉토리에 domain.posts 패키지를 생성하고, 테스트 클래스는 PostsRepositoryTest란 이름으로 생성하고 다음과 같은 코드를 추가한다. 

 

package com.daldalhada.springboot.domain.posts;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {
    @Autowired
    PostsRepository postsRepository;

    @After
    public void cleanup() {
        postsRepository.deleteAll();
    }

    @Test
    public void get_board() {
        //given
        String title = "테스트 게시글";
        String content = "테스트 본문";

        postsRepository.save(Posts.builder()
                .title(title)
                .content(content)
                .author("jojoldu@gmail.com")
                .build());

        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);
        assertThat(posts.getTitle()).isEqualTo(title);
        assertThat(posts.getContent()).isEqualTo(content);
    }
}

 

1. @After

  • JUnit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정한다. 
  • 보통 배포 전 전체 테스트를 수행할 때 테스트 간 데이터 침범을 막기 위해 사용한다. (여러 테스트가 동시에 실행되면 데이터베이스에 데이터가 그대로 남아 있을 수 있어 다음 테스트가 실패할 수 있으므로 보통 delete 해준다.)

2. postRepository.save

  • 위에서 인터페이스에 CRUD 메소드가 저절로 생성되었다고 하였는데, save는 테이블에 insert/update 쿼리를 실행한다.
  • id가 있으면 update가 id가 없으면 insert 쿼리가 실행된다.

3. postRepository.findAll

  • 테이블에 있는 모든 데이터를 조회해오는 메소드이다. 

4. @SpringBootTest

  • 별다른 설정 없이 H2 데이터베이스를 자동으로 샐행해준다.

 

 

 [참고] 흔히 할 수 있는 실수로 인터페이스명의 이름이 다른 경우 인식을 하지 못하므로 조심해야 한다. 필자는 Posts에서 's'가 없었다.

 

 

 

 테스트 코드를 실행해보면 테스트가 통과되는 것을 확인할 수 있다.

 

 

 

 그런데, 실제로 실행된 쿼리는 어떤 형태일지 궁금할 수 있다. 실행된 쿼리를 로그로 확인할 수 있는데 Java 클래스로 구현하거나 appication.properties 파일의 코드 수정으로 가능하다. 저번에 생성했던 resources 폴더의 application.properties로 들어가 다음 코드를 추가해준다. 

 

spring.jpa.show_sql=true

 

 

 다시 테스트를 수행해보면 콘솔에서 쿼리 로그를 확인할 수 있다. 

 

 

 

 만약 출력되는 쿼리 로그를 MySQL 버전으로 변경해보고 싶다면 appication.properties에서 다음 코드를 추가해준다. 

spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

 

 

 잘 적용되었음을 확인할 수 있다.

'스프링 부트 프로젝트 > 데이터베이스 다루기' 카테고리의 다른 글

7. API 만들기  (0) 2020.11.19
5. JPA  (0) 2020.10.07

    시험 정보    

 

  • 65문항, 모두 객관식
  • 복수 정답을 고르는 문제도 존재(2개 이상 고르시오)
  • 시험을 한국어로 설정하면 한국어와 영어를 번갈아가면서 볼 수 있음(단, 영어로 설정하면 한국어로는 변경할 수 없음)
  • 시험 범위는 다음과 같음

 

 

 


 

    시험 준비    

 

1. 시험 덤프 모음 사이트

www.examtopics.com/exams/amazon/aws-certified-cloud-practitioner/

 

AWS Certified Cloud Practitioner Amazon Exam Info and Free Practice Test | ExamTopics

 

www.examtopics.com

- 개인적으로 제일 도움이 많이되었다. 512문제(업데이트 되면 더 늘어날 수 있음)를 꼼꼼히 공부해본다면 시험을 무조건 통과할 수 있다고 장담한다. 풀다보면 똑같은 문제들이 계속 반복되어서 나오기 때문에 유형화하여 정리하면 훨씬 쉽다. 한국어로 시험을 본다고 하면 구글 번역기를 돌려서 시험 문제를 작성해보는 것도 나쁘지 않다. (512문제를 다 해석하기에는 힘들기 때문에...) 

 

 

 

2. AWS 시험 샘플 문항

 

d1.awsstatic.com/ko_KR/training-and-certification/docs-cloud-practitioner/AWS-Certified-Cloud-Practitioner_Sample-Questions.pdf

 

- 한국어로 어떻게 문제가 나오는지에 대한 예시가 주어진다. 총 10개의 문제이지만 이런 식으로 나온다는 것 정도만 파악해두면 좋다.

 

 

3. AWS 트레이닝 사이트

 

www.aws.training/Details/Curriculum?id=32442

 

AWS training and certification

 

www.aws.training

- AWS에서 제공하는 강의이다. 한 번쯤은 들어보면서 정리해보니 도움이 되었다. 

 

 


    합격    

 

 하루도 안되어서 자격증명서를 뽑을 수 있었다.

 

    4번 문제   

 

www.acmicpc.net/problem/1966

 

1966번: 프린터 큐

첫 줄에 test case의 수가 주어진다. 각 test case에 대해서 문서의 수 N(100이하)와 몇 번째로 인쇄되었는지 궁금한 문서가 현재 Queue의 어떤 위치에 있는지를 알려주는 M(0이상 N미만)이 주어진다. 다음

www.acmicpc.net

 



    내가 풀이한 답   

 

  문제에서 문서의 인덱스를 기준으로 문서를 찾아야 하기 때문에 <int, int> 쌍의 queue 자료구조와 문서의 중요도를 확인하기 위해 priority_queue 자료구조를 사용하였다. priority_queue의 첫 번째 요소와 queue의 중요도가 같지 않으면 queue의 첫 번째 요소를 뒤로 보내고 만약 priority_queue의 첫 번째 요소와 queue의 중요도가 같고 문서의 인덱스와 주어진 문제의 인덱스(m)와 같으면 종료하고 그렇지 않으면 priority_queue의 첫 번째 요소와 queue의 첫 번째 요소를 제거한다. 

 

//#include <bits/stdc++.h>
#include <iostream>
#include <queue>
using namespace std;

int main() {
	//freopen("input.txt", "rt", stdin);
	int t, i, n, m, j, a, idx, imp, cnt;
	cin >> t;
	for(i=0; i<t; i++){
		cnt=0;
		queue<pair<int, int> > q;
		priority_queue<int> pq;
		
		cin >> n >> m;
		for(j=0; j<n; j++){
			cin >> a;
			q.push({j, a});
			pq.push(a);
		}
		
		while(true){
			idx = q.front().first;
			imp = q.front().second;
			
			if(imp == pq.top()){
				cnt++;
				if(idx==m) break;
				q.pop();
				pq.pop();
			}
			else {
				q.push({idx, imp});
				q.pop();
			}
		}
		cout << cnt << endl;
	}

	return 0;
}

 

 

 

'알고리즘 & 자료구조 > 스택, 큐' 카테고리의 다른 글

3. 요세푸스 문제 0(Queue)  (0) 2020.10.31
2. 카드2(Queue)  (0) 2020.10.31
1. K진수 출력(Stack)  (0) 2020.10.24

    3번 문제   

 

www.acmicpc.net/problem/11866

 

11866번: 요세푸스 문제 0

첫째 줄에 N과 K가 빈 칸을 사이에 두고 순서대로 주어진다. (1 ≤ K ≤ N ≤ 1,000)

www.acmicpc.net

 



    내가 풀이한 답   

 

  k번째 까지 원소를 뒤로 넣은 다음 k번째 원소를 제거하는 행위를 원소가 하나 남을 때까지 이 과정을 반복하였다.

 

//#include <bits/stdc++.h>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;

int main() {
	//freopen("input.txt", "rt", stdin);
	queue<int> q;
	vector<int> v;
	int n, k, i;
	
	cin >> n >> k;
	
	for(i=1; i<=n; i++){
		q.push(i);
	}
	
	while(true){
		if(q.size()==1) {
			v.push_back(q.front());
			break;	
		}
		for(i=1; i<k; i++){
			q.push(q.front());
			q.pop();
		}
		v.push_back(q.front());
		q.pop();
	}
	
	cout << "<";
	for(i=0; i<v.size()-1; i++){
		cout << v[i] << ", ";
	}
	cout << v[v.size()-1] << ">";
	return 0;
}

 

 

 

'알고리즘 & 자료구조 > 스택, 큐' 카테고리의 다른 글

4. 프린터 큐(Queue)  (0) 2020.11.01
2. 카드2(Queue)  (0) 2020.10.31
1. K진수 출력(Stack)  (0) 2020.10.24

 

    2번 문제   

 

 

www.acmicpc.net/problem/2164

 

2164번: 카드2

N장의 카드가 있다. 각각의 카드는 차례로 1부터 N까지의 번호가 붙어 있으며, 1번 카드가 제일 위에, N번 카드가 제일 아래인 상태로 순서대로 카드가 놓여 있다. 이제 다음과 같은 동작을 카드가

www.acmicpc.net

 



    내가 풀이한 답   

 

  기본적인 큐를 사용하는 문제이다. 큐의 함수들을 안다면 쉽게 풀 수 있는 문제이다. 

 

//#include <bits/stdc++.h>
#include <iostream>
#include <queue>
using namespace std;

int main() {
	//freopen("input.txt", "rt", stdin);
	queue<int> q;
	int n, temp=0;
	cin >> n;
	
	for(int i=1; i<=n; i++){
		q.push(i);
	}
	
	
	while(true){
		if(q.size()==1) break;
		q.pop();
		temp = q.front();
		q.pop();
		q.push(temp);
	}
	
	cout << q.front();
}

 

 

 

'알고리즘 & 자료구조 > 스택, 큐' 카테고리의 다른 글

4. 프린터 큐(Queue)  (0) 2020.11.01
3. 요세푸스 문제 0(Queue)  (0) 2020.10.31
1. K진수 출력(Stack)  (0) 2020.10.24

    1번 문제   

 

 


 

    내가 풀이한 답   

 

 Stack 자료구조를 사용하였다. 이 문제를 풀 때 주의해야 할 점은 두 가지가 있는데 첫 번째는 while문에서 종료 조건문을 n이 k진수보다 작을 때 종료해야 한다는 점과 16진수에서 10이상 15이하의 숫자는 A~F로 표현된다는 점이다. 

//#include <bits/stdc++.h>
#include <iostream>
#include <stack>
using namespace std;

int main() {
	//freopen("input.txt", "rt", stdin);
	
	int n, k;
	char a[6] = {'A', 'B', 'C', 'D', 'E', 'F'};
	cin >> n >> k;
	stack<int> s;
	
	while(n>k-1){
		s.push(n%k);
		n = n/k;
 		if(n<k) {
 			s.push(n);	
		}
	}
	
	
	while(!s.empty()){
		if(k==16) {
			if(s.top()>=10) {
				cout << a[s.top()-10];
				s.pop();
			}
			cout << s.top();
			s.pop();	
		}
		else {
			cout << s.top();
			s.pop();	
		}	
	}
	
	return 0;
}

 

 결과는 통과하였다.

 

 



    사이트의 답안   

#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
int stack[100], top=-1;

void push(int x){
	stack[++top]=x;
}
int pop(){
	return stack[top--];
}

int main(){
	freopen("input.txt", "rt", stdin);
	int n, k;
	char str[20]="0123456789ABCDEF";
	scanf("%d %d", &n, &k);
	while(n>0){
		push(n%k);
		n=n/k;
	}
	while(top!=-1){
		printf("%c", str[pop()]);
	}	
	return 0;
}




#include<stdio.h>
#include<vector>
#include<stack>
#include<algorithm>
using namespace std;		
int main(){
	freopen("input.txt", "rt", stdin);
	int n, k;
	stack<int> s;
	char str[20]="0123456789ABCDEF";
	scanf("%d %d", &n, &k);
	while(n>0){
		s.push(n%k);
		n=n/k;
	}
	while(!s.empty()){
		printf("%c", str[s.top()]);
		s.pop();
	}	
	return 0;
}

'알고리즘 & 자료구조 > 스택, 큐' 카테고리의 다른 글

4. 프린터 큐(Queue)  (0) 2020.11.01
3. 요세푸스 문제 0(Queue)  (0) 2020.10.31
2. 카드2(Queue)  (0) 2020.10.31

 

    10번 문제   

 

programmers.co.kr/learn/courses/30/lessons/43165

 

코딩테스트 연습 - 타겟 넘버

n개의 음이 아닌 정수가 있습니다. 이 수를 적절히 더하거나 빼서 타겟 넘버를 만들려고 합니다. 예를 들어 [1, 1, 1, 1, 1]로 숫자 3을 만들려면 다음 다섯 방법을 쓸 수 있습니다. -1+1+1+1+1 = 3 +1-1+1+1+

programmers.co.kr

 

 


 

    내가 풀이한 답   

 

 더하는 경우와 빼는 경우만 고려하면 되므로 재귀 함수를 2번 호출하여 구하면 쉽게 구할 수 있다.

 

 

#include <string>
#include <vector>
#include <iostream>
using namespace std;
int answer = 0;

void dfs(int level, int sum, vector<int> numbers, int target){
    if(level == numbers.size()){
        if(target==sum) {
            answer++;
        }
    }
    else {
        dfs(level+1, sum+numbers[level], numbers, target);
        dfs(level+1, sum-numbers[level], numbers, target);
    }
}

int solution(vector<int> numbers, int target) {
    
    dfs(0, 0, numbers, target);
    
    return answer;
}