스프링 입문 정리
Mind
- Spring의 모든 것을 외울 수는 없다. 필요한 것을 찾는 능력이 중요하다.
- 입문 강의는 원리를 이해하기보다는 전체 과정을 훑고 기술의 필요성을 인지하는 단계이다.
Gradle
Spring Initializer 에서 초기 프로젝트를 생성할 때 Maven과 Gradle 옵션이 있다. 강의에서는 Gradle을 사용했는데 Legacy 프로젝트가 아니라면 최근에는 대부분 Gradle을 쓴다고 한다. 강의에서는 프로젝트를 Open 할 때 자동으로 빌드되었고 이후 Gradle 파일을 수정할 때마다 refresh 가 필요했다.
-
Groovy를 기반으로 한 오픈 소스 형태의 자동화 도구
- Groovy : 자바에 Python, Ruby, Smalltalk 등의 장점을 결합한 동적 객체 지향 프로그래밍 언어
-
안드로이드 앱 개발환경, JAVA, C/C++, Python 등의 언어로 빌드가 가능하다.
-
Groovy를 사용해서 만든 DSL(Domain Specific Language)을 스크립트 언어로 사용한다.
- DSL : 웹페이지 영역에 특화되어 사용되는 HTML과 같이 특정한 도메인, 즉 영역이나 용도에 맞게 기능을 구성한 언어
-
이전에 사용했던 태스크를 재사용하거나 다른 시스템의 태스크를 공유할 수 있는 빌드 캐시 기능을 지원하므로 빌드의 속도를 향상시킬 수 있다.
Controller
-
@Controller
를 사용하여 컨트롤러임을 명시해야함 -
@GetMapping("hello")
: /hello로 들어오면 해당 메소드 호출 -
return "hello"
; : hello.html 로 데이터를 넘겨서 렌더링 -
addAttribute(key, value)
: return 에 의해 이동되는 html 파일에 사용될 key를 넘겨 준다. 템플릿에서 key는 Thymeleaf 템플릿 엔진에 의해 value로 치환된다. -
spring-boot-devtools 라이브러리를 추가하면, html 파일을 컴파일만 해주면 서버 재시작 없이 View 파일 변경이 가능하다. 그렇지 않다면 서버를 재시작해야 변경된다.
Get / Post
-
Get : 어떠한 정보를 가져와서 조회하기 위해서 사용되는 방식
-
Post : 데이터를 서버로 제출하여 추가 또는 수정하기 위해서 사용하는 방식
-
같은 url에 대해서 Get / Post Mapping이 모두 가능하다.
Logging
현업에서는 System.out.println()으로 출력하지 않고 로그로 출력해서 에러와 로그 파일을 관리한다고 한다. 강의에서는 System.out.println()을 사용할 예정
스프링 부트 로그 관련 표준 라이브러리
- slf4j (인터페이스)
- logback (구현체)
웹 개발
-
정적 컨텐츠 : 서버는 가만히 있고 파일을 웹브라우저에 그대로 내려주는 것
스프링에서 요청을 처리하는 순서(이는 MVC에서도 똑같이 동작)
- 컨트롤러에서 같은 이름이 있는지 찾는다. (templates/home.html)
- resources.static에서 찾는다 (static/index.html)
-
MVC와 템플릿 엔진 : html을 서버에서 동적으로 프로그래밍한 후 내려주는 것 ex) JSP, PHP
MVC 패턴을 쓰는 이유 : 역할과 책임을 명확히 나누기 위해서
@RequestParm()
를 이용하여 Parameter를 넘길 수 있다. -
API
- 모바일 클라이언트와 개발을 할 때는 json 포맷으로 클라이언트에 데이터를 전달하면 클라이언트가 알아서 화면을 그림
- 서버끼리 데이터를 전달할 때 사용
- 정적 컨텐츠, MVC 방식과 페이지 소스보기를 통해 비교하면 확연한 차이를 알 수 있음
@ResponseBody
는 viewResolver를 사용하지 않고 return으로 반환되는 객체를 json 형식으로 http body부에 전달MappingJackson(spring default)과 구글의 Gson은 객체를 json으로 바꾸는 라이브러리
웹 어플리케이션 계층 구조
- 컨트롤러 : 웹 MVC의 컨트롤러 역할
- 서비스 : 핵심 비즈니스 로직 구현
- 리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리
- 도메인 : 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨
Test
-
src 폴더 밑에 main과 test 디렉터리가 나뉘어져있다. (요즘에는 그만큼 테스트 코드가 중요하다는 의미)
-
실무에서는 로직 구현에 걸리는 시간보다 테스트 코드를 작성하는 시간이 더 오래 걸린다고 한다.
-
main 메서드를 실행하거나 웹 어플리케이션의 컨트롤러를 통해서 실행할 때의 단점
- 준비하고 실행하는데 오래 걸린다
- 반복 실행하기 어렵다
- 여러 테스트를 한번에 실행하기 어렵다
-
junit 5 ver. 프레임워크를 사용해서 위의 문제를 해결
-
Assertions
Assertions.assertThrows(Exception.class, Logic)
: Logic을 실행했을 때Exception.class 예외가 터지기를 기대하며 확인
-
Assertions.assertEquals(Expected, actual)
: Expected와 actual이 다르다면 AssertionFailedError 출력Assertions.assertThat(Expected).isEqualTo(actual)
: 위와 똑같지만 더 편하다고 한다.
-
TDD란? (같이 스터디하는 punsoo님이 정리를 잘 해주셨다.)
-
테스트 케이스의 메소드 명은 마음대로 해도 된다. 한글로 쓰기도 한다.
- 통합 테스트 vs 단위 테스트
- 통합 테스트 : 편하지만 느리다
- 단위 테스트 : 불편하지만 빠르다
- 단위 테스트가 더 좋은 테스트일 확률이 높다
-
given-then-when 문법
-
given : 주어진 상황(data)
-
when : 실행할 것, 검증할 것
-
then : 결과
@Test void 회원가입() { //given Member member = new Member(); member.setName("spring"); //when Long saveId = memberService.join(member); //then Member findMember = memberService .findOne(saveId).get(); assertThat(member.getName()) .isEqualTo(findMember.getName()); }
-
-
어노테이션
@Test
: 메소드마다 run이 가능하도록한다.@AfterEach
: 같은 클래스 내의 메소드가 끝날때마다 해당 메소드를 Run, 강의에서는 데이터 클리어할 때 사용@BeforeEach
: 같은 클래스 내의 메소드가 시작하기 전에 해당 메소드를 Run@Transactional
: 테스트 시작 전에 트랙잭션을 시작하고 테스트 완료 후에 항상 롤백함, DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않음 (AfterEach를 통한 데이터 클리어와 동일한 기능)@Commit
:@Transactional
이 걸려있어도 테스트 돌린 후 결과를 DB에 반영함@SpringBootTest
(스프링통합테스트) : 스프링 컨테이너와 테스트를 함께 실행함
DI(Depedency Injection)
-
컴포넌트 스캔
@Controller
: 스프링이 뜰 때 스프링 컨테이너에 스프링 빈을 컨트롤러로 등록해 줌@Service
: 스프링이 뜰 때 스프링 컨테이너에 스프링 빈을 서비스로 등록해 줌@Repository
: 스프링이 뜰 때 스프링 컨테이너에 스프링 빈을 리포지토리로 등록해 줌@Autowired
: 생성자에 사용하면 객체 생성 시점에 스프링 컨테이너에서 해당 스프링 빈을 찾아서 주입함, 생성자가 하나라면 생략 가능스프링이 관리하는 객체에서만 동작한다. 스프링 빈으로 등록하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.
@Component
: 스프링 빈으로 자동 등록됨, main 메소드가 있는 클래스 파일의 패키지 하위에서만 탐색함(설정을 통해 변경가능)@Controller
,@Service
,@Repository
는 내부적으로 @Component를 품고있음- 스프링은 스프링 컨테이너에 스프링 빈을 등록할 때, 기본으로 싱글톤으로 등록한다.(유일하게 하나만 등록해서 공유한다.) 따라서 같은 스프링 빈이면 모두 같은 인스턴스다. 설정으로 싱글톤이 아니게 설정할 수 있지만, 특별한 경우를 제외하면 대부분 싱글톤을 사용한다.
-
직접 스프링 빈 등록하기
-
메인파일이 있는 위치에 스프링 빈을 등록할 클래스 파일을 하나 만들고
@Configuration
를 달아준다.(강의에서는 SpringConfig 파일이 이에 해당함)
-
@Bean
을 생성자에 달아줘서 스프링 빈에 등록한다
- 생성자 주입, field 주입, setter 주입 3가지 방법이 있다. 의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다. 생성하는 시점에만 값을 넣고 그 이후에는 변경을 못하도록 할 수 있기때문이다.
-
-
주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.
-
정형화되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다. (강의에서는 이 경우에 해당함)
@Bean public MemberRepository memberRepository(){ // return new MemoryMemberRepository(); // return new JdbcMemberRepository(dataSource); // return new JdbcTemplateMemberRepository(); return new JpaMemberRepository(); }
스프링 DB 접근 기술
-
서버에 데이터를 저장하면 서버가 꺼지면 데이터가 모두 사라지므로 DB에 데이터를 저장해서 관리한다.
-
H2 Database는 주로 교육용으로 사용하며 가볍고 웹으로 화면도 띄워줘서 편함
-
순수 JDBC
- 고대의 기술. 자세한 설명은 생략한다.
-
스프링 JdbcTemplate
- Mybatis와 비슷한 라이브러리
- JDBC API에서 반복 코드를 대부분 제거해주지만 SQL은 직접 작성해야함
- 디자인 패턴 중에 템플릿 메소드 패턴을 사용함
-
JPA (Java Persistence Api)
-
기존의 반복 코드는 물론이고 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
-
SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
-
개발생산성을 크게 높일 수 있다.
-
ORM 기술이다.
-
EntityManager로 모든 것이 동작함, 리포지토리에 주입해야함
-
jpql(qlString) : 쿼리를 객체(엔티티)를 대상으로 날리면 sql로 번역함
-
기본적으로 Hibernate 오픈소스 구현체가 사용됨
-
어노테이션
@Entity
: JPA가 관리하는 entity가 됨@Id
: pk로 설정@GeneratedValue(strategy = GenerationType.IDENTITY)
: id를 자동으로 증가시키면서 생성@Column(name = "something")
: Column명을 DB의 “something”열과 매핑됨
-
-
스프링 데이터 JPA
- 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 완료할 수 있게 해주고 기본 CRUD 기능도 제공함
- 단순하고 반복적인 코드를 줄여주므로 핵심 비즈니스 로직에 집중 가능
- 스프링 데이터 JPA는 JPA를 편리하게 사용하도록 도와주는 기술이므로 JPA 학습이 선행되어야함
- 실무에서는 기본적으로 JPA와 스프링 데이터 JPA를 사용하고 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용함
- 위의 기술들로도 해결이 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나 JdbcTemplate을 동반하여 사용
AOP(Aspect Oriented Programming)
- 공통 로직과 핵심 로직을 분리하여 관리한다.
- 원하는 곳에 공통 관심 사항(로직)을 적용할 수 있게 해주고 개발자가 핵심 비즈니스 로직 개발에 집중할 수 있게 해준다.
@Aspect
를 달아주면 AOP class로 사용 가능하다.@Component
를 달아주거나 직접 스프링 빈에 등록(이 방법이 선호됨)해야함@Around("execution()")
: AOP 적용범위를 정해줌
Java
-
Map 컬렉션
Map 컬렉션은 key와 value로 구성된 Entry 객체를 저장하는 구조
여기서 키와 객체는 모두 객체
Map<Key type, Value Type> map = ~;
-
put()
: 객체를 추가 -
get()
: 키로 객체를 찾아옴 -
remove()
: 객체 삭제
Python의 dict()와 비슷한듯한데 더 심오한 내용을 담고 있어서 추가적인 학습이 필요하다
강의 예시에서는 HashMap을 사용했지만 실무에서는 동시성 문제를 해결하기 위해 ConcurrentHashMap을 사용한다고 한다
-
-
동시성 문제
- 두 개 이상의 세션이 공통된 자원에 대해 모두 읽고 쓰는 작업(Read, Write)을 하려고 하는 경우 발생할 수 있는 문제
- 추가적인 내용
-
스트림
스트림은 자바 8부터 추가된 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자이다.
List<Sting> 컬렉션에서 요소를 순차적으로 처리하기 위해 Iterator 반복자를 사용할 때
List<String> list = Arrays.asList("java","python","kotlin"); Iterator<String> iterator = list.iterator(); while(iterator.hasNext()){ String name = iterator.next(); System.out.println(name); }
같은 문제에 Stream을 사용할 때
List<String> list = Arrays.asList("java","python","kotlin"); Stream<String> stream = list.stream(); stream.forEach( name -> System.out.println(name));
위 두가지 방법의 출력이 같다
- Stream의 특징
Stream은 Iterator와 비슷한 역할을 하는 반복자이지만 람다식으로 요소 처리 코드를 제공하는 점과 내부 반복자를 사용하므로 병렬 처리가 쉽다는 점 그리고 중간 처리와 최종 처리 작업을 수행하는 점에서 많은 차이를 가지고 있다.
-
java 람다식
(타입 매개변수, …) -> {실행문; …}
(타입 매개변수, …)는 {} 를 실행하기 위해 필요한 값을 제공하는 역할
(int a) -> {System.out.println(a); }
매개변수 타입은 런타임 시 대입되는 값에 따라 자동으로 인식될 수 있기때문에 일반적으로 생략한다
하나의 매개변수만 있다면 ()를 생략할 수 있다
하나의 실행문만 있다면 {}를 생략할 수 있다
a -> System.out.println(a)
-
필터링
filter(Predicate)
: 조건 필터링, Predicate가 true를 리턴하는 요소만 필터링- distinct() : 중복 제거
- 참고 : 파이썬 filter()
filter(function, iterable)
: 순회 가능한 데이터 구조(iterable)의 모든 요소에 함수를 적용하고 그 결과가 True인 것들을 filter object로 반환
Errors와 주의할 점
-
강사가 사용하는 IntelliJ는 유료버젼이고 내가 사용하는 IntelliJ는 커뮤니티 버젼이다. 이 차이로 인해 강의에서 보이는 화면과 내 화면이 다르게 보였는데 오타가 난 줄 알고 헤매며 많은 시간을 소요하게 되었다.
-
H2 콘솔을 켜지 않고 DB를 조작하는 테스트 코드를 Run하면 에러가 발생한다.
-
Preferences에서 Gradle 설정의 Build and run 부분을 Gradle에서 IntelliJ IDEA로 바꾸면 Gradle을 통하지 않고 IntelliJ에서 바로 java파일을 돌리므로 더 빠르게 돈다고 함
References
-
이것이 자바다 : 신용권의 Java 프로그래밍 정복