<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>cheers_to_every.log</title>
        <link>https://velog.io/</link>
        <description>한 발자국씩</description>
        <lastBuildDate>Sun, 26 Oct 2025 14:26:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. cheers_to_every.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/cheers_to_every" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[MySQL] 혼자 공부하는 SQL - 3, 4강]]></title>
            <link>https://velog.io/@cheers_to_every/MySQL-%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-SQL-3-4%EA%B0%95</link>
            <guid>https://velog.io/@cheers_to_every/MySQL-%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-SQL-3-4%EA%B0%95</guid>
            <pubDate>Sun, 26 Oct 2025 14:26:22 GMT</pubDate>
            <description><![CDATA[<h1 id="3장-sql-기본-문법">3장. SQL 기본 문법</h1>
<h2 id="31-select--from--where">3.1. SELECT ~ FROM ~ WHERE</h2>
<h3 id="1-select">1. SELECT</h3>
<ul>
<li>구축이 완료된 테이블에서 데이터를 추출하는 기능을 한다.</li>
</ul>
<h3 id="2-where-조건식">2. WHERE (조건식)</h3>
<ul>
<li>조건을 지정해 필요한 행만 선택한다.</li>
</ul>
<h4 id="1-조건식-예시">(1) 조건식 예시</h4>
<pre><code class="language-sql">SELECT mem_id, mem_name FROM member WHERE height &lt;= 162;</code></pre>
<h4 id="2-and--or">(2) AND / OR</h4>
<pre><code class="language-sql">SELECT mem_id, mem_name FROM member WHERE height &lt;= 162 AND mem_number &gt; 6;
SELECT mem_id, mem_name FROM member WHERE height &lt;= 162 OR mem_number &gt; 6;</code></pre>
<h4 id="3-between-and-숫자-범위">(3) BETWEEN AND (숫자 범위)</h4>
<pre><code class="language-sql">SELECT mem_id, mem_name FROM member WHERE height BETWEEN 163 AND 165;</code></pre>
<h4 id="4-in-문자열-집합">(4) IN (문자열 집합)</h4>
<ul>
<li>문자열 조건을 묶을 때 사용</li>
</ul>
<pre><code class="language-sql">SELECT mem_id, mem_name FROM member WHERE addr IN (&#39;경기&#39;, &#39;전남&#39;, &#39;경남&#39;);</code></pre>
<h4 id="5-like-문자-비교">(5) LIKE (문자 비교)</h4>
<ul>
<li>특정 문자를 포함하거나 시작하는 데이터를 찾는다.</li>
</ul>
<pre><code class="language-sql">SELECT * FROM member WHERE mem_name LIKE &#39;우%&#39;;</code></pre>
<p><strong>정규식 참고</strong></p>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>%</code></td>
<td>여러 글자(0개 이상)</td>
</tr>
<tr>
<td><code>_</code></td>
<td>한 글자</td>
</tr>
</tbody></table>
<hr>
<h3 id="3-데이터베이스-지정-">3. 데이터베이스 지정 (.)</h3>
<ul>
<li>현재 접속 중인 DB가 아니어도 “DB명.테이블명”으로 접근 가능</li>
</ul>
<pre><code class="language-sql">USE sys;
SELECT * FROM market_db.member WHERE mem_name = &#39;블랙핑크&#39;;</code></pre>
<hr>
<h3 id="4-지정한-열만-조회">4. 지정한 열만 조회</h3>
<ul>
<li><code>*</code>는 모든 열을 의미하므로, 필요 열만 선택 가능</li>
</ul>
<pre><code class="language-sql">USE market_db;
SELECT addr, height, debut_date FROM member;</code></pre>
<ul>
<li>열 순서에 따라 출력 결과 순서가 달라진다.</li>
</ul>
<hr>
<h3 id="5-별칭-alias">5. 별칭 (Alias)</h3>
<ul>
<li>열 이름에 별칭을 부여해 출력 시 보기 좋게 만든다.</li>
</ul>
<pre><code class="language-sql">SELECT height AS &#39;키&#39;, debut_date AS &#39;데뷔 날짜&#39;, addr FROM member;</code></pre>
<hr>
<h3 id="기타-명령어-요약">기타 명령어 요약</h3>
<pre><code class="language-sql">DROP DATABASE IF EXISTS market_db; -- 존재하면 삭제
CREATE DATABASE market_db;         -- DB 생성
USE market_db;                     -- DB 선택
CREATE TABLE member (...);         -- 테이블 생성</code></pre>
<hr>
<h2 id="32-select-심화">3.2. SELECT 심화</h2>
<h3 id="1-select-절의-순서">1. SELECT 절의 순서</h3>
<blockquote>
<p>SELECT → FROM → WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
(순서가 틀리면 문법 오류 발생)</p>
</blockquote>
<hr>
<h3 id="2-order-by-정렬">2. ORDER BY (정렬)</h3>
<ul>
<li>결과를 정렬하여 보여준다. (데이터 자체는 변경되지 않음)</li>
</ul>
<pre><code class="language-sql">SELECT * FROM member ORDER BY debut_date DESC, mem_id ASC;</code></pre>
<hr>
<h3 id="3-limit-출력-개수-제한">3. LIMIT (출력 개수 제한)</h3>
<pre><code class="language-sql">LIMIT 3;      -- 위에서 3개 행만 출력
LIMIT 3, 2;   -- 3행부터 2개 출력</code></pre>
<hr>
<h3 id="4-distinct-중복-제거">4. DISTINCT (중복 제거)</h3>
<pre><code class="language-sql">SELECT DISTINCT addr FROM member;</code></pre>
<hr>
<h3 id="5-group-by-그룹별-집계">5. GROUP BY (그룹별 집계)</h3>
<h4 id="1-집계-함수">(1) 집계 함수</h4>
<table>
<thead>
<tr>
<th>함수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>SUM()</code></td>
<td>합계</td>
</tr>
<tr>
<td><code>AVG()</code></td>
<td>평균</td>
</tr>
<tr>
<td><code>MIN()</code></td>
<td>최소값</td>
</tr>
<tr>
<td><code>MAX()</code></td>
<td>최대값</td>
</tr>
<tr>
<td><code>COUNT()</code></td>
<td>행 개수 (NULL 제외)</td>
</tr>
<tr>
<td><code>COUNT(DISTINCT)</code></td>
<td>중복 제외한 행 개수</td>
</tr>
</tbody></table>
<h4 id="2-예시">(2) 예시</h4>
<pre><code class="language-sql">SELECT mem_id, AVG(amount) AS &#39;평균 구매 개수&#39;
FROM buy
GROUP BY mem_id;</code></pre>
<hr>
<h3 id="6-having">6. HAVING</h3>
<ul>
<li><code>GROUP BY</code>의 결과에 조건을 걸 때 사용 (집계 함수 사용 가능)</li>
</ul>
<pre><code class="language-sql">SELECT mem_id, SUM(amount) AS &#39;총 구매량&#39;
FROM buy
GROUP BY mem_id
HAVING SUM(amount) &gt;= 10;</code></pre>
<hr>
<h2 id="33-데이터-변경을-위한-sql">3.3. 데이터 변경을 위한 SQL</h2>
<h3 id="1-insert-into">1. INSERT INTO</h3>
<h4 id="1-기본-삽입">(1) 기본 삽입</h4>
<pre><code class="language-sql">CREATE TABLE hongong1 (toy_id INT, toy_name CHAR(4), age INT);
INSERT INTO hongong1 VALUES (1, &#39;우디&#39;, 25);</code></pre>
<h4 id="2-열-지정-삽입">(2) 열 지정 삽입</h4>
<pre><code class="language-sql">INSERT INTO hongong1 (toy_id, toy_name, age)
VALUES (3, &#39;제시&#39;, 20);</code></pre>
<hr>
<h3 id="2-auto_increment-alter-table">2. AUTO_INCREMENT, ALTER TABLE</h3>
<ul>
<li>자동 증가 번호 지정</li>
<li>시작 번호 및 증가 간격 설정 가능</li>
</ul>
<pre><code class="language-sql">CREATE TABLE hongong2 (
    toy_id INT AUTO_INCREMENT PRIMARY KEY,
    toy_name CHAR(4),
    age INT
);

INSERT INTO hongong2 VALUES (NULL, &#39;보핍&#39;, 25);
INSERT INTO hongong2 VALUES (NULL, &#39;렉스&#39;, 21);

SELECT LAST_INSERT_ID(); -- 마지막 입력된 ID 확인
ALTER TABLE hongong2 AUTO_INCREMENT = 100;
SET @@auto_increment_increment = 3;</code></pre>
<hr>
<h3 id="3-insert-into--select">3. INSERT INTO ~ SELECT</h3>
<ul>
<li>다른 테이블의 데이터를 한 번에 복사 삽입</li>
</ul>
<pre><code class="language-sql">CREATE TABLE city_popul (city_name CHAR(35), population INT);
INSERT INTO city_popul
SELECT Name, Population FROM world.city;</code></pre>
<hr>
<h3 id="4-update">4. UPDATE</h3>
<pre><code class="language-sql">UPDATE city_popul
SET city_name = &#39;뉴욕&#39;, population = 0
WHERE city_name = &#39;New York&#39;;

UPDATE city_popul
SET population = population / 10000;</code></pre>
<hr>
<h3 id="5-delete">5. DELETE</h3>
<pre><code class="language-sql">DELETE FROM city_popul
WHERE city_name LIKE &#39;New%&#39;
LIMIT 5;</code></pre>
<hr>
<h3 id="참고-명령">참고 명령</h3>
<pre><code class="language-sql">DESC world.city; -- 테이블 구조 확인</code></pre>
<hr>
<h1 id="4장-자료형과-변수">4장. 자료형과 변수</h1>
<h2 id="41-숫자형-int">4.1. 숫자형 (INT)</h2>
<table>
<thead>
<tr>
<th>자료형</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>TINYINT</td>
<td>작은 정수</td>
</tr>
<tr>
<td>SMALLINT</td>
<td>중간 크기 정수</td>
</tr>
<tr>
<td>INT</td>
<td>일반 정수</td>
</tr>
<tr>
<td>BIGINT</td>
<td>매우 큰 정수</td>
</tr>
</tbody></table>
<blockquote>
<p>단순 숫자 외 “전화번호” 등은 <strong>문자형(CHAR/VARCHAR)</strong> 사용</p>
</blockquote>
<hr>
<h2 id="42-문자형">4.2. 문자형</h2>
<table>
<thead>
<tr>
<th>자료형</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>CHAR(n)</td>
<td>고정 길이 문자열 (최대 255자) — 빠름</td>
</tr>
<tr>
<td>VARCHAR(n)</td>
<td>가변 길이 문자열 (최대 16,383자) — 공간 효율적</td>
</tr>
</tbody></table>
<hr>
<h2 id="43-긴-문자열-및-바이너리">4.3. 긴 문자열 및 바이너리</h2>
<table>
<thead>
<tr>
<th>자료형</th>
<th>범위</th>
</tr>
</thead>
<tbody><tr>
<td>TEXT</td>
<td>1 ~ 65,535</td>
</tr>
<tr>
<td>LONGTEXT</td>
<td>1 ~ 4,294,967,295</td>
</tr>
<tr>
<td>BLOB</td>
<td>1 ~ 65,535</td>
</tr>
<tr>
<td>LONGBLOB</td>
<td>1 ~ 4,294,967,295</td>
</tr>
</tbody></table>
<hr>
<h2 id="44-실수형">4.4. 실수형</h2>
<table>
<thead>
<tr>
<th>자료형</th>
<th>크기</th>
</tr>
</thead>
<tbody><tr>
<td>FLOAT</td>
<td>4 byte</td>
</tr>
<tr>
<td>DOUBLE</td>
<td>8 byte</td>
</tr>
</tbody></table>
<hr>
<h2 id="45-날짜형">4.5. 날짜형</h2>
<table>
<thead>
<tr>
<th>자료형</th>
<th>크기</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>DATE</td>
<td>3 byte</td>
<td>연-월-일</td>
</tr>
<tr>
<td>TIME</td>
<td>3 byte</td>
<td>시:분:초</td>
</tr>
<tr>
<td>DATETIME</td>
<td>8 byte</td>
<td>날짜 + 시간</td>
</tr>
</tbody></table>
<hr>
<h2 id="46-변수의-사용">4.6. 변수의 사용</h2>
<ul>
<li>변수는 <strong>임시저장</strong> (Workbench 종료 시 사라짐)</li>
</ul>
<pre><code class="language-sql">SET @변수이름 = 값;
SELECT @변수이름;</code></pre>
<h3 id="prepare--execute">PREPARE / EXECUTE</h3>
<ul>
<li>LIMIT 내에서는 변수를 직접 사용할 수 없으므로, PREPARE 구문으로 대체</li>
</ul>
<pre><code class="language-sql">SET @limit_cnt = 5;
PREPARE stmt FROM &#39;SELECT * FROM member LIMIT ?&#39;;
EXECUTE stmt USING @limit_cnt;</code></pre>
<hr>
<h2 id="47-데이터-형변환">4.7. 데이터 형변환</h2>
<table>
<thead>
<tr>
<th>함수</th>
<th>문법</th>
</tr>
</thead>
<tbody><tr>
<td>CAST</td>
<td><code>CAST(값 AS 데이터형식[길이])</code></td>
</tr>
<tr>
<td>CONVERT</td>
<td><code>CONVERT(값, 데이터형식[길이])</code></td>
</tr>
</tbody></table>
<p><strong>동일한 기능</strong>이며 문법만 다름</p>
<hr>
<h2 id="48-문자열-결">4.8. 문자열 결<img src="https://velog.velcdn.com/images/cheers_to_every/post/cc7906ff-8edd-4c03-9cd1-39fef515038a/image.png" alt=""></h2>
<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/72a74c9b-d755-4f70-8feb-9d980dc7a673/image.png" alt="">
합</p>
<pre><code class="language-sql">SELECT CONCAT(&#39;아이유&#39;, &#39;님&#39;) AS 결과;</code></pre>
<hr>
<h1 id="5장-조인-join">5장. 조인 (JOIN)</h1>
<h2 id="51-개념">5.1. 개념</h2>
<ul>
<li>두 개 이상의 테이블을 연결하여 하나의 결과로 묶는 기능</li>
</ul>
<hr>
<h2 id="52-내부-조인-inner-join">5.2. 내부 조인 (INNER JOIN)</h2>
<ul>
<li>두 테이블이 <strong>일대다(PK-FK)</strong> 관계일 때 사용</li>
</ul>
<pre><code class="language-sql">SELECT 조회할_열
FROM 외래키_테이블 A
INNER JOIN 기본키_테이블 B
ON A.fk_column = B.pk_column
WHERE 조건;</code></pre>
<hr>
<h2 id="53-외부-조인-outer-join">5.3. 외부 조인 (OUTER JOIN)</h2>
<pre><code class="language-sql">SELECT 열_목록
FROM 첫번째_테이블
LEFT OUTER JOIN 두번째_테이블
ON 조건
WHERE 검색조건;</code></pre>
<ul>
<li>LEFT / RIGHT / FULL OUTER JOIN 형태로 사용</li>
</ul>
<hr>
<h2 id="54-상호-조인-cross-join">5.4. 상호 조인 (CROSS JOIN)</h2>
<ul>
<li>두 테이블의 모든 행 조합을 반환 (빅데이터용)</li>
</ul>
<hr>
<h2 id="55-자체-조인-self-join">5.5. 자체 조인 (SELF JOIN)</h2>
<ul>
<li>자기 자신과 조인 — 별칭을 이용해 구분</li>
</ul>
<pre><code class="language-sql">SELECT *
FROM emp_table A
INNER JOIN emp_table B
ON A.member = B.manager;</code></pre>
<hr>
<p><strong>참고</strong></p>
<pre><code class="language-sql">DROP TABLE IF EXISTS 테이블명;  -- 존재 시 삭제
SELECT CURRENT_DATE();         -- 현재 날짜
SELECT DATEDIFF(&#39;2025-12-31&#39;, &#39;2025-01-01&#39;); -- 날짜 차이 계산</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MySQL] 혼자 공부하는 SQL - 1, 2강]]></title>
            <link>https://velog.io/@cheers_to_every/MySQL-%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-SQL-1-2%EA%B0%95</link>
            <guid>https://velog.io/@cheers_to_every/MySQL-%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-SQL-1-2%EA%B0%95</guid>
            <pubDate>Wed, 15 Oct 2025 13:35:33 GMT</pubDate>
            <description><![CDATA[<h1 id="데이터베이스와-mysql-기초-정리">데이터베이스와 MySQL 기초 정리</h1>
<h2 id="1강-데이터베이스-알아보기">1강. 데이터베이스 알아보기</h2>
<h3 id="11-기본-개념">1.1. 기본 개념</h3>
<ul>
<li><p><strong>데이터베이스(DB, DataBase)</strong>: 데이터를 체계적으로 모아 놓은 집합</p>
</li>
<li><p><strong>DBMS (DataBase Management System)</strong>: 데이터베이스를 관리·운용하는 소프트웨어</p>
<ul>
<li><p><strong>조건</strong>:</p>
<ul>
<li>대용량 데이터 관리 가능 (엑셀은 한계 있음)</li>
<li>데이터 공유 가능 (여러 명이 동시에 접근 가능)</li>
</ul>
</li>
<li><p><strong>종류</strong>: MySQL, MongoDB, Oracle 등</p>
</li>
</ul>
</li>
<li><p><strong>SQL (Structured Query Language)</strong>: DBMS에서 데이터를 구축, 관리, 활용하기 위해 사용하는 언어</p>
</li>
</ul>
<h3 id="12-dbms-종류">1.2. DBMS 종류</h3>
<ol>
<li><p><strong>계층형 DBMS</strong>: 트리 형태 구조, 상하 관계 존재</p>
</li>
<li><p><strong>망형 DBMS</strong>: 계층 구조를 벗어나 여러 노드와 상호작용 가능</p>
</li>
<li><p><strong>관계형 DBMS(RDBMS)</strong></p>
<ul>
<li>최소 단위는 <strong>테이블</strong></li>
<li>테이블은 <strong>행(row)</strong>과 <strong>열(column)</strong>로 구성</li>
<li>예: MySQL, Oracle</li>
</ul>
</li>
<li><p><strong>객체지향형 DBMS</strong></p>
</li>
<li><p><strong>객체관계형 DBMS</strong></p>
</li>
</ol>
<h3 id="13-dbms에서-사용되는-언어">1.3. DBMS에서 사용되는 언어</h3>
<ul>
<li>SQL은 국제표준화기구에서 표준으로 지정됨</li>
<li>각 DBMS마다 고유 확장이 존재</li>
</ul>
<h3 id="14-mysql-설치">1.4. MySQL 설치</h3>
<ul>
<li><strong>Workbench</strong>: 서버 자체는 눈에 보이지 않음</li>
<li>SQL 문을 작성하고 실행하여 데이터베이스를 관리</li>
</ul>
<hr>
<h2 id="2강-실전용-sql-미리-맛보기">2강. 실전용 SQL 미리 맛보기</h2>
<h3 id="21-데이터베이스-모델링">2.1. 데이터베이스 모델링</h3>
<ul>
<li><p><strong>데이터베이스 모델링</strong>: 테이블 구조를 설계하는 과정</p>
</li>
<li><p><strong>프로젝트</strong>: 현실 세계 업무를 시스템으로 구현하는 과정</p>
</li>
<li><p><strong>폭포수 모델</strong>:</p>
<pre><code>프로젝트 계획 → 업무 분석 → 시스템 설계 → 프로그램 구현 → 테스트 → 유지보수</code></pre></li>
</ul>
<h4 id="데이터베이스-구성">데이터베이스 구성</h4>
<ul>
<li><p>DBMS(MySQL) → 데이터베이스(폴더) → 테이블(파일)</p>
</li>
<li><p><strong>행(row)</strong>: 실제 데이터 단위</p>
</li>
<li><p><strong>기본키(Primary Key)</strong>: 각 행을 구분하는 유일한 값</p>
<ul>
<li>중복 불가, NULL 불가</li>
<li>테이블 당 하나만 지정 가능</li>
</ul>
</li>
<li><p><strong>데이터 형식</strong>: 열에 지정, 문자, 숫자 등</p>
</li>
</ul>
<hr>
<h3 id="22-데이터베이스-구축-절차">2.2. 데이터베이스 구축 절차</h3>
<h4 id="1-데이터베이스-만들기">1. 데이터베이스 만들기</h4>
<pre><code class="language-sql">CREATE SCHEMA shop_db;</code></pre>
<h4 id="2-테이블-설계">2. 테이블 설계</h4>
<pre><code class="language-sql">CREATE TABLE shop_db.member (
  member_id CHAR(8) NOT NULL,
  member_name CHAR(5) NOT NULL,
  member_addr CHAR(20) NULL,
  PRIMARY KEY (member_id)
);

CREATE TABLE shop_db.product (
  product_name CHAR(10) NOT NULL,
  cost INT NOT NULL,
  make_date DATE NULL,
  company CHAR(5) NULL,
  amount INT NULL,
  PRIMARY KEY (product_name)
);</code></pre>
<h4 id="3-데이터-입력">3. 데이터 입력</h4>
<pre><code class="language-sql">INSERT INTO shop_db.member (member_id, member_name, member_addr) VALUES
(&#39;tess&#39;, &#39;나훈아&#39;, &#39;경기 부천시 중동&#39;),
(&#39;hero&#39;, &#39;임영웅&#39;, &#39;서울 은평구 증산동&#39;),
(&#39;iyou&#39;, &#39;아이유&#39;, &#39;인천 남구 주안동&#39;),
(&#39;jyp&#39;, &#39;박진영&#39;, &#39;경기 고양시 장향동&#39;);

INSERT INTO shop_db.product (product_name, cost, make_date, company, amount) VALUES
(&#39;바나나&#39;, 1500, &#39;2021-07-01&#39;, &#39;델몬트&#39;, 17),
(&#39;카스&#39;, 2500, &#39;2022-03-01&#39;, &#39;OB&#39;, 3),
(&#39;삼각김밥&#39;, 800, &#39;2023-09-01&#39;, &#39;CJ&#39;, 22);</code></pre>
<h4 id="4-데이터-수정">4. 데이터 수정</h4>
<pre><code class="language-sql">UPDATE shop_db.member
SET member_addr = &#39;영국 런던 먹자골목&#39;
WHERE member_id = &#39;carry&#39;;</code></pre>
<h4 id="5-데이터-삭제">5. 데이터 삭제</h4>
<pre><code class="language-sql">DELETE FROM shop_db.member
WHERE member_id = &#39;carry&#39;;</code></pre>
<h4 id="6-데이터-조회">6. 데이터 조회</h4>
<pre><code class="language-sql">SELECT * FROM member;
SELECT member_name, member_addr FROM member;
SELECT * FROM member WHERE member_name = &#39;아이유&#39;;</code></pre>
<hr>
<h3 id="23-데이터베이스-개체">2.3. 데이터베이스 개체</h3>
<ol>
<li><p><strong>테이블(Table)</strong>: 데이터 저장 기본 단위</p>
</li>
<li><p><strong>인덱스(Index)</strong>: 조회 속도 향상</p>
</li>
</ol>
<pre><code class="language-sql">CREATE INDEX idx_member_name ON member(member_name);</code></pre>
<ol start="3">
<li><strong>뷰(View)</strong>: 가상의 테이블, 실제 데이터는 없음</li>
</ol>
<pre><code class="language-sql">CREATE VIEW member_views AS
SELECT * FROM member;</code></pre>
<ol start="4">
<li><strong>스토어드 프로시저(Stored Procedure)</strong></li>
</ol>
<pre><code class="language-sql">DELIMITER //
CREATE PROCEDURE myProc()
BEGIN
    SELECT * FROM member WHERE member_name = &#39;나훈아&#39;;
    SELECT * FROM product WHERE product_name = &#39;삼각김밥&#39;;
END //
DELIMITER ;

CALL myProc();</code></pre>
<ol start="5">
<li><strong>트리거(Trigger), 함수(Function), 커서(Cursor)</strong></li>
</ol>
<ul>
<li><strong>트리거</strong>: 특정 이벤트 발생 시 자동 실행</li>
<li><strong>함수</strong>: 재사용 가능한 SQL 코드 블록</li>
<li><strong>커서</strong>: SELECT 결과를 한 행씩 처리</li>
</ul>
<hr>
<h2 id="정리">정리</h2>
<h2 id="1-데이터베이스">1. 데이터베이스</h2>
<h3 id="11-기본-개념-1">1.1. 기본 개념</h3>
<ol>
<li><strong>데이터베이스</strong></li>
<li><strong>DBMS</strong></li>
<li><strong>SQL</strong></li>
</ol>
<h3 id="12-dbms의-종류">1.2. DBMS의 종류</h3>
<ol>
<li><strong>계층형 DBMS</strong></li>
<li><strong>망형 DBMS</strong></li>
<li><strong>관계형 DBMS (RDBMS - MySQL, Oracle)</strong></li>
<li><strong>객체지향형 DBMS</strong></li>
<li><strong>객체관계형 DBMS</strong></li>
</ol>
<hr>
<h2 id="2-sql">2. SQL</h2>
<h3 id="21-데이터베이스">2.1. 데이터베이스</h3>
<h3 id="1-기본-개념2">1. 기본 개념2</h3>
<ol>
<li><strong>데이터베이스 모델링</strong></li>
<li><strong>프로젝트</strong></li>
<li><strong>폭포수 모델</strong></li>
</ol>
<h3 id="2-전체-데이터베이스-구상도">2. 전체 데이터베이스 구상도</h3>
<ol>
<li><strong>행</strong></li>
<li><strong>Primary key</strong></li>
<li><strong>데이터 형식</strong></li>
</ol>
<h3 id="22-데이터베이스">2.2. 데이터베이스</h3>
<h3 id="1-데이터베이스-구축-절차">1. 데이터베이스 구축 절차</h3>
<ol>
<li><strong>데이터베이스 구축</strong></li>
</ol>
<pre><code class="language-sql">CREATE SCHEMA &#39;데이터베이스 이름&#39;; 
-- CREATE DATABASE로 생성해도 된다.</code></pre>
<ol start="2">
<li><strong>테이블 구축</strong></li>
</ol>
<pre><code class="language-sql">CREATE TABLE &#39;데이터베이스 이름&#39;.&#39;테이블 이름&#39; (
  -- 구축 내용
  PRIMARY KEY(&#39;기본키 컬럼명&#39;)
);</code></pre>
<ol start="3">
<li><strong>데이터 입력/수정/삭제</strong></li>
</ol>
<h2 id="23-데이터베이스-개체-여러-형태">2.3. 데이터베이스 개체 (여러 형태)</h2>
<ol>
<li><strong>테이블</strong></li>
<li><strong>인덱스</strong></li>
</ol>
<pre><code class="language-sql">CREATE INDEX 인덱스_이름 ON 테이블_이름(인덱스로_만들_열_요소);</code></pre>
<ol start="3">
<li><strong>뷰(View)</strong></li>
</ol>
<pre><code class="language-sql">CREATE VIEW 뷰_이름 AS SELECT * FROM 테이블;</code></pre>
<ol start="4">
<li><strong>스토어드 프로시저(Stored Procedure)</strong></li>
</ol>
<pre><code class="language-sql">DELIMITER //
CREATE PROCEDURE 프로시저_이름()
BEGIN
  -- 프로시저 내용
END //
DELIMITER ;</code></pre>
<ol start="5">
<li><strong>트리거, 함수, 커서</strong></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] Spring Security + Google OAuth2 로그인 구현(3)]]></title>
            <link>https://velog.io/@cheers_to_every/Spring-Security-Spring-Security-Google-OAuth2-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%843</link>
            <guid>https://velog.io/@cheers_to_every/Spring-Security-Spring-Security-Google-OAuth2-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%843</guid>
            <pubDate>Tue, 14 Oct 2025 15:23:01 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/10a41ac5-adcb-4630-a183-900847271c05/image.png" alt=""></p>
<h1 id="spring-security--google-oauth2-로그인-구현3">Spring Security + Google OAuth2 로그인 구현(3)</h1>
<p>본 프로젝트는 최주호 님의 「스프링부트 시큐리티 &amp; JWT」 강의를 참고하여 진행하였습니다.</p>
<p>최주호 님의 「스프링부트 시큐리티 &amp; JWT」 강의 git 주소
<a href="https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3">https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3</a></p>
<p>버전 업데이트 이후 수정본으로 진행하는 git 주소
<a href="https://github.com/Solkot/Security_Oauth">https://github.com/Solkot/Security_Oauth</a>
-&gt; branch마다 저장하면서 진행하고 있습니다. OAuth 전의 과정의 경우 Profile branch에 저장되어 있습니다.</p>
<hr>
<h2 id="1-의존성-추가">1. 의존성 추가</h2>
<p><code>spring-boot-starter-oauth2-client</code> 의존성을 <code>pom.xml</code> 또는 <code>build.gradle</code>에 추가합니다.</p>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-oauth2-client&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<p>이 라이브러리가 있어야 Spring Security가 OAuth2 인증 플로우를 자동으로 처리할 수 있습니다.</p>
<hr>
<h2 id="2-google-api-console에서-oauth-프로젝트-생성">2. Google API Console에서 OAuth 프로젝트 생성</h2>
<ol>
<li><p><a href="https://console.cloud.google.com">Google Cloud Console</a> 접속
<img src="https://velog.velcdn.com/images/cheers_to_every/post/287a6d4e-1198-4973-82de-35d07241f63b/image.png" alt="">
<img src="https://velog.velcdn.com/images/cheers_to_every/post/e417bcfe-fd80-4a36-9969-5e46e57bae4b/image.png" alt=""></p>
<p>1.1. 콘솔로 접속</p>
</li>
<li><p>새 프로젝트 생성
<img src="https://velog.velcdn.com/images/cheers_to_every/post/07cd0555-ed36-40d4-9746-2447ffcf059c/image.png" alt=""></p>
</li>
<li><p><strong>OAuth 동의 화면 구성</strong> → 사용자 유형 선택 (외부/내부)</p>
</li>
<li><p><strong>OAuth 클라이언트 ID 생성</strong></p>
<ul>
<li>애플리케이션 유형: <strong>웹 애플리케이션</strong></li>
<li>승인된 리디렉션 URI에 다음 추가</li>
</ul>
</li>
</ol>
<pre><code class="language-bash">http://localhost:8080/login/oauth2/code/google</code></pre>
<p> 4.1. 고정된 기본 리디렉션 URI 패턴(/login/oauth2/code/{registrationId})</p>
<p> Spring Security는 OAuth2 로그인 플로우를 처리하기 위해
리디렉션 URI를 내부적으로 고정된 규칙으로 관리합니다.</p>
<pre><code class="language-bash">{baseUrl}/login/oauth2/code/{registrationId}</code></pre>
<ol start="5">
<li>클라이언트 ID / 클라이언트 비밀번호 복사
baseUrl: 우리 애플리케이션의 루트 주소 (예: <a href="http://localhost:8080">http://localhost:8080</a>
)</li>
</ol>
<p>registrationId: application.yml에 정의한 OAuth2 클라이언트 이름(google, naver 등), 이후 3에서 설정합니다.
 4.2. 고정된 기본 리디렉션 URI 패턴(변경)</p>
<pre><code class="language-yaml">spring:
  security:
    oauth2:
      client:
        registration:
          google:
            redirect-uri: &quot;{baseUrl}/oauth2/callback/{registrationId}&quot;</code></pre>
<p>로 바꿀 순 있다.</p>
<hr>
<h2 id="3-applicationyml-설정">3. <code>application.yml</code> 설정</h2>
<p> <code>client-id</code>, <code>client-secret</code>은 GitHub에 업로드되지 않도록 <code>.gitignore</code> 관리 주의, 아니면 직접 들어가서 지우기</p>
<pre><code class="language-yaml">spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: your-client-id
            client-secret: your-client-secret
            scope:
              - email
              - profile</code></pre>
<p>이 설정으로 Spring Security가 구글 OAuth2 클라이언트로 동작할 수 있게 됩니다.</p>
<p>client-id와 client-secret은 Google cloud API에서 생성된 프로젝트를 보고 채워주면 된다.</p>
<hr>
<h2 id="4-로그인-폼에-oauth-로그인-버튼-추가">4. 로그인 폼에 OAuth 로그인 버튼 추가</h2>
<p><code>loginForm.mustache</code> (또는 HTML)에서 다음과 같이 Google 로그인 링크를 추가합니다.</p>
<pre><code class="language-html">&lt;a href=&quot;/oauth2/authorization/google&quot;&gt;Google로 로그인&lt;/a&gt;</code></pre>
<p>하지만!
아직 <code>.oauth2Login()</code> 설정이 Security 필터 체인에 없기 때문에
<code>/oauth2/authorization/google</code>로 접근 시 404 에러가 발생합니다.</p>
<hr>
<h2 id="5-security-필터-체인-설정">5. Security 필터 체인 설정</h2>
<p>이제 OAuth2 인증 요청을 처리할 수 있도록 필터를 등록해야 합니다.</p>
<pre><code class="language-java"> @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -&gt; authorize
                        .requestMatchers(&quot;/user/**&quot;).authenticated()
                        .requestMatchers(&quot;/manager/**&quot;).hasAnyRole(&quot;ADMIN&quot;, &quot;MANAGER&quot;) //ROLE_ADMIN에서 이제 ROLE_ 같은 접두사는 자동으로 붙기 때문에 사용 X
                        .requestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                        .anyRequest().permitAll()
                )
                .formLogin(form -&gt; form
                        .loginPage(&quot;/loginForm&quot;)
                        .loginProcessingUrl(&quot;/login&quot;)// /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
                        .defaultSuccessUrl(&quot;/&quot;) //로그인 성공 후 이동할 기본 URL 지정
                        .permitAll() // 로그인 페이지와 로그인 요청 URL은 누구나 접근 가능하도록 허용
                ).oauth2Login(oauth2 -&gt; oauth2
                        .loginPage(&quot;/loginForm&quot;) // OAuth2 로그인도 같은 커스텀 로그인 페이지 사용
                        //구글 로그인이 완료된 후에 후처리가 필요, Tip 코드 X(액세스 토큰 + 사용자 프로필 정보)
                        .userInfoEndpoint(userInfo -&gt; userInfo
                                .userService(principalOauth2UserService)
                        )
                        .defaultSuccessUrl(&quot;/&quot;)
                );

        return http.build();
    }</code></pre>
<p>이제 <code>/oauth2/authorization/google</code> 요청이 정상적으로 동작하며,
Spring Security가 <strong>구글 → 인증 코드(code) → 액세스 토큰 → 사용자 정보(OAuth2User)</strong> 로 이어지는 과정을 자동으로 처리합니다.</p>
<hr>
<h2 id="6-spring-security의-oauth2-기본-동작">6. Spring Security의 OAuth2 기본 동작</h2>
<ol>
<li>사용자가 <code>/oauth2/authorization/google</code> 클릭</li>
<li>Google 로그인 페이지로 리디렉션</li>
<li>로그인 성공 후 Google이 <strong>인가 코드(code)</strong> 를 리디렉션 URI로 전달</li>
<li>Spring Security가 인가 코드를 받고 → Google로 <strong>액세스 토큰 요청</strong></li>
<li>Google로부터 <strong>사용자 정보(userinfo)</strong> 를 받아와 <code>OAuth2User</code> 객체 생성</li>
<li><code>OAuth2User</code>를 <code>SecurityContext</code>에 저장 → 인증 완료</li>
</ol>
<blockquote>
<p>하지만 이건 “최소한의 인증 처리”만 해줍니다.
DB 저장, 권한 매핑, 기존 회원 연동은 개발자가 직접 구현해야 합니다.</p>
</blockquote>
<hr>
<h2 id="7-principaloauth2userservice-추가">7. PrincipalOauth2UserService 추가</h2>
<p>OAuth2 로그인 시 받은 사용자 정보를 우리 서비스의 회원 시스템(DB)에 연동하려면
<strong>PrincipalOauth2UserService</strong>를 구현해야 합니다.
UserDetailsService라고 생각하시면 됩니다. 과정은 유사합니다.</p>
<pre><code class="language-java">@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserRepository userRepository; //해당 아이디로 로그인이 되어있는지 확인

    //구글로부터 받은 userRequest 데이터에 대한 후처리 되는 함수
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        System.out.println(&quot;userRequest : &quot; + userRequest);
        System.out.println(&quot;getClientRegistration: &quot;+ userRequest.getClientRegistration()); // registrationId로 어떤 OAuth로 로그인했는지 확인 가능
        System.out.println(&quot;getAccessToken: &quot;+userRequest.getAccessToken().getTokenValue());
        //System.out.println(&quot;getAttributes: &quot;+super.loadUser(userRequest).getAttributes());

        OAuth2User oauth2User =  super.loadUser(userRequest);
        // 구글 로그인 버튼 클릭 -&gt; 구글로그인창 -&gt; 로그인 완료 -&gt; code를 리턴(OAuth-Client라이브러리) -&gt; AccessToken 요청
        //UserRequest 정보 -&gt; 회원프로필 받아야함(loadUser함수 호출) -&gt;구글로 부터 회원프로필 받아준다.
        System.out.println(&quot;getAttributes: &quot;+oauth2User.getAttributes());

        String provider = userRequest.getClientRegistration().getClientId(); //google
        String providerId = oauth2User.getAttribute(&quot;sub&quot;);
        String username = provider + &quot;_&quot; + providerId; //google_...(sub)
        String password = bCryptPasswordEncoder.encode(&quot;비밀번호&quot;); //의미 없음, 인증 받는 곳에서 인증 후 준 코드를 사용하기 때문
        String email = oauth2User.getAttribute(&quot;email&quot;);
        String role = &quot;ROLE_USER&quot;;

        User userEntity = userRepository.findByUsername(username);

        if(userEntity == null){
            System.out.println(&quot;구글 로그인이 최초입니다.&quot;);
            userEntity = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .role(role)
                    .provider(provider)
                    .providerId(providerId)
                    .build();

            userRepository.save(userEntity);
        }//User가 없을 경우만 생성, 이후는 중복적인 생성을 못하게 차단
        else{
            System.out.println(&quot;구글 로그인을 이미 한 적이 있습니다. 당신은 자동회원가입이 되어 있습니다.&quot;);
        }

        //return super.loadUser(userRequest);
        return new PrincipalDetails(userEntity, oauth2User.getAttributes()); //이 정보가 대신해서 Authetication 객체 안으로 들어간다.
        //UserDetails와 OAuth2User을 implements한 PrincipalDetails를 대신 Authentication에 넣음
    }
}</code></pre>
<hr>
<h2 id="8-principaldetails-확장-userdetails--oauth2user">8. PrincipalDetails 확장 (UserDetails + OAuth2User)</h2>
<p>기존에는 <code>UserDetails</code>만 구현했지만,
이제 OAuth2User까지 통합해야 하므로 <strong>두 인터페이스를 모두 구현</strong>합니다.
<img src="blob:https://velog.io/26e644ce-eb08-4501-9976-fa9439c52022" alt="업로드중.."></p>
<pre><code class="language-java">public class PrincipalDetails implements UserDetails, OAuth2User {

    private User user;
    private Map&lt;String, Object&gt; attributes; // OAuth2 정보

    // 일반 로그인용 생성자
    public PrincipalDetails(User user) {
        this.user = user;
    }

    // OAuth2 로그인용 생성자
    public PrincipalDetails(User user, Map&lt;String, Object&gt; attributes) {
        this.user = user;
        this.attributes = attributes;
    }

    @Override
    public Map&lt;String, Object&gt; getAttributes() {
        return attributes;
    }

    @Override
    public String getName() {
        return user.getUsername();
    }

    // UserDetails의 메서드들도 그대로 구현 (getPassword, getAuthorities 등)
}</code></pre>
<p>이제 OAuth2 로그인 시에도 동일한 <code>PrincipalDetails</code> 객체를 사용하여
인증 정보를 <code>Authentication</code>에 담을 수 있습니다.</p>
<h3 id="81-userdetails와-oauth2user를-오버라이딩-한-이유">8.1. UserDetails와 OAuth2User를 오버라이딩 한 이유</h3>
<ol>
<li>OAuth와 UserDetails를 Principal details 타입으로 묶기 위해서</li>
<li>OAuth로 로그인 했을떄 회원가입을 강제로 진행시키기 위해서</li>
</ol>
<hr>
<h2 id="9-인증-정보-확인-controller">9. 인증 정보 확인 (Controller)</h2>
<p>로그인 성공 후, 현재 인증 객체를 확인해봅니다.</p>
<pre><code class="language-java">@GetMapping(&quot;/&quot;)
public String index(Authentication authentication,
                    @AuthenticationPrincipal PrincipalDetails principalDetails) {

    System.out.println(&quot;Authentication: &quot; + authentication.getPrincipal());
    System.out.println(&quot;UserDetails: &quot; + principalDetails.getUsername());
    return &quot;index&quot;;
}</code></pre>
<hr>
<h2 id="10-전체-흐름-정리">10. 전체 흐름 정리</h2>
<table>
<thead>
<tr>
<th>단계</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>OAuth2 의존성 추가</td>
</tr>
<tr>
<td>2</td>
<td>Google API Console에서 OAuth2 클라이언트 생성</td>
</tr>
<tr>
<td>3</td>
<td><code>application.yml</code>에 설정 추가</td>
</tr>
<tr>
<td>4</td>
<td>로그인 폼에 구글 로그인 버튼 추가</td>
</tr>
<tr>
<td>5</td>
<td><code>.oauth2Login()</code> 설정으로 필터 체인 등록</td>
</tr>
<tr>
<td>6</td>
<td>구글 → 인가 코드 → 액세스 토큰 → 사용자 정보 흐름 처리</td>
</tr>
<tr>
<td>7</td>
<td>CustomOAuth2UserService로 사용자 DB 저장/연동</td>
</tr>
<tr>
<td>8</td>
<td>PrincipalDetails: UserDetails + OAuth2User 통합</td>
</tr>
<tr>
<td>9</td>
<td>Authentication / PrincipalDetails로 인증 정보 확인</td>
</tr>
</tbody></table>
<hr>
<h2 id="마무리">마무리</h2>
<p>Spring Security의 OAuth2 로그인은 <strong>“기본 인증 흐름”은 자동</strong>,
<strong>“회원 정보 연동”은 직접 커스터마이징</strong>하는 구조입니다.</p>
<p>Spring Security의 기본 OAuth2 로그인은 사용자의 정보를 받아와 OAuth2User 객체를 생성해서 SecurityContext에 저장해서 로그인을 유지하는 것까지는 됩니다.</p>
<p>하지만 아직 사용자 정보(DB)와 연결되지 않았기에 우리 서비스 입장에서는 &quot;이 사용자가 우리 회원인지&quot;, &quot;권한이 뭔지&quot;를 모릅니다.</p>
<p>그렇기에 </p>
<ol>
<li>OAuth2UserService에서 사용자 정보 받아와 DB에서 해당 이메일로 회원 조회</li>
<li>아이디가 없으면 새로 가입시켜주고 DB에서 조회된 User 엔티티를 기반으로 PrincipalDetails 생성</li>
<li>Authentication 객체에 PrincipalDetails를 직접 넣어서
SecurityContextHolder에 저장</li>
</ol>
<p>하는 PrincipalDetails의 일관성을 유지시키는 작업이 필요하다.
pring Security의 인증 체계 안으로 OAuth2 사용자를 완전히 편입시키는 과정으로 일반 로그인과 OAuth2 로그인이 같은 PrincipalDetails 기반으로 동작할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] 시큐리티 권한 설정(2)]]></title>
            <link>https://velog.io/@cheers_to_every/Spring-Security-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EA%B6%8C%ED%95%9C-%EC%84%A4%EC%A0%952</link>
            <guid>https://velog.io/@cheers_to_every/Spring-Security-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0-%EA%B6%8C%ED%95%9C-%EC%84%A4%EC%A0%952</guid>
            <pubDate>Tue, 14 Oct 2025 13:55:36 GMT</pubDate>
            <description><![CDATA[<h1 id="시큐리티-권한-설정2">시큐리티 권한 설정(2)</h1>
<p>본 프로젝트는 최주호 님의 「스프링부트 시큐리티 &amp; JWT」 강의를 참고하여 진행하였습니다.</p>
<p>최주호 님의 「스프링부트 시큐리티 &amp; JWT」 강의 git 주소
<a href="https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3">https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3</a></p>
<p>버전 업데이트 이후 수정본으로 진행하는 git 주소
<a href="https://github.com/Solkot/Security_Oauth">https://github.com/Solkot/Security_Oauth</a>
-&gt; branch마다 저장하면서 진행하고 있습니다. OAuth 전의 과정의 경우 Profile branch에 저장되어 있습니다.</p>
<h2 id="1-loginpage-vs-loginprocessingurl">1. loginPage vs loginProcessingUrl</h2>
<pre><code class="language-java">public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -&gt; authorize
                        .requestMatchers(&quot;/user/**&quot;).authenticated()
                        .requestMatchers(&quot;/manager/**&quot;).hasAnyRole(&quot;ADMIN&quot;, &quot;MANAGER&quot;) //ROLE_ADMIN에서 이제 ROLE_ 같은 접두사는 자동으로 붙기 때문에 사용 X
                        .requestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                        .anyRequest().permitAll()
                )
                .formLogin(form -&gt; form
                        .loginPage(&quot;/loginForm&quot;)
                        .loginProcessingUrl(&quot;/login&quot;)// /login 주소가 호출이 되면 시큐리티가 낚아채서 대신 로그인을 진행해줍니다.
                        .defaultSuccessUrl(&quot;/&quot;) //로그인 성공 후 이동할 기본 URL 지정
                        .permitAll() // 로그인 페이지와 로그인 요청 URL은 누구나 접근 가능하도록 허용
                ));
                return http.build();}</code></pre>
<p>주석에 달아놓긴 하였으나 실제로 어떻게 차이나는지 저도 궁금해서 찾아보았습니다.</p>
<h3 id="11-loginpageloginform">1.1. .loginPage(&quot;/loginForm&quot;)</h3>
<p> 사용자가 로그인이 필요한 페이지에 접근했을 때, 즉 권한이 필요하다고 생각하는 페이지에 접근했을때 스프링 시큐리가 로그인 페이지로 리다이렉트시켜 이동시키는 경로입니다.</p>
<p> 즉, &quot;로그인 폼이 있는 페이지&quot; 입니다.</p>
<h3 id="12-loginprocessingurllogin">1.2. .loginProcessingUrl(&quot;/login&quot;)</h3>
<p> 사용자가 로그인 폼에서 아이디, 비밀번호를 입력하고 전송할 때 (POST)
이 경로로 요청이 들어오면 Spring Security가 자동으로 가로채서 로그인 인증을 수행합니다.</p>
<p>즉, &quot;로그인 검증을 실행하는 백엔드 처리 URL&quot; 이라고 생각하면 됩니다.</p>
<pre><code class="language-bash">&lt;form action=&quot;/login&quot; method=&quot;post&quot;&gt;
  &lt;input type=&quot;text&quot; name=&quot;username&quot; /&gt;
  &lt;input type=&quot;password&quot; name=&quot;password&quot; /&gt;
  &lt;button type=&quot;submit&quot;&gt;로그인&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>action=&quot;/login&quot;이면 사용자가 로그인 버튼을 눌렀을 때, /login으로 POST 요청 발생, UsernamePasswordAuthenticationFilter가 이 요청을 가로채고 인증 처리를 합니다.</p>
<p>따라서 따로 컨트롤러에 /login 매핑을 직접 만들 필요가 없습니다.</p>
<h2 id="2-로그인-과정">2. 로그인 과정</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>로그인 요청</td>
<td><code>/login</code>으로 POST</td>
</tr>
<tr>
<td>필터 가로채기</td>
<td><code>UsernamePasswordAuthenticationFilter</code></td>
</tr>
<tr>
<td>사용자 조회</td>
<td><code>UserDetailsService</code> → <code>loadUserByUsername()</code></td>
</tr>
<tr>
<td>사용자 정보 변환</td>
<td><code>User</code>(DB 객체) → <code>UserDetails</code>(<code>PrincipalDetails</code>)</td>
</tr>
<tr>
<td>비밀번호 검증</td>
<td><code>PasswordEncoder</code></td>
</tr>
<tr>
<td>인증 성공</td>
<td><code>Authentication</code> 객체 생성</td>
</tr>
<tr>
<td>세션 저장</td>
<td><code>SecurityContextHolder</code></td>
</tr>
<tr>
<td>보안 어노테이션</td>
<td><code>@EnableMethodSecurity</code></td>
</tr>
</tbody></table>
<p>로그인 과정은 위와 같다.</p>
<p>간단하게 보자면 User -&gt; UserDetails -&gt; Authetication -&gt; SecruitySession이라고 보면 된다. </p>
<p> 추가적으로 설명을 덧붙이자면 아래에 나와있다.</p>
<h3 id="21-로그인-요청">2.1. 로그인 요청</h3>
<p>사용자가 로그인 폼에서 아이디·비밀번호를 입력하고 /login 요청을 전송</p>
<h3 id="22-필터-가로채기">2.2. 필터 가로채기</h3>
<p>스프링 시큐리티가 자동으로 등록한 UsernamePasswordAuthenticationFilter가 이미 존재하고,
그 필터가 /loginProcessingUrl()로 지정된 주소(/login) 요청을 자동으로 가로채서 로그인 처리를 합니다.</p>
<h3 id="23-사용자-조회">2.3. 사용자 조회</h3>
<h4 id="231-인증-토큰-생성">2.3.1. 인증 토큰 생성</h4>
<p> UsernamePasswordAuthenticationToken, 필터가 username/password를 꺼내서 인증용 토큰 객체를 만든다. 임시용 토큰이라서 아직 인증은 되지 않은 상태</p>
<h4 id="232-인증-위임">2.3.2. 인증 위임</h4>
<p> AuthenticationManager, 토큰을 AuthenticationManager로 넘기면, 내부에서 UserDetailsService가 실행되어 loadUserByUsername을 통해 DB에서 사용자 정보를 조회</p>
<h3 id="24-사용자-정보-변환">2.4. 사용자 정보 변환</h3>
<h4 id="241-user---userdetails">2.4.1. User -&gt; UserDetails</h4>
<p> DB의 사용자 엔티티(User)를 시큐리티에서 사용할 수 있는 형태(UserDetails)로 변환</p>
<h4 id="242-user-vs-userdetails">2.4.2. User vs UserDetails</h4>
<p> 물론 Authentication 객체에 넘기려면 UserDetails 객체로 넘겨야 하는 이유도 있지만, 우리가 받는 정보에는 날 것의 정보가 들어가 있어, 필요한 정보만 가져가야만 보안에도 유리하다.
 그렇기에 User에서 로그인에 필요한 최소 정보만 갖는 UserDetails를 사용한다.</p>
<h3 id="25-비밀번호-검증">2.5. 비밀번호 검증</h3>
<p>PasswordEncoder, 요청한 비밀번호(평문)와 DB의 암호화된 비밀번호(BCryptPasswordEncoder)를 비교한다.
굳이 loadUserByUsername()에서 비밀번호를 확인하지 않고, 나중에 검증하는 이유는 &quot;단일 책임 원칙&quot;을 지키기 위해서라고 생각한다.
<img src="https://velog.velcdn.com/images/cheers_to_every/post/be4529f8-ba8c-4b0b-a636-c2389808981a/image.png" alt=""></p>
<h3 id="26-인증-성공">2.6. 인증 성공</h3>
<p>인증에 성공하면 Authentication 객체(인증된 사용자 정보, 권한 포함)를 생성한다.</p>
<h3 id="27-세션-저장">2.7. 세션 저장</h3>
<p>SecurityContext에 Authentication을 저장해야 이후에도 로그인 유지가 가능하다.</p>
<h3 id="28-보안-어노테이션">2.8. 보안 어노테이션</h3>
<p>이후 추가적으로 설명하지만 어노테이션을 통해 쉽게 웹페이지마다의 권한을 설정한다고 보면 된다.</p>
<h2 id="3-authentication-authenticationprincipal">3. Authentication, @AuthenticationPrincipal</h2>
<pre><code class="language-java">@GetMapping(&quot;/test/login&quot;)
    public @ResponseBody String testLogin(Authentication authentication, //DI -&gt; downcasting -&gt; userObject //DI(의존성 주입)
                                          @AuthenticationPrincipal PrincipalDetails userDetails) { //DI -&gt; getUser, 원랴는 UserDetails를 받기에 PrincipalDetails로도 받을 수 있음
        System.out.println(&quot;/test/login==&quot;);
        PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); //원래는 UserDetail로 DownCasting해서 받아야 하지만, PrincipalDetails이 UserDetail을 implementation하기에 가능
        System.out.println(&quot;authentication : &quot;+ principalDetails.getUser());
        System.out.println(&quot;userDetails: &quot;+ userDetails.getUser());
        return &quot;세션 정보 확인하기&quot;;
    }</code></pre>
<p>Authentication은 UserDetails를 받는만큼 클라이언트의 정보를 확인할 수 있다.</p>
<p>Authentication은 스프링 시큐리티의 핵심 인증 객체입니다.
로그인에 성공하면 SecurityContextHolder에 저장되고 로그인이 계속 유지된다.</p>
<p>Authentication은 다운캐스팅을 통해 정보를 확인할 수 있지만, @AuthenticationPrincipal은 그럴 필요가 없다.</p>
<p>왜 그런지는 추가적으로 조사해본 결과</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>Authentication</th>
<th>@AuthenticationPrincipal</th>
</tr>
</thead>
<tbody><tr>
<td><strong>의미</strong></td>
<td>스프링 시큐리티의 전체 인증 객체</td>
<td>인증 객체 안의 principal(UserDetails)만 주입</td>
</tr>
<tr>
<td><strong>포함 정보</strong></td>
<td>principal, credentials, authorities, details 등</td>
<td>principal (즉, 로그인한 사용자 정보)</td>
</tr>
<tr>
<td><strong>사용 위치</strong></td>
<td>컨트롤러, 서비스, 시큐리티 내부</td>
<td>주로 컨트롤러 파라미터</td>
</tr>
<tr>
<td><strong>형태</strong></td>
<td>인터페이스(Authentication)</td>
<td>어노테이션(@AuthenticationPrincipal)</td>
</tr>
<tr>
<td><strong>사용 목적</strong></td>
<td>전체 인증 상태 접근</td>
<td>로그인한 사용자 정보만 간단히 사용</td>
</tr>
</tbody></table>
<p>즉, Authentication이 더 여러 정보를 가지고 있지만, 로그인한 사용자 정보만 간단히 조회하려면 @AuthenticationPrincipal이 사용하기 편리하다.</p>
<h2 id="4-enablemethodsecurity">4. @EnableMethodSecurity</h2>
<h3 id="41-enableglobalmethodsecurity">4.1. @EnableGlobalMethodSecurity</h3>
<p>Spring Security 6.x부터는
@EnableGlobalMethodSecurity가 Deprecated(사용 중단) 되었고,
대신 @EnableMethodSecurity를 사용한다. 
<img src="https://velog.velcdn.com/images/cheers_to_every/post/7a6486ee-86d5-4b48-bfd1-7e64df73d87e/image.png" alt=""></p>
<p>기본적으로 스프링 필터에 @EnableMethodSecurity를 사용한다.</p>
<h3 id="42-securedenabled-secured">4.2. securedEnabled, @Secured</h3>
<p> <img src="https://velog.velcdn.com/images/cheers_to_every/post/a3b4d156-c4a5-48a2-9be4-fe0cc73f7319/image.png" alt="">
 @Secured 에노테이션을 사용할 수 있다.
 @Secured의 경우 조건식은 불가능해 권한을 한 가지 걸 수 있다. </p>
<h3 id="43-prepostenabled-preauthorize-postauthorize">4.3. prePostEnabled, @PreAuthorize, @PostAuthorize</h3>
<p> <img src="https://velog.velcdn.com/images/cheers_to_every/post/a6487609-5d9c-49f5-867b-194a5d0b64f7/image.png" alt="">
 @PreAuthorize, @PostAuthorize 애노테이션을 사용할 수 있다.
 @PreAuthorize, @PostAuthorize의 경우 조건식을 통해 다양한 조건을 사용할 수 있으며, 조건식이 존재한다.</p>
<h3 id="44-secured-vs-preauthoriz">4.4. @Secured vs @PreAuthoriz</h3>
<p>단순한 Role 체크만 필요할 때는 @Secured가 더 직관적이기에 사용한다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>@Secured</th>
<th>@PreAuthorize</th>
</tr>
</thead>
<tbody><tr>
<td>권한 표현</td>
<td>단일 Role만 가능</td>
<td>SpEL 사용 가능 → 복수 권한, 조건식 가능</td>
</tr>
<tr>
<td>장점</td>
<td>간단하고 직관적</td>
<td>복잡한 권한 로직 가능, 파라미터 검증 가능</td>
</tr>
<tr>
<td>단점</td>
<td>조건식 불가</td>
<td>단순 체크만 해도 다소 장황</td>
</tr>
<tr>
<td>사용 이유</td>
<td>단순 Role 체크 시 코드 간결</td>
<td>복잡한 조건 필요 시</td>
</tr>
<tr>
<td>참고로 메소드가 아닌 global하게 걸고 싶으면 SpringSecurity 체인에 가서 걸어버리면 된다.</td>
<td></td>
<td></td>
</tr>
</tbody></table>
<h2 id="5-trouble-shooting">5. Trouble Shooting</h2>
<h3 id="51-enableglobalmethodsecurity---enablemethodsecurity">5.1. @EnableGlobalMethodSecurity -&gt; @EnableMethodSecurity</h3>
<p>Spring Security 6.x부터는
@EnableGlobalMethodSecurity가 Deprecated(사용 중단) 되었고,
대신 @EnableMethodSecurity를 사용한다. </p>
<h3 id="6-마무리">6. 마무리</h3>
<p>이로써 Spring Boot Security + JWT 프로젝트의 기본 로그인 과정이 완료되었습니다.
다음 단계에서는 OAuth-goole 과정을 진행할 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] 환경설정(1)]]></title>
            <link>https://velog.io/@cheers_to_every/Spring-Security-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%951</link>
            <guid>https://velog.io/@cheers_to_every/Spring-Security-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%951</guid>
            <pubDate>Tue, 14 Oct 2025 09:39:40 GMT</pubDate>
            <description><![CDATA[<h1 id="환경설정">환경설정</h1>
<p>본 프로젝트는 <strong>최주호 님의 「스프링부트 시큐리티 &amp; JWT」 강의</strong>를 참고하여 진행하였습니다.</p>
<p>최주호 님의 「스프링부트 시큐리티 &amp; JWT」 강의 git 주소
<a href="https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3">https://github.com/codingspecialist/-Springboot-Security-OAuth2.0-V3</a></p>
<p>버전 업데이트 이후 수정본으로 진행하는 git 주소
<a href="https://github.com/Solkot/Security_Oauth">https://github.com/Solkot/Security_Oauth</a>
-&gt; branch마다 저장하면서 진행하고 있습니다. OAuth 전의 과정의 경우 Profile branch에 저장되어 있습니다.</p>
<hr>
<h2 id="1-환경설정-spring-initializer">1. 환경설정 (Spring Initializer)</h2>
<h3 id="11-spring-initializer-설정">1.1 Spring Initializer 설정</h3>
<ul>
<li><p>사이트 접속: <a href="https://start.spring.io/">https://start.spring.io/</a></p>
</li>
<li><p><strong>Project:</strong> Maven</p>
</li>
<li><p><strong>Language:</strong> Java</p>
</li>
<li><p><strong>Spring Boot Version:</strong> (최신 안정 버전)</p>
</li>
<li><p><strong>Dependencies:</strong></p>
<ul>
<li>Spring Security</li>
<li>Spring Web</li>
<li>Mustache</li>
<li>Lombok</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-git-설정">2. Git 설정</h2>
<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/c0bc33d9-ffd0-4a42-a598-1f723f9f0256/image.png" alt="">
터미널로 들어가서 진행하시면 됩니다.</p>
<h3 id="21-git-초기화">2.1 Git 초기화</h3>
<pre><code class="language-bash">git init</code></pre>
<h3 id="22-사용자-정보-설정">2.2 사용자 정보 설정</h3>
<p>이미 설정되어 있다면 생략하셔도 됩니다.</p>
<pre><code class="language-bash">git config user.email &quot;이메일 주소&quot;
git config user.name &quot;사용자 이름&quot;</code></pre>
<h3 id="23-브랜치-이름-변경">2.3 브랜치 이름 변경</h3>
<p>기본 브랜치를 <code>master</code> → <code>main</code>으로 변경합니다.</p>
<pre><code class="language-bash">git branch -M main</code></pre>
<h3 id="24-github-저장소-생성">2.4 GitHub 저장소 생성</h3>
<p>GitHub에서 새 repository를 만듭니다.
(예: <code>spring-security-jwt</code>)</p>
<h3 id="25-파일-추가-및-커밋">2.5 파일 추가 및 커밋</h3>
<pre><code class="language-bash">git add .
git commit -m &quot;프로젝트 초기 설정 완료&quot;</code></pre>
<hr>
<h2 id="3-mustache-설정-mvc">3. Mustache 설정 (MVC)</h2>
<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/7c34155c-766e-41bc-aa03-1f5bc55d6a98/image.png" alt=""></p>
<p>Spring Boot에서는 <strong>Mustache</strong> 템플릿 엔진을 사용하여 MVC 구조의 View를 구성할 수 있습니다.
기본적으로 <code>src/main/resources/templates</code> 경로에서 <code>.mustache</code> 파일을 자동 인식합니다.</p>
<pre><code class="language-plaintext">src
 └─ main
    ├─ java
    ├─ resources
    │  ├─ static
    │  └─ templates
    │     └─ index.mustache</code></pre>
<p>해당 위치에 index.html이 와서 mustache로 오도록 진행합니다.
Mustache는 서버 사이드 템플릿 엔진으로서, 웹 애플리케이션에서 동적으로 HTML을 생성하는 데 사용됩니다. 
물론, Thymleaf나 Mybatis 등 여러가지 엔진이 있어 원하시는 것으로 진행하셔도 무방합니다.</p>
<hr>
<h2 id="4-springfilter-chain-적용-전-후">4. SpringFilter chain 적용 전 후</h2>
<h3 id="41-springfilter-적용-전">4.1. SpringFilter 적용 전</h3>
<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/97376094-f842-4b80-b93b-636e314057c4/image.png" alt="">
SpringFilter 적용 전에는 Spring Security에서 제공해주는 창을 및 비밀번호를 통해 인증이 가능하다.
로그아웃의 경우 localhost:8080/logout 을 통해 가능하다.</p>
<h3 id="42-springfilter-적용-후">4.2. SpringFilter 적용 후</h3>
<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/12220c90-df48-4bed-98a0-ce841a53571b/image.png" alt="">
SpringFilter가 적용되면 스프링 시큐리티 필터가 필터체인에 등록되고 이전에 제공되었던 것은 해제된다.</p>
<hr>
<h2 id="5-trouble-shooting">5. Trouble Shooting</h2>
<h3 id="51-websecurityconfigureradapter-→-securityfilterchain">5.1 WebSecurityConfigurerAdapter → SecurityFilterChain</h3>
<ul>
<li><code>WebSecurityConfigurerAdapter</code>는 <strong>Spring Security 5.x 시절</strong>에 사용되던 방식으로,
현재는 <strong>deprecated(사용 권장되지 않음)</strong> 상태입니다.</li>
<li>대신 <strong>SecurityFilterChain</strong> 방식으로 변경되었으며,
메서드 체인 기반에서 <strong>람다 형식</strong>으로 작성하는 것이 권장됩니다.<ul>
<li>anyMatchers -&gt; requestMatchers</li>
<li>.access(hasRole(&quot;&quot;)) -&gt; .hasRole(&quot;&quot;) (단수일 경우) .hasAnyRole(&quot;&quot;, &quot;&quot;) (복수일 경우)</li>
<li>.formLogin의 경우도 내부 요소는 람다로 따로 처리하도록 변경
<img src="https://velog.velcdn.com/images/cheers_to_every/post/12355db6-38d8-43eb-a93b-31a3b9029e24/image.png" alt=""></li>
</ul>
</li>
</ul>
<p>예시:</p>
<pre><code class="language-java">@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(authorize -&gt; authorize
                        .requestMatchers(&quot;/user/**&quot;).authenticated()
                        .requestMatchers(&quot;/manager/**&quot;).hasAnyRole(&quot;ADMIN&quot;, &quot;MANAGER&quot;) //ROLE_ADMIN에서 이제 ROLE_ 같은 접두사는 자동으로 붙기 때문에 사용 X
                        .requestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                        .anyRequest().permitAll()
                )
                .formLogin(form -&gt; form
                        .loginPage(&quot;/login&quot;)
                        .permitAll() // 로그인 페이지와 로그인 요청 URL은 누구나 접근 가능하도록 허용</code></pre>
<hr>
<h3 id="52-lombok-문제-해결">5.2 Lombok 문제 해결</h3>
<ul>
<li><code>Lombok</code> 의존성을 추가했음에도 <code>User</code> 엔티티 작성 시 인식 오류가 발생할 수 있습니다.</li>
<li>IDE에서 annotation processor 설정을 켜거나, <strong>최신 버전 Lombok 의존성</strong>을 명시적으로 추가하면 해결됩니다.</li>
</ul>
<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
    &lt;artifactId&gt;lombok&lt;/artifactId&gt;
    &lt;version&gt;1.18.30&lt;/version&gt; &lt;!-- 최신 버전 사용 권장 --&gt;
    &lt;scope&gt;provided&lt;/scope&gt;
&lt;/dependency&gt;</code></pre>
<hr>
<h2 id="6-마무리">6. 마무리</h2>
<p>이로써 Spring Boot Security + JWT 프로젝트의 기본 환경 구성이 완료되었습니다.
다음 단계에서는 시큐리티 권한 처리를 진행할 예정입니다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 3. 람다표현식]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-3.-%EB%9E%8C%EB%8B%A4%ED%91%9C%ED%98%84%EC%8B%9D</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-3.-%EB%9E%8C%EB%8B%A4%ED%91%9C%ED%98%84%EC%8B%9D</guid>
            <pubDate>Thu, 25 Sep 2025 13:52:25 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-3-람다표현식">Chapter 3. 람다표현식</h1>
<h2 id="30-서론">3.0. 서론</h2>
<ul>
<li>이번 장에서는 <strong>람다 표현식</strong>을 어떻게 만들고, 사용하는지, 그리고 어떻게 코드를 간결하게 만들 수 있는지를 설명한다.</li>
<li>더불어 자바 8 API에 추가된 중요한 인터페이스와 <strong>형식 추론 기능</strong>을 확인하고, 메서드 참조와 같은 기법도 다룬다.</li>
<li>목표는 <strong>간결하고 유연한 코드</strong>를 작성하는 방법을 단계적으로 이해하는 것이다.</li>
</ul>
<hr>
<h2 id="31-람다란-무엇인가">3.1. 람다란 무엇인가?</h2>
<p>람다 표현식은 메서드로 전달할 수 있는 <strong>익명 함수</strong>를 단순화한 것이다.</p>
<h3 id="311-람다-특징">3.1.1. 람다 특징</h3>
<ol>
<li><strong>익명성</strong> : 메서드와 달리 이름이 없다.</li>
<li><strong>함수적 성격</strong> : 클래스에 종속되지 않으며, 파라미터 리스트·바디·반환 형식·예외 리스트를 포함한다.</li>
<li><strong>전달 가능</strong> : 메서드 인수로 전달하거나 변수에 저장할 수 있다.</li>
<li><strong>간결성</strong> : 익명 클래스를 구현할 때보다 코드가 훨씬 간단하다.</li>
</ol>
<h3 id="312-람다-구성-요소">3.1.2. 람다 구성 요소</h3>
<ol>
<li><strong>파라미터 리스트</strong> : 입력 값 정의</li>
<li><strong>화살표(<code>-&gt;</code>)</strong> : 파라미터와 바디를 구분</li>
<li><strong>람다 바디</strong> : 실행할 동작, 또는 반환 값</li>
</ol>
<h3 id="313-람다-예제">3.1.3. 람다 예제</h3>
<pre><code class="language-java">() -&gt; {}
() -&gt; &quot;Real&quot;
() -&gt; { return &quot;Mario&quot;; }</code></pre>
<ul>
<li><code>return</code>은 단일 표현식일 경우 생략 가능.</li>
<li>블록 <code>{}</code>을 사용할 경우 반드시 <code>return</code>을 명시해야 한다.</li>
</ul>
<hr>
<h2 id="32-어디에-어떻게-람다를-사용할까">3.2. 어디에, 어떻게 람다를 사용할까?</h2>
<p>람다는 <strong>함수형 인터페이스</strong>라는 문맥에서만 사용할 수 있다.</p>
<h3 id="321-함수형-인터페이스">3.2.1. 함수형 인터페이스</h3>
<ul>
<li>추상 메서드가 <strong>단 하나만 있는 인터페이스</strong>를 말한다.</li>
<li>디폴트 메서드가 여러 개 있어도 상관없다.</li>
<li><code>@FunctionalInterface</code> 어노테이션을 붙이면 함수형 인터페이스임을 명시할 수 있으며, 규칙 위반 시 컴파일 오류가 발생한다.</li>
</ul>
<h3 id="322-함수-디스크립터와-시그니처">3.2.2. 함수 디스크립터와 시그니처</h3>
<ul>
<li><strong>함수 시그니처</strong> : 자바 코드 차원, &quot;메서드 이름 + 매개변수 타입 목록&quot;</li>
<li><strong>함수 디스크립터</strong> : JVM 바이트코드 차원, &quot;매개변수 타입 + 반환 타입&quot;</li>
<li>함수형 인터페이스의 추상 메서드 시그니처는 곧 람다 표현식의 시그니처이다.</li>
</ul>
<hr>
<h2 id="33-실행-어라운드-패턴">3.3. 실행 어라운드 패턴</h2>
<h3 id="330-개요">3.3.0. 개요</h3>
<p>자원 처리(예: 파일 입출력, DB 연결)는</p>
<ul>
<li><strong>자원 열기 → 작업 수행 → 자원 닫기</strong>
구조를 반복한다.
이러한 구조를 <strong>실행 어라운드 패턴</strong>이라고 한다.</li>
</ul>
<h3 id="331-단계별-전개">3.3.1. 단계별 전개</h3>
<ol>
<li><p><strong>1단계 – 동작 파라미터화 기억</strong></p>
<ul>
<li>공통되는 준비와 정리 코드를 재사용하고, 실제 작업 코드만 파라미터화.</li>
</ul>
</li>
<li><p><strong>2단계 – 함수형 인터페이스 활용</strong></p>
<ul>
<li>추상 메서드 시그니처에 맞는 인터페이스 정의 후 람다 전달.</li>
</ul>
</li>
<li><p><strong>3단계 – 동작 실행</strong></p>
<ul>
<li>전달받은 람다를 실행. 원래는 익명 클래스를 통해 구현 가능.</li>
</ul>
</li>
<li><p><strong>4단계 – 람다 전달</strong></p>
<ul>
<li>익명 클래스 대신 람다로 작성해 코드 간소화.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="34-함수형-인터페이스-사용">3.4. 함수형 인터페이스 사용</h2>
<p>자바 8은 <code>java.util.function</code> 패키지에 다양한 함수형 인터페이스를 제공한다.</p>
<h3 id="341-predicatet">3.4.1. Predicate<T></h3>
<ul>
<li>입력 <code>T</code>를 받아 불리언을 반환.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Predicate&lt;T&gt; {
    boolean test(T t);
}
public &lt;T&gt; List&lt;T&gt; filter(List&lt;T&gt; list, Predicate&lt;T&gt; p) { ... }</code></pre>
<h3 id="342-consumert">3.4.2. Consumer<T></h3>
<ul>
<li>입력 <code>T</code>를 받아 소비하고 반환 값 없음.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Consumer&lt;T&gt; {
    void accept(T t);
}
public &lt;T&gt; void forEach(List&lt;T&gt; list, Consumer&lt;T&gt; c) { ... }</code></pre>
<h3 id="343-functiontr">3.4.3. Function&lt;T,R&gt;</h3>
<ul>
<li>입력 <code>T</code>를 받아 <code>R</code>을 반환.</li>
</ul>
<pre><code class="language-java">@FunctionalInterface
public interface Function&lt;T, R&gt; {
    R apply(T t);
}
public &lt;T,R&gt; List&lt;R&gt; map(List&lt;T&gt; list, Function&lt;T,R&gt; f) { ... }</code></pre>
<h3 id="344-기본형-특화">3.4.4. 기본형 특화</h3>
<ul>
<li>제네릭은 참조형만 사용 가능 → 기본형은 <strong>박싱/언박싱</strong> 발생.</li>
<li>박싱된 값은 힙 메모리에 저장 → 비용 발생.</li>
<li>자바는 <code>IntPredicate</code>, <code>DoubleFunction</code> 등 <strong>기본형 특화 인터페이스</strong> 제공.</li>
</ul>
<hr>
<h2 id="35-형식-검사-형식-추론-제약">3.5. 형식 검사, 형식 추론, 제약</h2>
<ul>
<li>컴파일러는 람다의 타입을 <strong>추론</strong>한다.</li>
<li>단, 외부 변수를 사용할 때는 <strong>final 혹은 사실상 final</strong>이어야 한다.</li>
<li>이는 메모리 안전성 때문. (지역 변수는 스택에, 람다는 힙에 저장되므로 수명 차이 발생)</li>
</ul>
<hr>
<h2 id="36-람다-캡처링과-클로저">3.6. 람다 캡처링과 클로저</h2>
<h3 id="361-람다-캡처링">3.6.1. 람다 캡처링</h3>
<p>람다는 바깥 스코프에 정의된 변수를 <strong>자유 변수(free variable)</strong>로 참조할 수 있다.
이 과정을 <strong>람다 캡처링</strong>이라고 한다.</p>
<p>하지만, 자바에서는 <strong>지역 변수 캡처에 제약</strong>이 있다.</p>
<ul>
<li><p>지역 변수는 <strong>final</strong> 또는 <strong>사실상 final(값이 변하지 않는)</strong>이어야 한다.</p>
</li>
<li><p>이는 메모리 안전성 때문이다.</p>
<ul>
<li>지역 변수는 메서드가 종료되면 스택 프레임과 함께 사라지지만,</li>
<li>람다는 힙에 저장될 수 있어 수명이 길다.</li>
<li>따라서 값이 바뀌면 동기화 문제나 예기치 못한 오류가 발생할 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="예시-1-final-또는-사실상-final-지역-변수">예시 1: final 또는 사실상 final 지역 변수</h4>
<pre><code class="language-java">public class LambdaCaptureExample {
    public static void main(String[] args) {
        String greeting = &quot;Hello&quot;; // 사실상 final (값을 변경하지 않음)

        Runnable r = () -&gt; System.out.println(greeting + &quot;, Lambda!&quot;);
        r.run(); // 출력: Hello, Lambda!
    }
}</code></pre>
<ul>
<li><code>greeting</code>은 한 번만 초기화되고 이후 변경되지 않으므로 <strong>사실상 final</strong>이다.</li>
<li>따라서 람다에서 안전하게 캡처 가능하다.</li>
</ul>
<hr>
<h4 id="예시-2-지역-변수-변경-시-컴파일-오류">예시 2: 지역 변수 변경 시 컴파일 오류</h4>
<pre><code class="language-java">public class LambdaCaptureExample {
    public static void main(String[] args) {
        String message = &quot;Hi&quot;;

        Runnable r = () -&gt; System.out.println(message);

        // message = &quot;Hello&quot;; // 컴파일 오류: 변수는 final 또는 사실상 final이어야 함

        r.run();
    }
}</code></pre>
<ul>
<li><code>message</code>를 람다에서 사용한 후 값을 바꾸려고 하면 컴파일 오류가 발생한다.</li>
</ul>
<hr>
<h4 id="예시-3-클래스-필드와-정적-변수는-변경-가능">예시 3: 클래스 필드와 정적 변수는 변경 가능</h4>
<pre><code class="language-java">public class LambdaCaptureExample {
    private String instanceVar = &quot;Instance&quot;;
    private static String staticVar = &quot;Static&quot;;

    public void test() {
        Runnable r = () -&gt; {
            System.out.println(instanceVar); // 인스턴스 변수 캡처 가능
            System.out.println(staticVar);   // 정적 변수도 캡처 가능
        };

        r.run();
    }

    public static void main(String[] args) {
        new LambdaCaptureExample().test();
    }
}</code></pre>
<ul>
<li>클래스 필드(<code>instanceVar</code>)와 정적 변수(<code>staticVar</code>)는 <strong>힙/메서드 영역</strong>에 저장되므로 자유롭게 변경 가능하다.</li>
<li>따라서 지역 변수와 달리 final 제약이 없다.</li>
</ul>
<hr>
<h3 id="362-클로저">3.6.2. 클로저</h3>
<p><strong>클로저(Closure)</strong>란 함수가 선언될 당시의 <strong>환경(변수 값 포함)</strong>을 함께 저장하는 개념이다.
람다는 클로저의 성격을 가지며, 실행 시점에도 선언 당시의 변수를 사용할 수 있다.</p>
<h4 id="예시-4-클로저-개념-확인">예시 4: 클로저 개념 확인</h4>
<pre><code class="language-java">import java.util.function.Function;

public class ClosureExample {
    public static void main(String[] args) {
        int factor = 2;

        Function&lt;Integer, Integer&gt; multiplier = (x) -&gt; x * factor;

        System.out.println(multiplier.apply(5)); // 출력: 10
    }
}</code></pre>
<ul>
<li><code>factor</code>는 람다 외부에서 정의된 지역 변수지만, 람다 안에서 참조 가능하다.</li>
<li>이 경우 <code>multiplier</code>는 <code>factor</code>를 캡처하여 <strong>클로저</strong> 역할을 한다.</li>
</ul>
<hr>
<h4 id="예시-5-클로저와-지역-변수-변경-문제">예시 5: 클로저와 지역 변수 변경 문제</h4>
<pre><code class="language-java">import java.util.function.Supplier;

public class ClosureExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder(&quot;Hello&quot;);

        Supplier&lt;String&gt; supplier = () -&gt; sb.toString();

        sb.append(&quot; World&quot;); // 변경 가능 (참조형 객체)

        System.out.println(supplier.get()); // 출력: Hello World
    }
}</code></pre>
<ul>
<li>객체 참조(<code>StringBuilder</code>)는 final로 선언된 것이 아니라 <strong>객체 내부 상태</strong>를 바꾼 것이므로 허용된다.</li>
<li>즉, &quot;변수 참조&quot;는 final이어야 하지만, &quot;객체 내부 값&quot;은 변경 가능하다.</li>
</ul>
<hr>
<h2 id="요약">요약</h2>
<ol>
<li>람다는 바깥 스코프의 변수를 자유롭게 참조할 수 있으며, 이를 <strong>람다 캡처링</strong>이라 한다.</li>
<li>지역 변수는 <code>final</code> 혹은 <strong>사실상 final</strong>이어야 한다.</li>
<li>인스턴스 변수와 정적 변수는 자유롭게 변경 가능하다.</li>
<li>람다는 선언 당시의 환경을 저장하여 실행 시점까지 활용할 수 있는데, 이를 <strong>클로저</strong>라고 한다.</li>
<li>참조형 객체의 내부 상태는 변경 가능하므로 주의해야 한다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 2. 동작 파라미터화 코드 전달하기]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-2.-%EB%8F%99%EC%9E%91-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%ED%99%94-%EC%BD%94%EB%93%9C-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-2.-%EB%8F%99%EC%9E%91-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%ED%99%94-%EC%BD%94%EB%93%9C-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 Sep 2025 14:13:04 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="chapter-2-동작-파라미터화-코드-전달하기">Chapter 2. 동작 파라미터화 코드 전달하기</h1>
<h2 id="20-서론">2.0. 서론</h2>
<ul>
<li><strong>동작 파라미터화(Behavior Parameterization)</strong>
: 아직 실행 방법을 결정하지 않은 코드 블록을 의미한다.</li>
<li>이 코드 블록은 나중에 프로그램에서 호출되어 실행을 지연시킬 수 있다.</li>
</ul>
<hr>
<h2 id="21-변화하는-요구사항에-대응하기">2.1. 변화하는 요구사항에 대응하기</h2>
<h3 id="211-첫-번째-시도--녹색-사과-필터링">2.1.1. 첫 번째 시도 : 녹색 사과 필터링</h3>
<pre><code class="language-java">public static List&lt;Apple&gt; filterGreenApples(List&lt;Apple&gt; inventory) {
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
    for (Apple apple : inventory) {
        if (&quot;green&quot;.equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}</code></pre>
<ul>
<li>단순히 <strong>if 조건문</strong>으로 필터링.</li>
<li>그러나 요구사항이 변하면(예: 색상별 필터링, 무게 조건 추가 등) 쉽게 대응 불가.</li>
</ul>
<hr>
<h3 id="212-두-번째-시도--색을-파라미터화">2.1.2. 두 번째 시도 : 색을 파라미터화</h3>
<pre><code class="language-java">public static List&lt;Apple&gt; filterApplesByColor(List&lt;Apple&gt; inventory, String color) {
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
    for (Apple apple : inventory) {
        if (color.equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}</code></pre>
<ul>
<li>**색상(color)**을 매개변수로 전달 → 다양한 색상 필터 가능.</li>
<li>하지만 무게 기준 등 다른 속성을 필터링할 때는 또 다른 메서드가 필요 → <strong>코드 중복 발생 (DRY 원칙 위배).</strong></li>
</ul>
<hr>
<h3 id="213-세-번째-시도--모든-속성을-조건으로">2.1.3. 세 번째 시도 : 모든 속성을 조건으로</h3>
<pre><code class="language-java">public static List&lt;Apple&gt; filterApples(List&lt;Apple&gt; inventory, String color, int weight, boolean flag) {
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
    for (Apple apple : inventory) {
        if ((flag &amp;&amp; color.equals(apple.getColor())) ||
            (!flag &amp;&amp; apple.getWeight() &gt; weight)) {
            result.add(apple);
        }
    }
    return result;
}</code></pre>
<ul>
<li>하나의 거대한 필터 메서드를 만들어 모든 조건 처리.</li>
<li>하지만 결국 <strong>중복 메서드</strong> 또는 <strong>복잡한 하나의 메서드</strong> → 유지보수 어려움.</li>
<li>해결책: <strong>동작 파라미터화</strong>.</li>
</ul>
<hr>
<h2 id="22-동작-파라미터화">2.2. 동작 파라미터화</h2>
<h3 id="220-서론">2.2.0. 서론</h3>
<ul>
<li><p>단순히 매개변수를 늘리는 방식이 아닌, <strong>요구사항 변화에 유연하게 대응</strong>할 방법 필요.</p>
</li>
<li><p><strong>Predicate 인터페이스</strong></p>
<ul>
<li>조건을 추상화 → <code>true / false</code> 반환.</li>
</ul>
</li>
<li><p><strong>전략 디자인 패턴</strong></p>
<ul>
<li>알고리즘(전략)을 캡슐화하고, 런타임에 선택 가능.</li>
</ul>
</li>
<li><p>따라서, 메서드는 다양한 동작(전략)을 인수로 받아 내부적으로 실행 가능해야 함.</p>
</li>
</ul>
<hr>
<h3 id="221-네-번째-시도--추상적-조건으로-필터링">2.2.1. 네 번째 시도 : 추상적 조건으로 필터링</h3>
<pre><code class="language-java">public interface ApplePredicate {
    boolean test(Apple apple);
}

public static List&lt;Apple&gt; filterApples(List&lt;Apple&gt; inventory, ApplePredicate p) {
    List&lt;Apple&gt; result = new ArrayList&lt;&gt;();
    for (Apple apple : inventory) {
        if (p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}</code></pre>
<ul>
<li>**인터페이스(ApplePredicate)**를 정의하고, 이를 구현하는 객체를 전달.</li>
<li>메서드의 조건을 외부에서 결정 → 다양한 동작을 전달 가능.</li>
<li>즉, <strong>코드 자체를 전달</strong>할 수 있게 됨.</li>
</ul>
<hr>
<h2 id="23-복잡한-과정-간소화">2.3. 복잡한 과정 간소화</h2>
<h3 id="230-서론">2.3.0. 서론</h3>
<ul>
<li>매번 클래스를 구현 → 인스턴스 생성은 <strong>복잡</strong>하고 <strong>번거로움</strong>.</li>
<li>자바는 이를 간단히 하기 위해 <strong>익명 클래스</strong>를 제공.</li>
</ul>
<hr>
<h3 id="231-익명-클래스">2.3.1. 익명 클래스</h3>
<pre><code class="language-java">List&lt;Apple&gt; redApples = filterApples(inventory, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return &quot;red&quot;.equals(apple.getColor());
    }
});</code></pre>
<ul>
<li>클래스 선언 + 인스턴스 생성을 동시에 처리.</li>
<li>하지만 코드가 여전히 장황하고 가독성이 떨어짐.</li>
</ul>
<hr>
<h3 id="232-다섯-번째-시도--람다-표현식">2.3.2. 다섯 번째 시도 : 람다 표현식</h3>
<pre><code class="language-java">List&lt;Apple&gt; redApples = filterApples(inventory, apple -&gt; &quot;red&quot;.equals(apple.getColor()));</code></pre>
<ul>
<li>자바 8부터는 <strong>람다 표현식</strong> 지원 → 코드 간결화.</li>
</ul>
<hr>
<h3 id="233-여섯-번째-시도--리스트-형식으로-추상화">2.3.3. 여섯 번째 시도 : 리스트 형식으로 추상화</h3>
<pre><code class="language-java">public static &lt;T&gt; List&lt;T&gt; filter(List&lt;T&gt; list, Predicate&lt;T&gt; p) {
    List&lt;T&gt; result = new ArrayList&lt;&gt;();
    for (T e : list) {
        if (p.test(e)) {
            result.add(e);
        }
    }
    return result;
}</code></pre>
<ul>
<li>제네릭 메서드 적용 → 다양한 객체 타입에 대해 사용 가능.</li>
</ul>
<hr>
<h2 id="24-실전-예제">2.4. 실전 예제</h2>
<h3 id="241-comparator로-정렬하기">2.4.1. Comparator로 정렬하기</h3>
<pre><code class="language-java">inventory.sort((a1, a2) -&gt; a1.getWeight().compareTo(a2.getWeight()));</code></pre>
<ul>
<li>요구사항에 맞는 Comparator를 전달 → 정렬 동작 유연화.</li>
</ul>
<hr>
<h3 id="242-runnable로-코드-블록-실행하기">2.4.2. Runnable로 코드 블록 실행하기</h3>
<pre><code class="language-java">Thread t = new Thread(() -&gt; System.out.println(&quot;Hello from another thread!&quot;));
t.start();</code></pre>
<ul>
<li>Runnable에 람다 전달 → 스레드 실행 단순화.</li>
</ul>
<hr>
<h3 id="243-gui-이벤트-처리하기">2.4.3. GUI 이벤트 처리하기</h3>
<pre><code class="language-java">ExecutorService executor = Executors.newFixedThreadPool(2);
Future&lt;Integer&gt; result = executor.submit(() -&gt; 42);</code></pre>
<ul>
<li><p><strong>ExecutorService + Callable</strong></p>
<ul>
<li>테스크 제출과 실행 과정 분리.</li>
<li>결과값을 비동기적으로 저장 가능.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="25-마치며">2.5. 마치며</h2>
<ol>
<li><strong>동작 파라미터화</strong> → 메서드 인수로 다양한 동작을 전달할 수 있음.</li>
<li>변화하는 요구사항에 더 잘 대응 → 유지보수 비용 절감.</li>
<li>자바 8 이전: 익명 클래스 사용 → 코드 장황.
자바 8 이후: <strong>람다 표현식</strong>으로 간결화.</li>
<li>자바 API는 정렬, 스레드, 이벤트 처리 등에서 <strong>동작 파라미터화</strong>를 폭넓게 활용.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] SecurityConfig]]></title>
            <link>https://velog.io/@cheers_to_every/Spring-Security-SecurityConfig</link>
            <guid>https://velog.io/@cheers_to_every/Spring-Security-SecurityConfig</guid>
            <pubDate>Tue, 23 Sep 2025 13:36:46 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/74919e9f-0086-4e29-873a-eeaacacb1c47/image.png" alt=""></p>
<h1 id="spring-security">Spring Security</h1>
<hr>
<h2 id="1-커스텀-로그인--userdetailsservice">1. 커스텀 로그인 &amp; UserDetailsService</h2>
<h3 id="11-securityconfig-확장">1.1. SecurityConfig 확장</h3>
<pre><code class="language-java">@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
            .antMatchers(&quot;/user/**&quot;).authenticated()
            .antMatchers(&quot;/manager/**&quot;).access(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or hasRole(&#39;ROLE_MANAGER&#39;)&quot;)
            .antMatchers(&quot;/admin/**&quot;).access(&quot;hasRole(&#39;ROLE_ADMIN&#39;)&quot;)
            .anyRequest().permitAll()
        .and()
        .formLogin()
            .loginPage(&quot;/loginForm&quot;)          // 사용자 정의 로그인 페이지
            .loginProcessingUrl(&quot;/login&quot;)     // login 요청을 시큐리티가 낚아채서 로그인 처리
            .defaultSuccessUrl(&quot;/&quot;);          // 로그인 성공 시 이동 경로
}</code></pre>
<p>핵심: <code>loginProcessingUrl(&quot;/login&quot;)</code> 등록 시 <strong>컨트롤러에 /login을 직접 만들 필요가 없다.</strong>
Spring Security 필터가 낚아채서 <code>AuthenticationManager</code>를 통해 인증 진행.</p>
<hr>
<h3 id="12-인증-객체-구조">1.2. 인증 객체 구조</h3>
<ul>
<li><p>Security가 로그인 성공 시 만드는 세션 구조:</p>
<pre><code>Security Session
 └── Authentication (인증 객체)
      └── UserDetails (PrincipalDetails)</code></pre></li>
</ul>
<hr>
<h3 id="13-principaldetails-userdetails-구현체">1.3. PrincipalDetails (UserDetails 구현체)</h3>
<pre><code class="language-java">public class PrincipalDetails implements UserDetails {
    private User user;

    public PrincipalDetails(User user) { this.user = user; }

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        Collection&lt;GrantedAuthority&gt; collect = new ArrayList&lt;&gt;();
        collect.add(() -&gt; user.getRole()); // 권한 반환
        return collect;
    }

    @Override public String getPassword() { return user.getPassword(); }
    @Override public String getUsername() { return user.getUsername(); }
    @Override public boolean isAccountNonExpired() { return true; }
    @Override public boolean isAccountNonLocked() { return true; }
    @Override public boolean isCredentialsNonExpired() { return true; }
    @Override public boolean isEnabled() { return true; }
}</code></pre>
<p><strong>UserDetails</strong> 인터페이스를 구현하여 <code>User</code> 엔티티를 시큐리티가 인식할 수 있는 객체로 변환.</p>
<hr>
<h3 id="14-principaldetailsservice-userdetailsservice-구현체">1.4. PrincipalDetailsService (UserDetailsService 구현체)</h3>
<pre><code class="language-java">@Service
public class PrincipalDetailsService implements UserDetailsService {
    @Autowired private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User userEntity = userRepository.findByUsername(username);
        if (userEntity != null) {
            return new PrincipalDetails(userEntity);
        }
        return null;
    }
}</code></pre>
<p>시큐리티가 <code>/login</code> 요청을 받으면 내부적으로 <code>loadUserByUsername</code> 실행 → DB 조회 후 PrincipalDetails 반환.</p>
<hr>
<h3 id="15-userrepository-확장">1.5. UserRepository 확장</h3>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;User, Integer&gt; {
    User findByUsername(String username); 
}</code></pre>
<p><strong>규칙 기반 메서드 생성</strong>: <code>findByUsername</code> → <code>select * from user where username=?</code></p>
<hr>
<hr>
<h2 id="2-메서드-단위-권한-처리">2. 메서드 단위 권한 처리</h2>
<h3 id="21-secured">2.1. <code>@Secured</code></h3>
<ul>
<li><strong>특정 메서드에 단일 권한만 허용</strong></li>
</ul>
<pre><code class="language-java">@EnableGlobalMethodSecurity(securedEnabled = true) // 활성화
@Secured(&quot;ROLE_ADMIN&quot;) 
public String adminOnly() { return &quot;admin&quot;; }</code></pre>
<hr>
<h3 id="22-preauthorize">2.2. <code>@PreAuthorize</code></h3>
<ul>
<li><strong>두 개 이상의 권한 허용 가능</strong></li>
</ul>
<pre><code class="language-java">@EnableGlobalMethodSecurity(prePostEnabled = true) // 활성화
@PreAuthorize(&quot;hasRole(&#39;ROLE_MANAGER&#39;) or hasRole(&#39;ROLE_ADMIN&#39;)&quot;)
public String managerOrAdmin() { return &quot;manager&quot;; }</code></pre>
<p><code>@Secured</code> = 단일 권한, <code>@PreAuthorize</code> = 다중 권한 제어.</p>
<hr>
<hr>
<h2 id="3-oauth2-로그인-준비-구글-예시">3. OAuth2 로그인 준비 (구글 예시)</h2>
<h3 id="31-구글-api-console-설정">3.1. 구글 API Console 설정</h3>
<ul>
<li><p>새 프로젝트 생성 → OAuth 동의 화면 외부 설정</p>
</li>
<li><p>OAuth 클라이언트 ID 발급 (웹 애플리케이션 선택)</p>
</li>
<li><p>승인된 리다이렉션 URI:</p>
<pre><code>http://localhost:8080/login/oauth2/code/google</code></pre></li>
<li><p>클라이언트 ID &amp; Secret 발급</p>
</li>
</ul>
<hr>
<h3 id="32-dependency-추가">3.2. dependency 추가</h3>
<pre><code class="language-xml">&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-oauth2-client&lt;/artifactId&gt;
&lt;/dependency&gt;</code></pre>
<hr>
<h3 id="33-applicationyml-설정">3.3. application.yml 설정</h3>
<pre><code class="language-yaml">spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 발급받은-client-id
            client-secret: 발급받은-client-secret
            scope:
              - email
              - profile</code></pre>
<hr>
<h3 id="34-로그인-링크-추가">3.4. 로그인 링크 추가</h3>
<pre><code class="language-html">&lt;a href=&quot;/oauth2/authorization/google&quot;&gt;구글 로그인&lt;/a&gt;</code></pre>
<p><code>/oauth2/authorization/google</code> 경로는 <strong>Spring Security OAuth2 Client</strong>가 자동 제공.</p>
<hr>
<h3 id="35-securityconfig-확장">3.5. SecurityConfig 확장</h3>
<pre><code class="language-java">@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
            .antMatchers(&quot;/user/**&quot;).authenticated()
            .antMatchers(&quot;/manager/**&quot;).access(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or hasRole(&#39;ROLE_MANAGER&#39;)&quot;)
            .antMatchers(&quot;/admin/**&quot;).access(&quot;hasRole(&#39;ROLE_ADMIN&#39;)&quot;)
            .anyRequest().permitAll()
        .and()
        .formLogin()
            .loginPage(&quot;/loginForm&quot;)
            .loginProcessingUrl(&quot;/login&quot;)
            .defaultSuccessUrl(&quot;/&quot;)
        .and()
        .oauth2Login()
            .loginPage(&quot;/loginForm&quot;); // OAuth2 로그인도 동일한 loginForm 페이지 사용
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Security] 기본 동작 개요]]></title>
            <link>https://velog.io/@cheers_to_every/Spring-Security-%EA%B8%B0%EB%B3%B8-%EB%8F%99%EC%9E%91-%EA%B0%9C%EC%9A%94</link>
            <guid>https://velog.io/@cheers_to_every/Spring-Security-%EA%B8%B0%EB%B3%B8-%EB%8F%99%EC%9E%91-%EA%B0%9C%EC%9A%94</guid>
            <pubDate>Mon, 22 Sep 2025 13:30:37 GMT</pubDate>
            <description><![CDATA[<h1 id="spring-security">Spring Security</h1>
<h2 id="1-spring-security-기본-동작">1. Spring Security 기본 동작</h2>
<ul>
<li><strong>의존성 추가 시 기본 보안 정책</strong>
Spring Security를 의존성에 추가하면 기본적으로 모든 요청이 차단되며, 서버의 모든 페이지 접근은 인증(로그인)이 필요하다.
따라서 SecurityConfig를 커스터마이징해야 원하는 접근 정책을 설정할 수 있음.</li>
</ul>
<hr>
<h2 id="2-securityconfig-보안-설정">2. SecurityConfig (보안 설정)</h2>
<ul>
<li><p><code>@EnableWebSecurity</code> : Spring Security 활성화</p>
</li>
<li><p><code>extends WebSecurityConfigurerAdapter</code> : 보안 설정을 재정의할 수 있음</p>
</li>
<li><p>주요 설정 예시:</p>
<pre><code class="language-java">http.csrf().disable() // CSRF 토큰 검증 비활성화 (개발 초기에 주로 사용)
    .authorizeRequests()
        .antMatchers(&quot;/user/**&quot;).authenticated() // 인증만 되면 접근 가능
        .antMatchers(&quot;/manager/**&quot;).access(&quot;hasRole(&#39;ROLE_ADMIN&#39;) or hasRole(&#39;ROLE_MANAGER&#39;)&quot;)
        .antMatchers(&quot;/admin/**&quot;).access(&quot;hasRole(&#39;ROLE_ADMIN&#39;)&quot;)
        .anyRequest().permitAll() // 그 외는 모두 허용
    .and()
    .formLogin()
        .loginPage(&quot;/loginForm&quot;); // 인증 안 된 사용자는 loginForm 페이지로 이동</code></pre>
</li>
</ul>
<p>이렇게 하면 <code>user</code>, <code>manager</code>, <code>admin</code> 경로는 각각 로그인 및 권한이 있어야 접근 가능하고, 나머지(예: index)는 누구나 접근 가능하다.</p>
<hr>
<h2 id="3-회원가입--로그인-페이지">3. 회원가입 &amp; 로그인 페이지</h2>
<ul>
<li><strong>loginForm</strong>: 로그인 전 보여줄 화면 (기본 <code>/login</code>을 커스터마이징)</li>
<li><strong>joinForm</strong>: 회원가입 폼</li>
<li>사용자가 joinForm에서 입력한 데이터를 DB에 저장하여 회원 계정을 생성한다.</li>
</ul>
<hr>
<h2 id="4-user-엔티티-설계">4. User 엔티티 설계</h2>
<ul>
<li><p>JPA 기반 User 테이블</p>
<pre><code class="language-java">@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    private String username;
    private String password;
    private String email;
    private String role; // ROLE_USER, ROLE_ADMIN 등

    @CreationTimestamp
    private Timestamp createDate;
}</code></pre>
</li>
<li><p>권한은 <code>ROLE_</code> prefix를 붙이는 것이 Spring Security 규칙.</p>
</li>
</ul>
<hr>
<h2 id="5-userrepository-생성">5. UserRepository 생성</h2>
<ul>
<li><p><code>JpaRepository&lt;User, Integer&gt;</code> 상속</p>
</li>
<li><p>기본 CRUD 사용 가능</p>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;User, Integer&gt; {}</code></pre>
</li>
</ul>
<hr>
<h2 id="6-회원가입-로직">6. 회원가입 로직</h2>
<ul>
<li><p>비밀번호 암호화 필요 → <strong>BCryptPasswordEncoder</strong> 사용</p>
</li>
<li><p><code>@Bean</code> 등록하여 IoC 컨테이너에 관리</p>
<pre><code class="language-java">@Bean
public BCryptPasswordEncoder encodePwd() {
    return new BCryptPasswordEncoder();
}</code></pre>
</li>
<li><p>회원가입 시 비밀번호 암호화 후 저장</p>
<pre><code class="language-java">@PostMapping(&quot;/join&quot;)
public String join(User user) {
    String rawPassword = user.getPassword();
    String encPassword = bCryptPasswordEncoder.encode(rawPassword);
    user.setPassword(encPassword);
    user.setRole(&quot;ROLE_USER&quot;);
    userRepository.save(user);
    return &quot;redirect:/loginForm&quot;; // 회원가입 완료 후 로그인 페이지로 이동
}</code></pre>
</li>
</ul>
<p><strong>중요</strong>: Spring Security는 로그인 시 입력된 패스워드를 DB에 저장된 암호화된 패스워드와 자동으로 비교하기 때문에, 반드시 <code>BCryptPasswordEncoder</code>로 인코딩해야 로그인 성공 가능.</p>
<hr>
<h2 id="7-로그인-처리">7. 로그인 처리</h2>
<ul>
<li><code>/loginForm</code> → 로그인 화면</li>
<li>로그인 요청은 Spring Security가 가로채서 처리함 (컨트롤러에서 직접 처리할 필요 없음)</li>
<li><code>UsernamePasswordAuthenticationFilter</code>가 동작하며, DB의 사용자 정보와 비교 후 인증 처리</li>
</ul>
<hr>
<h2 id="8-정리된-흐름">8. 정리된 흐름</h2>
<ol>
<li><p><strong>사용자 요청</strong></p>
<ul>
<li><code>/loginForm</code> → 로그인 화면</li>
<li><code>/joinForm</code> → 회원가입 화면</li>
</ul>
</li>
<li><p><strong>회원가입</strong></p>
<ul>
<li>비밀번호를 <code>BCryptPasswordEncoder</code>로 암호화 후 DB 저장</li>
<li>권한은 기본적으로 <code>ROLE_USER</code></li>
</ul>
</li>
<li><p><strong>로그인</strong></p>
<ul>
<li>Security가 <code>/login</code> POST 요청을 낚아채 인증 수행</li>
<li>DB의 <code>username</code>, <code>password</code>와 입력값 비교</li>
</ul>
</li>
<li><p><strong>접근 제어</strong></p>
<ul>
<li><code>user/**</code> → 로그인 필요</li>
<li><code>manager/**</code> → ROLE_ADMIN, ROLE_MANAGER 필요</li>
<li><code>admin/**</code> → ROLE_ADMIN 필요</li>
<li>나머지는 모두 접근 가능</li>
</ul>
</li>
</ol>
<hr>
<p><strong>Spring Security는 기본적으로 모든 요청을 차단하지만, SecurityConfig를 통해 인증/권한 정책을 설정할 수 있고, 회원가입 시 반드시 비밀번호를 암호화해야 로그인 인증이 정상적으로 동작한다.</strong></p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Git 되돌리기 (restore, revert, reset)]]></title>
            <link>https://velog.io/@cheers_to_every/Git-Git-%EB%90%98%EB%8F%8C%EB%A6%AC%EA%B8%B0-restore-revert-reset</link>
            <guid>https://velog.io/@cheers_to_every/Git-Git-%EB%90%98%EB%8F%8C%EB%A6%AC%EA%B8%B0-restore-revert-reset</guid>
            <pubDate>Sun, 21 Sep 2025 12:52:58 GMT</pubDate>
            <description><![CDATA[<h1 id="git-되돌리기-restore-revert-reset">Git 되돌리기 (restore, revert, reset)</h1>
<h2 id="1-사용-이유">1. 사용 이유</h2>
<ul>
<li><p>Git은 <strong>버전 관리 시스템</strong>이므로 언제든 이전 상태로 되돌릴 수 있습니다.</p>
</li>
<li><p>상황에 맞게 선택:</p>
<ul>
<li><strong><code>git restore</code></strong> → 파일 하나 복구</li>
<li><strong><code>git revert</code></strong> → 특정 commit 취소</li>
<li><strong><code>git reset</code></strong> → 특정 commit 시점으로 시간 되돌리기</li>
<li><strong><code>git switch</code> / <code>git checkout</code></strong> → 다른 branch나 commit으로 이동</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-git-restore-파일-복구">2. git restore (파일 복구)</h2>
<ul>
<li>파일을 최근 commit 상태 또는 특정 commit 상태로 되돌릴 수 있음.</li>
</ul>
<pre><code class="language-bash"># 최근 commit 상태로 되돌리기
git restore 파일명  

# 특정 commit 시점으로 복구
git restore --source 커밋아이디 파일명  

# staging 취소
git restore --staged 파일명  </code></pre>
<hr>
<h2 id="3-git-revert-commit-취소">3. git revert (commit 취소)</h2>
<ul>
<li>특정 commit의 변경 사항만 <strong>안전하게 되돌림</strong>.</li>
<li>새로운 commit을 생성하므로 협업 환경에서 많이 사용.</li>
</ul>
<pre><code class="language-bash"># 특정 commit 취소
git revert 커밋아이디  

# 여러 commit 한 번에 취소
git revert 커밋아이디1 커밋아이디2  

# 가장 최근 commit 취소
git revert HEAD  </code></pre>
<ul>
<li><p><strong>merge commit</strong>도 revert 가능 (merge 취소 효과).</p>
</li>
<li><p>실행 시 Vim 에디터가 열리면:</p>
<ul>
<li><code>i</code> → 메시지 수정</li>
<li><code>esc</code> → <code>:wq</code> 입력 후 종료</li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-git-reset-시간-되돌리기">4. git reset (시간 되돌리기)</h2>
<ul>
<li>특정 commit 시점으로 &quot;아예&quot; 되돌림.</li>
<li>협업 환경에서는 매우 위험 → <strong>개인 로컬에서만 사용 권장</strong>.</li>
</ul>
<pre><code class="language-bash"># 완전히 과거 시점으로 되돌림 (작업폴더도 reset)
git reset --hard 커밋아이디  

# staging area 유지
git reset --soft 커밋아이디  

# staging 해제 (작업 내용은 유지)
git reset --mixed 커밋아이디  </code></pre>
<hr>
<h2 id="5-branch-단위-되돌리기">5. branch 단위 되돌리기</h2>
<ul>
<li>commit이 아니라 <strong>branch 단위</strong>로 과거 시점으로 돌아가고 싶을 때 사용.</li>
</ul>
<pre><code class="language-bash"># 특정 commit 시점으로 이동 (detached HEAD 상태)
git checkout 커밋아이디  
# or 최신 방식
git switch --detach 커밋아이디  

# 다시 main 브랜치로 돌아오기
git switch main  </code></pre>
<ul>
<li><code>detached HEAD</code> 상태에서 수정 후 commit 하면, 임시 branch처럼 동작하므로 필요 시 새 branch 생성:</li>
</ul>
<pre><code class="language-bash">git switch -c 새브랜치명</code></pre>
<hr>
<h2 id="6-주의사항">6. 주의사항</h2>
<ul>
<li><code>git reset</code>은 협업 저장소에서 금지</li>
<li><code>untracked 파일</code>(<code>git add</code> 안 한 파일)은 reset으로 삭제되지 않음</li>
<li>untracked 파일까지 삭제하려면:</li>
</ul>
<pre><code class="language-bash">git clean -fd</code></pre>
<hr>
<h2 id="요약">요약:</h2>
<ul>
<li><strong>파일만 되돌릴 때 → restore</strong></li>
<li><strong>commit 취소 → revert</strong></li>
<li><strong>과거 시점으로 완전 리셋 → reset</strong></li>
<li><strong>브랜치/commit 이동 → switch/checkout</strong></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] GitHub]]></title>
            <link>https://velog.io/@cheers_to_every/Git-GitHub</link>
            <guid>https://velog.io/@cheers_to_every/Git-GitHub</guid>
            <pubDate>Sat, 20 Sep 2025 13:35:54 GMT</pubDate>
            <description><![CDATA[<h2 id="1-github">1. GitHub</h2>
<h3 id="11-repository">1.1. Repository</h3>
<ul>
<li>Git이 파일 버전을 저장하는 장소.</li>
<li><code>.git</code> 폴더가 로컬 저장소(repository).</li>
<li>내 컴퓨터에 만든 git 저장소를 의미.</li>
</ul>
<h3 id="12-github">1.2. GitHub</h3>
<ul>
<li>온라인 원격 저장소 서비스.</li>
<li>로컬 저장소를 원격에 업로드 가능.</li>
<li>협업(코드 공유, 협력 개발)에 필수.</li>
</ul>
<h3 id="13-사용-방법">1.3. 사용 방법</h3>
<pre><code class="language-bash"># 로컬 저장소 생성
git init  

# 기본 브랜치 이름 변경 (master → main)
git branch -M main  

# 로컬 저장소 → 원격 저장소 push
git push -u https://github.com/username/repo.git main  </code></pre>
<ul>
<li><code>-u</code>: 원격 주소 저장, 이후 <code>git push</code>만 입력해도 가능.</li>
</ul>
<h3 id="14-git-변수-생성-remote">1.4. Git 변수 생성 (remote)</h3>
<pre><code class="language-bash"># 원격 저장소 주소를 변수(origin)에 저장
git remote add origin https://github.com/username/repo.git  

# 변수 확인
git remote -v  

# push 시 활용
git push -u origin main</code></pre>
<h3 id="15-원격-저장소-내려받기-clone">1.5. 원격 저장소 내려받기 (clone)</h3>
<pre><code class="language-bash">git clone https://github.com/username/repo.git</code></pre>
<ul>
<li>협업 시에는 <code>git init</code> 필요 없음.</li>
</ul>
<h3 id="16-gitignore">1.6. .gitignore</h3>
<ul>
<li>불필요한 파일을 원격에 올리지 않도록 설정.</li>
</ul>
<pre><code class="language-bash"># .gitignore 예시
*.log
node_modules/
.env</code></pre>
<hr>
<h2 id="2-git-clone-pull">2. git clone, pull</h2>
<h3 id="21-git-clone">2.1. git clone</h3>
<ul>
<li>원격 저장소를 복제해서 새로운 작업 환경 생성.</li>
</ul>
<pre><code class="language-bash">git clone https://github.com/username/repo.git</code></pre>
<ul>
<li>특정 브랜치만 가져오기:</li>
</ul>
<pre><code class="language-bash">git clone -b feature-branch https://github.com/username/repo.git</code></pre>
<h3 id="22-git-pull">2.2. git pull</h3>
<ul>
<li>원격 저장소의 최신 변경 내용을 가져와 로컬에 병합.</li>
</ul>
<pre><code class="language-bash">git pull origin main</code></pre>
<ul>
<li>동작 원리: <code>git fetch + git merge</code>.</li>
</ul>
<h3 id="23-주의사항">2.3. 주의사항</h3>
<ul>
<li>원격과 로컬이 다른 경우 <code>git push</code>가 불가.</li>
<li>반드시 <code>git pull</code> → <code>git push</code> 순서로 진행.</li>
<li>충돌(conflict) 발생 시 수동 해결 필요.</li>
</ul>
<hr>
<h2 id="3-pull-request">3. Pull Request</h2>
<h3 id="31-원격-저장소-브랜치-생성">3.1. 원격 저장소 브랜치 생성</h3>
<pre><code class="language-bash"># 로컬에서 만든 브랜치 push
git push origin feature-branch</code></pre>
<ul>
<li>GitHub 웹에서 브랜치 생성도 가능.</li>
</ul>
<h3 id="32-pull-request-pr">3.2. Pull Request (PR)</h3>
<ul>
<li>팀 협업 시 코드 병합 전 검토 과정.</li>
<li>브랜치 변경사항 → main 병합 요청.</li>
</ul>
<h4 id="옵션">옵션</h4>
<ol>
<li><strong>Create a merge commit</strong> : 일반 merge (3-way merge).</li>
<li><strong>Squash and merge</strong> : commit 내역 압축 → main에 1개 commit.</li>
<li><strong>Rebase and merge</strong> : 브랜치 이력을 main 최신 commit으로 rebase 후 병합.</li>
</ol>
<h3 id="33-주의사항">3.3. 주의사항</h3>
<ul>
<li><code>git reset --hard</code> + <code>git push -f</code>로 강제 되돌리기 가능하나, 협업 환경에서는 위험.</li>
<li>GitHub에서 <code>Revert</code> 버튼 사용 시 &quot;되돌리는 commit&quot;이 생성됨.</li>
</ul>
<hr>
<h2 id="4-git-flow--trunk-based-전략">4. Git Flow / Trunk-based 전략</h2>
<h3 id="41-git-브랜치-전략">4.1. Git 브랜치 전략</h3>
<ul>
<li><p>대표적인 방법론:</p>
<ul>
<li><strong>Git Flow</strong></li>
<li><strong>GitHub Flow</strong></li>
<li><strong>GitLab Flow</strong></li>
<li><strong>Trunk-based Development</strong></li>
</ul>
</li>
</ul>
<h3 id="42-장점">4.2. 장점</h3>
<ul>
<li>브랜치 관리 용이.</li>
<li>팀원이 많아도 개발 절차가 체계적.</li>
<li>협업 시 충돌 최소화.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Branch - Rebase & Squash]]></title>
            <link>https://velog.io/@cheers_to_every/Git-Branch-Rebase-Squash</link>
            <guid>https://velog.io/@cheers_to_every/Git-Branch-Rebase-Squash</guid>
            <pubDate>Fri, 19 Sep 2025 14:02:43 GMT</pubDate>
            <description><![CDATA[<h1 id="git-브랜치와-병합-정리">Git 브랜치와 병합 정리</h1>
<h2 id="1-브랜치의-필요성">1. 브랜치의 필요성</h2>
<ul>
<li><p>개발 도중 새로운 기능을 추가해야 할 때, 기존 코드를 안전하게 유지하면서 개발 가능.</p>
</li>
<li><p>해결 방법: <strong>프로젝트의 복사본 생성 → 새로운 기능 개발 → 원본에 합치기</strong></p>
<ul>
<li>Git에서는 이를 <strong>branch</strong> 기능으로 쉽게 수행.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="2-git-branch">2. Git Branch</h2>
<h3 id="21-브랜치-생성-및-이동">2.1 브랜치 생성 및 이동</h3>
<pre><code class="language-bash">git branch 브랜치이름       # 브랜치 생성
git switch 브랜치이름       # 브랜치 이동</code></pre>
<ul>
<li>기존에는 <code>git checkout 브랜치이름</code> 사용</li>
<li>메인 브랜치로 돌아갈 때:</li>
</ul>
<pre><code class="language-bash">git switch main   # 또는 master, 설정에 따라 다름</code></pre>
<h3 id="22-브랜치-상태-확인">2.2 브랜치 상태 확인</h3>
<pre><code class="language-bash">git status</code></pre>
<ul>
<li>현재 위치한 브랜치 확인 가능.</li>
</ul>
<h3 id="23-브랜치-그래프-확인">2.3 브랜치 그래프 확인</h3>
<pre><code class="language-bash">git log --graph --oneline --all</code></pre>
<ul>
<li>브랜치와 커밋 내역을 <strong>그래프 형태</strong>로 한눈에 확인.</li>
</ul>
<h3 id="24-head">2.4 HEAD</h3>
<ul>
<li><code>HEAD</code> : 현재 작업 위치(브랜치)를 의미.</li>
</ul>
<hr>
<h2 id="3-브랜치-병합merge">3. 브랜치 병합(Merge)</h2>
<h3 id="31-기본-merge">3.1 기본 Merge</h3>
<pre><code class="language-bash">git switch main           # 메인 브랜치로 이동
git merge 브랜치이름       # 브랜치 병합</code></pre>
<ul>
<li><p>브랜치에서 개발한 내용을 <strong>메인 브랜치에 합침</strong>.</p>
</li>
<li><p>충돌 발생 시 (<code>merge conflict</code>) 주의:</p>
<ol>
<li>충돌 파일 열기 → <code>&lt;&lt;&lt;&lt;</code>, <code>====</code>, <code>&gt;&gt;&gt;&gt;</code> 제거 후 원하는 코드 유지</li>
<li>수정 완료 후:</li>
</ol>
</li>
</ul>
<pre><code class="language-bash">git add 파일명
git commit -m &quot;merge conflict 해결&quot;</code></pre>
<hr>
<h3 id="32-협업-시-브랜치-활용">3.2 협업 시 브랜치 활용</h3>
<ul>
<li><p>여러 명이 동시에 같은 프로젝트를 수정할 때 유용.</p>
</li>
<li><p>개발 흐름:</p>
<ol>
<li>새로운 기능 → 브랜치 생성 → 개발</li>
<li>테스트 후 → main 브랜치에 Merge</li>
</ol>
</li>
</ul>
<hr>
<h2 id="4-merge-방식">4. Merge 방식</h2>
<h3 id="41-3-way-merge">4.1 3-way Merge</h3>
<ul>
<li>브랜치와 기준 브랜치 모두 신규 커밋이 있는 경우 자동 생성되는 새로운 커밋.</li>
<li>Git의 기본 Merge 방식.</li>
</ul>
<h3 id="42-fast-forward-merge">4.2 Fast-forward Merge</h3>
<ul>
<li>기준 브랜치에 신규 커밋이 없는 경우 발생.</li>
<li>Git이 브랜치를 단순히 이어서 병합.</li>
</ul>
<pre><code class="language-bash">git merge 브랜치명       # Fast-forward merge 자동 수행
git merge --no-ff 브랜치명  # 강제로 3-way merge</code></pre>
<h3 id="43-브랜치-삭제">4.3 브랜치 삭제</h3>
<pre><code class="language-bash">git branch -d 브랜치이름    # 병합 완료 브랜치 삭제
git branch -D 브랜치이름    # 병합되지 않은 브랜치 강제 삭제</code></pre>
<hr>
<h2 id="5-rebase--merge">5. Rebase &amp; Merge</h2>
<h3 id="51-rebase">5.1 Rebase</h3>
<ul>
<li>브랜치의 시작점을 다른 커밋으로 옮기는 작업.</li>
<li>활용 예:</li>
</ul>
<pre><code class="language-bash">git switch 신규브랜치
git rebase main
git switch main
git merge 신규브랜치</code></pre>
<ul>
<li>장점: <strong>커밋 내역을 한 줄로 정리 → Fast-forward Merge</strong></li>
<li>단점: 브랜치 차이가 크면 충돌 발생 가능.</li>
</ul>
<hr>
<h3 id="52-squash--merge">5.2 Squash &amp; Merge</h3>
<ul>
<li>브랜치의 여러 커밋을 <strong>하나의 커밋</strong>으로 합쳐 main 브랜치에 적용.</li>
<li>장점: main 브랜치 커밋 내역 깔끔, 3-way merge보다 단순.</li>
</ul>
<pre><code class="language-bash">git merge --squash 브랜치명
git commit -m &quot;새 기능 통합&quot;</code></pre>
<hr>
<h1 id="요약">요약</h1>
<ul>
<li><p><strong>브랜치(branch)</strong> : 프로젝트 복사본 생성 → 안전하게 개발 → 메인에 병합</p>
</li>
<li><p><strong>Merge 방식</strong></p>
<ol>
<li>3-way merge : 기본, 양쪽 모두 신규 커밋 존재</li>
<li>Fast-forward merge : 기준 브랜치 신규 커밋 없음</li>
<li>Rebase &amp; merge : 커밋 이력 정리 + 강제 fast-forward</li>
<li>Squash &amp; merge : 브랜치 커밋을 하나로 합쳐 병합</li>
</ol>
</li>
<li><p>협업 시 브랜치를 활용하면 안정적이고 효율적인 개발 가능</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] Basic - Commit]]></title>
            <link>https://velog.io/@cheers_to_every/Git-Basic-Commit</link>
            <guid>https://velog.io/@cheers_to_every/Git-Basic-Commit</guid>
            <pubDate>Thu, 18 Sep 2025 13:30:03 GMT</pubDate>
            <description><![CDATA[<h1 id="git-basic---commit">[Git] Basic - Commit</h1>
<h2 id="1-git의-필요성">1. Git의 필요성</h2>
<ul>
<li><p>단순히 파일을 저장만 해서는 <strong>과거 상태로 되돌릴 수 없음</strong>.</p>
</li>
<li><p>해결 방법:</p>
<ol>
<li>매일 파일 복사본을 만들어 보관</li>
<li><strong>버전 관리 시스템(Git)</strong> 사용</li>
</ol>
</li>
</ul>
<p>Git의 <strong>Commit 기능</strong>을 활용하면 파일의 현재 상태(스냅샷)를 기록할 수 있으며, 다음과 같은 장점이 있다.</p>
<ul>
<li>과거 특정 시점으로 되돌리기 가능</li>
<li>과거 작업 내역 확인 가능</li>
<li>안정적인 버전 관리 제공</li>
</ul>
<hr>
<h2 id="2-git-시작하기">2. Git 시작하기</h2>
<h3 id="21-설치-확인">2.1 설치 확인</h3>
<pre><code class="language-bash">git --version</code></pre>
<h3 id="22-사용자-정보-등록">2.2 사용자 정보 등록</h3>
<pre><code class="language-bash">git config --global user.email &quot;사용자이메일@도메인.com&quot;
git config --global user.name &quot;사용자이름&quot;</code></pre>
<ul>
<li>컴퓨터에서 Git을 처음 사용할 때 등록해야 함.</li>
<li>&quot;누가 Commit했는지&quot; 구분하기 위한 기본 정보 설정.</li>
</ul>
<hr>
<h2 id="3-git-기본-명령어">3. Git 기본 명령어</h2>
<h3 id="31-저장소-초기화">3.1 저장소 초기화</h3>
<pre><code class="language-bash">git init</code></pre>
<ul>
<li>현재 폴더를 Git 저장소로 초기화.</li>
</ul>
<hr>
<h3 id="32-파일-추적-및-staging">3.2 파일 추적 및 Staging</h3>
<pre><code class="language-bash">git add 파일명
git add app.txt app2.txt   # 여러 개 파일
git add .                  # 하위 폴더 전체</code></pre>
<ul>
<li><code>git add</code> : 특정 파일을 **Staging Area(임시 저장소)**에 등록.</li>
</ul>
<hr>
<h3 id="33-commit">3.3 Commit</h3>
<pre><code class="language-bash">git commit -m &quot;작업 내용 설명&quot;</code></pre>
<ul>
<li><code>git add</code> 후 최종적으로 저장소에 기록.</li>
<li><code>add</code>와 <code>commit</code>을 나눈 이유: 필요 없는 파일은 Commit에서 제외할 수 있기 때문.</li>
</ul>
<hr>
<h3 id="34-상태-확인">3.4 상태 확인</h3>
<pre><code class="language-bash">git status</code></pre>
<ul>
<li>어떤 파일이 Staging 되었는지, 수정 여부 확인 가능.</li>
</ul>
<hr>
<h3 id="35-commit-내역-확인">3.5 Commit 내역 확인</h3>
<pre><code class="language-bash">git log --all --oneline</code></pre>
<ul>
<li>모든 Commit 내역을 간단히 확인 가능.</li>
</ul>
<hr>
<h3 id="36-git의-구조">3.6 Git의 구조</h3>
<pre><code>작업 디렉토리 → Staging Area → Local Repository</code></pre><ul>
<li><strong>작업 디렉토리</strong>: 실제 파일이 위치</li>
<li><strong>Staging Area</strong>: <code>git add</code> 된 파일들이 모여있는 공간</li>
<li><strong>Repository</strong>: <code>git commit</code>으로 기록된 파일들의 이력 저장소</li>
</ul>
<hr>
<h2 id="4-변경-사항-확인">4. 변경 사항 확인</h2>
<h3 id="41-git-diff">4.1 git diff</h3>
<pre><code class="language-bash">git diff</code></pre>
<ul>
<li>최근 Commit과 현재 파일의 차이점 확인.</li>
</ul>
<p><strong>단점</strong></p>
<ul>
<li>변경점이 많으면 가독성이 떨어짐</li>
<li>단순 Enter, 공백 차이까지 표시됨</li>
</ul>
<p>조작 키:</p>
<ul>
<li><code>j / k</code>: 스크롤 이동</li>
<li><code>q</code>: 종료</li>
</ul>
<hr>
<h3 id="42-git-difftool">4.2 git difftool</h3>
<pre><code class="language-bash">git difftool
git difftool 커밋ID
git difftool 커밋ID1 커밋ID2</code></pre>
<ul>
<li>차이점을 시각적으로 보여주는 도구 실행.</li>
<li>특정 Commit과 비교하거나, 두 Commit 간 비교 가능.</li>
</ul>
<p><strong>Vim 조작 키</strong></p>
<ul>
<li><code>h, j, k, l</code>: 방향 이동</li>
<li><code>:q</code>, <code>:qa</code>: 종료</li>
</ul>
<hr>
<h2 id="5-추가-도구">5. 추가 도구</h2>
<ul>
<li><p><strong>Git Graph (VS Code 확장)</strong></p>
<ul>
<li>Git 기록을 시각적으로 확인할 수 있는 도구</li>
<li>복잡한 <code>git log</code> 없이도 Commit 이력과 브랜치를 쉽게 확인 가능</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><p>Git은 <strong>스냅샷 기반 버전 관리 도구</strong></p>
</li>
<li><p>핵심 명령어 흐름:</p>
<ol>
<li><code>git init</code> (저장소 생성)</li>
<li><code>git add</code> (Staging Area 등록)</li>
<li><code>git commit</code> (Repository 저장)</li>
</ol>
</li>
<li><p>변경 사항은 <code>git diff</code>, <code>git difftool</code>로 확인</p>
</li>
<li><p>VS Code 등에서 Git Graph를 활용하면 가시성 향상</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 19. 네트워크 입출력]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-19.-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9E%85%EC%B6%9C%EB%A0%A5</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-19.-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%9E%85%EC%B6%9C%EB%A0%A5</guid>
            <pubDate>Wed, 17 Sep 2025 12:58:24 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-19-네트워크-입출력">Chapter 19. 네트워크 입출력</h1>
<h2 id="191-네트워크-기초">19.1. 네트워크 기초</h2>
<h3 id="1-네트워크-lan-wan">1) 네트워크, LAN, WAN</h3>
<ul>
<li><strong>네트워크</strong>: 여러 컴퓨터를 통신 회선으로 연결한 것</li>
<li><strong>LAN(Local Area Network)</strong>: 특정 영역(가정, 회사, 건물 등)에 존재하는 컴퓨터 연결</li>
<li><strong>WAN(Wide Area Network)</strong>: 여러 LAN을 연결한 것</li>
</ul>
<h3 id="2-서버와-클라이언트">2) 서버와 클라이언트</h3>
<ul>
<li><strong>서버(Server)</strong>: 서비스를 제공하는 프로그램</li>
<li><strong>클라이언트(Client)</strong>: 서비스를 요청하는 프로그램</li>
</ul>
<h3 id="3-ip-주소">3) IP 주소</h3>
<ul>
<li>컴퓨터(네트워크 어댑터)의 고유 주소</li>
<li>프로그램은 <strong>DNS</strong>를 통해 도메인 이름을 IP로 변환 후 통신</li>
<li>예: 웹 브라우저는 클라이언트로서 DNS에서 IP 조회 후 웹 서버와 통신</li>
</ul>
<h3 id="4-port-번호">4) Port 번호</h3>
<ul>
<li>하나의 컴퓨터(IP)에서 여러 서버 프로그램을 구분하기 위해 사용</li>
<li>서버는 Port 번호에 바인딩, 클라이언트는 OS가 부여한 동적 포트를 사용</li>
</ul>
<table>
<thead>
<tr>
<th>Port 범위</th>
<th>용도</th>
</tr>
</thead>
<tbody><tr>
<td>0 ~ 1023</td>
<td>ICANN 예약(HTTP: 80, HTTPS: 443 등)</td>
</tr>
<tr>
<td>1024 ~ 49151</td>
<td>등록 가능한 Port(기업/서비스)</td>
</tr>
<tr>
<td>49152 ~ 65535</td>
<td>동적/개인용 포트(OS 자동 할당)</td>
</tr>
</tbody></table>
<hr>
<h2 id="192-ip-주소-얻기">19.2. IP 주소 얻기</h2>
<p>자바에서는 <strong><code>java.net.InetAddress</code></strong> 클래스를 사용.</p>
<pre><code class="language-java">// 로컬 IP 주소 얻기
InetAddress local = InetAddress.getLocalHost();
System.out.println(local.getHostAddress());

// 도메인으로 IP 얻기
InetAddress naver = InetAddress.getByName(&quot;www.naver.com&quot;);
System.out.println(naver.getHostAddress());

// 하나의 도메인에 여러 IP 등록된 경우
InetAddress[] iaArr = InetAddress.getAllByName(&quot;www.google.com&quot;);
for (InetAddress ip : iaArr) {
    System.out.println(ip.getHostAddress());
}</code></pre>
<hr>
<h2 id="193-tcp-네트워킹">19.3. TCP 네트워킹</h2>
<h3 id="1-tcp-특징">1) TCP 특징</h3>
<ul>
<li>연결형 프로토콜 → 송수신자가 연결된 상태에서 통신</li>
<li>데이터가 <strong>순서대로</strong> 전달되고 손실 없음</li>
<li>흔히 <strong>TCP/IP</strong>라고 부름</li>
</ul>
<h3 id="2-자바-tcp-클래스">2) 자바 TCP 클래스</h3>
<ul>
<li><strong>ServerSocket</strong>: 서버에서 클라이언트 연결 수락</li>
<li><strong>Socket</strong>: 클라이언트 연결 요청 및 양방향 통신</li>
</ul>
<hr>
<h3 id="3-tcp-서버-예제">3) TCP 서버 예제</h3>
<pre><code class="language-java">import java.net.*;
import java.io.*;

public class TcpServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(50001)) {
            System.out.println(&quot;[서버] 시작됨&quot;);

            Socket socket = serverSocket.accept(); // 연결 대기
            InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
            System.out.println(&quot;[서버] 연결됨: &quot; + isa.getHostName());

            // 데이터 수신
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            String message = dis.readUTF();
            System.out.println(&quot;[서버] 받은 메시지: &quot; + message);

            // 데이터 전송
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF(&quot;Hello Client&quot;);
            dos.flush();

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}</code></pre>
<hr>
<h3 id="4-tcp-클라이언트-예제">4) TCP 클라이언트 예제</h3>
<pre><code class="language-java">import java.net.*;
import java.io.*;

public class TcpClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket(&quot;localhost&quot;, 50001)) {
            System.out.println(&quot;[클라이언트] 연결 성공&quot;);

            // 데이터 전송
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.writeUTF(&quot;Hello Server&quot;);
            dos.flush();

            // 데이터 수신
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            String response = dis.readUTF();
            System.out.println(&quot;[클라이언트] 받은 메시지: &quot; + response);

        } catch (UnknownHostException e) {
            System.out.println(&quot;잘못된 IP 주소&quot;);
        } catch (IOException e) {
            System.out.println(&quot;연결 실패&quot;);
        }
    }
}</code></pre>
<hr>
<h3 id="5-전체-과정-요약-tcp">5) 전체 과정 요약 (TCP)</h3>
<ul>
<li><p><strong>서버</strong></p>
<ol>
<li>ServerSocket 생성 및 포트 바인딩</li>
<li><code>accept()</code>로 클라이언트 요청 수락</li>
<li>Socket 통해 데이터 송수신</li>
<li>close()로 연결 종료</li>
</ol>
</li>
<li><p><strong>클라이언트</strong></p>
<ol>
<li>Socket 생성 (서버 IP + Port)</li>
<li>서버 연결 요청</li>
<li>Socket 통해 데이터 송수신</li>
<li>close()로 연결 종료</li>
</ol>
</li>
</ul>
<hr>
<h2 id="194-udp-네트워킹">19.4. UDP 네트워킹</h2>
<h3 id="특징">특징</h3>
<ul>
<li>비연결형 프로토콜 → 연결 과정 없음</li>
<li>속도는 빠르지만 순서 보장 안 되고 손실 가능성 있음</li>
<li>게임, 동영상 스트리밍 등 실시간성이 중요한 곳에서 사용</li>
</ul>
<h3 id="자바-udp-클래스">자바 UDP 클래스</h3>
<ul>
<li><strong>DatagramSocket</strong>: 송수신에 사용</li>
<li><strong>DatagramPacket</strong>: 전송할 데이터 캡슐화</li>
</ul>
<h3 id="udp-예제">UDP 예제</h3>
<pre><code class="language-java">// UDP 서버
import java.net.*;

public class UdpServer {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket(50001);
        byte[] buf = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buf, buf.length);

        System.out.println(&quot;[서버] 수신 대기&quot;);
        socket.receive(packet);

        String msg = new String(packet.getData(), 0, packet.getLength(), &quot;UTF-8&quot;);
        System.out.println(&quot;[서버] 받은 메시지: &quot; + msg);

        socket.close();
    }
}

// UDP 클라이언트
import java.net.*;

public class UdpClient {
    public static void main(String[] args) throws Exception {
        DatagramSocket socket = new DatagramSocket();
        byte[] buf = &quot;Hello UDP&quot;.getBytes(&quot;UTF-8&quot;);

        DatagramPacket packet = new DatagramPacket(
            buf, buf.length, new InetSocketAddress(&quot;localhost&quot;, 50001));

        socket.send(packet);
        socket.close();
    }
}</code></pre>
<hr>
<ul>
<li><strong>TCP</strong>: 연결형, 신뢰성 보장, 순서 유지 → <code>ServerSocket</code>, <code>Socket</code></li>
<li><strong>UDP</strong>: 비연결형, 속도 우선, 손실 가능 → <code>DatagramSocket</code>, <code>DatagramPacket</code></li>
<li><strong>IP 주소</strong>: 네트워크 어댑터 단위 고유 주소 (DNS로 변환 가능)</li>
<li><strong>Port 번호</strong>: 하나의 IP에서 여러 서버 프로그램 구분</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 17. 스트림 요소 처리]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-17.-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9A%94%EC%86%8C-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-17.-%EC%8A%A4%ED%8A%B8%EB%A6%BC-%EC%9A%94%EC%86%8C-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Tue, 16 Sep 2025 14:16:36 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/58e550c2-3413-4aef-a767-7b2016355549/image.png" alt=""></p>
<h1 id="chapter-17-스트림-요소-처리">Chapter 17. 스트림 요소 처리</h1>
<h2 id="171-스트림이란">17.1. 스트림이란?</h2>
<h3 id="1-정의">1. 정의</h3>
<ul>
<li>컬렉션 및 배열의 요소를 반복 처리하기 위해 기존에는 <code>for</code>문이나 <code>Iterator</code>를 사용</li>
<li><strong>Java 8</strong>부터는 스트림(Stream)을 이용해 반복 처리 가능</li>
<li>&quot;스트림&quot;은 <strong>데이터 요소들이 하나씩 흘러가며 처리된다</strong>는 의미</li>
</ul>
<h3 id="2-특징">2. 특징</h3>
<ul>
<li><strong>내부 반복자</strong>: 처리 속도가 빠르고 병렬 처리에 효율적</li>
<li><strong>람다식 지원</strong>: 다양한 요소 처리를 간단히 정의 가능</li>
<li><strong>파이프라인 구조</strong>: 중간 처리와 최종 처리를 연결하여 구성 가능</li>
</ul>
<hr>
<h2 id="172-내부-반복자">17.2. 내부 반복자</h2>
<h3 id="1-외부-반복자">1. 외부 반복자</h3>
<ul>
<li><code>for</code>문, <code>Iterator</code> → <strong>외부에서 직접 요소를 꺼내와 처리</strong></li>
<li>개발자가 데이터 추출과 처리 코드를 모두 작성해야 함</li>
</ul>
<h3 id="2-내부-반복자">2. 내부 반복자</h3>
<ul>
<li><strong>스트림은 내부 반복자 방식 사용</strong></li>
<li>데이터 처리 방법(람다식)을 컬렉션 내부로 전달하여 내부적으로 반복 처리</li>
<li><strong>멀티코어 CPU 활용</strong>: 요소를 분배해 병렬 처리 가능</li>
<li>따라서 외부 반복자보다 효율적</li>
</ul>
<hr>
<h2 id="173-중간-처리와-최종-처리">17.3. 중간 처리와 최종 처리</h2>
<ul>
<li><p>스트림은 여러 단계로 연결 가능 → 이를 <strong>스트림 파이프라인</strong>이라 함</p>
</li>
<li><p><strong>중간 처리</strong>: 필터링, 매핑, 정렬 등</p>
</li>
<li><p><strong>최종 처리</strong>: 반복, 집계(카운트, 합계, 평균 등)</p>
<p><strong>주의</strong>: 최종 처리가 없으면 스트림은 동작하지 않음</p>
</li>
<li><p>메소드 체이닝 패턴을 이용하면 코드가 간결해짐</p>
</li>
</ul>
<hr>
<h2 id="174-리소스로부터-스트림-얻기">17.4. 리소스로부터 스트림 얻기</h2>
<h3 id="1-스트림-종류">1. 스트림 종류</h3>
<ul>
<li><code>java.util.stream</code> 패키지 제공</li>
<li>부모 인터페이스: <code>BaseStream</code></li>
<li>주요 자식 인터페이스: <code>Stream</code>, <code>IntStream</code>, <code>LongStream</code>, <code>DoubleStream</code></li>
</ul>
<h3 id="2-생성-방법">2. 생성 방법</h3>
<h4 id="1-컬렉션에서-얻기">(1) 컬렉션에서 얻기</h4>
<pre><code class="language-java">Stream&lt;Product&gt; stream = list.stream();
stream.forEach(p -&gt; System.out.println(p));</code></pre>
<ul>
<li><code>Collection.stream()</code></li>
<li><code>Collection.parallelStream()</code></li>
</ul>
<h4 id="2-배열에서-얻기">(2) 배열에서 얻기</h4>
<ul>
<li><code>Arrays.stream(T[])</code></li>
<li><code>Stream.of(T[])</code></li>
</ul>
<pre><code class="language-java">String[] arr = {&quot;a&quot;, &quot;b&quot;, &quot;c&quot;};
Stream&lt;String&gt; stream = Arrays.stream(arr);</code></pre>
<h4 id="3-숫자-범위에서-얻기">(3) 숫자 범위에서 얻기</h4>
<ul>
<li><code>IntStream.range(start, end)</code> → end 불포함</li>
<li><code>IntStream.rangeClosed(start, end)</code> → end 포함</li>
</ul>
<pre><code class="language-java">IntStream.range(1, 5).forEach(System.out::print);   // 1234
IntStream.rangeClosed(1, 5).forEach(System.out::print); // 12345</code></pre>
<h4 id="4-파일에서-얻기">(4) 파일에서 얻기</h4>
<pre><code class="language-java">Path path = Paths.get(&quot;data.txt&quot;);
Stream&lt;String&gt; stream = Files.lines(path, Charset.defaultCharset());
stream.forEach(System.out::println);
stream.close();</code></pre>
<h4 id="5-기타">(5) 기타</h4>
<ul>
<li><code>Files.list(Path)</code> → 디렉토리 내 경로 스트림</li>
<li><code>Random.ints()</code>, <code>Random.longs()</code>, <code>Random.doubles()</code> → 랜덤 수 스트림</li>
</ul>
<hr>
<h2 id="175-요소-걸러내기-filtering">17.5. 요소 걸러내기 (Filtering)</h2>
<h3 id="주요-메소드">주요 메소드</h3>
<ol>
<li><p><strong><code>distinct()</code></strong>: 중복 제거</p>
</li>
<li><p><strong><code>filter()</code></strong>: 조건에 맞는 요소만 걸러냄</p>
<ul>
<li><code>Stream.filter(Predicate&lt;T&gt;)</code></li>
<li><code>IntStream.filter(IntPredicate)</code></li>
<li><code>LongStream.filter(LongPredicate)</code></li>
<li><code>DoubleStream.filter(DoublePredicate)</code></li>
</ul>
</li>
</ol>
<h3 id="함수형-인터페이스-람다식-사용-가능">함수형 인터페이스 (람다식 사용 가능)</h3>
<ul>
<li><code>Predicate&lt;T&gt;</code>: <code>boolean test(T t)</code></li>
<li><code>IntPredicate</code>: <code>boolean test(int value)</code></li>
<li><code>LongPredicate</code>: <code>boolean test(long value)</code></li>
<li><code>DoublePredicate</code>: <code>boolean test(double value)</code></li>
</ul>
<hr>
<h2 id="176-요소-변환-mapping">17.6. 요소 변환 (Mapping)</h2>
<ul>
<li>스트림 요소를 다른 형태로 변환하는 중간 처리 기능</li>
</ul>
<h3 id="1-요소-→-다른-요소">1. 요소 → 다른 요소</h3>
<ul>
<li><code>map(Function&lt;T,R&gt;)</code></li>
<li><code>mapToInt(ToIntFunction&lt;T&gt;)</code></li>
<li><code>mapToLong(ToLongFunction&lt;T&gt;)</code></li>
<li><code>mapToDouble(ToDoubleFunction&lt;T&gt;)</code></li>
</ul>
<h3 id="2-기본-타입-변환">2. 기본 타입 변환</h3>
<ul>
<li><code>asLongStream()</code> → int → long</li>
<li><code>asDoubleStream()</code> → int/long → double</li>
<li><code>boxed()</code> → 기본 타입 → Wrapper 클래스</li>
</ul>
<h3 id="3-다중-요소-변환-flatmap">3. 다중 요소 변환 (flatMap)</h3>
<ul>
<li>하나의 요소를 여러 개의 요소로 변환</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = Arrays.asList(&quot;a b c&quot;, &quot;d e f&quot;);
list.stream()
    .flatMap(str -&gt; Arrays.stream(str.split(&quot; &quot;)))
    .forEach(System.out::println);</code></pre>
<hr>
<h2 id="177-요소-정렬-sorting">17.7. 요소 정렬 (Sorting)</h2>
<h3 id="1-주요-메소드">1. 주요 메소드</h3>
<ul>
<li><code>Stream&lt;T&gt; sorted()</code> → 기본 Comparable 기준 정렬</li>
<li><code>Stream&lt;T&gt; sorted(Comparator&lt;T&gt;)</code> → 지정된 Comparator 기준 정렬</li>
<li><code>IntStream.sorted()</code>, <code>LongStream.sorted()</code>, <code>DoubleStream.sorted()</code> → 오름차순 정렬</li>
</ul>
<h3 id="2-객체-정렬">2. 객체 정렬</h3>
<ul>
<li>객체가 <code>Comparable</code>을 구현해야 <code>sorted()</code> 사용 가능</li>
<li>내림차순 정렬: <code>Comparator.reverseOrder()</code></li>
</ul>
<h3 id="3-comparator-이용">3. Comparator 이용</h3>
<pre><code class="language-java">studentList.stream()
    .sorted((s1, s2) -&gt; Integer.compare(s1.getScore(), s2.getScore()))
    .forEach(System.out::println);</code></pre>
<hr>
<h2 id="178-요소를-하나씩-처리-루핑">17.8. 요소를 하나씩 처리 (루핑)</h2>
<ul>
<li>최종 처리 기능 중 하나는 <strong>루핑(looping)</strong></li>
<li>단순히 요소를 반복해서 처리할 때 사용</li>
</ul>
<h3 id="메소드">메소드</h3>
<ul>
<li><code>forEach(Consumer&lt;T&gt; action)</code>
→ 스트림의 모든 요소를 순차적으로 처리</li>
<li><code>forEachOrdered(Consumer&lt;T&gt; action)</code>
→ 병렬 스트림 사용 시에도 <strong>순서를 보장</strong></li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = Arrays.asList(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;);
list.stream().forEach(System.out::println);</code></pre>
<hr>
<h2 id="179-요소-조건-만족-여부-매칭">17.9. 요소 조건 만족 여부 (매칭)</h2>
<ul>
<li>스트림의 요소들이 특정 조건을 만족하는지 여부를 검사</li>
</ul>
<h3 id="메소드-1">메소드</h3>
<ul>
<li><code>boolean allMatch(Predicate&lt;T&gt;)</code>
→ 모든 요소가 조건을 만족하면 <code>true</code></li>
<li><code>boolean anyMatch(Predicate&lt;T&gt;)</code>
→ 하나라도 조건을 만족하면 <code>true</code></li>
<li><code>boolean noneMatch(Predicate&lt;T&gt;)</code>
→ 모든 요소가 조건을 만족하지 않으면 <code>true</code></li>
</ul>
<pre><code class="language-java">List&lt;Integer&gt; list = Arrays.asList(2, 4, 6);
boolean result = list.stream().allMatch(n -&gt; n % 2 == 0);
System.out.println(result); // true</code></pre>
<hr>
<h2 id="1710-요소-기본-집계">17.10. 요소 기본 집계</h2>
<ul>
<li>집계(Aggregate): 스트림의 최종 처리 기능 중 하나</li>
<li>카운팅, 합계, 평균, 최대값, 최소값 등을 구함</li>
</ul>
<h3 id="메소드-2">메소드</h3>
<ul>
<li><code>long count()</code></li>
<li><code>Optional&lt;T&gt; max(Comparator&lt;T&gt;)</code></li>
<li><code>Optional&lt;T&gt; min(Comparator&lt;T&gt;)</code></li>
<li><code>OptionalDouble average()</code></li>
<li><code>sum()</code> (IntStream, LongStream, DoubleStream 전용)</li>
</ul>
<pre><code class="language-java">IntStream stream = IntStream.of(1, 2, 3, 4, 5);
int sum = stream.sum();
System.out.println(sum); // 15</code></pre>
<hr>
<h2 id="1711-요소-커스텀-집계-reduce">17.11. 요소 커스텀 집계 (reduce)</h2>
<ul>
<li><code>reduce()</code> 메소드를 이용해 <strong>직접 정의한 방법으로 집계</strong> 가능</li>
</ul>
<h3 id="메소드-형태">메소드 형태</h3>
<ul>
<li><code>Optional&lt;T&gt; reduce(BinaryOperator&lt;T&gt; accumulator)</code></li>
<li><code>T reduce(T identity, BinaryOperator&lt;T&gt; accumulator)</code></li>
</ul>
<h3 id="예제">예제</h3>
<pre><code class="language-java">List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5);

// 합계 구하기 (초기값 0)
int sum = list.stream()
              .reduce(0, (a, b) -&gt; a + b);
System.out.println(sum); // 15</code></pre>
<hr>
<h2 id="1712-요소-수집-collect">17.12. 요소 수집 (collect)</h2>
<ul>
<li>스트림 요소들을 원하는 자료구조로 수집할 때 사용</li>
</ul>
<h3 id="주요-메소드-1">주요 메소드</h3>
<ul>
<li><code>Collectors.toList()</code></li>
<li><code>Collectors.toSet()</code></li>
<li><code>Collectors.toMap(keyMapper, valueMapper)</code></li>
<li><code>Collectors.joining(delimiter)</code></li>
<li><code>Collectors.groupingBy(Function&lt;T,K&gt;)</code> → 그룹핑</li>
<li><code>Collectors.partitioningBy(Predicate&lt;T&gt;)</code> → 조건에 따라 분할</li>
</ul>
<h3 id="예제-1">예제</h3>
<pre><code class="language-java">List&lt;String&gt; names = Arrays.asList(&quot;Kim&quot;, &quot;Lee&quot;, &quot;Park&quot;);

// 리스트로 수집
List&lt;String&gt; result = names.stream()
                           .filter(n -&gt; n.length() &gt;= 3)
                           .collect(Collectors.toList());

System.out.println(result); // [Kim, Lee, Park]</code></pre>
<hr>
<h2 id="1713-요소-병렬-처리">17.13. 요소 병렬 처리</h2>
<ul>
<li>스트림은 내부적으로 <strong>ForkJoinPool</strong>을 사용하여 병렬 처리 지원</li>
<li>데이터가 많고 CPU 코어가 여러 개일 경우 성능 향상 가능</li>
</ul>
<h3 id="병렬-처리-방법">병렬 처리 방법</h3>
<ul>
<li><code>parallelStream()</code> → 컬렉션에서 병렬 스트림 생성</li>
<li><code>stream().parallel()</code> → 기존 스트림을 병렬 스트림으로 변환</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = Arrays.asList(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;);

// 병렬 처리 (순서 보장 안 됨)
list.parallelStream().forEach(System.out::println);

// 순서 보장 병렬 처리
list.parallelStream().forEachOrdered(System.out::println);</code></pre>
<hr>
<ul>
<li><strong>루핑</strong>: forEach, forEachOrdered</li>
<li><strong>매칭</strong>: allMatch, anyMatch, noneMatch</li>
<li><strong>기본 집계</strong>: count, sum, average, max, min</li>
<li><strong>커스텀 집계</strong>: reduce()</li>
<li><strong>수집</strong>: collect(), Collectors API 활용</li>
<li><strong>병렬 처리</strong>: parallelStream(), parallel()</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 16. 람다식]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-16.-%EB%9E%8C%EB%8B%A4%EC%8B%9D</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-16.-%EB%9E%8C%EB%8B%A4%EC%8B%9D</guid>
            <pubDate>Mon, 15 Sep 2025 11:51:25 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/28cf2379-6f16-43ff-9377-d0638e344c32/image.png" alt=""></p>
<h1 id="chapter-16-람다식">Chapter 16. 람다식</h1>
<h2 id="161-람다식이란">16.1. 람다식이란?</h2>
<h3 id="1-함수형-프로그래밍">1. 함수형 프로그래밍</h3>
<ul>
<li><strong>정의</strong>: 함수를 정의하고 이 함수를 데이터 처리부에 전달하여 데이터를 처리하는 기법</li>
<li>데이터 처리부는 <strong>데이터만 보유</strong>하고 있으며, 처리 방법은 외부에서 제공된 함수에 의존</li>
<li><strong>데이터 처리의 다형성</strong>: 제공되는 함수에 따라 처리 결과가 달라지는 것이 특징</li>
</ul>
<h3 id="2-람다식">2. 람다식</h3>
<ul>
<li>자바는 람다식을 **익명 구현 객체(인터페이스 구현 객체)**로 변환</li>
<li>따라서 람다식은 인터페이스 타입의 매개변수로 대입될 수 있음</li>
</ul>
<h3 id="3-함수형-인터페이스">3. 함수형 인터페이스</h3>
<ul>
<li>인터페이스에 <strong>추상 메소드가 단 하나만 존재</strong>할 경우, 이를 <strong>함수형 인터페이스</strong>라고 함</li>
<li>함수형 인터페이스는 람다식으로 표현 가능</li>
</ul>
<h3 id="4-functionalinterface">4. @FunctionalInterface</h3>
<ul>
<li>인터페이스가 함수형 인터페이스임을 보장하기 위해 <code>@FunctionalInterface</code> 어노테이션을 사용</li>
<li>선택 사항이지만, 컴파일 시 추상 메소드가 하나인지 검사하여 <strong>정확한 함수형 인터페이스 작성</strong> 가능</li>
</ul>
<pre><code class="language-java">// 예시
(x, y) -&gt; { 처리 내용 }</code></pre>
<blockquote>
<p>참고: 객체를 하나 더 만드는 이유는 main에서 직접 구현 객체를 생성하지 않고,
별도의 객체에서 구현 객체를 받아 람다식을 구현하기 위함이다.</p>
</blockquote>
<hr>
<h2 id="162-매개변수가-없는-람다식">16.2. 매개변수가 없는 람다식</h2>
<h3 id="작성-방식">작성 방식</h3>
<ol>
<li><p>여러 실행문</p>
<pre><code class="language-java">() -&gt; { 실행문1; 실행문2; }</code></pre>
</li>
<li><p>실행문이 하나일 경우</p>
<pre><code class="language-java">() -&gt; 실행문</code></pre>
</li>
</ol>
<p><strong>예시</strong></p>
<pre><code class="language-java">() -&gt; System.out.println(&quot;Hello Lambda&quot;);</code></pre>
<hr>
<h2 id="163-매개변수가-있는-람다식">16.3. 매개변수가 있는 람다식</h2>
<ul>
<li>매개변수 선언 시 타입은 생략 가능</li>
<li><code>var</code> 사용도 가능하지만 일반적으로는 <strong>타입 생략 방식</strong>을 사용</li>
</ul>
<h3 id="작성-방식-1">작성 방식</h3>
<ol>
<li><p>타입 명시</p>
<pre><code class="language-java">(타입 매개변수, ...) -&gt; 실행문
(타입 매개변수, ...) -&gt; { 실행문; 실행문; }</code></pre>
</li>
<li><p>var 사용</p>
<pre><code class="language-java">(var 매개변수, ...) -&gt; 실행문
(var 매개변수, ...) -&gt; { 실행문; 실행문; }</code></pre>
</li>
<li><p>타입 생략</p>
<pre><code class="language-java">(매개변수, ...) -&gt; 실행문
(매개변수, ...) -&gt; { 실행문; 실행문; }</code></pre>
</li>
<li><p>매개변수가 하나일 경우 괄호 생략 가능</p>
<pre><code class="language-java">매개변수 -&gt; 실행문</code></pre>
</li>
</ol>
<p><strong>예시</strong></p>
<pre><code class="language-java">word -&gt; System.out.println(word);</code></pre>
<hr>
<h2 id="164-리턴값이-있는-람다식">16.4. 리턴값이 있는 람다식</h2>
<ul>
<li>함수형 인터페이스의 추상 메소드에 리턴값이 있을 경우 사용</li>
</ul>
<h3 id="작성-방식-2">작성 방식</h3>
<ol>
<li><p>여러 실행문</p>
<pre><code class="language-java">(매개변수, ...) -&gt; { 실행문; return 값; }</code></pre>
</li>
<li><p>return 문 하나만 있을 경우</p>
<pre><code class="language-java">(매개변수, ...) -&gt; 값</code></pre>
</li>
</ol>
<ul>
<li><code>return</code> 문 하나만 있으면 <strong>중괄호와 return 키워드 생략 가능</strong></li>
</ul>
<p><strong>예시</strong></p>
<pre><code class="language-java">(x, y) -&gt; sum(x, y)</code></pre>
<hr>
<h2 id="165-메소드-참조">16.5. 메소드 참조</h2>
<ul>
<li><strong>정의</strong>: 메소드를 직접 참조하여 불필요한 매개변수를 제거하는 기법</li>
</ul>
<h3 id="예시">예시</h3>
<pre><code class="language-java">(left, right) -&gt; Math.max(left, right)</code></pre>
<pre><code class="language-java">Math::max</code></pre>
<h3 id="1-정적-메소드와-인스턴스-메소드-참조">1. 정적 메소드와 인스턴스 메소드 참조</h3>
<ul>
<li><p>정적 메소드 참조</p>
<pre><code class="language-java">클래스 :: 메소드
Computer :: staticMethod</code></pre>
</li>
<li><p>인스턴스 메소드 참조</p>
<pre><code class="language-java">참조변수 :: 메소드
com :: instanceMethod</code></pre>
</li>
</ul>
<h3 id="2-매개변수의-메소드-참조">2. 매개변수의 메소드 참조</h3>
<pre><code class="language-java">(a, b) -&gt; a.instanceMethod(b)</code></pre>
<pre><code class="language-java">클래스 :: instanceMethod</code></pre>
<hr>
<h2 id="166-생성자-참조">16.6. 생성자 참조</h2>
<ul>
<li>생성자를 참조한다는 것은 객체를 생성하는 것을 의미</li>
<li>람다식이 단순히 객체 생성만 한다면, <strong>생성자 참조</strong>로 대체 가능</li>
</ul>
<h3 id="예시-1">예시</h3>
<pre><code class="language-java">(a, b) -&gt; { return new 클래스(a, b); }</code></pre>
<pre><code class="language-java">클래스 :: new</code></pre>
<ul>
<li><strong>생성자가 오버로딩된 경우</strong>, 함수형 인터페이스의 추상 메소드 매개변수 개수와 타입에 맞는 생성자가 실행됨</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 15. 컬렉션 자료구조]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-15.-%EC%BB%AC%EB%A0%89%EC%85%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-15.-%EC%BB%AC%EB%A0%89%EC%85%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Sun, 14 Sep 2025 07:56:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/08d17dce-fe1f-4b47-b5a4-81abed698d36/image.png" alt=""></p>
<h1 id="chapter-15-컬렉션-자료구조">Chapter 15. 컬렉션 자료구조</h1>
<h2 id="151-컬렉션-프레임워크">15.1. 컬렉션 프레임워크</h2>
<p>자바 컬렉션 프레임워크는 객체를 효율적으로 <strong>저장·검색·삭제</strong>할 수 있도록 자료구조를 제공한다.
핵심 인터페이스는 <code>Collection</code>과 <code>Map</code>이다.</p>
<h3 id="1-collection">1. Collection</h3>
<ul>
<li><strong>List</strong> (<code>ArrayList</code>, <code>Vector</code>, <code>LinkedList</code>)
: 저장 순서를 유지, 중복 저장 가능</li>
<li><strong>Set</strong> (<code>HashSet</code>, <code>TreeSet</code>)
: 저장 순서 유지 안 함, 중복 저장 불가</li>
</ul>
<h3 id="2-map">2. Map</h3>
<ul>
<li>(<code>HashMap</code>, <code>Hashtable</code>, <code>TreeMap</code>, <code>Properties</code>)
: 키-값(<code>Entry</code>)으로 저장,
키는 중복 저장 불가, 값은 중복 가능</li>
</ul>
<hr>
<h2 id="152-list-collection">15.2. List Collection</h2>
<h3 id="특징">특징</h3>
<ul>
<li>객체 저장 시 <strong>인덱스</strong>가 부여된다.</li>
<li>인덱스로 검색·삭제 가능.</li>
<li>객체의 번지를 저장하므로 같은 객체를 중복 저장할 수 있다.</li>
<li><code>null</code> 저장 가능.</li>
</ul>
<h3 id="주요-메소드">주요 메소드</h3>
<ul>
<li><p><strong>추가</strong></p>
<ul>
<li><code>boolean add(E e)</code> : 맨 끝에 추가</li>
<li><code>void add(int index, E element)</code> : 인덱스에 추가</li>
<li><code>E set(int index, E element)</code> : 지정 인덱스의 객체를 변경</li>
</ul>
</li>
<li><p><strong>검색</strong></p>
<ul>
<li><code>boolean contains(Object o)</code> : 포함 여부</li>
<li><code>boolean isEmpty()</code> : 비었는지 확인</li>
<li><code>int size()</code> : 저장된 객체 수</li>
<li><code>E get(int index)</code> : 인덱스의 객체 반환</li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li><code>void clear()</code> : 전체 삭제</li>
<li><code>E remove(int index)</code> : 인덱스의 객체 삭제</li>
<li><code>boolean remove(Object o)</code> : 해당 객체 삭제</li>
</ul>
</li>
</ul>
<hr>
<h3 id="1-arraylist">1) ArrayList</h3>
<ul>
<li>크기 제한 없이 객체 저장 가능.</li>
<li>삭제 시 뒤의 모든 인덱스를 앞으로 당김 → 삽입/삭제가 빈번하면 성능 저하.</li>
<li>선언 예시:</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; list = new ArrayList&lt;&gt;();
list.add(&quot;A&quot;);
list.add(&quot;B&quot;);
System.out.println(list.get(0)); // A</code></pre>
<hr>
<h3 id="2-vector">2) Vector</h3>
<ul>
<li>내부 구조는 <code>ArrayList</code>와 동일.</li>
<li><strong>동기화(synchronized)</strong> 되어 있어 멀티스레드 환경에서 안전.</li>
</ul>
<pre><code class="language-java">List&lt;Integer&gt; vector = new Vector&lt;&gt;();
vector.add(1);
vector.add(2);
System.out.println(vector.size()); // 2</code></pre>
<hr>
<h3 id="3-linkedlist">3) LinkedList</h3>
<ul>
<li>인접 객체를 <strong>체인(링크)</strong> 형태로 연결.</li>
<li>삽입/삭제 시 인덱스 이동이 없어 빠르다.</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; linkedList = new LinkedList&lt;&gt;();
linkedList.add(&quot;X&quot;);
linkedList.add(&quot;Y&quot;);
linkedList.remove(&quot;X&quot;);
System.out.println(linkedList); // [Y]</code></pre>
<hr>
<h2 id="153-set-컬렉션">15.3. Set 컬렉션</h2>
<h3 id="특징-1">특징</h3>
<ul>
<li>저장 순서 유지 안 됨.</li>
<li><strong>중복 저장 불가</strong>, <code>null</code>은 1개 가능.</li>
<li>구현체: <code>HashSet</code>, <code>LinkedHashSet</code>, <code>TreeSet</code>.</li>
</ul>
<h3 id="주요-메소드-1">주요 메소드</h3>
<ul>
<li><p><strong>추가</strong></p>
<ul>
<li><code>boolean add(E e)</code> : 성공 시 <code>true</code>, 중복 시 <code>false</code></li>
</ul>
</li>
<li><p><strong>검색</strong></p>
<ul>
<li><code>boolean contains(Object o)</code></li>
<li><code>boolean isEmpty()</code></li>
<li><code>int size()</code></li>
<li><code>Iterator&lt;E&gt; iterator()</code> : 반복자 획득</li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li><code>void clear()</code></li>
<li><code>boolean remove(Object o)</code></li>
</ul>
</li>
</ul>
<hr>
<h3 id="1-hashset">1) HashSet</h3>
<ul>
<li><code>hashCode()</code> 값과 <code>equals()</code> 값이 같으면 같은 객체로 간주.</li>
</ul>
<pre><code class="language-java">Set&lt;String&gt; set = new HashSet&lt;&gt;();
set.add(&quot;Java&quot;);
set.add(&quot;Java&quot;); // 중복 저장 안 됨
System.out.println(set.size()); // 1</code></pre>
<hr>
<h3 id="2-iterator">2) Iterator</h3>
<ul>
<li><strong>Set은 인덱스로 접근 불가 → 반복자 사용</strong></li>
</ul>
<pre><code class="language-java">Set&lt;String&gt; set = new HashSet&lt;&gt;();
set.add(&quot;A&quot;);
set.add(&quot;B&quot;);

Iterator&lt;String&gt; iter = set.iterator();
while (iter.hasNext()) {
    String element = iter.next();
    System.out.println(element);
}</code></pre>
<p><strong>Iterator 메소드</strong></p>
<ul>
<li><code>boolean hasNext()</code> : 다음 요소 존재 여부</li>
<li><code>E next()</code> : 다음 요소 반환</li>
<li><code>void remove()</code> : 최근 반환한 객체 삭제</li>
</ul>
<hr>
<h2 id="154-map-컬렉션">15.4. Map 컬렉션</h2>
<h3 id="특징-2">특징</h3>
<ul>
<li><strong>Key-Value 쌍(Entry)</strong> 형태 저장.</li>
<li>키 중복 불가, 값 중복 가능.</li>
<li>같은 키로 저장하면 기존 값이 덮어쓰기 됨.</li>
</ul>
<h3 id="주요-메소드-2">주요 메소드</h3>
<ul>
<li><p><strong>추가</strong></p>
<ul>
<li><code>V put(K key, V value)</code></li>
</ul>
</li>
<li><p><strong>검색</strong></p>
<ul>
<li><code>boolean containsKey(Object key)</code></li>
<li><code>boolean containsValue(Object value)</code></li>
<li><code>boolean isEmpty()</code></li>
<li><code>int size()</code></li>
<li><code>Set&lt;Map.Entry&lt;K,V&gt;&gt; entrySet()</code></li>
<li><code>Set&lt;K&gt; keySet()</code></li>
<li><code>Collection&lt;V&gt; values()</code></li>
</ul>
</li>
<li><p><strong>삭제</strong></p>
<ul>
<li><code>void clear()</code></li>
<li><code>V remove(Object key)</code></li>
</ul>
</li>
</ul>
<hr>
<h3 id="1-hashmap">1) HashMap</h3>
<ul>
<li><code>hashCode()</code>와 <code>equals()</code> 기준으로 동일 키 여부 판단.</li>
</ul>
<pre><code class="language-java">Map&lt;Integer, String&gt; map = new HashMap&lt;&gt;();
map.put(1, &quot;Java&quot;);
map.put(2, &quot;Python&quot;);
System.out.println(map.get(1)); // Java</code></pre>
<hr>
<h3 id="2-hashtable">2) Hashtable</h3>
<ul>
<li><code>HashMap</code>과 구조 동일.</li>
<li><strong>동기화된 메소드</strong> 제공 → 멀티스레드 환경에서 안전.</li>
</ul>
<pre><code class="language-java">Map&lt;String, String&gt; table = new Hashtable&lt;&gt;();
table.put(&quot;id&quot;, &quot;admin&quot;);
System.out.println(table.containsKey(&quot;id&quot;)); // true</code></pre>
<hr>
<h3 id="3-properties">3) Properties</h3>
<ul>
<li><code>Hashtable</code>의 하위 클래스.</li>
<li><strong>문자열(Key-Value) 쌍</strong>만 저장.</li>
<li>주로 <strong>설정 파일(.properties)</strong> 로 사용됨.</li>
</ul>
<pre><code class="language-java">Properties props = new Properties();
props.setProperty(&quot;username&quot;, &quot;admin&quot;);
props.setProperty(&quot;password&quot;, &quot;1234&quot;);

System.out.println(props.getProperty(&quot;username&quot;)); // admin</code></pre>
<hr>
<h1 id="155-검색-기능을-강화시킨-컬렉션">15.5. 검색 기능을 강화시킨 컬렉션</h1>
<h3 id="1-treeset">1) TreeSet</h3>
<ul>
<li><strong>이진 트리 기반</strong>의 <code>Set</code> 컬렉션.</li>
<li>객체를 저장하면 <strong>자동 정렬</strong>된다. (기본은 오름차순)</li>
</ul>
<pre><code class="language-java">TreeSet&lt;Integer&gt; treeSet = new TreeSet&lt;&gt;();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);

System.out.println(treeSet); // [1, 2, 3]</code></pre>
<hr>
<h3 id="2-treemap">2) TreeMap</h3>
<ul>
<li><strong>이진 트리 기반의 Map 컬렉션</strong>.</li>
<li><code>TreeSet</code>과 달리 <strong>키-값(Entry)</strong> 를 저장.</li>
<li>저장 시 <strong>키를 기준으로 자동 정렬</strong>됨.</li>
</ul>
<pre><code class="language-java">TreeMap&lt;Integer, String&gt; treeMap = new TreeMap&lt;&gt;();
treeMap.put(2, &quot;B&quot;);
treeMap.put(1, &quot;A&quot;);
treeMap.put(3, &quot;C&quot;);

System.out.println(treeMap); // {1=A, 2=B, 3=C}</code></pre>
<hr>
<h3 id="3-comparable--comparator">3) Comparable &amp; Comparator</h3>
<ul>
<li><code>TreeSet</code>과 <code>TreeMap</code>에 저장되는 객체는 자동 정렬되며,
정렬 기준은 <strong>객체가 <code>Comparable</code> 인터페이스를 구현</strong>해야 한다.</li>
<li>정렬 기준을 외부에서 부여하려면 <strong><code>Comparator</code> 구현 객체</strong>를 제공한다.</li>
</ul>
<h4 id="1-comparable">(1) Comparable</h4>
<ul>
<li><p>객체 자체에 **비교 기준(자연 순서)**을 구현.</p>
</li>
<li><p><code>int compareTo(T o)</code></p>
<ul>
<li>같으면 <code>0</code></li>
<li>작으면 음수</li>
<li>크면 양수</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class Person implements Comparable&lt;Person&gt; {
    int age;
    public Person(int age) { this.age = age; }

    @Override
    public int compareTo(Person o) {
        return Integer.compare(this.age, o.age);
    }
}</code></pre>
<hr>
<h4 id="2-comparator">(2) Comparator</h4>
<ul>
<li><p><code>Comparable</code>을 구현하지 않은 객체라도,
<strong>외부 비교자(Comparator)</strong> 를 제공하면 정렬 가능.</p>
</li>
<li><p><code>int compare(T o1, T o2)</code></p>
<ul>
<li>같으면 <code>0</code></li>
<li>앞에 오게 하려면 음수</li>
<li>뒤에 오게 하려면 양수</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class Fruit {
    int price;
    public Fruit(int price) { this.price = price; }
}

public class FruitComparator implements Comparator&lt;Fruit&gt; {
    @Override
    public int compare(Fruit o1, Fruit o2) {
        return Integer.compare(o1.price, o2.price);
    }
}

// 사용 예시
TreeSet&lt;Fruit&gt; set = new TreeSet&lt;&gt;(new FruitComparator());
set.add(new Fruit(3000));
set.add(new Fruit(1000));
set.add(new Fruit(2000));</code></pre>
<hr>
<h1 id="156-lifo-fifo-컬렉션">15.6. LIFO, FIFO 컬렉션</h1>
<p>자바는 <strong>스택(LIFO)</strong> 과 <strong>큐(FIFO)</strong> 자료구조를 제공한다.</p>
<hr>
<h3 id="1-stack">1) Stack</h3>
<ul>
<li><code>Stack</code> 클래스는 <strong>LIFO(후입선출)</strong> 자료구조.</li>
</ul>
<pre><code class="language-java">Stack&lt;String&gt; stack = new Stack&lt;&gt;();
stack.push(&quot;A&quot;);
stack.push(&quot;B&quot;);
System.out.println(stack.pop());  // B
System.out.println(stack.peek()); // A</code></pre>
<p><strong>주요 메소드</strong></p>
<ul>
<li><code>E push(E item)</code> : 객체 저장</li>
<li><code>E pop()</code> : 맨 위 객체 꺼내기</li>
<li><code>E peek()</code> : 맨 위 객체 확인 (삭제 안 함)</li>
<li><code>boolean isEmpty()</code> : 비었는지 여부</li>
<li><code>void clear()</code> : 전체 삭제</li>
</ul>
<hr>
<h3 id="2-queue">2) Queue</h3>
<ul>
<li><code>Queue</code> 인터페이스는 <strong>FIFO(선입선출)</strong> 구조.</li>
<li>주로 <code>LinkedList</code>가 구현체로 사용된다.</li>
</ul>
<pre><code class="language-java">Queue&lt;String&gt; queue = new LinkedList&lt;&gt;();
queue.offer(&quot;A&quot;);
queue.offer(&quot;B&quot;);
System.out.println(queue.poll()); // A
System.out.println(queue.poll()); // B</code></pre>
<p><strong>주요 메소드</strong></p>
<ul>
<li><code>boolean offer(E e)</code> : 객체 삽입</li>
<li><code>E poll()</code> : 객체 꺼내기</li>
</ul>
<hr>
<h1 id="157-동기화된-컬렉션">15.7. 동기화된 컬렉션</h1>
<ul>
<li>대부분의 컬렉션(<code>ArrayList</code>, <code>HashSet</code>, <code>HashMap</code>)은 <strong>동기화 미지원</strong> → 멀티스레드 환경에서 안전하지 않음.</li>
<li><code>Vector</code>, <code>Hashtable</code>은 <strong>동기화 지원</strong>.</li>
<li>필요 시 <code>Collections.synchronizedXXX()</code> 메소드로 동기화된 컬렉션을 생성 가능.</li>
</ul>
<pre><code class="language-java">// 동기화된 List
List&lt;Integer&gt; syncList = Collections.synchronizedList(new ArrayList&lt;&gt;());

// 동기화된 Set
Set&lt;String&gt; syncSet = Collections.synchronizedSet(new HashSet&lt;&gt;());

// 동기화된 Map
Map&lt;String, String&gt; syncMap = Collections.synchronizedMap(new HashMap&lt;&gt;());</code></pre>
<hr>
<h1 id="158-수정할-수-없는-컬렉션">15.8. 수정할 수 없는 컬렉션</h1>
<ul>
<li><strong>불변(immutable) 컬렉션</strong> → 요소 추가/삭제 불가.</li>
<li>데이터 변경이 필요 없는 경우 안전하게 사용 가능.</li>
</ul>
<hr>
<h3 id="1-of-메소드">1) <code>of()</code> 메소드</h3>
<pre><code class="language-java">List&lt;String&gt; list = List.of(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;);
Set&lt;Integer&gt; set = Set.of(1, 2, 3);
Map&lt;Integer, String&gt; map = Map.of(1, &quot;One&quot;, 2, &quot;Two&quot;);</code></pre>
<hr>
<h3 id="2-copyof-메소드">2) <code>copyOf()</code> 메소드</h3>
<ul>
<li>기존 컬렉션을 복사하여 불변 컬렉션 생성.</li>
</ul>
<pre><code class="language-java">List&lt;String&gt; origin = new ArrayList&lt;&gt;();
origin.add(&quot;X&quot;);
List&lt;String&gt; immutableList = List.copyOf(origin);</code></pre>
<hr>
<h3 id="3-배열로-생성">3) 배열로 생성</h3>
<pre><code class="language-java">String[] arr = {&quot;A&quot;, &quot;B&quot;, &quot;C&quot;};
List&lt;String&gt; immutableList = Arrays.asList(arr);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 14. 멀티스레드]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-14.-%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-14.-%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%93%9C</guid>
            <pubDate>Sat, 13 Sep 2025 13:53:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/f882d942-4e9e-445f-ad8f-3c7a69e01365/image.png" alt=""></p>
<h1 id="chapter-14-멀티스레드">Chapter 14. 멀티스레드</h1>
<h2 id="141-멀티-스레드-개념">14.1 멀티 스레드 개념</h2>
<ul>
<li><strong>프로세스(Process)</strong>: 운영체제가 실행 중인 프로그램.</li>
<li><strong>멀티태스킹(Multi-Tasking)</strong>: 두 개 이상의 작업을 동시에 처리하는 것. (반드시 멀티 프로세스일 필요는 없음)</li>
<li><strong>스레드(Thread)</strong>: 코드 실행의 최소 단위(흐름).</li>
</ul>
<h3 id="멀티-프로세스-vs-멀티-스레드">멀티 프로세스 vs 멀티 스레드</h3>
<ul>
<li><strong>멀티 프로세스</strong>: 독립적 실행. 하나가 오류 나도 다른 프로세스에 영향 없음.</li>
<li><strong>멀티 스레드</strong>: 프로세스 내부에서 동작. 하나의 스레드 예외 발생 시 전체 프로세스 종료 위험.</li>
<li><strong>활용 예시</strong>: 서버에서 다수의 클라이언트 요청 처리.</li>
</ul>
<hr>
<h2 id="142-메인-스레드">14.2 메인 스레드</h2>
<ul>
<li>모든 자바 프로그램은 <strong>메인 스레드</strong>(main 메소드)에서 시작.</li>
<li><code>main()</code> 코드 실행 후 <code>return</code>을 만나면 종료.</li>
</ul>
<h3 id="싱글-스레드-vs-멀티-스레드">싱글 스레드 vs 멀티 스레드</h3>
<ul>
<li><strong>싱글 스레드</strong>: 메인 스레드 종료 = 프로세스 종료.</li>
<li><strong>멀티 스레드</strong>: 메인 스레드가 종료돼도 다른 스레드가 실행 중이면 프로세스는 종료되지 않음.</li>
</ul>
<pre><code class="language-java">public class MainThreadExample {
    public static void main(String[] args) {
        Thread worker = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5; i++) {
                System.out.println(&quot;작업 스레드 실행 중: &quot; + i);
                try { Thread.sleep(500); } catch (InterruptedException e) {}
            }
        });
        worker.start();
        System.out.println(&quot;메인 스레드 종료&quot;); // 메인 스레드가 끝나도 worker가 남아있음
    }
}</code></pre>
<hr>
<h2 id="143-작업-스레드-생성과-실행">14.3 작업 스레드 생성과 실행</h2>
<ul>
<li><p>멀티스레드로 동작하는 프로그램을 개발하려면, 먼저 몇 개의 작업을 병렬로 실행할지 결정하고 작업별로 스레드를 생성해야 한다.</p>
</li>
<li><p>자바 프로그램은 항상 메인 스레드가 존재하므로, 메인 작업 이외에 실행할 추가 작업의 수만큼 스레드를 생성하면 된다.</p>
</li>
<li><p>자바에서는 작업 스레드도 객체로 관리되므로, 스레드를 정의하려면 Runnable을 구현하거나 Thread를 상속하는 등의 클래스(혹은 익명 구현)가 필요하다.</p>
<h3 id="1-thread--runnable-사용">1. <code>Thread</code> + <code>Runnable</code> 사용</h3>
</li>
</ul>
<pre><code class="language-java">class Task implements Runnable {
    @Override
    public void run() {
        System.out.println(&quot;작업 스레드 실행!&quot;);
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        Runnable task = new Task();
        Thread thread = new Thread(task);
        thread.start(); // run() 직접 호출이 아니라 start()로 실행
    }
}</code></pre>
<p><strong>익명 클래스</strong> 활용:</p>
<pre><code class="language-java">Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(&quot;익명 Runnable 실행!&quot;);
    }
});
thread.start();</code></pre>
<p><strong>람다식</strong> 활용 (자주 사용됨):</p>
<pre><code class="language-java">Thread thread = new Thread(() -&gt; {
    System.out.println(&quot;람다식으로 실행!&quot;);
});
thread.start();</code></pre>
<hr>
<h3 id="2-thread-클래스-상속">2. <code>Thread</code> 클래스 상속</h3>
<pre><code class="language-java">class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(&quot;Thread 상속 실행!&quot;);
    }
}

public class ExtendThreadExample {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}</code></pre>
<hr>
<h2 id="144-스레드-이름">14.4 스레드 이름</h2>
<ul>
<li>기본 이름: <code>main</code>, <code>Thread-0</code>, <code>Thread-1</code> …</li>
<li>이름 변경: <code>setName(&quot;이름&quot;)</code></li>
<li>현재 실행 스레드 확인: <code>Thread.currentThread()</code></li>
</ul>
<pre><code class="language-java">public class ThreadNameExample {
    public static void main(String[] args) {
        Thread mainThread = Thread.currentThread();
        System.out.println(&quot;메인 스레드 이름: &quot; + mainThread.getName());

        Thread worker = new Thread(() -&gt; {
            System.out.println(&quot;작업 스레드 실행: &quot; +
                Thread.currentThread().getName());
        });
        worker.setName(&quot;Worker-1&quot;);
        worker.start();
    }
}</code></pre>
<hr>
<h2 id="145-스레드-상태">14.5 스레드 상태</h2>
<p>스레드의 주요 상태:</p>
<ul>
<li><strong>NEW</strong> → 객체 생성됨 (<code>new Thread()</code>)</li>
<li><strong>RUNNABLE(실행 대기)</strong> → <code>start()</code> 호출 시 진입</li>
<li><strong>RUNNING(실행)</strong> → CPU 점유 시</li>
<li><strong>WAITING/ TIMED_WAITING(일시 정지)</strong> → <code>sleep()</code>, <code>join()</code>, <code>wait()</code></li>
<li><strong>TERMINATED(종료)</strong> → run() 종료</li>
</ul>
<h3 id="상태-전환-메소드">상태 전환 메소드</h3>
<ul>
<li><p><strong>일시 정지</strong></p>
<ul>
<li><code>sleep(long millis)</code> → 지정 시간 동안 정지 후 자동 대기 상태</li>
<li><code>join()</code> → 특정 스레드 종료까지 대기</li>
<li><code>wait()</code> → 동기화 블록 내 정지</li>
</ul>
</li>
<li><p><strong>재실행</strong></p>
<ul>
<li><code>interrupt()</code> → InterruptedException 발생시켜 복귀</li>
<li><code>notify()</code>, <code>notifyAll()</code> → <code>wait()</code> 상태 해제</li>
</ul>
</li>
<li><p><strong>양보</strong></p>
<ul>
<li><code>yield()</code> → 실행 상태 → 실행 대기 상태 전환</li>
</ul>
</li>
</ul>
<hr>
<h3 id="예제-sleep--join">예제: <code>sleep()</code> &amp; <code>join()</code></h3>
<pre><code class="language-java">public class JoinExample {
    public static void main(String[] args) {
        Thread sumThread = new Thread(() -&gt; {
            int sum = 0;
            for (int i = 1; i &lt;= 5; i++) {
                sum += i;
                try { Thread.sleep(500); } catch (InterruptedException e) {}
            }
            System.out.println(&quot;합계: &quot; + sum);
        });

        sumThread.start();
        try {
            sumThread.join(); // sumThread가 끝날 때까지 main 대기
        } catch (InterruptedException e) {}

        System.out.println(&quot;메인 스레드 종료&quot;);
    }
}</code></pre>
<hr>
<h2 id="146-스레드-동기화-synchronized">14.6 스레드 동기화 (synchronized)</h2>
<ul>
<li>여러 스레드가 <strong>하나의 공유 객체</strong>를 동시에 접근하면 데이터 불일치 문제가 발생할 수 있음.</li>
<li>이를 방지하기 위해 한 스레드가 작업하는 동안 다른 스레드가 접근하지 못하도록 <strong>객체 잠금(lock)</strong> 필요.</li>
<li>자바는 <code>synchronized</code> 키워드로 <strong>동기화 메소드</strong> 또는 <strong>동기화 블록</strong>을 제공.</li>
</ul>
<h3 id="동기화-메소드">동기화 메소드</h3>
<pre><code class="language-java">public class SharedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class SyncMethodExample {
    public static void main(String[] args) throws InterruptedException {
        SharedCounter counter = new SharedCounter();

        Runnable task = () -&gt; {
            for (int i = 0; i &lt; 1000; i++) {
                counter.increment();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start(); t2.start();
        t1.join(); t2.join();

        System.out.println(&quot;최종 카운트: &quot; + counter.getCount()); // 항상 2000
    }
}</code></pre>
<h3 id="동기화-블록">동기화 블록</h3>
<pre><code class="language-java">public void add(int value) {
    // 여러 스레드가 접근 가능한 영역
    synchronized (this) {
        // 단 하나의 스레드만 실행 가능
        count += value;
    }
    // 다시 여러 스레드 접근 가능
}</code></pre>
<hr>
<h3 id="wait--notify-활용-교대-작업">wait() &amp; notify() 활용 (교대 작업)</h3>
<ul>
<li><strong><code>wait()</code></strong>: 현재 스레드를 일시 정지 상태로 보냄.</li>
<li><strong><code>notify()</code></strong>: 대기 중인 다른 스레드 하나를 깨움.</li>
<li>반드시 <strong>동기화 블록 내</strong>에서 사용해야 함.</li>
</ul>
<pre><code class="language-java">class WorkObject {
    public synchronized void methodA() {
        System.out.println(&quot;ThreadA 실행&quot;);
        notify();  // 상대 스레드 깨움
        try { wait(); } catch (InterruptedException e) {}
    }
    public synchronized void methodB() {
        System.out.println(&quot;ThreadB 실행&quot;);
        notify();
        try { wait(); } catch (InterruptedException e) {}
    }
}

public class WaitNotifyExample {
    public static void main(String[] args) {
        WorkObject work = new WorkObject();

        Thread threadA = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5; i++) work.methodA();
        });

        Thread threadB = new Thread(() -&gt; {
            for (int i = 0; i &lt; 5; i++) work.methodB();
        });

        threadA.start();
        threadB.start();
    }
}</code></pre>
<hr>
<h2 id="147-스레드-안전-종료">14.7 스레드 안전 종료</h2>
<p>스레드는 <code>run()</code> 종료 시 자동으로 끝남. 하지만 즉시 멈춰야 할 경우 안전 종료가 필요.
<code>stop()</code> 메소드는 위험하므로 사용하지 않음.</p>
<h3 id="방법-1-조건-변수-활용">방법 1: 조건 변수 활용</h3>
<pre><code class="language-java">public class SafeStopThread extends Thread {
    private volatile boolean stop = false;

    public void setStop(boolean stop) {
        this.stop = stop;
    }

    @Override
    public void run() {
        while (!stop) {
            System.out.println(&quot;스레드 실행 중...&quot;);
        }
        System.out.println(&quot;자원 정리 후 종료&quot;);
    }

    public static void main(String[] args) throws InterruptedException {
        SafeStopThread thread = new SafeStopThread();
        thread.start();
        Thread.sleep(1000);
        thread.setStop(true);
    }
}</code></pre>
<h3 id="방법-2-interrupt-활용">방법 2: interrupt() 활용</h3>
<pre><code class="language-java">public class InterruptExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -&gt; {
            try {
                while (true) {
                    System.out.println(&quot;작업 중...&quot;);
                    Thread.sleep(200); // 일시 정지 상태
                }
            } catch (InterruptedException e) {
                System.out.println(&quot;인터럽트 발생, 안전 종료&quot;);
            }
        });

        thread.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
        thread.interrupt(); // 강제 종료 유도
    }
}</code></pre>
<hr>
<h2 id="148-데몬-스레드">14.8 데몬 스레드</h2>
<ul>
<li>데몬 스레드는 주 스레드의 작업을 보조하는 역할을 수행하며, 주 스레드가 종료되면 데몬 스레드도 자동으로 종료된다.</li>
<li>스레드를 데몬으로 만들려면, 해당 스레드 객체에 setDaemon(true)를 호출하면 된다.</li>
<li>데몬 스레드는 모든 작업 스레드가 종료되면 강제로 종료되며, 주로 백그라운드 작업에 활용된다.</li>
<li>스레드를 생성한 후 데몬 스레드로 선언하여 실행하면 된다.</li>
</ul>
<pre><code class="language-java">public class DaemonExample {
    public static void main(String[] args) {
        Thread autoSave = new Thread(() -&gt; {
            while (true) {
                System.out.println(&quot;자동 저장 실행...&quot;);
                try { Thread.sleep(500); } catch (InterruptedException e) {}
            }
        });
        autoSave.setDaemon(true); // 데몬 스레드 지정
        autoSave.start();

        try { Thread.sleep(2000); } catch (InterruptedException e) {}
        System.out.println(&quot;메인 종료 → 데몬 스레드도 종료됨&quot;);
    }
}</code></pre>
<hr>
<h2 id="149-스레드-풀-thread-pool">14.9 스레드 풀 (Thread Pool)</h2>
<ul>
<li>병렬 작업으로 인해 스레드가 과도하게 생성되는 것을 방지하기 위해 스레드 풀을 사용하는 것이 좋다.  </li>
<li>스레드 풀은 작업 처리를 위해 제한된 개수의 스레드를 유지하고, 작업 큐에 들어오는 작업을 스레드가 하나씩 처리하는 방식으로 운영된다.  </li>
<li>작업 처리가 끝난 스레드는 다시 큐에서 새로운 작업을 가져와 처리한다.  </li>
</ul>
<h3 id="1-스레드-풀-생성">1. 스레드 풀 생성</h3>
<ul>
<li>자바는 <code>java.util.concurrent</code> 패키지에서 <code>ExecutorService</code> 인터페이스와 <code>Executors</code> 클래스를 제공하여 스레드 풀을 쉽게 생성할 수 있다.  </li>
<li><code>Executors</code>의 정적 메소드를 사용하면 간단히 <code>ExecutorService</code> 구현 객체를 만들 수 있다.  </li>
</ul>
<h4 id="newcachedthreadpool">newCachedThreadPool()</h4>
<ul>
<li>초기 스레드 수와 코어 수는 0이며, 작업 개수가 많아지면 필요한 만큼 새 스레드를 생성하여 처리한다.  </li>
<li>60초 동안 작업이 없으면 해당 스레드를 풀에서 제거한다.  </li>
</ul>
<h4 id="newfixedthreadpooln">newFixedThreadPool(n)</h4>
<ul>
<li>초기 스레드 수는 0이며, 최대 n개의 스레드를 생성하여 작업을 처리한다.  </li>
<li>생성된 스레드는 제거되지 않고 계속 유지된다.  </li>
</ul>
<h3 id="2-스레드-풀-종료">2. 스레드 풀 종료</h3>
<ul>
<li>스레드 풀의 스레드는 기본적으로 데몬 스레드가 아니므로, main 스레드가 종료되어도 작업이 남아있으면 계속 실행 상태로 남는다.  </li>
<li>따라서 스레드 풀을 종료하려면 <code>ExecutorService</code>의 다음 메소드 중 하나를 호출해야 한다.  </li>
</ul>
<ol>
<li><code>shutdown()</code> : 현재 처리 중인 작업과 큐에 대기 중인 모든 작업을 마친 후 스레드 풀 종료  </li>
<li><code>shutdownNow()</code> : 현재 실행 중인 작업을 인터럽트하여 강제로 종료하고, 큐에 남아 있는 미처리 작업 목록을 반환  </li>
</ol>
<ul>
<li>남아 있는 작업을 마무리한 후 종료하려면 <code>shutdown()</code>을, 강제 종료할 경우에는 <code>shutdownNow()</code>를 호출하면 된다.  </li>
</ul>
<h3 id="3-작업-생성과-처리-요청">3. 작업 생성과 처리 요청</h3>
<ul>
<li>하나의 작업은 <code>Runnable</code> 또는 <code>Callable</code> 구현 객체로 표현할 수 있다.  </li>
<li><code>Runnable</code>은 작업 완료 후 반환값이 없으며, <code>Callable</code>은 작업 완료 후 결과 값을 반환한다.  </li>
<li><code>Callable</code>의 반환 타입은 <code>Callable&lt;T&gt;</code>에서 지정한 T 타입과 동일해야 한다.  </li>
<li>작업 처리 요청이란, <code>ExecutorService</code>의 작업 큐에 <code>Runnable</code> 또는 <code>Callable</code> 객체를 넣는 행위를 의미한다.</li>
</ul>
<h3 id="스레드-풀-예제">스레드 풀 예제</h3>
<pre><code class="language-java">import java.util.concurrent.*;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // Runnable 작업 제출
        executor.execute(() -&gt; {
            System.out.println(&quot;Runnable 작업 실행: &quot; + Thread.currentThread().getName());
        });

        // Callable 작업 제출
        Future&lt;Integer&gt; future = executor.submit(() -&gt; {
            int sum = 0;
            for (int i = 1; i &lt;= 5; i++) sum += i;
            return sum;
        });

        try {
            System.out.println(&quot;Callable 결과: &quot; + future.get());
        } catch (Exception e) { e.printStackTrace(); }

        executor.shutdown(); // 남은 작업 끝내고 종료
    }
}</code></pre>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 13. 제네릭]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-13.-%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-13.-%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Fri, 12 Sep 2025 13:16:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/637946ce-fcbc-4e77-bdd5-fc2726ffb2df/image.png" alt=""></p>
<h1 id="chapter-13-제네릭-generics">Chapter 13. 제네릭 (Generics)</h1>
<h2 id="131-제네릭-기본-개념">13.1. 제네릭 기본 개념</h2>
<ul>
<li><p><strong>제네릭(Generic)</strong>: 아직 결정되지 않은 타입을 파라미터로 처리하고, <strong>실제 사용할 때 구체적인 타입으로 대체</strong>하는 기능.</p>
</li>
<li><p>타입 파라미터 기호: <code>&lt;T&gt;</code></p>
<ul>
<li>T는 <strong>타입 변수</strong>(Type Parameter) → 객체 생성 시점에 실제 타입으로 치환됨.</li>
</ul>
</li>
<li><p>타입 파라미터는 <strong>클래스/인터페이스만 가능</strong> (기본 타입 불가).</p>
<ul>
<li>기본 타입을 사용하려면 <strong>래퍼 클래스(Integer, Double …)</strong> 를 활용해야 함.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">// 제네릭 클래스 정의
public class Box&lt;T&gt; {
    public T content;
}

// 사용 예제
Box&lt;String&gt; strBox = new Box&lt;&gt;();
strBox.content = &quot;Hello Generics&quot;;

Box&lt;Integer&gt; intBox = new Box&lt;&gt;();
intBox.content = 100;</code></pre>
<hr>
<h2 id="132-제네릭-타입">13.2. 제네릭 타입</h2>
<ul>
<li><p><strong>제네릭 타입(Generic Type)</strong>: 결정되지 않은 타입을 파라미터로 가지는 클래스/인터페이스.</p>
</li>
<li><p>문법:</p>
<pre><code class="language-java">class 클래스명&lt;T, U, ...&gt; { ... }
interface 인터페이스명&lt;T, U, ...&gt; { ... }</code></pre>
</li>
<li><p>타입을 지정하지 않으면 <strong>기본적으로 Object로 취급</strong>됨.</p>
</li>
</ul>
<pre><code class="language-java">// 제네릭 클래스
public class Pair&lt;K, V&gt; {
    private K key;
    private V value;

    public Pair(K key, V value){
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

// 사용
Pair&lt;String, Integer&gt; student = new Pair&lt;&gt;(&quot;Alice&quot;, 90);
System.out.println(student.getKey() + &quot; : &quot; + student.getValue());</code></pre>
<hr>
<h2 id="133-제네릭-메소드">13.3. 제네릭 메소드</h2>
<ul>
<li><strong>제네릭 메소드(Generic Method)</strong>: 메소드 자체가 타입 파라미터를 가짐.</li>
<li>선언부에 <code>&lt;T&gt;</code> 와 같은 타입 매개변수 명시.</li>
</ul>
<pre><code class="language-java">// 제네릭 메소드
public class Util {
    public static &lt;T&gt; Box&lt;T&gt; boxing(T t) {
        Box&lt;T&gt; box = new Box&lt;&gt;();
        box.content = t;
        return box;
    }
}

// 사용
Box&lt;String&gt; strBox = Util.boxing(&quot;Hello&quot;);
Box&lt;Integer&gt; intBox = Util.boxing(123);</code></pre>
<hr>
<h2 id="134-제한된-타입-파라미터-bounded-type-parameter">13.4. 제한된 타입 파라미터 (Bounded Type Parameter)</h2>
<ul>
<li>특정 타입 <strong>또는 그 하위 타입만</strong> 대체 가능하도록 제한.</li>
<li>문법: <code>&lt;T extends 상위타입&gt;</code></li>
</ul>
<pre><code class="language-java">public class CompareUtil {
    public static &lt;T extends Number&gt; boolean compare(T t1, T t2) {
        return t1.doubleValue() == t2.doubleValue();
    }
}

// 사용
System.out.println(CompareUtil.compare(10, 10));       // true
System.out.println(CompareUtil.compare(3.14, 3.14));   // true
// System.out.println(CompareUtil.compare(&quot;a&quot;, &quot;b&quot;));  // 오류 (Number 타입 아님)</code></pre>
<hr>
<h2 id="135-와일드카드-타입-파라미터">13.5. 와일드카드 타입 파라미터</h2>
<ul>
<li><p><code>?</code> (와일드카드): 범위 내 모든 타입 가능.</p>
</li>
<li><p>용도: <strong>제네릭 타입을 매개값이나 리턴 타입</strong>으로 사용할 때 유연성 확보.</p>
</li>
<li><p>세 가지 방식:</p>
<ol>
<li><code>&lt;?&gt;</code> : 모든 타입 허용</li>
<li><code>&lt;? extends 상위타입&gt;</code> : 상위 타입 및 하위 클래스 허용</li>
<li><code>&lt;? super 하위타입&gt;</code> : 하위 타입 및 상위 클래스 허용</li>
</ol>
</li>
</ul>
<pre><code class="language-java">public static void printList(List&lt;?&gt; list) {
    for(Object obj : list) {
        System.out.println(obj);
    }
}

List&lt;String&gt; strList = Arrays.asList(&quot;A&quot;, &quot;B&quot;, &quot;C&quot;);
List&lt;Integer&gt; intList = Arrays.asList(1, 2, 3);

printList(strList); // 가능
printList(intList); // 가능</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] Chapter 12. java.base 모듈]]></title>
            <link>https://velog.io/@cheers_to_every/Java-Chapter-12.-java.base-%EB%AA%A8%EB%93%88</link>
            <guid>https://velog.io/@cheers_to_every/Java-Chapter-12.-java.base-%EB%AA%A8%EB%93%88</guid>
            <pubDate>Thu, 11 Sep 2025 13:35:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/cheers_to_every/post/50d6a258-0bca-49cb-b552-2f362a26cbd6/image.png" alt=""></p>
<h1 id="chapter-12-javabase-모듈">Chapter 12. java.base 모듈</h1>
<h2 id="121-api-도큐먼트">12.1. API 도큐먼트</h2>
<ul>
<li><p><strong>API 도큐먼트</strong>: 자바 라이브러리를 쉽게 찾고 사용하는 방법 제공</p>
</li>
<li><p>주요 구성</p>
<ol>
<li><p>클래스 선언부 확인</p>
</li>
<li><p>멤버 보기 (SUMMARY)</p>
<ul>
<li>NESTED: 중첩 클래스/인터페이스</li>
<li>FIELD: 필드 목록</li>
<li>CONSTR: 생성자 목록</li>
<li>METHOD: 메소드 목록</li>
</ul>
</li>
<li><p>메소드 필터</p>
<ul>
<li>All Methods, Static Methods, Instance Methods, Concrete Methods, Deprecated Methods</li>
</ul>
</li>
</ol>
</li>
</ul>
<hr>
<h2 id="122-javabase-모듈">12.2. java.base 모듈</h2>
<ul>
<li><p>모든 모듈이 의존하는 기본 모듈 (<code>requires</code> 없이 사용 가능)</p>
</li>
<li><p>주요 패키지</p>
<ul>
<li><code>java.lang</code> : 기본 클래스 (System, String, Integer …)</li>
<li><code>java.util</code> : 자료구조, 유틸 (Scanner, Collections …)</li>
<li><code>java.text</code> : 날짜/숫자 포맷</li>
<li><code>java.time</code> : 날짜/시간 처리</li>
<li><code>java.io</code> : 입출력 스트림</li>
<li><code>java.net</code> : 네트워크 통신</li>
<li><code>java.nio</code> : 버퍼 및 NIO 입출력</li>
</ul>
</li>
</ul>
<hr>
<h2 id="123-object-클래스">12.3. Object 클래스</h2>
<ul>
<li><p>모든 클래스의 최상위 부모</p>
</li>
<li><p>주요 메소드</p>
<ul>
<li><code>equals(Object obj)</code> : 객체 동등 비교</li>
<li><code>hashCode()</code> : 객체 해시코드 반환</li>
<li><code>toString()</code> : 객체 문자열 정보</li>
</ul>
</li>
</ul>
<pre><code class="language-java">class Student {
    int no;
    String name;

    // equals 재정의
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Student s) {
            return no == s.no &amp;&amp; name.equals(s.name);
        }
        return false;
    }

    // hashCode 재정의
    @Override
    public int hashCode() {
        return no + name.hashCode();
    }

    // toString 재정의
    @Override
    public String toString() {
        return &quot;학생번호: &quot; + no + &quot;, 이름: &quot; + name;
    }
}</code></pre>
<ul>
<li><strong>레코드 (Java 14~)</strong></li>
</ul>
<pre><code class="language-java">public record Person(String name, int age) {}</code></pre>
<p>자동으로 <code>private final 필드</code>, 생성자, <code>getter</code>, <code>equals</code>, <code>hashCode</code>, <code>toString</code> 생성</p>
<ul>
<li><strong>Lombok 예시</strong></li>
</ul>
<pre><code class="language-java">import lombok.Data;

@Data
public class User {
    private String id;
    private String name;
}</code></pre>
<hr>
<h2 id="124-system-클래스">12.4. System 클래스</h2>
<ul>
<li><p>운영체제 일부 기능 접근</p>
</li>
<li><p>주요 필드: <code>System.out</code>, <code>System.err</code>, <code>System.in</code></p>
</li>
<li><p>주요 메소드:</p>
<ul>
<li><code>exit(int status)</code> : 프로세스 종료 (0=정상)</li>
<li><code>currentTimeMillis()</code>, <code>nanoTime()</code> : 시간 측정</li>
<li><code>getProperty()</code>, <code>getenv()</code> : 시스템 속성/환경 변수 조회</li>
</ul>
</li>
</ul>
<pre><code class="language-java">System.out.println(System.currentTimeMillis());
System.out.println(System.nanoTime());

System.out.println(System.getProperty(&quot;os.name&quot;));
System.out.println(System.getenv(&quot;PATH&quot;));</code></pre>
<ul>
<li>키보드 입력</li>
</ul>
<pre><code class="language-java">int key = System.in.read();
System.out.println(&quot;입력된 키 코드: &quot; + key);</code></pre>
<hr>
<h2 id="125-문자열-클래스">12.5. 문자열 클래스</h2>
<h3 id="1-string">1. String</h3>
<pre><code class="language-java">byte[] arr = &quot;안녕&quot;.getBytes(&quot;UTF-8&quot;);
String str = new String(arr, &quot;UTF-8&quot;);</code></pre>
<h3 id="2-stringbuilder-문자열-수정에-유리">2. StringBuilder (문자열 수정에 유리)</h3>
<pre><code class="language-java">StringBuilder sb = new StringBuilder(&quot;Hello&quot;);
sb.append(&quot; World&quot;);
sb.insert(5, &quot; Java&quot;);
System.out.println(sb.toString());</code></pre>
<h3 id="3-stringtokenizer-구분자-기반-분리">3. StringTokenizer (구분자 기반 분리)</h3>
<pre><code class="language-java">String text = &quot;apple,banana,grape&quot;;
StringTokenizer st = new StringTokenizer(text, &quot;,&quot;);
while (st.hasMoreTokens()) {
    System.out.println(st.nextToken());
}</code></pre>
<hr>
<h2 id="126-포장-클래스">12.6. 포장 클래스</h2>
<ul>
<li>기본 타입을 객체로 감싸는 클래스 (Integer, Double 등)</li>
<li><strong>박싱 / 언박싱</strong></li>
</ul>
<pre><code class="language-java">Integer obj = 100;   // 박싱
int value = obj;     // 언박싱</code></pre>
<ul>
<li><strong>문자열 → 기본 타입 변환</strong></li>
</ul>
<pre><code class="language-java">int num = Integer.parseInt(&quot;123&quot;);
double d = Double.parseDouble(&quot;3.14&quot;);</code></pre>
<ul>
<li><strong>비교 주의</strong></li>
</ul>
<pre><code class="language-java">Integer a = new Integer(100);
Integer b = new Integer(100);

System.out.println(a == b);       // false (주소 비교)
System.out.println(a.equals(b));  // true  (값 비교)</code></pre>
<hr>
<h2 id="127-수학-클래스-math--random">12.7. 수학 클래스 (Math &amp; Random)</h2>
<ul>
<li><p><code>Math</code> 클래스의 메서드는 모두 <strong>정적(static)</strong> → 객체 생성 없이 바로 사용 가능.</p>
</li>
<li><p>주요 메서드:</p>
<ul>
<li><code>abs(x)</code>, <code>ceil(x)</code>, <code>floor(x)</code>, <code>max(a,b)</code>, <code>min(a,b)</code>, <code>random()</code>, <code>round(x)</code></li>
</ul>
</li>
</ul>
<pre><code class="language-java">System.out.println(Math.abs(-10));    // 10
System.out.println(Math.ceil(3.2));   // 4.0
System.out.println(Math.floor(3.7));  // 3.0
System.out.println(Math.max(5, 9));   // 9
System.out.println(Math.random());    // 0.0 &lt;= ~ &lt; 1.0</code></pre>
<h3 id="javautilrandom">java.util.Random</h3>
<ul>
<li><p>난수 생성을 위한 클래스</p>
</li>
<li><p>생성자</p>
<ul>
<li><code>new Random()</code> : 현재 시간 기반 seed</li>
<li><code>new Random(long seed)</code> : 지정된 seed</li>
</ul>
</li>
<li><p>주요 메서드</p>
<ul>
<li><code>nextBoolean()</code></li>
<li><code>nextDouble()</code></li>
<li><code>nextInt()</code></li>
<li><code>nextInt(int n)</code> : 0 ≤ 값 &lt; n</li>
</ul>
</li>
</ul>
<pre><code class="language-java">Random rand = new Random();
System.out.println(rand.nextBoolean()); // true/false
System.out.println(rand.nextInt(10));   // 0~9</code></pre>
<hr>
<h2 id="128-날짜와-시간-클래스">12.8. 날짜와 시간 클래스</h2>
<h3 id="date">Date</h3>
<pre><code class="language-java">Date now = new Date();
System.out.println(now.toString()); // 기본 형식 출력

SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy.MM.dd HH:mm:ss&quot;);
System.out.println(sdf.format(now)); // 원하는 형식 출력</code></pre>
<h3 id="calendar">Calendar</h3>
<pre><code class="language-java">Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR);
int month = now.get(Calendar.MONTH) + 1;
int day = now.get(Calendar.DAY_OF_MONTH);</code></pre>
<ul>
<li>다른 시간대(Calendar)</li>
</ul>
<pre><code class="language-java">TimeZone tz = TimeZone.getTimeZone(&quot;America/Los_Angeles&quot;);
Calendar laTime = Calendar.getInstance(tz);</code></pre>
<h3 id="localdatetime-javatime-패키지">LocalDateTime (java.time 패키지)</h3>
<ul>
<li>조작 : <code>plusXxx()</code>, <code>minusXxx()</code></li>
<li>비교 : <code>isAfter()</code>, <code>isBefore()</code>, <code>isEqual()</code></li>
<li>생성 : <code>now()</code>, <code>of(year, month, day, hour, min, sec)</code></li>
</ul>
<pre><code class="language-java">LocalDateTime now = LocalDateTime.now();
LocalDateTime future = now.plusDays(5);

System.out.println(now.isBefore(future)); // true</code></pre>
<hr>
<h2 id="129-형식-클래스">12.9. 형식 클래스</h2>
<ul>
<li><strong>숫자 형식화</strong></li>
</ul>
<pre><code class="language-java">DecimalFormat df = new DecimalFormat(&quot;#,###.0&quot;);
System.out.println(df.format(1234567.89)); // 1,234,567.9</code></pre>
<ul>
<li><strong>날짜 형식화</strong></li>
</ul>
<pre><code class="language-java">SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy년 MM월 dd일&quot;);
System.out.println(sdf.format(new Date()));</code></pre>
<hr>
<h2 id="1210-정규-표현식-클래스">12.10. 정규 표현식 클래스</h2>
<ul>
<li>문자열 검증에 사용</li>
</ul>
<pre><code class="language-java">boolean result = Pattern.matches(&quot;[0-9]{3}-[0-9]{4}&quot;, &quot;123-4567&quot;);
System.out.println(result); // true</code></pre>
<hr>
<h2 id="1211-리플렉션-reflection">12.11. 리플렉션 (Reflection)</h2>
<ul>
<li>클래스, 인터페이스의 <strong>메타 정보(패키지, 필드, 메서드 등)</strong> 에 접근하고 조작 가능.</li>
</ul>
<h3 id="class-객체-얻기">Class 객체 얻기</h3>
<pre><code class="language-java">Class clazz1 = Car.class;
Class clazz2 = Class.forName(&quot;com.example.Car&quot;);
Car car = new Car();
Class clazz3 = car.getClass();</code></pre>
<h3 id="메타-정보-확인">메타 정보 확인</h3>
<pre><code class="language-java">System.out.println(clazz1.getPackage().getName());   // 패키지명
System.out.println(clazz1.getSimpleName());          // 클래스명
System.out.println(clazz1.getName());                // 전체 경로</code></pre>
<h3 id="생성자메서드-정보">생성자/메서드 정보</h3>
<pre><code class="language-java">Method[] methods = clazz1.getDeclaredMethods();
for(Method method : methods){
    System.out.println(method.getName());
}</code></pre>
<h3 id="리소스-경로-얻기">리소스 경로 얻기</h3>
<pre><code class="language-java">String path = clazz1.getResource(&quot;config.xml&quot;).getPath();
InputStream is = clazz1.getResourceAsStream(&quot;config.xml&quot;);</code></pre>
<hr>
<h2 id="1212-어노테이션-annotation">12.12. 어노테이션 (Annotation)</h2>
<ul>
<li><strong>코드의 메타데이터</strong>를 제공 (<code>@</code> 기호 사용)</li>
</ul>
<h3 id="정의-및-용도">정의 및 용도</h3>
<ol>
<li>컴파일 정보 전달 (<code>@Override</code>)</li>
<li>빌드 시 코드 생성</li>
<li>실행 시 기능 처리</li>
</ol>
<h3 id="작성">작성</h3>
<pre><code class="language-java">public @interface MyAnnotation {
    String value();
    int number() default 1;
}</code></pre>
<h3 id="적용-대상-target">적용 대상 (@Target)</h3>
<pre><code class="language-java">@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {}</code></pre>
<h3 id="유지-정책-retention">유지 정책 (@Retention)</h3>
<ul>
<li>SOURCE / CLASS / RUNTIME</li>
</ul>
<pre><code class="language-java">@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {}</code></pre>
<h3 id="어노테이션-활용">어노테이션 활용</h3>
<pre><code class="language-java">Method[] methods = Service.class.getDeclaredMethods();
for(Method method : methods){
    if(method.isAnnotationPresent(PrintAnnotation.class)){
        PrintAnnotation pa = method.getAnnotation(PrintAnnotation.class);
        System.out.println(pa.value());
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>