스프링 입문 정리
컨트롤러 : 웹 페이지의 첫번째 진입점. 클래스에 @Controller 애노테이션을 붙여준다.
@GetMapping("hello") : 웹페이지/hello 주소로 get요청을 보낸다.
Model : 데이터를 보관하여 넘겨주는 역할을 한다. model.addAttribute("key", "value") 로 추가한다.
return "hello" : hello.html을 viewResolver를 통해 Model을 넣고 반환한다. 기본값이 resources:templates/{ViewName}.html 으로 동작한다.
html에서는 ${data}로 model의 값을 받아올 수 있는데, 이는 thymeleaf가 지원해준다.
3 - 웹 개발 기초
정적 컨텐츠는 서버 처리 없이 html 파일 자체를 반환한다.
동적 컨텐츠는 MVC를 모델을 틀으로 동작한다.
@RequestParam 을 추가할 경우 주소를 통해 파라미터를 받아올 수 있는데, 이는 default = true로 되어있어 파라미터를 넣지않으면 오류가 발생한다.
API : 어플리케이션 개발 시 JSON 형태로 데이터를 전송하는 경우가 많다.
@ResponseBody를 추가하면 viewResolver를 사용하지 않고, HttpMessageConverter를 사용하여 객체의 값을 JSON 형태로 변환하여 반환해준다. 객체를 반환하지 않고 문자를 반환하였을 경우 HTML의 BODY에 문자를 직접 삽입하여 반환한다. (html 형태가 아니다)
문자라면 StringHttpMessageConverter, 객체라면 MappingJackson2HttpMessageConverter, byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있으며 클라이언트의 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보를 조합하여 HttpMessageConveter가 선택되어 동작한다.
4 - 회원 관리 예제 - 백엔드 개발
비즈니스 요구사항 정의
- 데이터 : 회원ID, 이름
- 기능 : 회원 등록, 조회
- DB의 종류는 아직 모른다.
일반적인 웹 어플리케이션 구조
컨트롤러 -> 서비스 -> 리포지토리 -> DB
DB를 제외한 컨트롤러, 서비스, 리포지토리는 모두 도메인과 연결되어 있다.
서비스에서는 도메인 객체를 기반으로 동작하는 핵심 비즈니스 로직을 구현한다.
리포지토리는 DB에 접근하고 관리하는 것을 담당한다.
도메인은 비즈니스 도메인 객체라고 볼 수 있다. ex. 회원, 주문, 쿠폰 등
초기에는 구현체로 메모리 기반의 DB를 사용하고, 나중에 바꿔 끼울 수 있도록 리포지토리는 인터페이스로 구현한다. 서비스는 인터페이스를 구현한다.
인터페이스에서 Optional은 null을 처리 시에 Optional로 감싸서 반환해준다. Optional.ofNullable(...)
Map을 가지는 리포지터리 메모리 구현체인 MemoryMemberRepository를 작성한다.
Map<Long, Member> store와 long sequence를 가짐
테스트케이스를 given when then 기법을 통해 작성
테스트케이스별로 실행되는 순서가 보장되어 있지 않으므로 afterEach를 통해 clearStore를 해줌으로써 독립적인 테스트를 보장함
회원 서비스 : 리포지토리와 도메인(Member 객체)를 활용하여 비즈니스 로직을 작성하는 것
회원가입(join), 전체회원조회, 회원조회 기능을 구현한다.
MemberService에서는 memberRepository를 DI 받아야한다. 그래야 공유해서 쓰지..
5 - 스프링 빈과 의존관계
컨트롤러(외부 요청) -> 서비스(비즈니스 로직 동작) -> 리포지토리(데이터 저장) 로 의존관계가 연결되어 있고, 이를 스플이 컨테이너에 스프링 빈이 관리한다.
MemberController에서는 @Autowired를 통해 memberService를 주입받는다.
스프링 빈 등록 2가지 방법
1. 컴포넌트 스캔과 자동 의존관계 설정 (위 방법)
2. 자바 코드로 직접 스프링 빈 등록하기
컴포넌트 스캔 원리
- @Component - 스프링 빈으로 자동 등록
다음을 포함함 : @Controller, @Service, @Repository ...
생성자가 1개면 @Autowired는 생략 가능
스프링 빈은 싱글톤으로 관리됨. 모두 같은 인스턴스다!!
HelloSpringApplicatoin이 속해있는 패키지 이외의 패키지에서 애노테이션은 스프링에서 스캔하지 않으므로 적용되지 않음
자바 코드로 직접 스프링 빈 등록
SpringConfig 클래스 생성, @Configuration 애노테이션 등록
@Bean 을 메소드에 붙여 싱글톤 객체를 반환함
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
옛날엔 XML으로 설정했지만 요즘엔 잘 사용하지 않음
실무에서는 자바코드로 많이 사용
DI에는 필드 주입, setter 주입, 생성자 주입 3가지 방법이 있는데, 의존관계가 실행중 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장!!
실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용함
정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면(리포지토리를 변경하는 경우)설정을 통해 스프링 빈으로 등록한다. (지금 코드 그대로 멤버리포지토리만 바꿔치기 할 수 있다!! 설정만 변경하면 되니깐.. 컴포넌트 스캔을 사용하면 모든 코드를 손대야댄다)
@Autowired를 통한 DI는 스프링이 관리하는 객체에서만 동작하며, 스프링 빈으로 동작하지 않고 내가 직접 생성한 객체에서는 동작하지 않는다.
6 - 회원 관리 예제 : 웹 MVC 개발
간단한 회원가입 폼을 만들어보자!
"/" 으로 가는 컨트롤러를 만드면, 컨트롤러가 정적 파일보다 우선 순위가 높기때문에 컨트롤러가 호출된다.
// post로 회원가입한것을 받는다
@PostMapping(value = "/members/new")
public String create(MemberForm form) {
Member member = new Member();
member.setName(form.getName());
memberService.join(member);
return "redirect:/";
}
7 - DB 접근 기술
H2 데이터베이스 설치
옛날 방식...
sql구문의 ?에 파라미터를 넣음
Connection으로 연결을 설정
PreparedStatement로 sql과 파라미터를 넣고
ResultSet으로 결과를 받음
rs에서 처음에 빈공간을 가르키므로 rs.next()를 해서 첫번째 공간을 가르켜줘야함.
연결을 닫기 위해 예외처리도 해주고....
만드는 순서와 반대로 닫아줘야함 ResultSet -> PreparedStatement -> Conncetion
개방-폐쇄 원칙(OCP, Open-Closed Principle) : 확장에는 열려있고, 수정, 변경에는 닫혀있다.
스프링의 DI(Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다!!
@SpringBootTest : 스프링 부트를 실행하여 테스트
@Transactional : 테스트를 편리하게 하도록 AfterEach의 롤백 역할을 해준다.
JPA
- 기존의 반복 코드는 물론이고, 기본적인 SQL도 직접 만들어서 실행해준다
- JPA를 사용하면 SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
- JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
- 하나의 인터페이스이며, 객체와 ORM(Object, Relational, Mapping)으로 이루어져 있다.
- JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.
- 기본적인 CRUD 기능도 제공한다.
package hello.hello_spring.repository;
import hello.hello_spring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaRepository extends JpaRepository<Member, Long>, MemberRepository{
// JPQL select m from Member m where m.name = ?
// 위 명령어를 SQL로 실행한다.
// 메소드만으로도 구현이 끝난것!
@Override
Optional<Member> findByName(String name);
}
JpaRepository와 MemberRepository를 상속받은 인터페이스다.
메소드를 전혀 구현할 필요 없이, 이름과 파라미터만 적어주면 끝나 SQL로 실행하게 된다.
메소드이름을 findByNameAndId 이런식으로 파라미터와 함께 바꾸면 또 자동으로 적용된다.
스프링 데이터 JPA에서 기본적인 메소드들을 제공해준다.
인터페이스를 통한 기본적인 CRUD, 메소드 이름만으로 조회, 페이징 기능 자동 제공....
8 - AOP
AOP : Aspect Oriented Programming(관점 지향 프로그래밍)
공통 관심 사항(cross-cutting concern)과 핵심 관심 사항(core concern)으로 나뉜다.
모든 메소드에 대한 시간 측정은 공통 관심 사항이라고 할 수 있다.
시간 측정 로직은 핵심 관심 사항이 아니기 때문에 핵심 비즈니스 로직과 섞이게 되면 유지보수가 어렵고 공통 로직으로 만들기 매우 어렵다는 단점이 있기때문에 AOP를 이용해 분리해주는 것이다.
package hello.hello_spring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class TimeTraceApp {
// AOP가 적용되는 메소드들을 지정하는 문법
@Around("execution(* hello.hello_spring..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("START: " + joinPoint.toString());
try {
return joinPoint.proceed();
} finally {
long finish = System.currentTimeMillis();
long timeMs = finish - start;
System.out.println("END: " + joinPoint.toString() + " " + timeMs + "ms");
}
}
}
SpringConfig에 추가하는 대신 @Component로 대체하였다.
가상의 프록시라는 가짜 스프링 빈을 만들어 실제 메소드를 실행시킨다.
각 클래스에서 className.getClass()를 통해 CGLibrary를 통한 가상의 프록시명을 확인할 수 있다.