Java 핵심 개념과 Spring 프레임워크 완벽 정리

1. Java 기본 문법

1.1 개발 환경 단축키

IDE에서 자주 사용하는 단축키를 정리합니다.

단축키기능
soutSystem.out.println() 자동 완성
Ctrl+/한 줄 주석 토글
Ctrl+Shift+/여러 줄 주석 토글

1.2 식별자 규칙

변수, 클래스, 메서드 이름 작성 시 준수해야 할 규칙입니다.

  • 사용 가능 문자: 영문 소문자(a-z), 숫자(0-9), 밑줄(_), 달러 기호($)
  • 숫자로 시작할 수 없음
  • 예약어(keyword) 사용 불가

1.3 주석 종류

// 한 줄 주석 (Ctrl+/)

/* 여러 줄 주석
   (Ctrl+Shift+/) */

/** 문서화 주석 - JavaDoc 생성용 */

1.4 상수와 변수

상수(Constant): 변경 불가능한 값

  • 정수 상수, 실수 상수, 논리 상수, 문자 상수, 문자열 상수, null 상수
  • 선언 시 대문자와 밑줄 사용 (관례)
final int MAX_VALUE = 100;

변수(Variable): 실행 중 값 변경 가능

int counter = 0;

문자열 vs 문자

구분표기법예시
문자(char)작은따옴표'A'
문자열(String)큰따옴표"Hello"

1.5 진법 변환

이진수 → 십진수

8-4-2-1 가중치 방식 적용

1 0 1 0 = 1×8 + 0×4 + 1×2 + 0×1 = 10
1 0 1 1 = 11
1 1 0 1 = 13

이진수 ↔ 십육진수

4비트씩 묶어 변환 (부족한 자리는 0으로 채움)

이진수: 0011 0101 0101 0101 1101 0101
        ↓    ↓    ↓    ↓    ↓    ↓
십육진수:   3    5    5    5    D    5

결과: 3555D5

음수 표현 (2의 보수)

  1. 양수 이진수 작성
  2. 모든 비트 반전 (1의 보수)
  3. 1을 더함
양수 5:  0101
반전:    1010
+1:      1011 → -5의 표현

1.6 데이터 타입

기본 타입(Primitive Types)

분류타입크기범위
정수형byte8bit-128 ~ 127
short16bit-32,768 ~ 32,767
int32bit약 -21억 ~ 21억
long64bit약 -900경 ~ 900경
실수형float32bit단정밀도
double64bit배정밀도
문자형char16bitUnicode 문자
논리형boolean1bittrue/false

단위 환산

1 byte = 8 bit
1 KB = 1024 byte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB

명명 규칙

규칙적용 대상예시
카멜 케이스변수, 메서드processUserData
파스칼 케이스클래스, 인터페이스UserDataProcessor

타입 변환

자동 변환 (확대 변환): 작은 타입 → 큰 타입

byte small = 10;
int large = small + 300;  // 자동 변환됨

강제 변환 (축소 변환): 큰 타입 → 작은 타입 (데이터 손실 가능)

int bigNumber = 10;
byte smallNumber = (byte) bigNumber;  // 명시적 캐스팅 필요

int result = 10;
byte converted = (byte) (result + 50);  // 연산 결과도 int이므로 캐스팅 필요

1.7 연산자

산술 연산자

+  -  *  /  % (나머지/모듈로)

문자열 연결: + 연산자로 문자열 결합

String message = "값: " + 100;  // "값: 100"

실수 연산 주의: float, double은 정밀도 문제 발생 가능

// 해결책: 정수로 변환 후 연산
double precise = (int)(value * 100) / 100.0;

비교 연산자

>   >=   <   <=   ==   !=

// 객체 타입 확인
"hello" instanceof String  // true

논리 연산자

연산자설명
&논리 AND (모든 조건 검사)
&&단축 AND (앞이 false면 뒤 검사 안함)
|논리 OR (모든 조건 검사)
||단축 OR (앞이 true면 뒤 검사 안함)
!논리 NOT
^배타적 OR (XOR)

비트 연산자

<<   // 왼쪽 시프트 (×2)
>>   // 오른쪽 시프트 (부호 유지)
>>>  // 부호 없는 오른쪽 시프트

int value = 6;      // 0110
int shifted = value << 1;  // 1100 = 12

삼항 연산자

int max = (a > b) ? a : b;

// 중첩 가능
int result = (score >= 90) ? 'A' : (score >= 80) ? 'B' : 'C';

1.8 제어문

조건문

// if-else
if (condition) {
    // 참일 때 실행
} else if (anotherCondition) {
    // 다른 조건
} else {
    // 거짓일 때 실행
}

// switch (Java 7+ 문자열 지원)
switch (expression) {
    case 1:
        System.out.println("하나");
        break;
    case 2:
        System.out.println("둘");
        break;
    default:
        System.out.println("기타");
}

반복문

// for
for (int i = 0; i < 10; i++) {
    System.out.println(i);
}

// while (선 검사)
while (condition) {
    // 조건이 참일 때 반복
}

// do-while (후 검사)
do {
    // 최소 1회 실행 후 조건 검사
} while (condition);

// 무한 루프
for (;;) { }
while (true) { }

제어 키워드

키워드동작
break반복문 완전 종료
continue현재 반복만 건너뛰고 다음 반복 계속

1.9 배열

선언과 초기화

// 방법 1: 크기만 지정
int[] numbers = new int[5];  // 기본값 0으로 초기화

// 방법 2: 값 접 지정
int[] values = new int[] {1, 2, 3, 4, 5};

// 방법 3: 간단 초기화
int[] simple = {1, 2, 3, 4, 5};

배열 순회

// 인덱스 기반
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

// 향상된 for문 (읽기 전용)
for (int num : arr) {
    System.out.println(num);
}

배열 복사

// System.arraycopy 사용
int[] source = {1, 2, 3};
int[] target = new int[3];
System.arraycopy(source, 0, target, 0, 3);

// Arrays.copyOf 사용
int[] copied = Arrays.copyOf(source, source.length);

1.10 메모리 구조

영역저장 내용
스택(Stack)지역 변수, 메서드 호출 정보
힙(Heap)객체, 배열 (new로 생성)
메서드 영역클래스 정보, 정적 변수
네이티브 메서드 영역JNI 관련 데이터
PC 레지스터현재 실행 중인 명령어 주소

참조 타입 특성

int[] original = new int[10];
int[] reference = original;  // 같은 힙 메모리 참조

original[0] = 100;
System.out.println(reference[0]);  // 100 (동일 객체 공유)

2. 객체지향 프로그래밍

2.1 추상 클래스

추상 메서드를 포함하는 클래스로, 직접 인스턴스화할 수 없습니다.

abstract class Vehicle {
    // 추상 메서드 - 구현 없이 선언만
    public abstract void move();
    
    // 일반 메서드도 포함 가능
    public void stop() {
        System.out.println("정지");
    }
}

class Car extends Vehicle {
    @Override
    public void move() {
        System.out.println("자동차가 달립니다");
    }
}

// 사용
Vehicle myCar = new Car();
myCar.move();  // 다형성 적용

제약사항: abstractfinal, private, static와 함께 사용 불가

2.2 인터페이스

완전한 추상화를 제공하는 타입입니다.

interface Drawable {
    // 상수 (자동으로 public static final)
    int MAX_SIZE = 100;
    
    // 추상 메서드 (자동으로 public abstract)
    void draw();
    
    // 디폴트 메서드 (Java 8+)
    default void printInfo() {
        System.out.println("도형 정보 출력");
    }
    
    // 정적 메서드 (Java 8+)
    static void showVersion() {
        System.out.println("v1.0");
    }
}

class Circle implements Drawable {
    @Override
    public void draw() {
        System.out.println("원을 그립니다");
    }
}

// 다중 구현
class Rectangle implements Drawable, Comparable<Rectangle> {
    @Override
    public void draw() {
        System.out.println("사각형을 그립니다");
    }
}

2.3 다형성

업캐스팅 (Upcasting)

Animal animal = new Dog();  // 자동 형변환
animal.makeSound();  // "멍멍" - 동적 바인딩

다운캐스팅 (Downcasting)

Animal animal = new Dog();
Dog dog = (Dog) animal;  // 명시적 형변환

// 안전한 다운캐스팅
if (animal instanceof Dog) {
    Dog safeDog = (Dog) animal;
}

메서드 오버로딩 vs 오버라이딩

구분오버로딩오버라이딩
정의같은 이름, 다른 매개변수상속받은 메서드 재정의
반환 타입달라도 됨같거나 하위 타입
접근 제어자제한 없음더 넓게만 가능
예외제한 없음같거나 하위 예외

2.4 내부 클래스

인스턴스 내부 클래스

class Outer {
    private int outerValue = 10;
    
    class Inner {
        void accessOuter() {
            System.out.println(outerValue);  // 외부 클래스 멤버 접근
        }
    }
}

// 생성
Outer.Inner inner = new Outer().new Inner();

정적 내부 클래스

class Outer {
    static class StaticInner {
        void display() {
            System.out.println("정적 내부 클래스");
        }
    }
}

// 생성 (외부 객체 불필요)
Outer.StaticInner staticInner = new Outer.StaticInner();

지역 내부 클래스

void someMethod() {
    final String localVar = "지역 변수";
    
    class LocalClass {
        void print() {
            System.out.println(localVar);  // final 또는 사실상 final 변수만 접근
        }
    }
    
    LocalClass local = new LocalClass();
    local.print();
}

익명 클래스

// 인터페이스 구현
Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("익명 클래스 실행");
    }
};

// 추상 클래스 구현
AbstractClass instance = new AbstractClass() {
    @Override
    void abstractMethod() {
        System.out.println("상 메서드 구현");
    }
};

2.5 예외 처리

예외 계층

Throwable
├── Error (시스템 레벨, 복구 불가)
└── Exception (애플리케이션 레벨)
    ├── RuntimeException (unchecked, 선택적 처리)
    │   ├── NullPointerException
    │   ├── ArrayIndexOutOfBoundsException
    │   └── IllegalArgumentException
    └── 기타 Exception (checked, 반드시 처리)
        ├── IOException
        ├── SQLException
        └── ...

try-catch-finally

try {
    // 예외 발생 가능 코드
    int result = 10 / 0;
} catch (ArithmeticException e) {
    // 특정 예외 처리
    System.out.println("0으로 나눌 수 없습니다");
} catch (Exception e) {
    // 일반 예외 처리
    System.out.println("예외 발생: " + e.getMessage());
} finally {
    // 항상 실행
    System.out.println("정리 작업");
}

사용자 정의 예외

// unchecked 예외
class BusinessException extends RuntimeException {
    public BusinessException(String message) {
        super(message);
    }
    
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 사용
if (amount < 0) {
    throw new BusinessException("금액은 음수가 될 수 없습니다");
}

try-with-resources

// 자동 자원 해제 (AutoCloseable 구현체)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
     Connection conn = dataSource.getConnection()) {
    
    String line = reader.readLine();
    // conn 사용
    
} catch (IOException | SQLException e) {
    e.printStackTrace();
}
// reader와 conn이 자동으로 close() 호출

3. Java API 활용

3.1 String 클래스

문자열 생성 방식 비교

String literal = "hello";           // 문자열 상수 풀 사용
String object = new String("hello"); // 힙에 새 객체 생성

// == vs equals()
System.out.println(literal == object);      // false (참조 비교)
System.out.println(literal.equals(object)); // true (내용 비교)

주요 메서드

메서드설명예시
length()문자열 길이"abc".length() → 3
charAt(int)특정 인덱스 문자"abc".charAt(1) → 'b'
substring(int, int)부분 문자열"abcde".substring(1,4) → "bcd"
indexOf(String)첫 등장 위치"abcde".indexOf("cd") → 2
replace(char, char)문자 치환"a-b-c".replace('-', '/') → "a/b/c"
split(String)분리"a,b,c".split(",") → ["a","b","c"]
trim()양쪽 공백 제거" hello ".trim() → "hello"
toLowerCase()소문자 변환"HELLO".toLowerCase() → "hello"
compareTo(String)사전 비교"a".compareTo("b") → -1

3.2 StringBuilder/StringBuffer

특징StringBuilderStringBuffer
동기화비동기 (빠름)동기화됨 (안전)
용도단일 스레드멀티 스레드
StringBuilder sb = new StringBuilder();
sb.append("Hello")
  .append(" ")
  .append("World");
  
String result = sb.toString();  // "Hello World"

3.3 래퍼 클래스와 오토박싱

// 오토박싱 (기본형 → 객체)
Integer wrapped = 100;  // Integer.valueOf(100)

// 오토언박싱 (객체 → 기본형)
int primitive = wrapped;  // wrapped.intValue()

// 주의: -128 ~ 127 캐싱됨
Integer a = 100;
Integer b = 100;
System.out.println(a == b);  // true (같은 객체)

Integer c = 200;
Integer d = 200;
System.out.println(c == d);  // false (다른 객체)

3.4 날짜/시간 API

Java 8 이전 (Date, Calendar)

// Date - 거의 deprecated
Date now = new Date();

// Calendar
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
cal.set(Calendar.YEAR, 2025);

Java 8+ (java.time 패키지)

// LocalDate: 날짜만
LocalDate today = LocalDate.now();
LocalDate specific = LocalDate.of(2025, 3, 15);

// LocalTime: 시간만
LocalTime currentTime = LocalTime.now();

// LocalDateTime: 날짜 + 시간
LocalDateTime dateTime = LocalDateTime.now();

// Duration: 시간 차이
Duration between = Duration.between(start, end);
long hours = between.toHours();

// 포맷팅
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = dateTime.format(formatter);

// 파싱
LocalDate parsed = LocalDate.parse("2025-03-15", formatter);

3.5 컬렉션 프레임워크

계층 구조

Collection
├── List (순서 있음, 중복 허용)
│   ├── ArrayList (배열 기반, 빠른 조회)
│   ├── LinkedList (연결 리스트, 빠른 삽입/삭제)
│   └── Vector (동기화됨, 레거시)
├── Set (순서 없음, 중복 불가)
│   ├── HashSet (해시 기반, 빠름)
│   ├── LinkedHashSet (입력 순서 유지)
│   └── TreeSet (정렬됨)
└── Queue (FIFO)
    ├── PriorityQueue (우선순위)
    └── Deque (양방향)

Map (키-값 쌍)
├── HashMap (해시 기반)
├── LinkedHashMap (입력 순서 유지)
├── TreeMap (키 정렬)
└── Hashtable (동기화됨, 레거시)

ArrayList 사용

List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");

// 순회
for (String item : list) {
    System.out.println(item);
}

// 람다 순회
list.forEach(System.out::println);

// 스트림 활용
list.stream()
    .filter(s -> s.startsWith("A"))
    .map(String::toUpperCase)
    .forEach(System.out::println);

HashMap 사용

Map<String, Integer> scores = new HashMap<>();
scores.put("Kim", 90);
scores.put("Lee", 85);

// 순회 (Entry 사용)
for (Map.Entry<String, Integer> entry : scores.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

// getOrDefault
int score = scores.getOrDefault("Park", 0);

정렬 (Comparable vs Comparator)

// Comparable - 클래스 내부 정의
class Student implements Comparable<Student> {
    private int age;
    
    @Override
    public int compareTo(Student other) {
        return this.age - other.age;  // 오름차순
    }
}

// Comparator - 외부 정의
Comparator<Student> byName = Comparator
    .comparing(Student::getName)
    .thenComparingInt(Student::getAge);

// 람다 표현
list.sort((a, b) -> b.getScore() - a.getScore());  // 내림차순

3.6 입출력 스트림

바이트 스트림 vs 문자 스트림

구분바이트 스트림문자 스트림
최상위 클래스InputStream, OutputStreamReader, Writer
처리 단위1 byte2 byte (Unicode)
용도이미지, 동영상 등 바이너리텍스트 파일

파일 복사 (버퍼 사용)

// try-with-resources로 자원 자동 해제
try (InputStream in = new FileInputStream("source.jpg");
     BufferedInputStream bin = new BufferedInputStream(in);
     OutputStream out = new FileOutputStream("target.jpg");
     BufferedOutputStream bout = new BufferedOutputStream(out)) {
    
    byte[] buffer = new byte[8192];
    int bytesRead;
    while ((bytesRead = bin.read(buffer)) != -1) {
        bout.write(buffer, 0, bytesRead);
    }
}

텍스트 파일 읽기

// Files 클래스 사용 (Java 7+)
List<String> lines = Files.readAllLines(
    Paths.get("data.txt"), 
    StandardCharsets.UTF_8
);

// BufferedReader (대용량 파일)
try (BufferedReader reader = Files.newBufferedReader(path)) {
    String line;
    while ((line = reader.readLine()) != null) {
        process(line);
    }
}

직렬화 (Serialization)

// Serializable 구현
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private transient String password;  // 직렬화 제외
}

// 저장
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("person.ser"))) {
    oos.writeObject(person);
}

// 복원
try (ObjectInputStream ois = new ObjectInputStream(
        new FileInputStream("person.ser"))) {
    Person restored = (Person) ois.readObject();
}

4. 멀티스레딩

4.1 스레드 생성

Thread 클래스 상속

class WorkerThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(getName() + ": " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
}

// 사용
WorkerThread thread = new WorkerThread();
thread.start();  // start() 호출 필수, run() 직접 호출하면 스레드 생성 안됨

Runnable 인터페이스 구현

class Task implements Runnable {
    @Override
    public void run() {
        System.out.println("작업 실행: " + Thread.currentThread().getName());
    }
}

// 사용
Thread thread = new Thread(new Task());
thread.start();

// 람다 표현
Thread lambdaThread = new Thread(() -> {
    System.out.println("람다로 실행");
});

4.2 스레드 동기화

synchronized 키워드

class BankAccount {
    private int balance = 0;
    private final Object lock = new Object();
    
    // 메서드 동기화
    public synchronized void deposit(int amount) {
        balance += amount;
    }
    
    // 블록 동기화 (권장)
    public void withdraw(int amount) {
        synchronized (lock) {
            if (balance >= amount) {
                balance -= amount;
            }
        }
    }
}

ReentrantLock (고급 동기화)

class AdvancedAccount {
    private final Lock lock = new ReentrantLock();
    private final Condition sufficientFunds = lock.newCondition();
    private int balance;
    
    public void withdraw(int amount) throws InterruptedException {
        lock.lock();
        try {
            while (balance < amount) {
                sufficientFunds.await();  // 조건 대기
            }
            balance -= amount;
        } finally {
            lock.unlock();  // 반드시 해제
        }
    }
    
    public void deposit(int amount) {
        lock.lock();
        try {
            balance += amount;
            sufficientFunds.signalAll();  // 대기 중인 스레드 깨우기
        } finally {
            lock.unlock();
        }
    }
}

4.3 스레드 풀

// 고정 크기 풀
ExecutorService fixedPool = Executors.newFixedThreadPool(4);

// 캐시 풀 (동적 크기)
ExecutorService cachedPool = Executors.newCachedThreadPool();

// 스케줄링 풀
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);

// 작업 제출
Future<Integer> future = fixedPool.submit(() -> {
    return heavyComputation();
});

// 결과 대기
Integer result = future.get();  // 블로킹

// 종료
fixedPool.shutdown();
fixedPool.awaitTermination(1, TimeUnit.MINUTES);

4.4 동시성 컬렉션

동기화된 버전동시성 버전특징
VectorCopyOnWriteArrayList읽기 중심, 쓰기 시 복사
HashtableConcurrentHashMap세그먼트 단위 잠금
Collections.synchronizedSetConcurrentSkipListSet정렬 + 동시성
-BlockingQueue생산자-소비자 패턴
// ConcurrentHashMap
ConcurrentHashMap<String, Long> map = new ConcurrentHashMap<>();
map.putIfAbsent("key", 1L);
map.computeIfPresent("key", (k, v) -> v + 1);

// BlockingQueue
BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
queue.put("item");  // 공간 없으면 대기
String item = queue.take();  // 비어있으면 대기

5. 데이터베이스 연동

5.1 JDBC 기본

// 연결
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false";
try (Connection conn = DriverManager.getConnection(url, "user", "pass");
     PreparedStatement pstmt = conn.prepareStatement(
         "SELECT * FROM users WHERE age > ?")) {
    
    pstmt.setInt(1, 18);
    
    try (ResultSet rs = pstmt.executeQuery()) {
        while (rs.next()) {
            int id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id + ": " + name);
        }
    }
}

5.2 MyBatis 연동

설정 파일

<!-- mybatis-config.xml -->
<configuration>
    <properties resource="jdbc.properties"/>
    
    <typeAliases>
        <package name="com.example.entity"/>
    </typeAliases>
    
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <package name="com.example.mapper"/>
    </mappers>
</configuration>

Mapper 인터페이스 + XML

// UserMapper.java
public interface UserMapper {
    List<User> selectAll();
    User selectById(@Param("id") int id);
    int insert(User user);
    int update(User user);
    int delete(@Param("id") int id);
}
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
    
    <resultMap id="userMap" type="User">
        <id property="id" column="user_id"/>
        <result property="name" column="user_name"/>
    </resultMap>
    
    <select id="selectAll" resultMap="userMap">
        SELECT * FROM users
    </select>
    
    <select id="selectById" resultMap="userMap">
        SELECT * FROM users WHERE user_id = #{id}
    </select>
    
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO users(user_name, email) 
        VALUES(#{name}, #{email})
    </insert>
    
    <!-- 동적 SQL -->
    <select id="search" resultMap="userMap">
        SELECT * FROM users
        <where>
            <if test="name != null">
                AND user_name LIKE CONCAT('%', #{name}, '%')
            </if>
            <if test="minAge != null">
                AND age >= #{minAge}
            </if>
        </where>
    </select>
</mapper>

SqlSession 사용

try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 조회
    User user = mapper.selectById(1);
    
    // 삽입
    User newUser = new User("Kim", "kim@email.com");
    mapper.insert(newUser);
    session.commit();  // 수동 커밋
}

5.3 연관 매핑

1:1 관계

<!-- CardMapper.xml -->
<resultMap id="cardWithPerson" type="Card">
    <id property="id" column="card_id"/>
    <result property="number" column="card_number"/>
    <association property="owner" javaType="Person">
        <id property="id" column="person_id"/>
        <result property="name" column="person_name"/>
        <result property="age" column="person_age"/>
    </association>
</resultMap>

1:N 관계

<!-- ClassMapper.xml -->
<resultMap id="classWithStudents" type="Class">
    <id property="id" column="class_id"/>
    <result property="name" column="class_name"/>
    <collection property="students" ofType="Student">
        <id property="id" column="student_id"/>
        <result property="name" column="student_name"/>
    </collection>
</resultMap>

M:N 관계

<!-- StudentMapper.xml -->
<resultMap id="studentWithCourses" type="Student">
    <id property="id" column="student_id"/>
    <collection property="courses" ofType="Course">
        <id property="id" column="course_id"/>
        <result property="name" column="course_name"/>
        <result property="credit" column="credit"/>
    </collection>
</resultMap>

<select id="selectWithCourses" resultMap="studentWithCourses">
    SELECT s.id student_id, s.name student_name,
           c.id course_id, c.name course_name, c.credit
    FROM students s
    JOIN student_course sc ON s.id = sc.student_id
    JOIN courses c ON sc.course_id = c.id
    WHERE s.id = #{id}
</select>

6. Spring 프레임워크

6.1 핵심 개념

IoC (제어의 역전)

객체의 생성과 의존성 주입을 컨테이너가 담당합니다.

DI (의존성 주입) 방식

// 1. 생성자 주입 (권장)
@Service
public class OrderService {
    private final UserRepository userRepository;
    private final PaymentGateway paymentGateway;
    
    public OrderService(UserRepository userRepository, 
                       PaymentGateway paymentGateway) {
        this.userRepository = userRepository;
        this.paymentGateway = paymentGateway;
    }
}

// 2. Setter 주입
@Autowired
public void setUserRepository(UserRepository repo) {
    this.userRepository = repo;
}

// 3. 필드 주입 (테스트 어려움)
@Autowired
private UserRepository userRepository;

6.2 설정 방식

XML 설정

<beans>
    <bean id="userService" class="com.example.service.UserService">
        <constructor-arg ref="userRepository"/>
        <property name="maxRetryCount" value="3"/>
    </bean>
    
    <bean id="userRepository" class="com.example.repository.JdbcUserRepository">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <!-- ... -->
    </bean>
</beans>

Java Config (권장)

@Configuration
@ComponentScan("com.example")
@PropertySource("classpath:application.properties")
public class AppConfig {
    
    @Bean
    public DataSource dataSource(
            @Value("${jdbc.url}") String url,
            @Value("${jdbc.username}") String username) {
        DruidDataSource ds = new DruidDataSource();
        ds.setUrl(url);
        ds.setUsername(username);
        return ds;
    }
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(ds);
        factory.setMapperLocations(
            new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/*.xml"));
        return factory.getObject();
    }
}

6.3 스코프와 라이프사이클

스코프설명
singleton기본값, 컨테이너당 1개 인스턴스
prototype요청마다 새 인스턴스 생성
requestHTTP 요청당 1개 (웹)
sessionHTTP 세션당 1개 (웹)
@Bean(initMethod = "init", destroyMethod = "cleanup")
@Scope("prototype")
public MyService myService() {
    return new MyService();
}

// 애노테이션 방식
@Component
public class LifecycleBean {
    @PostConstruct
    public void init() { /* 초기화 */ }
    
    @PreDestroy
    public void cleanup() { /* 정리 */ }
}

6.4 AOP (관점 지향 프로그래밍)

핵심 애노테이션

애노테이션설명
@Aspect클래스를 Aspect로 선언
@Pointcut포인트컷 표현식 정의
@Before메서드 실행 전
@After메서드 실행 후 (성공/실패 무관)
@AfterReturning정상 반환 후
@AfterThrowing예외 발생 후
@Around메서드 실행 전후 (가장 강력)

예제: 로깅 Aspect

@Aspect
@Component
public class LoggingAspect {
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    @Around("serviceLayer()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        String methodName = joinPoint.getSignature().getName();
        
        log.info("[시작] {} - 파라미터: {}", 
                methodName, Arrays.toString(joinPoint.getArgs()));
        
        try {
            Object result = joinPoint.proceed();
            long duration = System.currentTimeMillis() - start;
            log.info("[완료] {} - 결과: {}, 소요: {}ms", 
                    methodName, result, duration);
            return result;
        } catch (Exception e) {
            log.error("[실패] {} - 예외: {}", methodName, e.getMessage());
            throw e;
        }
    }
}

트랜잭션 관리

@Configuration
@EnableTransactionManagement
public class TransactionConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource ds) {
        return new DataSourceTransactionManager(ds);
    }
}

// 서비스에서 사용
@Service
public class TransferService {
    
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 10,
        rollbackFor = Exception.class
    )
    public void transfer(int fromId, int toId, BigDecimal amount) {
        accountDao.decrease(fromId, amount);
        accountDao.increase(toId, amount);
    }
}

6.5 Spring MVC

핵심 구성요소

// 1. DispatcherServlet 초기화
public class MyWebAppInitializer 
    extends AbstractAnnotationConfigDispatcherServletInitializer {
    
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }
    
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }
    
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

// 2. 웹 설정
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
                .addResourceLocations("/static/");
    }
    
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.jsp("/WEB-INF/views/", ".jsp");
    }
}

컨트롤러 작성

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    private final UserService userService;
    
    public UserController(UserService userService) {
        this.userService = userService;
    }
    
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        return userService.findById(id)
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity.notFound().build());
    }
    
    @PostMapping
    public ResponseEntity<User> createUser(
            @RequestBody @Valid UserCreateRequest request) {
        User created = userService.create(request);
        URI location = ServletUriComponentsBuilder
            .fromCurrentRequest()
            .path("/{id}")
            .buildAndExpand(created.getId())
            .toUri();
        
        return ResponseEntity.created(location).body(created);
    }
    
    @GetMapping
    public Page<User> listUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String keyword) {
        return userService.search(page, size, keyword);
    }
}

예외 처리

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(new ErrorResponse(e.getMessage(), LocalDateTime.now()));
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationErrors(
            MethodArgumentNotValidException e) {
        
        Map<String, String> errors = new HashMap<>();
        e.getBindingResult().getFieldErrors().forEach(error -> 
            errors.put(error.getField(), error.getDefaultMessage())
        );
        
        return ResponseEntity.badRequest().body(errors);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneric(Exception e) {
        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(new ErrorResponse("서버 오류가 발생했습니다", LocalDateTime.now()));
    }
}

인터셉터

@Component
public class AuthInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) {
        String token = request.getHeader("Authorization");
        if (token == null || !isValid(token)) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
        request.setAttribute("userId", extractUserId(token));
        return true;
    }
}

// 등록
@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    @Autowired
    private AuthInterceptor authInterceptor;
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/auth/**", "/api/public/**");
    }
}

6.6 Spring + MyBatis 통합

@Configuration
@MapperScan("com.example.mapper")
public class MyBatisSpringConfig {
    
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource ds) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(ds);
        
        // 설정 추가
        org.apache.ibatis.session.Configuration config = 
            new org.apache.ibatis.session.Configuration();
        config.setMapUnderscoreToCamelCase(true);
        config.setCacheEnabled(true);
        factory.setConfiguration(config);
        
        return factory.getObject();
    }
    
    @Bean
    public MapperScannerConfigurer mapperScanner() {
        MapperScannerConfigurer scanner = new MapperScannerConfigurer();
        scanner.setBasePackage("com.example.mapper");
        return scanner;
    }
}

// Mapper 인터페이스 (Spring 관리)
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(Long id);
    
    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(User user);
}

7. 웹 개발 심화

7.1 RESTful API 설계

HTTP 메서드와 의미

메서드의미예시
GET조회/users/123
POST생성/users
PUT전체 수정/users/123
PATCH부분 수정/users/123
DELETE삭제/users/123

응답 상태 코드

코드의미사용 상황
200 OK성공GET, PUT, PATCH, DELETE 성공
201 Created생성 완료POST 성공
204 No Content내용 없음DELETE 성공, 응답 본문 없음
400 Bad Request잘못된 요청유효성 검사 실패
401 Unauthorized인증 필요로그인 필요
403 Forbidden권한 없음인증됐지만 권한 없음
404 Not Found리소스 없음존재하지 않는 URL
409 Conflict충돌중복 데이터
500 Internal Server Error서버 오류예상치 못한 예외

7.2 JSON 처리

Jackson 라이브러리

// 객체 → JSON 문자열
ObjectMapper mapper = new ObjectMapper();
User user = new User("Kim", 30);
String json = mapper.writeValueAsString(user);
// {"name":"Kim","age":30}

// JSON 문자열 → 객체
User parsed = mapper.readValue(json, User.class);

// 컬렉션 처리
List<User> users = mapper.readValue(jsonArray, new TypeReference<List<User>>() {});

Spring에서 자동 변환

@RestController
public class UserController {
    
    // @RequestBody: JSON → 객체
    @PostMapping("/users")
    public User create(@RequestBody @Valid UserRequest request) {
        return userService.create(request);
    }
    
    // @ResponseBody: 객체 → JSON (@RestController에 포함)
    @GetMapping("/users/{id}")
    public UserResponse get(@PathVariable Long id) {
        return userService.findById(id);
    }
}

7.3 파일 업로드/다운로드

@RestController
public class FileController {
    
    private final Path uploadDir = Paths.get("uploads");
    
    @PostMapping("/upload")
    public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) {
        try {
            if (file.isEmpty()) {
                return ResponseEntity.badRequest().body("파일이 비어있습니다");
            }
            
            String filename = UUID.randomUUID() + "_" + file.getOriginalFilename();
            Path target = uploadDir.resolve(filename);
            Files.copy(file.getInputStream(), target);
            
            return ResponseEntity.ok("업로드 성공: " + filename);
        } catch (IOException e) {
            return ResponseEntity.status(500).body("업로드 실패");
        }
    }
    
    @GetMapping("/download/{filename}")
    public ResponseEntity<Resource> download(@PathVariable String filename) {
        Path file = uploadDir.resolve(filename);
        Resource resource = new UrlResource(file.toUri());
        
        return ResponseEntity.ok()
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .header(HttpHeaders.CONTENT_DISPOSITION, 
                    "attachment; filename=\"" + filename + "\"")
            .body(resource);
    }
}

8. 유틸리티와 모범 사례

8.1 유틸리티 클래스

// 문자열 처리
String joined = String.join(", ", names);
boolean blank = StringUtils.isBlank(input);  // null, "", "   " 모두 체크

// 날짜 포맷
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
String koreanDate = LocalDate.now().format(formatter);

// 컬렉션
List<String> immutable = Collections.unmodifiableList(list);
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());

// 객체 비교
Objects.equals(a, b);  // null-safe equals
Objects.requireNonNull(obj, "obj는 null일 수 없습니다");

// 스트림 유틸리티
List<Integer> numbers = IntStream.range(1, 100)
    .boxed()
    .collect(Collectors.toList());

8.2 모범 사례 체크리스트

코드 품질

  • 의미 있는 변수명과 메서드명 사용
  • 메서드는 단일 책임 원칙 준수 (10줄 이상 시 분리 검토)
  • 매직 넘버 상수화
  • null 반환 지양, Optional 사용
  • 예외는 구체적으로 처리

성능

  • StringBuilder로 문자열 누적
  • 컬렉션 크기 예상 시 초기 용량 지정
  • 스트림 대신 루프 사용 검토 (단순 반복)
  • 데이터베이스 쿼리 N+1 문제 방지
  • 캐싱 적용 (반복 조회 데이터)

보안

  • SQL 인젝션 방지: PreparedStatement 사용
  • XSS 방지: 출력 시 이스케이프 처리
  • 민감 정보 암호화
  • 세션 관리 및 CSRF 토큰 사용
  • 입력값 검증 (클라이언트 + 서버)

8.3 테스트

@SpringJUnitConfig(AppConfig.class)
class UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @MockBean
    private EmailSender emailSender;
    
    @Test
    @DisplayName("회원가입 성공 시 이메일 발송")
    void signupSendsEmail() {
        // given
        SignupRequest request = new SignupRequest("test@email.com", "password123");
        
        // when
        User created = userService.signup(request);
        
        // then
        assertThat(created.getEmail()).isEqualTo("test@email.com");
        verify(emailSender).sendWelcomeEmail(anyString());
    }
    
    @Test
    @DisplayName("중복 이메일 가입 시 예외 발생")
    void duplicateEmailThrowsException() {
        // given
        when(userRepository.existsByEmail(anyString())).thenReturn(true);
        
        // when & then
        assertThatThrownBy(() -> userService.signup(request))
            .isInstanceOf(DuplicateEmailException.class)
            .hasMessageContaining("이미 등록된 이메일");
    }
}

8.4 빌드 도구 (Maven)

<!-- pom.xml 핵심 구조 -->
<project>
    <groupId>com.example</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0.0</version>
    <packaging>war</packaging>
    
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.30</version>
        </dependency>
        
        <!-- MyBatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.1.2</version>
        </dependency>
        
        <!-- 테스트 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.4.0</version>
            </plugin>
        </plugins>
    </build>
</project>

이 문서는 Java 프로그래밍의 핵심 개념부터 Spring 프레임워크를 활용한 웹 애플리케이션 개발까지 폭넓게 다룹니다. 각 섹션은 실제 코드 예시와 함께 제공되어 즉시 적용 가능한 형태로 구성되었습니다.

태그: java Spring Framework MyBatis JDBC MVC

6월 26일 00:04에 게시됨