카테고리 없음

6. 자바 최고급 학습: 설계와 최적화까지 초보자를 위한 A-Z 가이드

후라이펜 2025. 3. 13. 09:52
반응형

"자바 최고급 학습: 설계와 최적화까지 초보자를 위한 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를 만들고, 실무 수준의 기능을 추가해요.

단계별 구현:

  1. 데이터베이스 설정: 테이블 생성.

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
);
  1. 도메인 객체: 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;
}
  1. 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 요청/응답에 사용할 데이터를 정의해요.

  1. 매퍼: 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);
}
  1. 매퍼 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}
    

  1. 서비스: 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);
    }
}
  1. 예외 클래스: 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);
    }
}
  1. 컨트롤러: 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());
    }
}
  1. 테스트: 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으로 게시글 생성을 테스트해요.

  1. 실행 및 테스트: 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 추천 자료
  • : "Effective Java" (링크)
  • 강의: "백기선의 더 자바" (링크)
소요 시간

12~16주 (하루 2~3시간).

결론

최고급 기술로 실무 전문가 되기!

태그

#자바최고급 #SpringBoot #MyBatis #디자인패턴 #JVM #마이크로서비스 #2025프로그래밍

반응형