[면접공부] - java, spring (feat.gpt)
- 국비지원 (스파르타)
- 2025. 4. 23. 23:57
- 클래스 멤버 변수 초기화 순서에 대해 설명해주세요.
- 의존성 주입(DI, Dependency Injection)에 대해 설명해주세요.
- Spring Filter와 Interceptor에 대해 설명하고, 사용 예시를 설명해주세요.
- @Transactional에 readOnly 속성을 사용하는 이유에 대해서 설명해주세요.
- JPA와 같은 ORM을 사용하면서 쿼리가 복잡해지는 경우에는 어떻게 해결하는게 좋을까요?
이렇게 총 5가지로 질문을 주셨다.
뭔가 java와 스프링을 섞어서 질문을 주셨다.
대부분 아는거이긴 한데 이번에도 정리가 좀 부족하다는 생각이 들었다.
그리고 5월초아니면 국비종료한후에 지금까지 질문받았던 내용들을 한번더 정리하는 시간을 갖는것이 좋을거 같다.
1번 질문 같은 경우는 클래스 맴버 변수에 대해 알아야 할거 같다.
내가 알기로는 인스턴스 변수로 알고 있는데 이 용어에 대해 그리 와닿지는 않는 듯한 느낌이든다.
클래스 멤버 변수는 클래스에 속한 변수를 말하는데, 크게 두 가지로 나누어진다.:
- 인스턴스 변수 (Instance Variable)
- 클래스 변수 / 정적 변수 (Class Variable / Static Variable)
그러니까 메모리에 직접 올라가냐 아니냐의 차이 같은데
이에 대해 초기화 순서에 대해 알 필요가 있을거 같다.
초기화 순서는 내가 생각할때는 다음과 같다. 정적 변수 즉, 클래스 변수가 초기화가 가장먼저 발생하게 되어진다. 왜냐하면 클래스 변수는 메모리에 올라가기때문에 먼저 올라가줘야 된다고 생각했기 때문이다. 그리고 나서 인스턴스 변수가 초기화 되어진다.
초기화 방법에는 3가지가 존재한다. 생성자, 변수, 메소드 초기화 방법이 있는데 제일먼저 초기화 되는건 클래스 변수 -> 생성자 -> 메소드 -> 변수 순으로 초기화가 되어지지 않을까 추측한다.
자바에서 클래스 멤버 변수 초기화는 static 변수와 인스턴스 변수로 나눠서 순서가 다릅니다.
- static 변수는 클래스가 처음 로딩될 때 한 번 초기화되고,
순서는 static 변수 선언 → static 초기화 블록 순서대로 실행됩니다. - 인스턴스 변수는 객체가 생성될 때마다 초기화되고,
순서는 인스턴스 변수 선언 → 인스턴스 초기화 블록 → 생성자 순으로 진행됩니다.
상속이 있을 경우에는
부모 static → 자식 static → 부모 인스턴스 → 자식 인스턴스 순서로 실행됩니다.
즉, 전반적인 흐름은 정적(static) → 동적(instance) 순서로 진행된다고 보시면 됩니다.
추가로 이러한것을 질문 할 수 있다고 한다.
- static 블록의 용도는?
static 블록은 클래스가 JVM에 로딩될 때 단 한 번 실행되는 블록입니다.
주로 static 변수의 복잡한 초기화 로직이 필요할 때 사용되며,
정적 자원을 로딩하거나 로깅, 캐시 세팅 같은 초기 설정 작업에 자주 활용됩니다. - 생성자에서 인스턴스 변수 재할당 가능성?
인스턴스 변수는 선언과 동시에 기본값이나 초기값을 가질 수 있지만, 생성자에서 해당 값을 덮어쓸 수 있습니다.
즉, 생성자는 인스턴스 초기화의 최종 단계로, 초기화 순서상 가장 마지막에 실행되기 때문에 재할당이 가능합니다. - static 변수 초기화 순서를 활용한 패턴 예시 (ex. 싱글톤 초기화)
static 변수 초기화 순서를 활용한 대표적인 예는 싱글톤 패턴입니다.
static 블록 또는 static final 변수를 활용해서 클래스가 로딩될 때 객체를 딱 한 번만 생성하게 만들 수 있습니다.
간단하게 작성해봤구 이걸 그림으로 한번 그려보자.
물론 gpt로 그려봤다. 이 질문은 요정도만 하면 되지 않을까 싶다.
두번째 의존성 주입.. 이거 같은 경우는 자바 객체를 생성할때 new 키워드를 통해 인스턴스를 생성하지만 DI를 사용하게 되면, 외부에서 객체를 주입을 시키는 방식입니다. 이렇게 하게 되면 생성하는곳과 실제로 구현하는 곳을 분리를 시켜 SRP를 지킬 수 있어 더 유연한 구조로 개발할 수 있다는 장점을 가지고 있습니다.
의존성 주입은 객체 간의 의존 관계를 외부에서 주입해주는 설계 패턴입니다.
자바에서는 보통 한 클래스가 다른 클래스를 사용할 때 new 키워드로 직접 의존 객체를 생성하게 되는데요,
이렇게 되면 결합도가 높아져서 테스트나 유지보수에 어려움이 생길 수 있습니다.
DI를 사용하면 객체를 직접 생성하는 게 아니라, 필요한 객체를 외부에서 주입받는 방식으로 설계를 할 수 있어서
느슨한 결합(loose coupling)을 유지할 수 있고, 테스트 코드에서도 Mock 객체로 쉽게 대체할 수 있는 장점이 있습니다.
[어떻게 주입하나요?]
"대표적으로 세 가지 방식이 있습니다."
- 생성자 주입
"가장 권장되는 방식이고, 객체 불변성을 유지할 수 있어요." - 필드 주입
"편하긴 하지만 테스트나 리팩터링 시 불편할 수 있어서 요즘은 잘 사용하지 않습니다." - Setter 주입
"선택적인 의존성에 사용할 수 있지만, 명시적인 불변성을 보장하진 못합니다."
[Spring에서는 어떻게 DI가 동작하나요?]
Spring에서는 @Component, @Service 같은 어노테이션으로 빈을 등록하고,
@Autowired 또는 생성자를 통해 필요한 의존 객체를 자동으로 주입받을 수 있습니다.
요즘은 롬복의 @RequiredArgsConstructor를 함께 사용하는 방식이 실무에서 가장 많이 쓰입니다.
흐음 이거는 스프링 문제였구먼...
자바인줄...
더 자세하게 학습은 하고 싶지만 나도 다른거 공부하는 입장이라
다음은 Filter이랑 인터셉터 물어보는 질문인데 아 이거.. 그전에 학습했던건데... 후..
- 필터는 프로토콜 레벨에서 전체적인 요청 흐름을 다루며, 서블릿 컨테이너 수준에서 먼저 실행됩니다. HTTP 요청을 전반적으로 처리하고 싶을 때 유용합니다.
- 리퀘스트,리스펀스 조작이 가능하다.
- 자바 스팩
- 필터 체이닝으로 오더링
- 인터셉터는 디스패치 서블릿과 핸들러 메소드 사이에서 실행되므로, 컨트롤러 메소드 호출 전후로 처리해야 할 때 적합합니다. 보통 권한 검사나 로깅 등을 처리할 때 사용됩니다.
- 오더링주는 방법을 알아보면 좋겠다.
- AOP는 비즈니스 로직 단위로 메소드 실행 전후로 적용됩니다. 주로 로깅, 트랜잭션 관리, 성능 측정 등의 메소드 단위 공통 관심사를 처리합니다.
요렇게 학습했단말야!!
근데 이거 그전에 AOP질문나온건데..ㅎ
Spring에서 Filter와 Interceptor는 공통 관심사 처리를 위한 도구이지만,
작동 시점과 사용 목적에 차이가 있습니다.
Filter
- Servlet 스펙 기반의 기능으로, DispatcherServlet 앞단에서 동작합니다.
- 모든 요청이 DispatcherServlet에 도달하기 전에 가로채 처리할 수 있어요.
- 주로 인코딩 설정, CORS, 보안 필터, 로그 처리 등에 사용됩니다.
Interceptor
- Spring MVC의 기능이며, HandlerMapping 이후, 컨트롤러 진입 전/후에 동작합니다.
- 주로 인증/인가 처리, 세션 체크, 비즈니스 전후 작업 로깅, API 사용 기록 등에 활용됩니다.
사용예시
실무에서는 Filter를 통해 CORS 처리나 XSS 방지 필터를 등록해서
모든 요청에 대해 사전 처리하는 용도로 사용했고,
Interceptor는 주로 JWT 인증 토큰 검증에 사용했습니다.
컨트롤러 진입 전에 토큰을 확인하고, 유효하지 않으면 요청을 차단하는 방식으로 구현했습니다.
네번째, @Tranjaction의 readOnly같은 경우는 그냥 무심결 사용하는거 같다. 내가 알기로는 읽기와 쓰기를 구분짓기 위해 사용이 되어지는 정도로 알고 있다.
그렇다면 진짜는 어떨까?
@Transactional(readOnly = true)는 해당 트랜잭션이 데이터를 읽기만 하고 변경하지 않는다는 힌트를
JPA나 데이터베이스에게 주는 역할을 합니다.
이 설정을 통해 내부적으로는
- JPA가 dirty checking을 생략하고
- flush도 생략하게 되며,
- 일부 DB에서는 read-only 트랜잭션 최적화가 적용될 수 있기 때문에
결과적으로 성능이 개선될 수 있습니다.
이정도만 하면 될거 같다.
마지막 질문은 JPA에서 ORM을 사용할때 쿼리가 복잡해지는 경우 어떻게 되나는 질문인데
내가 알기로는 가장먼저 JPQL을 먼저 사용하지만 동적 쿼리 같은 경우 JPQL로 부족할 수 있다고 생각합니다. 그럴때 사용할 수 있는 것이 Query DSL을 사용할 수 있습니다. 그럼에도 불구하고 통계쿼리 같은 경우 쿼리 DSL로 부족할 수 있습니다. 그럴때는 네이티브 쿼리를 사용할 수 있다는 정도로 알고 있습니다.
라고 말했던거 같다.
JPA는 객체 중심 개발에 유리하지만, 복잡한 쿼리를 작성할 때는 여러 제약이 따릅니다.
그래서 저는 복잡도에 따라 아래와 같은 방식들을 단계적으로 적용하고 있습니다
1단계: JPQL 활용
"먼저 단순한 조건 추가나 조인을 통해 해결 가능한 경우는
@Query나 EntityManager를 통해 JPQL을 사용합니다.
2단계: QueryDSL 사용
"쿼리 조건이 동적으로 바뀌거나, 조건 조합이 많아지는 경우는
QueryDSL을 사용해서 타입 안정성과 가독성을 확보합니다."
- 장점: 자동완성, 컴파일 체크, 동적 쿼리 유리
- 실무에서 필터링, 검색 조건이 많은 경우에 많이 사용
3단계: Native Query 또는 SQL Mapper 분리
"JPA로 처리하기 어렵거나 퍼포먼스가 중요한 쿼리는
Native Query를 사용하거나, MyBatis 같은 SQL Mapper와 혼용하기도 합니다.
복잡한 서브쿼리, 윈도우 함수 등 SQL 전용 기능이 필요할 때 활용
결국 복잡한 쿼리는 유연성, 유지보수성, 성능을 고려해서
QueryDSL → Native Query 순으로 전략적으로 선택하고 있습니다.
이렇게 하면 ORM의 장점은 살리면서 복잡한 요구사항도 충분히 대응할 수 있다고 생각합니다.
특히 QueryDSL을 도입한 이후에는
조회 조건이 많은 화면에서도 조건 분기 로직을 깔끔하게 관리할 수 있어서
프론트 요구사항 변경에도 빠르게 대응할 수 있었습니다.
요런식으로 대답할 수 있다고 한다. 그리고 추가 질문으로 JPQL은 1차 캐시에 영향을 받는냐는 질문에는 답변하지 못했지만
1차캐시에는 영향을 받지 않는다고 한다.
요거 같은 경우 추후에 학습을 더 해보는것도 좋을거 같다.
'국비지원 (스파르타)' 카테고리의 다른 글
[학습] 젠킨스 (CI/CD) (2) | 2025.04.25 |
---|---|
[면접공부] - JPA (feat.gpt) (1) | 2025.04.24 |
[면접공부] - db (feat.gpt) (4) (1) | 2025.04.18 |
[면접공부] - db (feat.gpt) (3) (0) | 2025.04.17 |
[면접공부] - db (feat.gpt) (2) (1) | 2025.04.16 |