Skip to content

YGwan/spring-is-coming

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

spring-is-coming

Spring framework를 다루기 위한 기본부터 시작하기


  • Http
  • HttpServlet 직접 구현해보기
  • SpringMVC
  • JDBC
  • Spring JDBC


공부한 것 정리


spring과 springboot의 차이


- springboot는 내장 톰켓을 사용하기 때문에 톰켓 설정을 안해도 된다.
- springboot의 의존성 버전들을 자동으로 관리해주기 때문에 의존성 간의 버전 충돌을 해결하기 쉽다.
- springboot는 autoConfiguration을 제공한다.
- spring은 war파일로 배포해야하지만, springboot는 jar파일로 배포해서 더 손쉽게 배포가 가능하다.


BeanFactory와 ApplicationContext 차이


BeanFactory
  - 스프링 컨테이너의 최상위 인터페이스
  - 스프링 빈을 관리하고 조회하는 역할을 담당
      
ApplicationContext  
  - ApplicationContext는 BeanFactory의 기능을 상속
  - ApplicationContext는 빈 관리기능 + 편리한 부가 기능을 제공

BeanFactory나 ApplicationContext를 스프링 컨테이너라고 합니다.(BeanFactory을 직접 사용할 일은 거의 없고 대부분 ApplicationContext를 사용합니다.)


싱글톤 패턴에서 주의할 점


싱글톤 패턴이란 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴입니다.
스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리합니다.(싱글톤 컨테이너 역할) 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 합니다.

싱글톤 패턴 적용시 주의할점
  - 여러 클라이언트가 하나의 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태 유지(stateful)하게 설계하면 안됩니다.
    => 무상태로 설계해야 합니다.
        - 특정 클라이언트에 의존적인 필드 X
        - 특정 클라이언트가 변결할 수 있는 필드 X
        
스프링 빈의 필드에 공유 값을 설정하면 큰 장애가 발생할 수 있습니다.


@Configuration의 비밀


@Configuration이 붙은 클래스의 className을 출력해보면 클래스명@CGLIB~ 이런식으로 클래스명이 나타난 것을 확인할 수 있습니다.
이는 내가 만든 클래스가 아니라 스프링이 CGLIB라는 바이트코드 조작 라이브러리를 사용해서 AppConfig 클래스를 상속받은 임의의 다른 클래스를 만들고, 그 다른 클래스를 스프링 빈으로 등록한 것임을 확인할 수 있습니다.

해당 부분의 조작 라이브러리는 만약 특정 클래스를 스프링 컨테이너에 등록하려고 할때 이미 등록되어 있으면 스프링 컨테이너에서 찾아서 반환하고 
등록되어있지 않으면 기존 로직을 호출해서 객체를 생성하고 스프링 컨테이너에 등록하는 동작합니다.
(@Bean이 붙은 메서드마다 이미 스프링 빈이 존재하면 존재하는 빈을 반환하고, 스프링 빈이 없으면 생성해서 스프링 빈으로 등록하고 반환하는 코드가 동적으로 만들어집니다.)

이를 통해 싱글톤이 보장됩니다.


조회 대상 빈이 2개 이상일때 처리할 수 있는 방법


- @Autowired 필드명 매칭
- @Qualifier
- @Primary
등을 사용하여 처리할 수 있습니다.

@Autowired의 경우 필드명을 매칭할때 첫번째로 타입으로 매칭하고 그 후 타입이 같은게 여러개 있으면 해당 변수의 필드명, 파라미터 명으로 빈 이름을 매칭하기 때문에 사용 가능합니다.
@Qualifier의 경우 @Qualifer끼리 매칭을 하고 만약 해당 이름이 없으면 스프링 빈 이름으로 매칭을 진행합니다. (하지만 주로 @Qualifier을 통한 매칭을 선호합니다.)
@Primary의 경우 우선순위를 정하는 방법으로 여러개의 빈이 있을때 우선권을 가지게 하는 방법입니다.


스프링 빈의 라이프사이클(싱글톤의 경우)


스프링 컨테이너 생성 -> 스프링 빈 생성 -> 의존관계 주입 -> 초기화 콜백 -> 사용 -> 소멸전 콜백 -> 스프링 종료

빈 생명주기 콜백 지원 방법

- 인터페이스 사용(InitializingBean, DisposableBean)
  - 스프링에 너무 의존적이게 됨(메서드 명 수정 불가)
  - 초창기 주로 사용, 현재는 거의 사용 X

- 설정 정보(configuration)에 초기화 메서드 & 종료 메서드 지정
  - @Bean(initMethod = "초기화 메서드명", destroyMethod = "종료 메서드명")
  - 코드를 고칠 수 없는 외부 라이브러리에도 적용 가능
  - 스프링에 의존 X
  - destroyMethod의 경우 기본값이 (inferred)로 되어 있어 close, shutdown이라는 이름의 메서드를 자동으로 추론 호출

- @PostConstruct, @PreDestroy 애노테이션 사용
  - 최신 스프링에서 권장하는 방법
  - 어노테이션 하나만 붙이면 되므로 매우 편리하다.
  - 외부 라이브러리에는 적용하지 X(적용이 필요할 경우 위의 방법을 사용)


프로토타입 빈이란?


- 스프링 컨테이너에 요청할 때 마다 새로 생성됩니다.
- 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여합니다.
- 싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 메서드가 실행 되지만, 프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행됩니다.
- 자주 사용하지 않음(대부분 싱글톤 빈으로 문제를 해결할 수 있기 때문입니다.)


프로토타입 스코프와 싱글톤 빈을 함께 사용시 문제 해결 방법


keyword : DL(Dependency Lookup)
  - 의존관계를 외부에서 주입(DI) 받는 것이 아니라 직접 필요한 의존관계를 찾는 것을 말합니다.

- 스프링 컨테이너에 매번 요청
  - 권장되지 않음(ApplicationContext 전체와 의존성을 가지게 되기 때문에)

- ObjectFactory, ObjectProvider 사용
  - ObjectProvider를 주로 사용(ObjectFactory에 여러 편의 기능을 추가한 것)
  - ObjectProvider 의 getObject() 사용 -> DL 기능 제공

- JSR-330 Provider
  - javax.inject.Provider 라는 JSR-330 자바 표준을 사용하는 방법입니다.
  - 추가적인 라이브러리를 gradle에 추가해야 합니다.
  - provider 의 get() 사용 -> DL 기능 제공


JPA를 사용해서 개발할때 Entity의 경우 기본 생성자가 필수적으로 필요한 이유


JPA 구현 라이브러리가 객체(Entity 등)를 생성할때 기본적으로 리플랙션, 프록시 등의 기술을 써야되는데 기본 생성자가 없으면 해당 작업이 안돼 에러가 발생한다.
기본 생성자가 public이면 접근이 다 가능하기 때문에 spring에서는 생성자의 경우 protected 접근제한자까지 허용한다.


JPA를 사용해서 개발할때는 왜 특정 Entity에 접근한 뒤에 값을 해당 Entity의 값을 변경하고 save()를 하지 않았는대도 DB에 변경사항이 반영될까?


JPA의 Dirty Checking 덕분입니다.
 - Dirty Checking 이란 transaction 안에서 엔티티의 변경이 일어나면, 변경 내용을 자동으로 데이터베이스에 반영하는 JPA 특징 중 하나입니다.
 - Entity 안에 있는 데이터들이 바뀔때 바뀐 변경포인트를 JPA가 알아서 찾고 DB에 UPDATE 쿼리를 날립니다.
 - 영속성 컨택스트(Persistence Context) 안에 있는 엔티티를 대상으로 더티 체킹이 일어납니다.


대표적인 Spring 개발 패턴 2가지


1. 도메인 모델 패턴
  - 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것
  - 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할만을 담당합니다.
  - 각 객체에 객체가 수행해야 하는 업무를 분담시킵니다.
  - 재사용성, 확장성, 그리고 유지 보수의 편리하지만 엔티티 설계시 많은 노력이 필요합니다.

2. 트랜잭션 스크립트 패턴
  - 엔티티에는 비즈니스 로직이 거의 없고 서비스 계층에서 대부분 의 비즈니스 로직을 처리하는 것
  - 구현이 쉽지만 비즈니스 로직이 복잡해지면 코드가 난잡해질 수 있습니다.

둘 중에 뭐가 맞고 틀리다는 정해져 있지 않습니다. 하지만 개인적으로 비즈니스 로직으로 나눌때 해당 비즈니스 로직이 커져 코드의 관리가 힘들다면 도메인 모델 패턴으로 개발하는 것이 더 코드 관리가 좋다고 생각합니다.
프로젝트를 하다보면 하나의 서비스에 많은 Repository가 의존되는 경우가 종종 있습니다. 이렇게 해서 코드를 작성할때 해당 서비스 코드가 너무 커져 코드 관리가 힘든 적이 있었습니다. 이때 이를 도메인 모델 패턴으로 바꾼다면 좀 더 코드의 관리가 편해지고 더 객체지향적인 설계가 가능할 것이라고 생각합니다.


데이터 업데이트 방식 2가지와 선호되는 방식


이미 DB에 저장되어있는 값을 업데이트 하는 방법은 크게 2가지가 존재합니다.
  1. Dirty Checking(변경 감지)
  2. Merge(병합)

변경 감지 방법은 영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법입니다.
트랜잭션 커밋 시점에 JPA가 엔티티 변경을 감지하고 변경된 사항이 있다면 UPDATE 쿼리를 날립니다.

병합 방법은 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회하고 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체합니다.
이후 트랜잭션 커밋 시점에 변경 감지 기능을 통해서 데이터베이스에 UPDATE 쿼리를 날립니다.
이 방법은 결국 모두 교체하기 때문에 만약 병합시에 특정 값이 없으면 null로 업데이트하기 때문에 위험이 있을 수 있습니다.

따라서 값을 업데이트할때는 변경 감지 기법을 통해 업데이트 하는것이 좋습니다.



코드 작성 시 고민했던 내용


Query문에서 =, EXISTS, IN의 차이가 뭘까요?

"exist"는 존재 여부를 판단하고 결과는 참 또는 거짓입니다.(여러 값 비교)
"in"은 값이나 집합에 속하는지 여부를 판단하고 결과는 참 또는 거짓입니다.(여러 값 비교)
"="은 두 값이 동일한지 여부를 판단하고 결과는 참 또는 거짓입니다.(단일 값 비교)

EXISTS는 조건에 해당하는 ROW의 존재 유무와 체크 후 더이상 수행하지 않지만 IN의 경우 조건에 해당하는 ROW의 컬럼을 비교하여 체크한다.

즉 EXISTS은 SELECT 절을 평가하지 않으므로 일반적으로 IN에 비해 성능이 좋습니다.
EXISTS : 메인 쿼리의 결과값을 서브 쿼리에 대입하여 조건 비교 후 결과를 출력한다. ( 메인쿼리 -> EXISTS 쿼리 )
IN : 서브 쿼리의 결과값을 메인 쿼리에 대입하여 조건 비교 후 결과를 출력한다. ( IN쿼리 -> 메인 쿼리 )

프로젝트에서 토큰을 다루기 위해 이 의존성들이 모두 필요한가요?

jjwt-api는 토큰 생성을 위한 객체들을 추상화 하기 위한 라이브러리입니다.
jjwt-impl는 JJWT 라이브러리의 구현을 가리킵니다. 이 종속성을 Java 프로젝트로 가져오면 JJWT 라이브러리에서 제공하는 클래스 및 메서드에 액세스하여 JWT를 사용할 수 있습니다.
jjwt-jackson의 경우 JWT의 JSON 직렬화 및 역직렬화를 처리하기 위해 JJWT(Java JWT)와 함께 자주 사용되는 또 다른 종속성입니다.
(그런데 jjwt-jackson의 종속성을 추가하지 않고 실행했을때 실행은 원활하게 됐는데 실제 jwt를 생성하는 과정에서 에러가 발생하는 것을 확인했습니다. 따라서 최종적으로 해당 종속성을 다시 추가하니 문제 없이 돌아가는 것을 확인했습니다.)

그러므로 토큰을 생성 & 다루기 위해서는 3가지의 종속성이 다 필요합니다.

JWT Token의 payLoad에 들어가야할 내용이 뭘까요?

JWT TOKEN은 크게 Header, payLoad, SIGNATURE 이렇게 3부분으로 나누어져 있습니다.

Header (헤더): JWT 토큰의 헤더는 토큰의 타입과 해싱 알고리즘을 지정합니다. 일반적으로 JSON 형식으로 표현되며 다음과 같은 구조를 갖습니다.
Payload (페이로드): JWT 토큰의 페이로드는 클레임(claim)이라고도 불리는 정보를 포함합니다.
Signature (서명): 헤더와 페이로드를 이용하여 서명된 부분입니다. 서명은 토큰이 유효한지 검증하는 데 사용됩니다.

즉, 페이로드에 사용자를 구분할 수 있는 정보가 들어갑니다. 이를 통해 앱 내에서 사용자의 정보를 가져올때 쓸 수 있습니다.
그런데, 페이로드의 경우 SecretKey가 없이도 해석(Decoding)이 가능합니다.
발급 받은 토큰을 jwt.io에다가 넣게 되면 SecretKey없이 해당 페이로드의 정보를 가져올 수 있는 것을 확인할 수 있습니다. 즉, 페이로드에는 사용자의 중요한 정보(비밀번호 등)을 넣으면 안됩니다.
SecretKey는 서명 부분에 사용되며 해당 키를 통해 토큰이 유효한지 검증할 수 있습니다.

메서드명 validate & valid의 개발자가 바라보는 관점의 차이가 뭘까요?

valid는 '유효한' 이라는 의미입니다. 따라서 개발자들은 해당 단어로 시작하는 함수명(validLogIn, validPasswd 등)을 보고 해당 함수의 동작의 반환으로 true & false값을 기대할 것입니다.
validate는 '검증/확인하다'의 의미입니다. 따라서 개발자들은 해당 단어로 시작하는 함수명(validateLogIn, validatePasswd 등)을 보고 해당 함수의 동작의 반환으로 void값을 기대할 것입니다.

이런 부분의 경우 나만의 스타일을 가지고 정하거나 현재 통용되고 있는 형식에 맞춰 하는것이 팀 프로젝트에서 매우 중요하다는 것을 느꼈습니다.

DB에러를 바로 사용자에게 보여줘도 될까요?

DB에러의 경우 사용자에게 바로 보여주는 것은 좋지 않습니다. DB오류의 내용을 사용자에게 노출하면 해커나 악의적인 사용자가 정보를 얻을 수 있습니다.
이러한 정보를 통해 공격을 할 수 있기 때문에 보안상 이유로 바로 보여주는 것은 안전하지 않습니다.
또한, DB에러의 경우 로그의 역할로써 개발자를 위한 디버깅 도구로서는 의미가 있지만 사용자에게는 사용자에게 혼동을 주거나 사용자 경험을 저하하는 등의 부정적인 영향을 끼칠 수 있습니다.(서비스에 대한 신뢰도 저하 등)

따라서 사용자에게 DB 오류 메시지를 직접 보여주는 대신, 시스템은 안정성을 유지하고 개발자에게 알림을 보내는 것이 좋습니다.
개발자는 알림을 받고 빠르게 DB 오류를 확인하고 해결할 수 있기 때문입니다.
사용자에게는 친절한 오류 메시지를 제공하여 문제가 발생했음을 알리고, 개발자가 조치를 취하고 시스템을 복구할 때까지 기다리도록 안내하는 것이 바람직합니다.


주요 관점 및 배운 내용


  1. 주석 없이 해당 함수 or 변수가 어떤걸 의미하는지 알 수 있는 명확한 변수명을 선택하기
  2. DB에서 나는 에러는 사용자에게 바로 보여지면 안되고 항상 필터링 해서 보여주기(해킹 위험)
  3. 의미 없는 줄 바꿈이나, 클래스 간 코드 형식을 항상 고려해서 코드를 작성하기
  4. 초반 설계의 중요성 (DB를 합쳐보면서 초반 설계를 잘못하면 대규모 프로젝트에서는 매우 힘들겠다는 것을 느꼈습니다.)
  5. 임시 로직, 테스트 용 코드는 리뷰 요청 전에 제거 or 수정하기
  6. 스프링의 전반적인 구조 및 사용방법
  7. 코드 리뷰 및 팀 프로젝트의 문화를 배웠습니다.(PR, CODE REVIEW, MERGE)

About

springboot framework 기본기

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published