자바 프로그래밍 최종 학습일지

1주차

자료구조 (Data Structure)

1. List - 선형 자료구조

순서가 있는 데이터를 저장하는 선형 구조
배열 (Array): 고정 크기, 인덱스로 접근
링크드 리스트 (LinkedList): 동적 크기, 포인터로 연결
포인터: 다음 요소의 주소를 가리킴

2. Map - 키-값 쌍 구조

<Key, Value> 형태로 데이터를 저장
키(Key)를 통해 값(Value)에 접근
키는 중복 불가, 값은 중복 가능

3. Set - 중복 제거 집합

중복된 요소를 허용하지 않음
get() 함수가 없음 (키가 없기 때문)
집합 연산에 특화

연산 처리

자료구조에서 복잡한 연산을 수행하려면 Stream() 사용

인터페이스 (Interface)

특징

Abstract 메소드만 포함 (Java 1.8 이후 default, static 메소드 추가)
구현체에서 반드시 오버라이드 필요
다중 상속 가능
interface Animal { void makeSound(); // abstract 메소드 default void sleep() { // Java 1.8+ System.out.println("잠을 잡니다."); } }
Java
복사

주석 처리

주석 종류

// : 한줄 주석 - 간단한 설명
/* */ : 여러줄 주석 - 코드 블록 설명
/** */ : 문서 주석 - JavaDoc 생성용, 무조건 사용해야 함
/** * 사용자 정보를 관리하는 클래스 * @author 개발자명 * @version 1.0 */ public class User { // 사용자 이름 private String name; /* * 여러줄로 된 * 복잡한 설명 */ }
Java
복사

Optional - Null 안전성

목적

NullPointerException 방지를 위한 null 체크를 간편하게 처리

생성 방법

import java.util.Optional; // null이 아닌 값으로 생성 Optional<String> opt01 = Optional.of("value01"); // null일 수도 있는 값으로 생성 Optional<String> opt02 = Optional.ofNullable(null); // 빈 Optional 생성 Optional<String> opt03 = Optional.empty();
Java
복사

주요 메소드

// 값 추출 (null이면 예외 발생) opt01.get(); // null일 경우 기본값 반환 opt02.orElse("기본값"); // 값이 있을 때만 실행 opt03.ifPresent(value -> System.out.println(value)); // 메소드 참조 사용 Optional<String> str = Optional.of("Hello"); str.ifPresent(System.out::println);
Java
복사

장점

if문으로 null 체크하는 번거로움 해결
코드 가독성 향상
함수형 프로그래밍 스타일 지원

람다 (Lambda Expression)

개념

코드를 줄이는 문법이지만, 나머지 부분을 이해해야 전체 맥락 파악 가능

문법 변화

// 1. 기본 메소드 형태 int max(int a, int b) { return a > b ? a : b; } // 2. 람다 기본형 (int a, int b) -> { return a > b ? a : b; } // 3. 타입 추론 + 중괄호 생략 (a, b) -> a > b ? a : b; // 4. 매개변수 하나일 때 괄호 생략 a -> a + 1;
Java
복사

핵심 특징

함수형 인터페이스에만 사용 가능
코드 간결성 vs 가독성 트레이드오프
익명 함수 형태로 동작

스트림 (Stream)

정의

다양한 데이터 소스를 표준화된 방법으로 다루기 위한 API

특징

데이터 소스를 변경하지 않음 (불변성)
일회용 (한 번 사용하면 재사용 불가)
내부 반복 처리

기본 구조

데이터소스.stream() .중간연산() .중간연산() .최종연산();
Java
복사

주요 연산

중간 연산 (Intermediate Operations)
filter() : 조건 필터링
map() : 데이터 변환
sorted() : 정렬
최종 연산 (Terminal Operations)
collect() : 결과 수집
forEach() : 각 요소에 작업 수행
reduce() : 요소들을 하나로 결합

사용 예시

List<String> names = Arrays.asList("김철수", "이영희", "박민수"); names.stream() .filter(name -> name.startsWith("김")) .map(String::toUpperCase) .forEach(System.out::println);
Java
복사

핵심 포인트

1.
Optional로 null 안전성 확보
2.
람다로 코드 간결성 추구 (단, 가독성 고려)
3.
Stream으로 데이터 처리 파이프라인 구축
4.
문서 주석 필수 작성으로 코드 품질 향상

2주차

1. 기본 개념 정리

SQL: 데이터베이스를 다루는 언어
데이터베이스(DB): 정보를 저장하는 시스템
RDB (관계형 데이터베이스): Oracle, MySQL, MariaDB, MS-SQL
NoSQL (예: MongoDB): 비관계형 데이터베이스 (DynamoDB 등)
라이브러리: 코드 묶음 (필요할 때 가져다 쓰는 도구)
프레임워크: 개발을 위한 뼈대 (예: Spring, Spring Boot)

2. Java 기초 개념

Stream 만들기

Stream<>을 사용해 생성 가능

중간 연산 vs 최종 연산

중간 연산: 연산 결과가 Stream
예: filter(), map()
최종 연산: 연산 결과가 Stream이 아님
예: forEach(), collect()

객체 관계

상속 (is-a)
extends (클래스-클래스, 인터페이스-인터페이스)
implements (클래스-인터페이스)
포함 관계 (has-a) → 변수

final 키워드

final 변수: 상수
final 메서드: 오버라이딩 불가
final 클래스: 상속 불가

초기화 방법

생성자: 클래스명과 같고 리턴형 없음
선언 시 초기화
초기화 블럭
※ "에러: 클래스와 이름이 같고 리턴형 없음" → 오타, 생성자가 맞음

3. 스프링부트 3 구조 이해

학습 목표

스프링부트 3의 계층 구조와 실행 과정 이해
프로젝트 구조 파악

계층 구조

1.
Controller (프레젠테이션 계층)
HTTP 요청 → Service 전달
예: TestController
2.
Service (비즈니스 계층)
핵심 로직 처리
권한, 유효성 검사, 비즈니스 로직 담당
예: TestService
3.
Repository (퍼시스턴스 계층)
DB 접근/처리
JPA/DAO 사용
예: MemberRepository

프로젝트 기본 구조

main: 실제 코드 작성 공간
test: 테스트 코드 작성 공간
build.gradle: 빌드 설정
settings.gradle: 프로젝트 정보

프로젝트 발전시키기

의존성 추가 (build.gradle)
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'com.h2database:h2' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
Plain Text
복사
계층별 코드 구현
Controller: 요청 처리
Service: 비즈니스 로직
Entity: DB 매핑
Repository: DB 접근
샘플 데이터 (resources/data.sql)
INSERT INTO member (id, name) VALUES (1, '이름1'); INSERT INTO member (id, name) VALUES (2, '이름2'); INSERT INTO member (id, name) VALUES (3, '이름3');
SQL
복사
환경 설정 (application.yml)
spring: jpa: show-sql: true properties: hibernate: format_sql: true defer-datasource-initialization: true
YAML
복사

요청-응답 과정 요약

1.
클라이언트 → HTTP 요청 (톰캣)
2.
Controller → 요청 처리
3.
Service/Repository → 로직 & DB 처리
4.
View Resolver & Template Engine → 응답 생성 (HTML, JSON 등)
5.
Dispatcher Servlet → 클라이언트 응답 반환

핵심 요약

Controller → 요청 전달
Service → 로직 처리
Repository → DB 처리

3주차

학습한 내용 정리 — 스프링 부트 3 구조 & 애플리케이션 개발

1. 스프링 부트 3 프로젝트 기본 구조

src/main/java : 메인 자바 코드 파일
src/main/resources : 설정 파일, 정적 리소스
application.properties / application.yml : 애플리케이션 설정
pom.xml (Maven) / build.gradle (Gradle) : 의존성 관리 파일

2. 계층별 구조 및 역할

계층
역할
주요 애노테이션
Controller
클라이언트 요청 수신 및 응답 처리
@RestController, @RequestMapping, @GetMapping, @PostMapping
Service
비즈니스 로직 처리
@Service
Repository
데이터베이스 접근 처리
@Repository, JpaRepository
Entity
DB 테이블과 매핑되는 객체
@Entity, @Id, @GeneratedValue
DTO
계층 간 데이터 전달용 객체
(일반 클래스)

3. 주요 개념 정리

Entity (엔티티)

DB 테이블과 1:1 매핑되는 JPA 객체
@Entity, @Table, @Id 등의 애노테이션 사용
데이터베이스 구조를 반영

DTO (Data Transfer Object)

Controller Service 계층 간 데이터 전달
엔티티의 민감한 정보 보호
JSON 형태로 클라이언트와 통신할 때 사용

JPA Repository

Spring Data JPA의 기본 CRUD 제공
JpaRepository<Entity, ID> 상속
findById(), save(), delete() 등 자동 제공

H2 Database

인메모리 데이터베이스로 빠른 테스트 가능
애플리케이션 실행 시 자동 생성
/h2-console로 데이터 확인 가능
종료 시 데이터 삭제됨

RESTful API 구현

GET : 조회
POST : 생성
PUT : 전체 수정
DELETE : 삭제

Postman API 테스트

다양한 HTTP 메서드 테스트 가능
Body에 JSON 입력 후 요청/응답 확인 가능

4. 연습문제 정답

1.
비즈니스 로직을 처리하는 계층은?
서비스 계층
2.
HTTP 요청을 받아 비즈니스 계층으로 전달하는 계층은?
프레젠테이션 계층
3.
데이터 처리를 담당하는 계층은?
퍼시스턴스 계층
4.
요청/응답 흐름 중 틀린 단계는?
2번 (URL 매핑은 Controller가 직접 수행하지 않고 DispatcherServlet이 처리함)

4주차

학습한 내용

스프링 부트 서비스 계층 리팩토링과 테스트 코드 작성에 대해 학습
1. 서비스 계층의 인터페이스 분리
인터페이스 정의: TestService 인터페이스를 생성하여 서비스 계층의 추상화 구현
구현체 분리: TestServiceImpl 클래스에서 실제 비즈니스 로직 구현
의존성 역전 원칙: 상위 모듈이 하위 모듈에 의존하지 않도록 인터페이스 활용
2. 서비스 메서드별 역할
getAllMembers(): 전체 회원 목록 조회
saveMember(): 회원 정보 저장 (유효성 검증 포함)
getMemberById(): ID로 특정 회원 조회
deleteMember(): ID로 회원 삭제 (두 가지 방식 구현)
3. 입력값 검증 및 예외 처리
StringUtils.hasText(): 문자열 유효성 검증 유틸리티 활용
Optional 패턴: orElse(null)을 통한 안전한 데이터 조회
존재성 확인: existsById()와 findById()를 활용한 삭제 방식
4. 스프링 부트 테스트 프레임워크
@SpringBootTest: 전체 애플리케이션 컨텍스트를 로드하는 통합 테스트
@AutoConfigureMockMvc: MockMvc 자동 구성으로 웹 계층 테스트 환경 설정
MockMvc: HTTP 요청을 시뮬레이션하여 컨트롤러 테스트 수행
5. 테스트 생명주기 관리
@BeforeEach: 각 테스트 메서드 실행 전 초기화 작업
@AfterEach: 각 테스트 메서드 실행 후 데이터베이스 초기화
@DisplayName: 테스트 메서드에 의미있는 한글 설명 추가
6. HTTP 메서드별 테스트 패턴
GET/POST/DELETE: mockMvc.perform()으로 각 HTTP 메서드 테스트
응답 검증: jsonPath()를 활용한 JSON 응답 내용 검증
Given-When-Then 패턴: 테스트 시나리오를 명확하게 구조화

실습코드

// 서비스 인터페이스 public interface TestService { List<Member> getAllMembers(); Member saveMember(Member member); Member getMemberById(Long id); boolean deleteMember(Long id); } // 서비스 구현체 @Service public class TestServiceImpl implements TestService { final MemberRepository memberRepository; @Override public Member saveMember(Member member) { String name = member.getName() == null ? "" : member.getName(); if(!StringUtils.hasText(name)){ return null; } member.setName(name); return memberRepository.save(member); } @Override public boolean deleteMember(Long id) { if (memberRepository.existsById(id)) { memberRepository.deleteById(id); return true; } else { return false; } } } // 컨트롤러 테스트 @SpringBootTest @AutoConfigureMockMvc class TestControllerTest { @Autowired protected MockMvc mockMvc; @DisplayName("전부 가지고 오는 api 테스트") @Test void getAllMembers() throws Exception { // given final String url = "/members"; Member saved = memberRepository.save(new Member(1L, "seo")); // when final ResultActions result = mockMvc.perform( get(url).accept(MediaType.APPLICATION_JSON)); // then - 검증 result.andExpected(status().isOk()) .andExpect(jsonPath("$[0].id").value(saved.getId())); } }
TypeScript
복사
1.
인터페이스 기반 서비스 설계
추상화 구현: TestService 인터페이스로 서비스 계층의 공통 메서드 정의
생성자 주입: final 키워드와 함께 생성자 주입으로 의존성 불변성 보장
2.
입력값 검증 로직
null 안전성: member.getName() == null 체크로 NullPointerException 방지
문자열 검증: StringUtils.hasText()로 빈 문자열과 공백 문자열 모두 검증
3.
삭제 메서드 구현
existsById() 방식: 존재성을 먼저 확인한 후 삭제 수행으로 효율적인 조회
boolean 반환: 삭제 성공/실패 여부를 명확하게 반환
4.
테스트 환경 및 검증
통합 테스트: @SpringBootTest로 실제 애플리케이션 컨텍스트 로드
Given-When-Then: 테스트 시나리오를 명확한 3단계로 구분
JSON 응답 검증: jsonPath()를 활용하여 응답 JSON의 특정 필드값 검증

5주차

1. 학습한 내용 정리

이번 주차에서는 Spring Boot 애플리케이션에서 테스트 코드를 작성하는 방법을 중심으로 학습하였다. 테스트는 크게 서비스 계층 테스트컨트롤러 계층 테스트로 나누어 진행되었고, 각각의 목적과 접근 방식이 다르다는 점을 확인할 수 있었다.
서비스 계층 테스트에서는 Mockito를 활용하여 Repository를 Mock 객체로 만들고, 실제 데이터베이스 연결 없이 비즈니스 로직만을 독립적으로 검증했다. MockitoExtension을 통해 Mock 객체를 주입하고, 테스트는 주로 given-when-then 패턴으로 구성했다.
컨트롤러 계층 테스트에서는 MockMvc를 사용하여 HTTP 요청을 시뮬레이션하고 응답을 검증하는 통합 테스트를 작성했다. AutoConfigureMockMvc를 적용해 MockMvc 환경을 설정하고, 다양한 HTTP 메서드(GET, POST, DELETE)에 대해 API 엔드포인트가 정상적으로 동작하는지 확인했다.
전체적으로 테스트 코드 작성 시에는 given-when-then 패턴을 일관되게 적용하여 테스트 흐름이 명확하게 유지되도록 구성했다. 이를 통해 테스트의 목적과 의도를 쉽게 이해할 수 있도록 했다.

2. 작성한 핵심 코드 설명

서비스 계층 테스트 코드

서비스 계층 테스트는 Repository를 Mock 객체로 만들어 서비스 로직을 단일 기능 단위로 검증하는 방식으로 작성하였다. MemberRepository를 Mock 처리하고, TestServiceImpl에 InjectMocks로 주입하였다.
전체 회원 조회 테스트에서는 findAll의 반환값을 미리 설정한 뒤, 서비스 메서드 실행 결과를 리스트 크기와 내용으로 검증했다.
회원가입 테스트는 정상적인 데이터 입력과 잘못된 데이터 입력 두 가지 케이스로 나누어 작성했으며, 각각 정상 저장과 실패 처리가 올바르게 동작하는지 확인했다.
회원 ID 조회 테스트에서는 Optional을 활용하여 성공 케이스와 실패 케이스를 모두 검증하였다.
회원 삭제 테스트는 deleteById 호출 여부와 동작 성공 여부를 검증하는 방식으로 구성하였다.

컨트롤러 계층 테스트 코드

컨트롤러 테스트에서는 MockMvc를 통해 실제 서버를 띄우지 않고도 HTTP 요청을 테스트할 수 있었다.
각 테스트 실행 전 MockMvc를 초기화하여 독립적인 환경을 유지했다.
전체 회원 조회 API 테스트는 테스트 데이터를 먼저 저장하고, GET 요청이 반환하는 JSON 응답이 의도한 형식과 값을 가지는지 검증하였다.
데이터 추가 API 테스트는 POST 요청을 전송하고 응답 메시지 구조와 값이 정상적으로 반환되는지 확인하였다.
단일 회원 조회와 삭제 API 테스트에서도 각각의 기능이 예상대로 동작하는지 HTTP 응답 코드와 JSON 응답 필드를 기반으로 검증하였다.

3. 오류 해결 과정 및 주의사항

테스트 작성 과정에서 몇 가지 주의해야 할 점을 확인할 수 있었다.
먼저 BeforeEach 메서드의 이름은 동작에 직접적인 영향은 없지만, 일관된 네이밍 컨벤션을 유지하는 것이 가독성 측면에서 중요함을 알 수 있었다.
또한 jsonPath 문법에 오탈자가 있는 경우 테스트가 실패할 수 있다는 점도 확인하였다. JSON 구조 검증 시에는 정확한 표현식을 사용해야 한다.
Mock 객체를 사용할 때는 테스트에 필요한 모든 Mock 동작을 명확히 설정해야 하며, 특히 Repository처럼 Optional을 반환하는 경우 Optional.of와 Optional.empty를 명확히 구분해 설정해야 한다.
테스트 간 데이터 격리 역시 중요한 부분이었다. 각 테스트가 독립적으로 실행될 수 있도록 매번 필요한 데이터를 생성하거나 정리하는 방식으로 구성해야 한다는 점을 확인하였다.
이러한 과정을 통해 테스트 코드 작성 시 주의해야 할 점과 패턴을 자연스럽게 익힐 수 있었다.

6주차

1. 학습한 내용 정리

이번 주에는 JUnit을 이용한 테스트 코드 작성 방법과 Spring Boot 환경에서의 MockMvc 활용법을 학습하였다.
각 테스트는 서로 영향을 주지 않도록 테스트 실행마다 독립적인 인스턴스로 수행되며,
테스트의 구조를 명확히 하기 위해 given - when - then 패턴을 사용하였다.
테스트 라이프사이클 관련 어노테이션을 사용하여 테스트 실행 전후의 초기화 및 정리 작업을 관리하는 방법을 배웠다.
JSON 응답을 검증하기 위해 jsonPath를 활용하였고,
MockMvc를 통해 실제 서버를 띄우지 않고도 HTTP 요청/응답 테스트가 가능함을 실습하였다.

2. 작성한 핵심 코드 설명

아래 코드는 Spring Boot 환경에서 Controller 계층의 API를 테스트하기 위해 작성한 JUnit 테스트 코드이다.
MockMvc를 이용하여 엔드포인트를 호출하고, 정상적인 응답과 데이터가 반환되는지를 검증한다.
@SpringBootTest @AutoConfigureMockMvc class TestControllerTest { @Autowired protected MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @Autowired protected MemberRepository memberRepository; @Autowired private ObjectMapper objectMapper; @BeforeEach public void mockMySetup() { mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build(); } @DisplayName("전부 가지고 오는 api 테스트") @Test void getAllMembers() throws Exception { final String url = "/members"; Member saved = memberRepository.save(new Member(null, "seo")); final ResultActions result = mockMvc.perform(get(url).accept(MediaType.APPLICATION_JSON)); result.andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value(saved.getId())) .andExpect(jsonPath("$[0].name").value(saved.getName())); } @DisplayName("데이터 추가") @Test void create() throws Exception { final String url = "/member"; String memberName = "seoTest"; Member _member = memberRepository.save(new Member(memberName)); String _strBody = objectMapper.writeValueAsString(_member); final ResultActions _result = mockMvc.perform( post(url) .contentType(MediaType.APPLICATION_JSON) .content(_strBody)); _result.andExpect(status().isOk()) .andExpect(jsonPath("$.res").value("success")) .andExpect(jsonPath("$.data.name").value(memberName)); } @DisplayName("하나 가지고 오는 api test") @Test void getMemberById() throws Exception { final String url = "/member/{id}"; Member saved = memberRepository.save(new Member("seo")); final Long memberId = saved.getId(); final ResultActions result = mockMvc.perform(get(url, memberId).accept(MediaType.APPLICATION_JSON)); result.andExpect(status().isOk()) .andExpect(jsonPath("$.res").value("success")) .andExpect(jsonPath("$.data.id").value(saved.getId())) .andExpect(jsonPath("$.data.name").value(saved.getName())); } @DisplayName("하나 지우는 api") @Test void deleteMember() throws Exception { final String url = "/member/{id}"; Member saved = memberRepository.save(new Member("seo")); final Long memberId = saved.getId(); final ResultActions result = mockMvc.perform(delete(url, memberId)); result.andExpect(status().isOk()) .andExpect(jsonPath("$.res").value("success")) .andExpect(jsonPath("$.msg").value("delete Member OK id : " + memberId)); } }
Java
복사

3. 오류 해결 과정 및 주의사항

1.
ObjectMapper 관련 오류
JSON 직렬화 시 objectMapper.writeValueAsString() 누락 → 400 Bad Request
해결: 요청 본문(content)에 직렬화된 문자열을 넣도록 수정
2.
엔드포인트 경로 불일치 오류
@PostMapping("/member")와 테스트 요청 URL 불일치로 404 발생
해결: Controller의 매핑 경로와 테스트 URL을 동일하게 유지
3.
독립적 테스트 실행 주의
테스트마다 MockMvc를 @BeforeEach에서 초기화
테스트 데이터가 섞이지 않도록 매번 새로운 엔티티 저장
4.
응답 검증 시 jsonPath 경로 오류
잘못된 JSON 경로 사용 시 테스트 실패
해결: 실제 응답 구조를 확인한 뒤 정확한 경로 지정

4. 연습문제

1.
JUnit
2.
given - 저장할 새로운 메뉴 정보를 생성한다. when - 저장할 새로운 메뉴를 저장한다. then - 저장된 메뉴 정보가 given에서 준비한 메뉴 정보와 같은지 검증한다.
3.
@BeforeAll → @BeforeEach → @Test → @AfterEach → @AfterAll
4.
assertThat(a + b).isEqualTo(sum);
5.
HTTP 응답 코드가 201이며, 반환된 배열의 0번째 요소의 id가 15인지 검증하는 코드

7주차

1. 학습한 내용 정리

ORM(Object Relational Mapping) 개념과 그 구현체인 JPA(Java Persistence API)를 중심으로 학습하였다.
ORM은 자바 객체(Object)와 데이터베이스 테이블을 자동으로 매핑해주는 기술로, SQL문을 직접 작성하지 않고도 데이터를 조작할 수 있게 한다.
특히 JPA는 자바에서 표준으로 정의된 ORM 스펙으로, 대표적인 구현체로는 Hibernate가 있다.
이번 실습에서는 JPA를 사용하여 Customer 엔티티를 데이터베이스에 매핑하고, CustomerRepository를 통해 CRUD 기능을 수행하였다.
또한, 테스트 코드에서 SQL 스크립트를 활용하여 테스트 데이터를 주입하는 방법을 배웠다.
Spring Boot의 @DataJpaTest 어노테이션을 이용하면 데이터베이스와 관련된 JPA 기능만 테스트할 수 있으며, @Sql 어노테이션을 통해 테스트 실행 전에 특정 SQL 파일을 자동으로 실행할 수 있다.
이 과정을 통해 다음 내용을 이해하였다:
JPA를 사용하여 엔티티(Entity) 클래스와 테이블을 자동으로 매핑하는 원리
@DataJpaTest를 이용한 JPA 단위 테스트 수행 방법
@Sql을 활용해 테스트 실행 전에 데이터베이스 초기 데이터를 세팅하는 방법
JpaRepository의 findAll, findById, findByName, save 등 주요 메서드 활용

2. 작성한 핵심 코드 설명

@DataJpaTest public class CustomerRepositoryTest { @Autowired private CustomerRepository customerRepository; @Test @Sql("/insert-members.sql") void getAllCustomer() { customerRepository.findAll().forEach(it -> System.out.println(it)); } @Test @Sql("/insert-members.sql") void geCustomerById() { Customer customer = customerRepository.findById(1L).get(); System.out.println(customer); } @Test @Sql("/insert-members.sql") void geCustomerByName() { Customer customer = customerRepository.findByName("이름1").get(); System.out.println(customer); } @Test @Sql("/insert-members.sql") void geCustomerByAge() { Customer customer = customerRepository.findByNameAndAge("이름1", 12).get(); System.out.println(customer); } @Test @Sql("/insert-members.sql") void InsertCustomer() { Customer customer = new Customer("최사번","101-0101-010",40); customerRepository.save(customer); Optional<Customer> cusIn = customerRepository.findByName("최사번"); System.out.println(cusIn.get()); } }
Java
복사
@DataJpaTest: Spring Boot에서 JPA 관련 컴포넌트만 로드하여 테스트하는 전용 어노테이션
@Sql("/insert-members.sql"): 테스트 실행 전에 SQL 스크립트를 실행해 테스트 데이터를 삽입
findAll / findById / findByName: JpaRepository가 제공하는 CRUD 쿼리 메서드
save(customer): 새로운 고객을 DB에 저장
테스트가 실행되기 전 아래 SQL 파일이 자동 실행되어 동일한 초기 데이터를 유지하게 된다.
INSERT INTO customer (name, phone, age) VALUES ('이름1', '010-0101-0101',12); INSERT INTO customer (name, phone, age) VALUES ('이름2', '010-0101-0101',14); INSERT INTO customer (name, phone, age) VALUES ('이름3', '010-0101-0101',14);
SQL
복사

3. 오류 해결 과정 및 주의사항

원인: 엔티티와 SQL 컬럼명이 불일치함
@Column(name = "useename", nullable = false) private String name;
Java
복사
→ 실제 SQL에서는 name 컬럼을 사용 → 컬럼명이 다르므로 INSERT 실패
해결: 컬럼명 엔티티와 일치시키기
@Column(nullable = false) private String name;
Java
복사
SQL 스크립트 예시
INSERT INTO customer (name, phone, age) VALUES ('이름1', '010-0101-0101',12); INSERT INTO customer (name, phone, age) VALUES ('이름2', '010-0102-0102',34);
SQL
복사
테스트용 DB 설정
spring.jpa.hibernate.ddl-auto=create spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1 spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=
Plain Text
복사

4. 연습문제

1.
틀린 설명: ④ 기본키는 NULL 값이 들어갈 수 없다.
2.
객체와 데이터베이스를 연결하는 프로그래밍 기법은? → ORM
3.
영속성 컨텍스트의 특징이 아닌 것 → ① 연결 풀링
4.
엔티티에서 사용되는 애너테이션이 아닌 것 → ⑤ @Transactional
5.
CRUD 메서드를 제공하는 스프링 데이터 JPA 인터페이스 → JpaRepository

8주차

1. 학습한 내용 정리 (요약)

이번 수업에서는 Spring Data JPA의 **JPQL(@Query)**과 Query Method를 중심으로 학습하였다.
JPQL (Java Persistence Query Language)
SQL과 유사하지만 테이블이 아닌 엔티티와 필드명을 대상으로 쿼리 작성
JPA 구현체가 JPQL을 SQL로 변환 후 실행
SELECT, UPDATE, DELETE 지원 (INSERT는 엔티티 저장으로 처리)
@Query 애노테이션으로 JPQL 직접 정의 가능, 복잡한 조회/조인/서브쿼리 구현에 유리
Query Method
메서드 이름만으로 쿼리를 자동 생성
간단한 조건 조회 시 JPQL 없이 빠르게 구현 가능
키워드: And, Or, Between, LessThan, GreaterThanEqual, Like, Containing, StartingWith, EndingWith, IsNull, OrderBy, CountBy, ExistsBy, DeleteBy 등
실습 요약
JPQL 기반 CourseQueryRepository와 Query Method 기반 CourseQueryMethodRepository 구현
JPQL 직접 작성과 메서드 네이밍 방식 차이 비교 실습

2. 작성한 핵심 코드 설명 (목적 및 동작)

Course 엔티티

@Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @Getter @ToString public class Course { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true, nullable = false) private String name; @Column(nullable = false) private String category; private int rating; @Column(nullable = false) private String description; public Course(String name, String category, int rating, String description) { this.name = name; this.category = category; this.rating = rating; this.description = description; } }
Java
복사
목적: JPA가 관리하는 도메인 엔티티, 테이블의 row에 대응
주요 요소: @Entity, @Id, @GeneratedValue — 엔티티 식별자 자동 생성
노트: Lombok(@Getter, @NoArgsConstructor 등) 사용 시 IDE에서 Lombok 플러그인과 annotation processing 활성화 필요

CourseQueryRepository (JPQL 사용)

public interface CourseQueryRepository extends JpaRepository<Course, Long> { @Query("SELECT c FROM Course c where c.name = :name OR c.category = :category") List<Course> findByNameOrCategory( @Param("name") String name, @Param("category") String category); }
Java
복사
목적: JPQL로 조건(name 또는 category) 조회
동작: @Query 안 JPQL 문자열을 JPA가 SQL로 변환 후 실행, @Param으로 파라미터 바인딩
사용 시기: 복잡한 조건이나 명시적 조인이 필요할 때

테스트 코드 (DataJpaTest)

@DataJpaTest class CourseQueryRepositoryTest { @Autowired private CourseQueryRepository courseQueryRepository; @Test @Sql("/insert-courses.sql") void findByNameOrCategory() { List<Course> byNameOrCategory = courseQueryRepository.findByNameOrCategory("Java Fundamentals", "Phyton"); assertThat(byNameOrCategory).extracting(Course::getCategory).contains("Java", "Phyton"); } }
Java
복사
목적: JPA 레포지토리 메서드 동작 검증
@DataJpaTest: 인메모리 DB(H2)로 JPA 계층만 테스트
주의: @Sql 경로가 올바른지 확인 필요

3. 오류 해결 과정 및 주의사항

오류 A

findAll(Example<S>) 충돌
원인: 같은 시그니처 메서드가 서로 다른 반환 타입을 가지는 클래스가 섞여 있을 때 발생 (Spring Data 라이브러리 버전 충돌)
해결 방법
1.
build.gradle / pom.xml에서 spring-boot-starter-data-jpa만 사용
2.
Gradle: ./gradlew dependencies --configuration runtimeClasspath Maven: mvn dependency:tree로 의존성 트리 확인
3.
Spring Boot 버전과 Spring Data 모듈 버전 일치 확인
4.
QueryByExampleExecutor 직접 추가 시 제거
5.
클린 빌드: ./gradlew clean build --refresh-dependencies

오류 B

Cannot resolve symbol Course
원인 가능성
1.
테스트 클래스와 Course 클래스 패키지 불일치
2.
Course 클래스 컴파일 실패
3.
Lombok 미설치/설정 문제
4.
IDE 캐시 문제
해결 방법
패키지와 import 확인
Course 클래스 컴파일 오류 수정
Lombok 플러그인 설치, annotation processing 활성화
IDE 캐시 무효화 및 재시작

9주차

1. 학습한 내용 정리

Spring Data JPA에서 제공하는 Query Method 개념을 학습
메서드 이름만으로 자동으로 쿼리가 생성되는 원리 이해
단순 조회 및 조건 검색 시 Query Method 유용
복잡한 쿼리 실행을 위해 @Query 애너테이션을 사용하여 JPQL 작성
JPQL은 엔티티와 필드 중심 언어이며, SQL과 유사하지만 테이블 기반 아님
@Query로 네이티브 쿼리도 사용 가능
H2 메모리 데이터베이스를 활용하여 쿼리 결과 검증 테스트 코드 작성
초과
>
GreaterThan
findByAgeGreaterThan(int age)
이상
>=
GreaterThanEqual
findByAgeGreaterThanEqual(int age)
미만
<
LessThan
findByAgeLessThan(int age)
이하
<=
LessThanEqual
findByAgeLessThanEqual(int age)
같음
=
Equal (생략 가능)
findByName(String name) or findByNameEqual(String name)
같지 않음
!=
NotEqual
findByStatusNotEqual(String status)
Between
BETWEEN
Between
findByAgeBetween(int min, int max)

2. 작성한 핵심 코드 설명

findByName(String name)
특정 강의명을 기준으로 단일 엔티티 조회.
Optional 타입 반환으로 null 처리 안전성 확보
findAllByCategory(String category)
강의 카테고리 기준으로 전체 목록 조회.
Category 컬럼 기준 조건 검색 동작 확인
findAllByCategoryOrderByName(String category)
Category 조건 만족 데이터 강의명 오름차순 정렬
메서드 이름만으로 정렬 조건 포함 가능
countByCategory(String category)
Category 기준 데이터 개수 계산
Count 쿼리 사용법 학습
findByNameOrCategory(String name, String category)
이름 또는 카테고리 조건 데이터 조회
OR 조건 쿼리 메서드 명으로 생성 확인
findByCategoryAndRatingLessThan(String category, int rating)
카테고리 AND 평점 기준 조건 검색
비교 연산 키워드(LessThan) 활용 가능
@Query("SELECT c.category, AVG(c.rating) FROM Course c group by c.category")
JPQL을 사용하여 카테고리별 평균 평점 조회
그룹 함수와 Group By 기능 적용 가능

3. 오류 해결 과정 및 주의사항

Query Method 작성 시 필드명과 엔티티명 대소문자 정확히 일치 필요
JPQL 작성 시 엔티티명 사용 (테이블명 아님)
네이티브 쿼리 사용 시 nativeQuery = true 옵션 필수
잘못된 메서드 네이밍 구조 (find, findByStartingWith, find(String)) → IDE/실행 오류 발생 가능

4. 연습문제 풀이

1.
특정 강의명(name)이 존재하는지 확인하는 쿼리 메서드
boolean existsByName(String name)
Java
복사
주의: exists에 s 포함, 대문자 주의
1.
강의명(name)이 특정 문자열로 시작(StartingWith) 하는 모든 강의 목록 조회
List<Course> findAllByNameStartingWith(String name)
Java
복사
대문자 주의

10주차

1. 학습한 내용 정리

QueryDSL을 사용하여 JPQL을 타입 안전하게 대체하고, 동적 쿼리를 유연하게 작성하는 방법 학습
QueryDSL은 엔티티 기반 Q클래스를 자동 생성하여 자바 코드로 쿼리를 작성
JPAQueryFactory를 활용해 빌더 스타일로 쿼리 작성
fetch(), fetchOne(), fetchFirst()의 차이점 학습
fetch() : 여러 개 결과를 리스트로 반환
fetchOne() : 하나의 결과 반환 (두 개 이상 시 오류)
fetchFirst() : 첫 번째 결과만 반환
BooleanExpression, BooleanBuilder를 사용하여 동적 조건(where절) 구성
QueryDSL 장점
IDE 자동완성 지원
복잡한 조건 조립 가능
유지보수 용이
기본 비교 메소드 (숫자, 문자, 날짜 등)
메소드
의미
예시 코드
JPQL 대응 연산자
설명
eq()
같다
course.name.eq("Java")
=
문자열, 숫자 등 같음 비교
ne()
같지 않다
!=
다른 값 비교
gt()
초과
>
greater than — 초과
goe()
이상
course.rating.goe(3)
>=
greater or equal — 이상
lt()
미만
<
less than — 미만
loe()
이하
course.rating.loe(3)
<=
less or equal — 이하
문자열 관련 메소드
메소드
의미
예시 코드
JPQL 대응 연산자
설명
contains()
포함
course.name.contains("Spring")
LIKE %Spring%
부분 일치 (양쪽 %)
startsWith()
~로 시작
course.name.startsWith("Data")
LIKE Data%
접두어 검색
endsWith()
~로 끝남
course.name.endsWith("Boot")
LIKE %Boot
접미어 검색
like()
직접 LIKE 표현
course.name.like("%SQL%")
LIKE
와일드카드 직접 사용
불리언 및 논리 연산
메소드
의미
예시 코드
설명
and()
그리고
course.rating.gt(3).and(course.category.eq("Spring"))
조건 AND 연결, + and는 ,로 간략 표현 가능
or()
또는
course.rating.lt(2).or(course.category.eq("React"))
조건 OR 연결
not()
부정
course.name.eq("Java").not()
조건 반전

2. 작성한 핵심 코드 설명

BooleanExpression 메서드 예시
private BooleanExpression eqCategory(String category) { return StringUtils.hasText(category) ? course.category.eq(category) : null; } private BooleanExpression goeMinRating(Integer minRating) { return minRating != null ? course.rating.goe(minRating) : null; } private BooleanExpression startingWithName(String keyword) { return StringUtils.hasText(keyword) ? course.name.startsWith(keyword) : null; }
Java
복사
findByConditions
@Override public List<Course> findByConditions(String category, Integer minRating, String prefix) { return queryFactory.selectFrom(course) .where( eqCategory(category), goeMinRating(minRating), startingWithName(prefix) ) .orderBy(course.rating.desc()) .fetch(); }
Java
복사

3. 오류 해결 과정 및 주의사항

fetchOne() 사용 시 조회 결과가 2개 이상이면 예외 발생 → 조건 명확히 설정 필요
BooleanExpression에 null 반환 시 자동 무시되지만, 논리적 순서(and, or)를 잘못 설정하면 결과가 달라질 수 있음

11주차

1. 학습한 내용 정리

QueryDSL의 기본 문법과 동적 쿼리를 생성하는 방법을 복습하였습니다.
BooleanBuilder를 사용하여 조건을 유연하게 조합하는 방식을 학습하였습니다.
eq, goe 등을 사용하여 필드 값을 비교하는 방법을 다시 정리하였습니다.
CRUD 기능을 구현하기 위한 프로젝트 환경을 설정하고 테스트 코드를 작성하는 방법을 학습하였습니다.
@Sql 어노테이션을 사용하여 테스트 데이터 삽입 후 QueryDSL로 조회하는 흐름을 이해하였습니다.

2. 작성한 핵심 코드 설명

@Override public List<Course> findByConditionsWithBuilder(String type, String keyword) { return queryFactory.selectFrom(course).where(buildKey(type,keyword)).orderBy(course.rating.asc()).fetch(); }
Java
복사
findByConditionsWithBuilder 메서드는 BooleanBuilder를 기반으로 동적 조회 조건을 적용하기 위해 작성하였습니다.
이 메서드는 입력된 type(N, C, D)에 따라 강의명, 카테고리, 설명 중 하나를 containsIgnoreCase로 검색하도록 동작하게 구성하였습니다.
buildKey 메서드는 사용자가 넣은 type 값에 따라 적절한 조건을 BooleanBuilder에 추가하며, Keyword가 포함되는지 비교하는 필터 역할을 하였습니다.
테스트 코드에서는 type과 keyword를 기준으로 조회된 모든 Course 객체가 조건을 만족하는지 검증하였습니다.
findByCategoryAndMinRating 메서드는 category가 동일하고 rating이 주어진 값 이상인 강의를 조회하기 위해 사용되었으며, 평점 기준 내림차순으로 정렬하도록 구성하였습니다.
eqCategory와 goeMinRating을 조합하여 where 절을 구성하는 방식을 통해 QueryDSL이 메서드 기반으로 조건을 추가할 수 있다는 점을 확인하였습니다.

3. 오류 해결 과정 및 주의사항

BooleanBuilder 사용 시 반환 값이 없는 경우 null을 넣어야 where 절이 비정상적으로 동작하지 않는다는 점을 알 수 있었습니다.
QueryDSL에서 or 조건을 사용할 때에는 BooleanBuilder에 값을 계속 추가해야 하며, 잘못된 필드명을 작성할 경우 빌드 시점에서 오류가 발생한다는 점을 알 수 있었습니다.
@Sql로 테스트 데이터를 넣는 과정에서 파일 경로가 맞지 않는 경우 테스트가 빈 데이터로 실행된다는 문제를 확인하였고, 파일 위치를 정확하게 맞춰야 한다는 점을 주의할 점으로 기록하였습니다.

4. 연습문제 풀이

QueryDSL을 사용하여 category가 동일(eq)하고 rating이 주어진 값 이상(goe)인 강의를 조회하는 메서드를 작성하였습니다.
@Override public List<Course> findByCategoryAndMinRating(String category, int rating) { return queryFactory.selectFrom(course).where( eqCategory(category), goeMinRating(rating) ).orderBy(course.rating.desc()).fetch(); }
Java
복사
이 코드를 통해 where 절에서 여러 조건을 콤마로 나열하면 기본적으로 and 조건으로 묶여 처리된다는 것을 알 수 있었습니다.
또한 orderBy를 통해 결과를 평점 기준 내림차순으로 정렬할 수 있다는 점을 복습하였습니다.