1.https://spring.io/tools접속하여 STS(for Eclipse) 설치 후 실행 2. 상단 Quick Access에 Git Repositories 검색 후 클릭 3. Git Repositories 탭으로 가서 Clone a Git repository 4. gitlab 사이트 Import 하려는 프로젝트로 들어가서 Clone -> Clone with HTTPS 부분 복사 5. STS로 돌아가서 URL 부분에 붙여넣기 하면 세부 항목까지 자동 입력(Port는 빈칸) 6. master 브랜치 체크 후 Next & Finish 7. Git Repositories 탭의 프로젝트 우클릭 후 Import Projects 8. Package Explorer에 받아진 프로젝트 확인 9. Window > Preferences > Server > Runtime Environments > 우측 Add > Tomcat 버전 선택 > Browse... 클릭 후 다운로드한 Tomcat 폴더 선택 > Finish > Apply and Close
(Tomcat 다운로드https://tomcat.apache.org) 10. 상단 Quick Access에 Servers 검색 후 클릭 11. Servers 탭에서 우클릭 > New > Server > Tomcat 버전 선택 > Available 부분에 있는 항목 선택 후 Add > Finish 12. Project는 우클릭 후 Maven > Update Project...와 최상단 탭 Project > Clean...
13. x 표시가 사라지지 않는다면 설치되지 않은 플러그인이 있는지 확인
- Lombok을 사용하는 경우 -
Eclipse의 경우 Maven / Gradle에 Lombok Dependency가 추가되어 있어도 Lombok을 따로 설치해 줘야 한다.
Spring Security의 Bcrypt 암호화를 사용해 만든 비밀번호를 유저 테이블에 저장할 때 입력한 비밀번호에 랜덤한 salt 값이 더해져 암호화되는 것을 알 수 있다.
(같은 비밀번호로 변경해도 저정되는 값이 다르기 때문) 그렇다면 입력한 비밀번호 외에 암호화에 사용된 salt 값이 어떤 값인지, 그 값은 어디에 저장되는지 알아야 한다. 하지만 Bcrypt 암호화의 salt 값은 따로 저장되는 게 아닌 해시값에 이어붙여 함께 저장되기 때문에 따로 salt 값 저장이
필요하지 않다.
$2a$10$Yz4die0hPMjS3.6njaxmmOyrwArwsC9NEs5kwOI0Gh04uLjHaON2e 위와 같은 형태로 해시값을 생성하는데 이는 '$'로 구분되는 3가지의 필드라고 보면 된다.
'2a'는 사용된 bcrypt알고리즘의 버전을 의미하며 '10'은 cost factor이다. cost factor가 10이라는 의미는 key derivation 함수가 2^10번 반복 실행된다는 의미로 이 값이 커질 수록 해시값을 구하는 속도가 느려진다. 'Yz4die0hPMjS3.6njaxmmOyrwArwsC9NEs5kwOI0Gh04uLjHaON2e'가 바로 salt 값과 암호화된 비밀번호 값이다.
Window의 기능을 사용하기 위해 WindowAdapter 클래스를 상속받았기 때문에 POJO라고 할 수 없다.
이렇게 되면 다른 솔루션을 사용하고자 할 때 많은 양의 코드를 리팩토링해야 하는 문제가 있다.
이처럼 특정 기술과 환경에 종속되어 의존하게 되면, 코드 가독성 뿐만 아니라 유지보수, 확장성에도 어려움이 생긴다.
이러한 객체지향의 장점을 잃어버린 자바를 되살리기 위해 POJO라는 개념이 등장한 것이다.
진정한 POJO란 객체지향적인 원리에 충실하면서, 환경과 기술에 종속되지 않고 필요에 따라 재활용될 수 있는 방식으로
설계된 오브젝트이다. - 토비의 스프링 -
POJO는 A. 미리 지정된 클래스를 extends 하면 안 된다. B. 미리 정의된 인터페이스를 implements 하면 안 된다. C. 미리 정의된 Annotation을 포함하면 안 된다.
POJO의 장점 특정 규약에 종속되지 않아 객체지향 설계를 할 수 있다. 특정 환경에 종속되지 않아 테스트하기 좋다. 특정 규약에 종속되지 않아 낮은 레벨 코드와 비즈니스 코드가 분리되어 깔끔한 코드 작성이 가능하다.
애플리케이션을 구성하는 객체(Bean)가 생성되고 동작하는 틀을 제공해 줄 뿐만 아니라,
애플리케이션 코드를 어떻게 작성해야 하는지에 대한 기준도 제공한다.
이를 일반적으로 프로그래밍 모델이라고 부르는데, Spring에는 크게 3가지 핵심 프로그래밍 모델을 지원한다.
1. AOP(Aspect Oriented Programming 관점 지향 프로그래밍) : 여러 객체에 공통으로 적용할 수 있는 기능을
구분함으로써 재사용을 높여주는 프로그래밍 기법이다. AOP는 핵심 기능과 공통 기능의 구현을 분리함으로써
핵심 기능을 구현한 코드의 수정 없이 공통 기능을 적용할 수 있게 만들어준다.
2. PSA(Portable Service Abstraction 서비스 추상화) : 환경의 변화와 관계없이 일관된 방식의 기술로의 접근 환경을
제공하는 추상화 구조를 말한다. 이는 POJO 원칙을 철저히 따른 Spring의 기능으로 Spring에서 동작할 수 있는
Library들은 POJO 원칙을 지키도록 PSA 형태의 추상화가 되어있음을 의미한다. 잘 만든 인터페이스 하나가 열 클래스 부럽지 않다. PSA를 '잘 만든 인터페이스'라고도 한다. PSA가 적용된 코드는 나의 코드가 바뀌지 않고, 다른 기술로 간편하게 바꿀 수 있도록 확장성이 좋고 기술에 특화되어 있지 않는 코드를 의미한다. Spring은 Spring Web MVC, Spring Transaction, Spring Cache 등 다양한 PSA를 제공한다.
● Spring Web MVC
● @Component
● @Controller
●@Repository
●@Service
● @RequestMapping
● ...
● Spring Transaction
● JDBC
● JPA
● Spring Cache
● Cacheable
● CacheEvict
● ...
3. IoC / DI
IoC(Inversion of Control 제어의 역전) :이는 Spring뿐만 아니라 모든 프로그래밍에서 사용될 수 있는 범용적인 개념이다.
package com.example.firstproject.service;
import com.example.firstproject.dto.ArticleDto;
import com.example.firstproject.entity.Article;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
importstatic org.junit.jupiter.api.Assertions.*;
@SpringBootTest// 해당 클래스는 스프링부트와 연동되어 테스팅된다.classArticleServiceTest{
@Autowired ArticleService articleService;
@TestvoidreadAll(){
// 예상
Article a = new Article(2L, "AAAA", "1111");
Article b = new Article(3L, "BBBB", "2222");
Article c = new Article(4L, "CCCC", "3333");
List<Article> expectedList = new ArrayList<Article>(Arrays.asList(a, b, c));
// 실제
List<Article> articleList = articleService.readAll();
// 비교
assertEquals(expectedList.toString(), articleList.toString());
}
@Testvoidread_success(){ // read 메소드 성공 테스트// 예상
Long id = 2L;
Article expected = new Article(id, "AAAA", "1111");
// 실제
Article article = articleService.read(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Testvoidread_fail(){ // read 메소드 실패 테스트 // 존재하지 않는 id를 입력한 경우// 예상
Long id = -1L;
Article expected = null;
// 실제
Article article = articleService.read(id); // return articleRepository.findById(id).orElse(null); 이렇게 작성되어 있으므로 null 값을 갖는 expected로 비교// 비교
assertEquals(expected, article); // null은 toString 메소드를 호출할 수 없음
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voidcreate_success(){ // create 메소드 성공 테스트 // title과 content만 있는 dto 입력// 예상
String title = "DDDD";
String content = "4444";
ArticleDto dto = new ArticleDto(null, title, content);
Article expected = new Article(1L, title, content);
// 실제
Article article = articleService.create(dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voidcreate_fail(){ // create 메소드 실패 테스트 // id가 포함된 dto 입력// 예상
String title = "DDDD";
String content = "4444";
ArticleDto dto = new ArticleDto(2L, title, content);
Article expected = null;
// 실제
Article article = articleService.create(dto); // id가 존재하는 경우 null return하도록 작성되어 있음// 비교
assertEquals(expected, article);
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voidupdate_success_1(){ // update 메소드 성공 테스트 케이스 1 // 존재하는 id와 변경할 title, content만 있는 dto 입력// 예상
Long id = 2L; // 대상 id
String title = "AAAAAAAA"; // 변경할 title
String content = "11111111"; // 변경할 content
ArticleDto dto = new ArticleDto(null, title, content);
Article expected = new Article(id, "AAAAAAAA", "11111111");
// 실제
Article article = articleService.update(id, dto);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voidupdate_success_2(){ // update 메소드 성공 테스트 케이스 2 작성해보기
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voidupdate_fail_1(){ // update 메소드 실패 테스트 케이스 1 // 존재하지 않는 id와 변경할 title, content만 있는 dto 입력// 예상
Long id = -1L; // 대상 id
String title = "AAAAAAAA"; // 변경할 title
String content = "11111111"; // 변경할 content
ArticleDto dto = new ArticleDto(null, title, content);
Article expected = null;
// 실제
Article article = articleService.update(id, dto); // id에 해당하는 엔티티가 없는 경우 null return하도록 작성되어 있음// 비교
assertEquals(expected, article);
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voidupdate_fail_2(){ // update 메소드 실패 테스트 케이스 2 작성해보기
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voiddelete_success_1(){ // delete 메소드 성공 테스트 케이스 1 // 존재하는 id 입력// 예상
Long id = 2L;
Article expected = new Article(id, "AAAA", "1111");
// 실제
Article article = articleService.delete(id);
// 비교
assertEquals(expected.toString(), article.toString());
}
@Test@Transactional// 조회가 아닌 생성, 변경, 삭제의 경우 Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다.voiddelete_fail_1(){ // delete 메소드 실패 테스트 케이스 1 // 존재하지 않는 id 입력// 예상
Long id = -1L;
Article expected = null;
// 실제
Article article = articleService.delete(id);
// 비교
assertEquals(expected, article);
}
}
테스트 코드 작성 후 메소드 옆 재생 버튼 클릭 > Run ArticleServiceTest 클릭하면 해당 메소드의 테스트가 시작된다.
화면 좌측 하단 Show Passed, Show Ignored를 선택하고
메소드명 옆에 녹색 체크(테스트 정상) 또는 X 표시(테스트 실패)를 확인한다.
모든 메소드에 대해 테스트를 진행하고자 한다면 클래스명 옆에 있는 재생 버튼을 클릭하면 된다.
조회가 아닌 생성, 변경, 삭제 테스트의 경우
Transaction으로 묶어서 Rollback할 수 있게 처리해줘야 한다. (어노테이션 추가 @Transactional)
# H2 DB, 웹 콘솔 접근 허용
spring.h2.console.enabled=true
# 실행과 동시에 데이터 생성 가능하도록 변경
spring.jpa.defer-datasource-initialization=true
# JPA 로깅 설정
# 디버그 레벨로 쿼리 출력
logging.level.org.hibernate.SQL=DEBUG
# 정리해서 보여주기
spring.jpa.properties.hibernate.format_sql=true
# 파라미터 보여주기
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
# DB URL 고정 설정
# 유니크 URL 생성 X
spring.datasource.generate-unique-name=false
# 고정 URL 설정
spring.datasource.url=jdbc:h2:mem:testdb
<build.gradle에 추가=""> (Lombok 추가 : 코드 간소화, 로깅 가능하게 해주는 라이브러리)</build.gradle에>