1. Java 기본 문법
1.1 개발 환경 단축키
IDE에서 자주 사용하는 단축키를 정리합니다.
| 단축키 | 기능 |
|---|---|
sout | System.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의 보수)
- 1을 더함
양수 5: 0101
반전: 1010
+1: 1011 → -5의 표현
1.6 데이터 타입
기본 타입(Primitive Types)
| 분류 | 타입 | 크기 | 범위 |
|---|---|---|---|
| 정수형 | byte | 8bit | -128 ~ 127 |
| short | 16bit | -32,768 ~ 32,767 | |
| int | 32bit | 약 -21억 ~ 21억 | |
| long | 64bit | 약 -900경 ~ 900경 | |
| 실수형 | float | 32bit | 단정밀도 |
| double | 64bit | 배정밀도 | |
| 문자형 | char | 16bit | Unicode 문자 |
| 논리형 | boolean | 1bit | true/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(); // 다형성 적용
제약사항: abstract는 final, 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
| 특징 | StringBuilder | StringBuffer |
|---|---|---|
| 동기화 | 비동기 (빠름) | 동기화됨 (안전) |
| 용도 | 단일 스레드 | 멀티 스레드 |
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, OutputStream | Reader, Writer |
| 처리 단위 | 1 byte | 2 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 동시성 컬렉션
| 동기화된 버전 | 동시성 버전 | 특징 |
|---|---|---|
| Vector | CopyOnWriteArrayList | 읽기 중심, 쓰기 시 복사 |
| Hashtable | ConcurrentHashMap | 세그먼트 단위 잠금 |
| Collections.synchronizedSet | ConcurrentSkipListSet | 정렬 + 동시성 |
| - | 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 | 요청마다 새 인스턴스 생성 |
| request | HTTP 요청당 1개 (웹) |
| session | HTTP 세션당 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 프레임워크를 활용한 웹 애플리케이션 개발까지 폭넓게 다룹니다. 각 섹션은 실제 코드 예시와 함께 제공되어 즉시 적용 가능한 형태로 구성되었습니다.