"자바 최고급 학습: 설계와 최적화까지 초보자를 위한 A-Z 완벽 가이드"
서론: 자바 전문가로의 마지막 도약
고급 단계를 넘어 이제 자바의 최고급 기술을 마스터할 시간이에요. 이 단계에서는 시스템 설계, 성능 최적화, 대규모 프로젝트를 다루는 법을 배웁니다. 디자인 패턴, JVM 내부, 마이크로서비스, 그리고 Spring Boot와 MyBatis를 활용한 완전한 API 서버를 구축해 봅니다. 실무에서 전문가로 인정받을 수 있는 수준으로 끌어올릴 거예요. 이 글을 끝내면 여러분은 단순한 개발자를 넘어 설계와 최적화까지 책임질 수 있는 엔지니어가 될 겁니다. 준비됐죠? 그럼 최고급 여정을 시작합시다!
6.1 최고급 개념: 시스템 설계와 최적화
최고급 단계에서는 코드를 넘어 전체 시스템을 설계하고, 성능을 극대화하는 기술을 익힙니다.
디자인 패턴: 설계의 정석
디자인 패턴이란?: 코딩 문제를 해결하는 검증된 설계 방법이에요. 실무에서 코드를 깔끔하고 확장 가능하게 유지하려면 필수예요.
주요 패턴:
- 싱글톤(Singleton): 객체를 하나만 생성.
- 팩토리(Factory): 객체 생성을 공장처럼 관리.
- 옵저버(Observer): 이벤트 감지 및 반응.
예제 1: 싱글톤: 설정 관리자를 하나만 만들어요.
public class ConfigManager {
private static ConfigManager instance;
private String configData;
private ConfigManager() {
configData = "기본 설정";
System.out.println("ConfigManager 초기화!");
}
public static ConfigManager getInstance() {
if (instance == null) {
synchronized (ConfigManager.class) { // 멀티스레드 안전
if (instance == null) {
instance = new ConfigManager();
}
}
}
return instance;
}
public String getConfigData() { return configData; }
public void setConfigData(String configData) { this.configData = configData; }
}
class Main {
public static void main(String[] args) {
ConfigManager config1 = ConfigManager.getInstance();
ConfigManager config2 = ConfigManager.getInstance();
config1.setConfigData("최적화 설정");
System.out.println("config1: " + config1.getConfigData());
System.out.println("config2: " + config2.getConfigData());
System.out.println("같은 객체? " + (config1 == config2));
}
}
출력: "ConfigManager 초기화!" → "config1: 최적화 설정" → "config2: 최적화 설정" → "같은 객체? true"
설명: synchronized
로 멀티스레드 환경에서 안전하게 객체를 생성해요.
예제 2: 팩토리: 객체 생성 공장을 만들어 봅시다.
interface Product {
void use();
}
class Book implements Product {
public void use() { System.out.println("책을 읽어요!"); }
}
class Pen implements Product {
public void use() { System.out.println("펜으로 써요!"); }
}
class ProductFactory {
public static Product createProduct(String type) {
if ("book".equals(type)) return new Book();
if ("pen".equals(type)) return new Pen();
return null;
}
}
class Main {
public static void main(String[] args) {
Product book = ProductFactory.createProduct("book");
Product pen = ProductFactory.createProduct("pen");
book.use();
pen.use();
}
}
출력: "책을 읽어요!" → "펜으로 써요!"
실무 적용: 객체 생성 로직을 한 곳에서 관리해 코드 중복을 줄여요.
JVM: 자바의 심장 이해하기
JVM이란?: 자바 코드를 실행하는 가상 머신이에요. 코드를 컴퓨터가 이해할 수 있는 바이트코드로 바꾸고 실행해요.
JVM 구조:
- 클래스 로더:
.class
파일을 메모리에 로드. - 런타임 데이터 영역:
- Heap: 객체와 배열 저장.
- Stack: 메소드 호출과 지역 변수.
- Method Area: 클래스 정보와 정적 변수.
- 실행 엔진: 바이트코드를 실행 (인터프리터 + JIT 컴파일러).
- 가비지 컬렉터(GC): 사용 안 하는 메모리 정리.
예제: 메모리 모니터링:
public class JvmExample {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
System.out.println("최대 메모리: " + runtime.maxMemory() / 1024 / 1024 + "MB");
System.out.println("현재 메모리: " + runtime.totalMemory() / 1024 / 1024 + "MB");
System.out.println("남은 메모리: " + runtime.freeMemory() / 1024 / 1024 + "MB");
// 메모리 사용
String[] memoryHog = new String[1000000];
for (int i = 0; i < 1000000; i++) {
memoryHog[i] = "데이터 " + i;
}
System.out.println("메모리 사용 후 남은 메모리: " + runtime.freeMemory() / 1024 / 1024 + "MB");
// GC 호출
memoryHog = null; // 참조 제거
System.gc();
System.out.println("GC 후 남은 메모리: " + runtime.freeMemory() / 1024 / 1024 + "MB");
}
}
출력 예시: "최대 메모리: 4096MB" → "현재 메모리: 256MB" → "남은 메모리: 250MB" → "메모리 사용 후: 200MB" → "GC 후: 240MB"
최적화 팁:
- 큰 객체는 필요한 만큼만 생성.
- GC 로그를 확인해 메모리 누수를 체크(-Xlog:gc
옵션).
마이크로서비스: 시스템을 작게 쪼개기
마이크로서비스란?: 큰 시스템을 작은 독립 서비스로 나누는 설계 방식이에요. 예를 들어, 쇼핑몰을 "주문", "결제", "배송" 서비스로 나눠요.
장점: 한 서비스가 고장 나도 전체가 멈추지 않고, 배포와 확장이 쉬워요.
Spring Boot와 연계: Spring Cloud로 마이크로서비스를 쉽게 구현할 수 있어요.
핵심 요소: - 서비스 분리: 각 서비스가 독립 DB를 가짐. - API 통신: REST나 메시지 큐로 연결. - 컨테이너: Docker로 배포.
6.2 Spring Boot + MyBatis 심화 셋팅
실무 수준의 프로젝트를 위해 Spring Boot와 MyBatis를 심화 설정해 봅시다.
의존성 추가: pom.xml
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
3.0.3
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
true
설명: lombok
은 Getter/Setter를 자동 생성해 코드 길이를 줄여줘요.
application.yml: src/main/resources/application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/board_db?useSSL=false&serverTimezone=UTC
username: root
password: password123
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mappers/*.xml
type-aliases-package: com.example.boardapp.domain
configuration:
map-underscore-to-camel-case: true # DB의 snake_case를 camelCase로 변환
server:
port: 8081 # 기본 포트 변경 (마이크로서비스 대비)
디렉토리 구조: 실무 표준 구조로 확장.
boardapp/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/boardapp/
│ │ │ ├── BoardappApplication.java
│ │ │ ├── config/ # 설정 클래스
│ │ │ │ └── MyBatisConfig.java
│ │ │ ├── controller/
│ │ │ │ └── BoardController.java
│ │ │ ├── domain/
│ │ │ │ └── Post.java
│ │ │ ├── dto/ # 데이터 전송 객체
│ │ │ │ └── PostDto.java
│ │ │ ├── mapper/
│ │ │ │ └── PostMapper.java
│ │ │ ├── service/
│ │ │ │ └── BoardService.java
│ │ │ └── exception/ # 예외 처리
│ │ │ └── ResourceNotFoundException.java
│ │ └── resources/
│ │ ├── application.yml
│ │ └── mappers/
│ │ └── PostMapper.xml
│ └── test/
└── pom.xml
6.3 실습: 마이크로서비스 스타일 API 서버 구축
목표: Spring Boot와 MyBatis로 완전한 게시판 API를 만들고, 실무 수준의 기능을 추가해요.
단계별 구현:
- 데이터베이스 설정: 테이블 생성.
CREATE TABLE posts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
author VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
- 도메인 객체:
src/main/java/com/example/boardapp/domain/Post.java
package com.example.boardapp.domain;
import lombok.Data;
import java.time.LocalDateTime;
@Data // Lombok으로 Getter/Setter 자동 생성
public class Post {
private Long id;
private String title;
private String content;
private String author;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
- DTO:
src/main/java/com/example/boardapp/dto/PostDto.java
package com.example.boardapp.dto;
import lombok.Data;
@Data
public class PostDto {
private String title;
private String content;
private String author;
}
설명: DTO는 API 요청/응답에 사용할 데이터를 정의해요.
- 매퍼:
src/main/java/com/example/boardapp/mapper/PostMapper.java
package com.example.boardapp.mapper;
import com.example.boardapp.domain.Post;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface PostMapper {
List findAll();
Post findById(Long id);
void insertPost(Post post);
void updatePost(Post post);
void deletePost(Long id);
}
- 매퍼 XML:
src/main/resources/mappers/PostMapper.xml
INSERT INTO posts (title, content, author) VALUES (#{title}, #{content}, #{author})
SELECT LAST_INSERT_ID()
UPDATE posts SET title = #{title}, content = #{content}, author = #{author} WHERE id = #{id}
DELETE FROM posts WHERE id = #{id}
- 서비스:
src/main/java/com/example/boardapp/service/BoardService.java
package com.example.boardapp.service;
import com.example.boardapp.domain.Post;
import com.example.boardapp.dto.PostDto;
import com.example.boardapp.mapper.PostMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BoardService {
@Autowired
private PostMapper postMapper;
public List getAllPosts() {
return postMapper.findAll();
}
public Post getPostById(Long id) {
return postMapper.findById(id);
}
public Post createPost(PostDto postDto) {
Post post = new Post();
post.setTitle(postDto.getTitle());
post.setContent(postDto.getContent());
post.setAuthor(postDto.getAuthor());
postMapper.insertPost(post);
return post;
}
public void updatePost(Long id, PostDto postDto) {
Post post = new Post();
post.setId(id);
post.setTitle(postDto.getTitle());
post.setContent(postDto.getContent());
post.setAuthor(postDto.getAuthor());
postMapper.updatePost(post);
}
public void deletePost(Long id) {
postMapper.deletePost(id);
}
}
- 예외 클래스:
src/main/java/com/example/boardapp/exception/ResourceNotFoundException.java
package com.example.boardapp.exception;
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}
- 컨트롤러:
src/main/java/com/example/boardapp/controller/BoardController.java
package com.example.boardapp.controller;
import com.example.boardapp.domain.Post;
import com.example.boardapp.dto.PostDto;
import com.example.boardapp.exception.ResourceNotFoundException;
import com.example.boardapp.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/posts")
public class BoardController {
@Autowired
private BoardService boardService;
@GetMapping
public ResponseEntity> getAllPosts() {
List posts = boardService.getAllPosts();
return ResponseEntity.ok(posts);
}
@GetMapping("/{id}")
public ResponseEntity getPostById(@PathVariable Long id) {
Post post = boardService.getPostById(id);
if (post == null) {
throw new ResourceNotFoundException("게시글 ID " + id + "를 찾을 수 없어요.");
}
return ResponseEntity.ok(post);
}
@PostMapping
public ResponseEntity createPost(@RequestBody PostDto postDto) {
Post post = boardService.createPost(postDto);
return ResponseEntity.ok(post);
}
@PutMapping("/{id}")
public ResponseEntity updatePost(@PathVariable Long id, @RequestBody PostDto postDto) {
boardService.updatePost(id, postDto);
return ResponseEntity.ok().build();
}
@DeleteMapping("/{id}")
public ResponseEntity deletePost(@PathVariable Long id) {
boardService.deletePost(id);
return ResponseEntity.ok().build();
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(404).body(ex.getMessage());
}
}
- 테스트:
src/test/java/com/example/boardapp/BoardappApplicationTests.java
package com.example.boardapp;
import com.example.boardapp.domain.Post;
import com.example.boardapp.service.BoardService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@SpringBootTest
class BoardappApplicationTests {
@Autowired
private BoardService boardService;
@Test
void testCreatePost() {
Post post = new Post();
post.setTitle("테스트 게시글");
post.setContent("테스트 내용");
post.setAuthor("테스터");
boardService.createPost(new PostDto(post.getTitle(), post.getContent(), post.getAuthor()));
assertNotNull(boardService.getAllPosts().get(0));
}
}
설명: JUnit으로 게시글 생성을 테스트해요.
- 실행 및 테스트:
BoardappApplication
실행 후 curl/Postman으로 테스트.
예시 요청:
- 생성: curl -X POST http://localhost:8081/api/posts -H "Content-Type: application/json" -d '{"title":"최고급 학습","content":"마이크로서비스 완성","author":"Grok"}'
- 조회: curl http://localhost:8081/api/posts
6.4 실무 적용 팁
- 로깅: SLF4J와 Logback으로 상세 로그 추가.
- 성능 최적화: MyBatis 캐시 설정, 인덱스 추가.
- 배포: Docker로 컨테이너화.
6.5 추천 자료
소요 시간
12~16주 (하루 2~3시간).
결론
최고급 기술로 실무 전문가 되기!
태그
#자바최고급 #SpringBoot #MyBatis #디자인패턴 #JVM #마이크로서비스 #2025프로그래밍