<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>super-cola.log</title>
        <link>https://velog.io/</link>
        <description>공부하는거 정리</description>
        <lastBuildDate>Wed, 30 Jul 2025 00:40:01 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>super-cola.log</title>
            <url>https://images.velog.io/images/bell-ho/profile/91a1cd3e-daf6-42b4-a759-9e5a3899c182/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. super-cola.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/bell-ho" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Role 체크 어노테이션 만들기 2]]></title>
            <link>https://velog.io/@bell-ho/Role-%EC%B2%B4%ED%81%AC-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-2</link>
            <guid>https://velog.io/@bell-ho/Role-%EC%B2%B4%ED%81%AC-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-2</guid>
            <pubDate>Wed, 30 Jul 2025 00:40:01 GMT</pubDate>
            <description><![CDATA[<p>클래스에 @CheckRole을 한 경우 특정 메서드는 예외로 할 수 있게 무시 어노테이션을 따로 만들어줌</p>
<pre><code class="language-java">
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreRoleCheck {}
</code></pre>
<p>Aspect 만들기</p>
<pre><code class="language-java">
@Aspect
@Component
public class RoleCheckAspect {

    // 클래스와 메서드 단위로 @CheckRole이 붙어있으면 적용
    @Around(&quot;@within(exe.annotation.CheckRole) || @annotation(exe.annotation.CheckRole)&quot;)
    public Object checkRole(ProceedingJoinPoint joinPoint) throws Throwable {
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();

        // 무시 어노테이션이 있으면 넘어감
        if (method.isAnnotationPresent(IgnoreRoleCheck.class)) {
            return joinPoint.proceed();
        }

        // 해당 메서드 또는 클래스에 @CheckRole이 없다면, 권한 체크를 하지 않고 바로 메서드를 실행
        CheckRole checkRole = findCheckRoleAnnotation(method, joinPoint.getTarget().getClass());

        if (checkRole == null) {
            return joinPoint.proceed();
        }

        return validateRoleAndProceed(joinPoint, checkRole.value());
    }

    // 메서드 → 클래스 순서로 @CheckRole 어노테이션을 찾음
    private CheckRole findCheckRoleAnnotation(Method method, Class&lt;?&gt; targetClass) {
        CheckRole methodAnnotation = method.getAnnotation(CheckRole.class);
        if (methodAnnotation != null) {
            return methodAnnotation;
        }
        return targetClass.getAnnotation(CheckRole.class);
    }

    // 여기서 실제 사용자 인증 정보에서 권한이 있는지 검사
    private Object validateRoleAndProceed(ProceedingJoinPoint joinPoint, String requiredRole) throws Throwable {

        // 1) 인증 객체 가져오기 =&gt; 필터 과정에서 넣어준 권한을 가져옴
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null || !authentication.isAuthenticated()) {
            throw new Exception(&quot;오류&quot;);
        }

        Collection&lt;? extends GrantedAuthority&gt; authorities = authentication.getAuthorities();

        String normalizedRequiredRole = requiredRole.startsWith(&quot;ROLE_&quot;) ? requiredRole : &quot;ROLE_&quot; + requiredRole;

        boolean hasRole = authorities.stream()
                .anyMatch(auth -&gt; auth.getAuthority().equals(normalizedRequiredRole));

        if (!hasRole) {
            throw new Exception(&quot;오류&quot;);
        }

        return joinPoint.proceed();
    }
}

</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Role 체크 어노테이션 만들기 1]]></title>
            <link>https://velog.io/@bell-ho/Role-%EC%B2%B4%ED%81%AC-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-1</link>
            <guid>https://velog.io/@bell-ho/Role-%EC%B2%B4%ED%81%AC-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-%EB%A7%8C%EB%93%A4%EA%B8%B0-1</guid>
            <pubDate>Fri, 25 Jul 2025 08:20:19 GMT</pubDate>
            <description><![CDATA[<p>스프링에서 권한 체크를 좀 더 깔끔하게 하고 싶어서,
어노테이션으로 역할(Role)을 체크하는 방식으로 만들어봤다.</p>
<ol>
<li>어노테이션 정의하기
먼저 커스텀 어노테이션을 만든다. 클래스나 메서드에 붙일 수 있도록 @Target을 지정해주고, 런타임 시점에 리플렉션으로 읽을 수 있게 @Retention(RetentionPolicy.RUNTIME)도 넣어준다.</li>
</ol>
<pre><code class="language-java">@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckRole {
    String value();
}</code></pre>
<p>@Target → 클래스, 메서드 둘 다 타겟 가능</p>
<p>@Retention(RetentionPolicy.RUNTIME) → 어노테이션 정보를 런타임에도 유지
왜 필요하냐면, 나중에 AOP로 이 어노테이션을 읽어서 권한 체크를 할 건데
이게 런타임에 메모리에 있어야 리플렉션으로 읽을 수 있다. 만약 이걸 안 쓰면 컴파일 후 .class 파일엔 남아있지만, 실행 중엔 JVM이 이 정보를 못 읽는다.</p>
<ol start="2">
<li>사용 예시
이제 컨트롤러나 서비스 같은 데에 이렇게 붙여주면 된다</li>
</ol>
<pre><code class="language-java">
ex)

@CheckRole(&quot;ROLE_ADMIN&quot;)
@GetMapping(&quot;/admin&quot;)
public String adminOnlyApi() {
    return &quot;관리자 전용 API입니다.&quot;;
}

@CheckRole(&quot;ROLE_SUPER&quot;)
@RequestMapping(&quot;/api/admin&quot;)
@RestController
public class AdminController {
}</code></pre>
<p>다음 단계: RoleCheckAspect
이제 어노테이션은 준비됐고, 진짜 핵심인 AOP 구현만 남았다.
RoleCheckAspect를 만들어서, 메서드 실행 전에 권한 체크 로직을 태우면 된다.</p>
<p>이 부분은 다음 글에서...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 트랜잭션 전파 속성]]></title>
            <link>https://velog.io/@bell-ho/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-%EC%86%8D%EC%84%B1</link>
            <guid>https://velog.io/@bell-ho/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98-%EC%A0%84%ED%8C%8C-%EC%86%8D%EC%84%B1</guid>
            <pubDate>Fri, 18 Jul 2025 00:06:47 GMT</pubDate>
            <description><![CDATA[<p>스프링에서 트랜잭션 전파는 트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 있거나 없을 때 어떻게 동작할 것인가를 결정하는 기능</p>
<p>@Transactional 어노테이션이 존재하는 메서드 호출시 기존에 트랜잭션이 존재하면 재사용할지 예외를 던질지 등 행동을 결정 가능</p>
<p>트랜잭션 전파 속성에는 REQUIRED, NESTED, NEVER, REQUIRED_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, 가 있고, @Transactional 어노테이션의 propagation 속성에 값을 설정할 수 있음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수형 프로그래밍이란?]]></title>
            <link>https://velog.io/@bell-ho/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@bell-ho/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Wed, 18 Jun 2025 05:48:38 GMT</pubDate>
            <description><![CDATA[<p>핵심 개념 요약:
순수 함수(Pure Function)
→ 입력이 같으면 항상 결과가 같고, 외부 상태를 변경하지 않는 함수</p>
<p>불변성(Immutable Data)
→ 데이터를 수정하지 않고, 복사본을 만들어 변경</p>
<p>고차 함수(Higher-Order Function)
→ 함수를 인자로 받거나, 함수를 리턴하는 함수</p>
<p>선언형 프로그래밍
→ “무엇을 할 것인가”에 집중 (반대로 명령형은 “어떻게 할 것인가”에 집중)</p>
<pre><code class="language-javascript">/////////////// 함수형 ///////////////

const numbers = [1, 2, 3, 4, 5];

// map: 각 요소에 2배 하기
const doubled = numbers.map(n =&gt; n * 2); 
console.log(doubled); // [2, 4, 6, 8, 10]

// filter: 짝수만 남기기
const evens = numbers.filter(n =&gt; n % 2 === 0);
console.log(evens); // [2, 4]

// reduce: 합계 구하기
const sum = numbers.reduce((acc, n) =&gt; acc + n, 0);
console.log(sum); // 15

/////////////// 명령형 ///////////////

const numbers = [1, 2, 3, 4, 5];

// 1. map: 각 요소에 2배 하기
const doubled = [];
for (let i = 0; i &lt; numbers.length; i++) {
  doubled.push(numbers[i] * 2);
}
console.log(doubled); // [2, 4, 6, 8, 10]

// 2. filter: 짝수만 남기기
const evens = [];
for (let i = 0; i &lt; numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    evens.push(numbers[i]);
  }
}
console.log(evens); // [2, 4]

// 3. reduce: 합계 구하기
let sum = 0;
for (let i = 0; i &lt; numbers.length; i++) {
  sum += numbers[i];
}
console.log(sum); // 15</code></pre>
<p>명령형 프로그래밍: 레시피처럼 직접 물을 끓이고 → 면을 넣고 → 10분 삶고 를 하나하나 지시
함수형 프로그래밍: 라면을 만든다는 의미 중심으로 기술</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 버블링]]></title>
            <link>https://velog.io/@bell-ho/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EB%B8%94%EB%A7%81</link>
            <guid>https://velog.io/@bell-ho/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EB%B8%94%EB%A7%81</guid>
            <pubDate>Mon, 09 Jun 2025 06:14:51 GMT</pubDate>
            <description><![CDATA[<p><strong>이벤트 버블링(Event Bubbling)</strong>은
HTML 요소에서 이벤트가 발생했을 때, 그 이벤트가 해당 요소에서 시작하여, 부모 → 조상 요소까지 전파되는 현상입니다.</p>
<p>왜 이벤트 버블링이 발생하는가? (왜 생겨났는가?)</p>
<p>🔹 이유 1: 트리 구조인 DOM의 자연스러운 전파 모델
DOM은 트리 구조입니다. 이벤트가 루트 방향으로 올라가도록 설계하는 게 논리적입니다.</p>
<p>🔹 이유 2: 이벤트 위임(Event Delegation) 가능
자식 요소마다 리스너를 붙이는 대신, 부모에 하나만 붙이고 자식에서 일어난 이벤트를 감지할 수 있음.</p>
<p>특히 동적으로 생성되는 요소 처리에 매우 유리합니다.</p>
<p>🔹 이유 3: 유연한 이벤트 흐름 제어 가능
필요하면 stopPropagation()으로 중간에 끊을 수 있어, 선택적 제어 가능.</p>
<p>🔹 이유 4: 퍼포먼스와 관리 측면에서 유리
대규모 UI에서는 리스너 수를 줄이고 성능과 메모리를 절약할 수 있음.</p>
<table>
<thead>
<tr>
<th>단점</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>❗ 예기치 않은 이벤트 중복 처리</td>
<td>의도치 않게 상위 요소의 이벤트까지 실행될 수 있음</td>
</tr>
<tr>
<td>❗ 디버깅 어려움</td>
<td>어떤 요소가 이벤트를 수신했는지 파악이 어려울 수 있음</td>
</tr>
<tr>
<td>❗ 의도하지 않은 동작 유발</td>
<td>자식 이벤트 핸들러와 부모 핸들러가 충돌할 수 있음</td>
</tr>
<tr>
<td>❗ 보안 리스크</td>
<td>잘못된 위임 구조로 인해 외부 입력이 상위 로직에 영향을 줄 수 있음</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redis가 싱글 스레드로 동작하는 이유]]></title>
            <link>https://velog.io/@bell-ho/Redis%EA%B0%80-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EB%A1%9C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@bell-ho/Redis%EA%B0%80-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EB%A1%9C-%EB%8F%99%EC%9E%91%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 05 Jun 2025 00:09:08 GMT</pubDate>
            <description><![CDATA[<pre><code>싱글 스레드(Single Thread)란?
싱글 스레드는 하나의 실행 흐름(Thread)만을 사용하는 프로그래밍 또는 시스템 구조를 의미
이 구조에서는 한 번에 하나의 작업만 처리할 수 있으며 모든 명령은 순차적으로 실행</code></pre><p>동시성 문제를 피하기 위해
멀티스레드에서는 레이스 컨디션이나 데드락 같은 문제가 생기기 쉬운데 싱글 스레드에선 이런 걱정이 없음 모든 명령을 순차적으로 처리해서 데이터 일관성을 쉽게 유지할 수 있음</p>
<p>빠른 성능을 위해
Redis는 메모리 기반이라 I/O가 빠름 싱글 스레드 구조 덕분에 컨텍스트 스위칭 같은 오버헤드 없이 빠르게 처리 가능함</p>
<p>설계가 단순해서 유지보수가 쉬움
복잡한 락이나 동기화 로직이 필요 없어서 구조가 깔끔하고 유지보수도 쉬움</p>
<p>이벤트 기반으로 높은 동시성 처리 가능
싱글 스레드라도 이벤트 루프를 이용해 비동기로 여러 클라이언트 요청을 효율적으로 처리할 수 있음</p>
<h3 id="redis-싱글-스레드-구조의-단점">Redis 싱글 스레드 구조의 단점</h3>
<p>CPU 코어를 1개밖에 활용하지 못함
Redis는 기본적으로 하나의 스레드만 사용하기 때문에 멀티 코어 CPU 환경에서도 한 코어만 사용</p>
<p>블로킹 작업이 전체 처리 흐름을 막을 수 있음
하나의 명령이 시간이 오래 걸리면 그 명령이 끝날 때까지 다른 요청들도 대기해야 함</p>
<p>복잡한 연산에는 비효율적
Redis는 빠른 읽기/쓰기 작업에 최적화되어 있지만 복잡한 계산이나 대용량 데이터 처리에는 성능이 급격히 떨어질 수 있음
멀티 스레드 기반 시스템에 비해 병렬 연산이나 CPU 집약적인 작업 처리에 약함</p>
<p>Scale-up의 한계
성능을 더 높이기 위해 서버 사양을 늘리더라도 싱글 스레드 구조 때문에 일정 이상 성능 향상이 어려움</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로세스 컨텍스트 vs 스레드 컨텍스트]]></title>
            <link>https://velog.io/@bell-ho/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-vs-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@bell-ho/%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-vs-%EC%8A%A4%EB%A0%88%EB%93%9C-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 20 May 2025 07:00:43 GMT</pubDate>
            <description><![CDATA[<p>스레드 컨텍스트 스위칭이 프로세스보다 빠른 이유
프로세스 컨텍스트 스위칭의 경우 MMU 새로운 주소 체계 바로 보도록 수정하고, TLB 가상 메모리 실제 메모리 저장된 캐시를 비우는 등의 메모리 주소 관련 작업을 하는데 스레드 컨텍스트는 CPU의 상태 정보만 바꿔주면 되기 때문</p>
<p>비유: 직원 교체 vs 의자만 바꾸기
등장인물 비유
프로세스 = 한 명의 직원 (개인 책상, 컴퓨터, 책, 로그인 정보 전부 다 가짐)
스레드 = 같은 직원 그룹 안의 팀원 (책상은 같이 쓰고, 컴퓨터/자료도 공유함)</p>
<p>컨텍스트 스위칭 비유
프로세스 컨텍스트 스위칭 = 직원 전체 교체
기존 직원이 모든 자료 정리하고 로그아웃</p>
<p>새 직원이 자신의 계정으로 로그인하고, 책상 세팅을 다시 함</p>
<p>기존에 쓰던 책상 도구들(컴퓨터 설정, 파일 위치, 책 등)을 모두 갈아치움</p>
<p>시간이 오래 걸림</p>
<p>-&gt; 여기서 MMU 재설정, TLB 캐시 비우기 등 = 책상과 컴퓨터 완전 초기화 후 재설정
-&gt; CPU 입장에서 굉장히 번거롭고 느린 작업</p>
<p>스레드 컨텍스트 스위칭 = 같은 팀에서 의자만 바꾸기
같은 책상에서 팀원이 의자만 바꿔 앉음</p>
<p>컴퓨터 로그인도 되어 있고, 자료도 공유하고 있으므로 설정 변경 없음</p>
<p>그냥 자신의 이름만 말하고 일을 이어서 함</p>
<p>-&gt; 메모리는 공유되어 있어서, MMU/TLB 작업이 필요 없음
-&gt; CPU 입장에선 거의 <strong>레지스터 상태(잠깐 메모)</strong>만 바꾸고 바로 실행 가능</p>
<h3 id="그럼-프로세스를-쓸-이유가-있나-그냥-다-스레드로-하면-되는-거-아닌가"><strong>그럼 프로세스를 쓸 이유가 있나? 그냥 다 스레드로 하면 되는 거 아닌가?</strong></h3>
<p>단순 성능이 아니라, 안정성, 독립성, 보안까지 포함한 관점으로 접근해야 함</p>
<p><strong>스레드는 빠르지만, 프로세스는 안전함</strong></p>
<p>프로세스 컨텍스트를 쓰는 이유</p>
<ol>
<li><p>격리(Isolation)
프로세스는 독립된 주소 공간을 가짐
다른 프로세스의 메모리에 접근 불가 → 보안, 안정성 뛰어남
하나가 죽어도 다른 프로세스는 영향 없음
크롬 브라우저 탭 하나마다 프로세스가 따로 도는 이유 = 탭 하나 죽어도 나머지 영향 없게 하기 위함</p>
</li>
<li><p>스레드는 공유된 공간에서 돌아감
같은 주소 공간을 공유하기 때문에 하나의 스레드가 잘못된 동작을 하면, 전체 프로세스가 터질 수 있음
동기화 문제, 경쟁 조건 등으로 인해 디버깅과 관리가 어려움
예: 서버에서 한 스레드가 메모리 오염시키면 전체 애플리케이션이 죽을 수 있음</p>
</li>
<li><p>그래서 현실에서는 절충함
프로세스는 독립성과 안정성 확보용
스레드는 프로세스 내부에서 성능 향상용으로 사용</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA에서 ID 생성 전략]]></title>
            <link>https://velog.io/@bell-ho/JPA%EC%97%90%EC%84%9C-ID-%EC%83%9D%EC%84%B1-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@bell-ho/JPA%EC%97%90%EC%84%9C-ID-%EC%83%9D%EC%84%B1-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Mon, 19 May 2025 06:34:52 GMT</pubDate>
            <description><![CDATA[<p>JPA에서 엔티티의 기본 키(ID)는 두 가지 방식으로 생성할 수 있습니다.</p>
<p>✅ 직접 할당: @Id만 사용. 애플리케이션 코드에서 ID 값을 명시적으로 설정합니다.</p>
<p>✅ 자동 생성: @Id + @GeneratedValue를 함께 사용. JPA가 식별자 생성 전략에 따라 자동으로 ID를 할당합니다.</p>
<p>자동 생성의 경우, @GeneratedValue의 strategy 속성을 통해 사용할 수 있는 4가지 전략은 다음과 같습니다:</p>
<pre><code class="language-java">
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

public enum GenerationType {
    AUTO,
    IDENTITY,
    SEQUENCE,
    TABLE
}
</code></pre>
<p>1️⃣ IDENTITY 전략: DB에게 맡기는 전략
사용 DB: MySQL, PostgreSQL 등
특징: DB의 auto_increment 기능을 활용하여 식별자 생성
동작 방식:
persist() 호출 시 즉시 INSERT 실행
DB가 ID 생성 → JPA가 그 값을 받아서 영속성 컨텍스트에 저장</p>
<p>장점: 설정이 간편하고 DB가 ID를 관리하므로 별도 시퀀스 설정 불필요</p>
<p>단점:
<strong>쓰기 지연(persist 후 insert 지연)</strong>이 불가능</p>
<p>배치 INSERT 불가 (ID를 먼저 알아야 하므로)
테스트 시 DB 의존도 높음</p>
<p>2️⃣ SEQUENCE 전략: 시퀀스 객체를 활용한 유연한 전략
사용 DB: Oracle, PostgreSQL, H2 등 (시퀀스 지원 DB)</p>
<p>동작 방식:
persist() 시, 먼저 DB에서 시퀀스 값을 조회 (select nextval from sequence)
ID 값을 엔티티에 할당한 후 INSERT 수행
장점:
쓰기 지연 및 배치 INSERT 가능
시퀀스 값을 미리 가져오기 때문에 영속성 컨텍스트 내부에서 ID 사용 가능</p>
<p>단점:
시퀀스 이름 및 증가값 설정 필요
DB 시퀀스가 없는 환경에서는 사용 불가</p>
<p>3️⃣ TABLE 전략: 테이블로 시퀀스를 흉내내는 전략
사용 DB: 모든 DB에서 사용 가능
동작 방식:
별도의 키 생성 테이블을 만들고, SELECT 후 UPDATE로 ID 증가
장점: DB에 독립적 (모든 DB에서 작동)
단점:
성능 가장 느림 (SELECT + UPDATE로 2회 통신 필요)
동시성 처리 주의 (락이나 낙관적 제어 필요)</p>
<p>4️⃣ AUTO 전략: JPA가 알아서 결정
동작 방식: 사용하는 DB 방언(dialect)에 따라 적절한 전략을 자동 선택
예: MySQL → IDENTITY, Oracle → SEQUENCE
장점: 개발 초기에 DB를 바꿔도 코드 변경이 필요 없음
단점: DB마다 동작 방식이 달라짐 → 이식성과 예측 가능성 낮음
현업에서는 AUTO 전략은 학습이나 테스트용으로 사용하고, 운영 환경에서는 명시적으로 전략을 지정하는 것을 권장합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성(Concurrency)이란?]]></title>
            <link>https://velog.io/@bell-ho/%EB%8F%99%EC%8B%9C%EC%84%B1Concurrency%EC%9D%B4%EB%9E%80</link>
            <guid>https://velog.io/@bell-ho/%EB%8F%99%EC%8B%9C%EC%84%B1Concurrency%EC%9D%B4%EB%9E%80</guid>
            <pubDate>Tue, 13 May 2025 00:21:05 GMT</pubDate>
            <description><![CDATA[<p>비유: 웨이터가 있는 식당
식당에 웨이터 한 명(= CPU)이 있고, 여러 손님(= 작업, 스레드)이 있다고 가정해보겠습니다.</p>
<ol>
<li>단일 작업 처리 (동시성 X)
웨이터가 한 손님의 주문을 받고, 음식 나오기를 기다리고, 그 손님에게 음식 가져다주고, 계산까지 다 끝낸 후에야 다음 손님에게 갑니다.</li>
</ol>
<p>-&gt; 한 번에 한 사람만 처리 = 병목 발생</p>
<ol start="2">
<li>동시성 (Concurrency)
이번엔 웨이터가 좀 똑똑합니다.
손님 1의 주문을 받고
주방에 주문을 넣고 기다리는 동안
손님 2의 주문을 받고
그 사이에 주방에서 손님 1의 음식이 나왔으면 가져다주고
또 손님 3의 주문도 받고...
이렇게 계속 &quot;기다리는 시간&quot;을 낭비하지 않고 다른 손님의 일을 처리합니다.</li>
</ol>
<p>-&gt; 실제로 동시에 여러 손님을 처리하는 건 아니지만, 
짧은 시간에 여러 손님의 요청을 번갈아 처리하면서 마치 동시에 처리하는 것처럼 보입니다.</p>
<p>이게 바로 동시성입니다
단일 코어(=웨이터 1명)가 여러 작업(=손님)을 효율적으로 처리하려면 시간을 쪼개고 문맥을 바꾸며 번갈아 일하는 방식으로 작동합니다.</p>
<p>이런 방식이 동시성입니다. 실제로는 한 번에 한 작업만 하지만, 빠르게 전환하며 처리하니 동시에 돌아가는 것처럼 보이는 것이죠.</p>
<p>주의할 점
손님 1과 손님 2가 동시에 계산하려고 하면 문제가 생길 수 있습니다 (= Race Condition)</p>
<p>웨이터가 어떤 손님의 요청만 계속 처리하고 나머지는 무시하면 문제가 됩니다 (= Starvation)</p>
<p>두 손님이 서로 음식을 주고받으려다 멈추는 경우도 있습니다 (= Deadlock)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동시성 제어를 위한 JPA Lock]]></title>
            <link>https://velog.io/@bell-ho/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-JPA-Lock</link>
            <guid>https://velog.io/@bell-ho/%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-JPA-Lock</guid>
            <pubDate>Tue, 29 Apr 2025 05:21:48 GMT</pubDate>
            <description><![CDATA[<p>동시성 상황(여러 사용자가 동시에 Stack 생성 요청)에서 중복 생성 문제를 확실히 막기 위해
@Lock(LockModeType.PESSIMISTIC_WRITE)를 이용해 락을 걸어주는 방식</p>
<p>비관적 락이란?
&quot;다른 트랜잭션이 이 데이터를 수정할 수도 있다&quot; 라고 비관적으로 예상하고,
아예 데이터를 읽을 때부터 락을 걸어서 다른 트랜잭션이 접근 못 하게 막는 것입니다.</p>
<p>즉, 데이터를 조회하는 순간부터 &quot;이건 내 거야, 너 손대지 마&quot; 하고 락을 걸어버리는 방식입니다.</p>
<p>동작 설명</p>
<ol>
<li>트랜잭션이 특정 데이터를 조회할 때, 데이터베이스에 락(Lock)을 건다.</li>
<li>락이 걸린 데이터는 다른 트랜잭션이 수정하거나 삭제하려고 하면 대기하거나 에러가 발생한다.</li>
<li>내 트랜잭션이 끝나기 전까지 다른 트랜잭션은 그 데이터를 변경할 수 없다.</li>
</ol>
<p>비관적 락의 종류</p>
<p>종류    설명
PESSIMISTIC_READ    읽을 때 락을 걸지만 다른 트랜잭션도 읽기는 가능 (수정만 못함)
PESSIMISTIC_WRITE    읽을 때 락을 걸고, 다른 트랜잭션은 읽기, 쓰기 모두 막음 (완전 점유)
보통 쓰는 건 PESSIMISTIC_WRITE입니다. (쓰기 충돌 방지)</p>
<p>비관적 락을 써야 하는 상황</p>
<ol>
<li>데이터 충돌(중복 생성, 중복 수정)이 절대 발생하면 안 될 때</li>
<li>동시에 여러 사용자가 특정 데이터를 조작할 가능성이 높을 때
ex) 상품 재고 차감, 회원 가입 시 유일한 닉네임 생성</li>
</ol>
<p>비관적 락의 단점
락을 걸기 때문에 성능이 느려질 수 있습니다.
락을 오래 점유하면 <strong>Deadlock(교착 상태)</strong>이 발생할 수 있습니다.
락을 관리하는 부하가 DB에 추가됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[private 메서드, @Transactional 동작할까?]]></title>
            <link>https://velog.io/@bell-ho/private-%EB%A9%94%EC%84%9C%EB%93%9C%EC%97%90-Transactional%EC%9D%84-%EC%84%A0%EC%96%B8%ED%95%98%EB%A9%B4-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@bell-ho/private-%EB%A9%94%EC%84%9C%EB%93%9C%EC%97%90-Transactional%EC%9D%84-%EC%84%A0%EC%96%B8%ED%95%98%EB%A9%B4-%ED%8A%B8%EB%9E%9C%EC%9E%AD%EC%85%98%EC%9D%B4-%EB%8F%99%EC%9E%91%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Tue, 29 Apr 2025 00:17:29 GMT</pubDate>
            <description><![CDATA[<p>결론부터 말하면 동작하지 않는다</p>
<p>Spring에서는 @Transactional, @Cacheable, @Async 같은 애너테이션이 런타임에 동작하는 Spring AOP 기반으로 처리된다.</p>
<p>Spring AOP는 JDK Dynamic Proxy나 CGLIB 방식을 이용해 프록시 객체를 생성하고, 메서드 호출 전후로 횡단 관심사를 적용한다.</p>
<p>스프링은 빈을 생성할 때, 해당 클래스에 AOP 애너테이션이 존재하는지 검사한 후, 존재하면 프록시 객체로 감싸서 등록한다.</p>
<p>Spring의 기본 프록시 방식 (JDK 동적 프록시 또는 CGLIB)을 사용할 때, private 메서드는 프록시 클래스에 존재하지 않거나 접근할 수 없기 때문에 호출이 불가능</p>
<p>@Transactional이 붙은 메서드는 프록시 객체가 호출할 때만 트랜잭션이 시작</p>
<p>하지만 private 메서드는 프록시가 아닌 자기 자신(this) 에서 직접 호출되기 때문에 프록시가 개입할 수 없다.</p>
<pre><code class="language-java">@Slf4j
@RequiredArgsConstructor
@Service
public class SelfInvocation {

    private final MemberRepository memberRepository;

    public void outerSaveWithPublic(Member member) {
        saveWithPublic(member);
    }

    @Transactional
    public void saveWithPublic(Member member) {
        log.info(&quot;call saveWithPublic&quot;);
        memberRepository.save(member);
        throw new RuntimeException(&quot;rollback test&quot;);
    }

    public void outerSaveWithPrivate(Member member) {
        saveWithPrivate(member);
    }

    @Transactional
    private void saveWithPrivate(Member member) {
        log.info(&quot;call saveWithPrivate&quot;);
        memberRepository.save(member);
        throw new RuntimeException(&quot;rollback test&quot;);
    }
}
</code></pre>
<p>위 코드에서 saveWithPublic 메서드는 public 접근 제어자이기 때문에 프록시를 통해 트랜잭션이 적용된다.
반면 saveWithPrivate 메서드는 private 접근 제어자이기 때문에 트랜잭션이 적용되지 않는다.</p>
<p>또한, 같은 클래스 내에서 메서드를 호출하는 경우(Sefl Invocation)에는 프록시를 거치지 않고 직접 호출하게 되어 트랜잭션 어드바이스가 적용되지 않는다.</p>
<p>테스트 결과 요약
외부에서 selfInvocation.saveWithPublic()을 직접 호출하면 트랜잭션이 정상 동작하고 롤백된다.
outerSaveWithPublic()처럼 내부 메서드를 통해 호출하면 트랜잭션이 적용되지 않아 롤백이 되지 않는다.
private 메서드는 애초에 AOP 적용 대상이 아니기 때문에 트랜잭션이 적용되지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[n8n을 사용해 IMAP 이메일 수신 시 Slack으로 메시지 전송하기]]></title>
            <link>https://velog.io/@bell-ho/n8n%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4-IMAP-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%88%98%EC%8B%A0-%EC%8B%9C-Slack%EC%9C%BC%EB%A1%9C-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@bell-ho/n8n%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4-IMAP-%EC%9D%B4%EB%A9%94%EC%9D%BC-%EC%88%98%EC%8B%A0-%EC%8B%9C-Slack%EC%9C%BC%EB%A1%9C-%EB%A9%94%EC%8B%9C%EC%A7%80-%EC%A0%84%EC%86%A1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 21 Nov 2024 01:06:14 GMT</pubDate>
            <description><![CDATA[<p>n8n을 활용해 IMAP 이메일을 수신하면, 그 내용을 자동으로 Slack에 메시지로 전송하는 방법을 소개합니다.</p>
<hr>
<h2 id="준비사항">준비사항</h2>
<ul>
<li>n8n 인스턴스</li>
<li>IMAP 이메일 계정 (Gmail, Outlook 등)</li>
<li>Slack 워크스페이스 및 Webhook URL</li>
</ul>
<hr>
<h2 id="워크플로우-단계">워크플로우 단계</h2>
<h3 id="1-imap-email-노드-설정">1. IMAP Email 노드 설정</h3>
<ol>
<li>n8n 편집 화면에서 <strong>IMAP Email</strong> 노드를 추가합니다.</li>
<li>이메일 계정 정보를 입력합니다.<ul>
<li><strong>Hostname</strong>: 이메일 서비스의 IMAP 호스트 (예: Gmail은 <code>imap.gmail.com</code>)</li>
<li><strong>Port</strong>: 993 (SSL을 사용하는 경우)</li>
<li><strong>User</strong>: 이메일 주소</li>
<li><strong>Password</strong>: 이메일 비밀번호 또는 앱 비밀번호</li>
</ul>
</li>
<li>폴더를 <code>INBOX</code>로 설정합니다.</li>
<li>이메일 체크 주기를 설정합니다. (예: 1분마다)</li>
</ol>
<hr>
<h3 id="2-slack-노드-설정">2. Slack 노드 설정</h3>
<ol>
<li><p><strong>Slack</strong> 노드를 추가하고, <code>IMAP Email</code> 노드와 연결합니다.</p>
</li>
<li><p>Slack Webhook URL을 입력합니다.</p>
<ul>
<li><a href="https://api.slack.com/messaging/webhooks">Slack Incoming Webhooks 설정 가이드</a>를 참고해 Webhook URL을 생성하세요.</li>
</ul>
</li>
<li><p>전송할 메시지의 포맷을 지정합니다:</p>
<pre><code class="language-plaintext"> *새 이메일이 도착했습니다!*
 *제목*: {{$node[&quot;IMAP Email&quot;].json[&quot;subject&quot;]}}
 *내용*: {{$node[&quot;IMAP Email&quot;].json[&quot;text&quot;]}}</code></pre>
</li>
</ol>
<hr>
<h3 id="3-워크플로우-활성화">3. 워크플로우 활성화</h3>
<ol>
<li>워크플로우를 저장하고 활성화합니다.</li>
<li>이제 IMAP 이메일이 수신되면 자동으로 Slack에 알림 메시지가 전송됩니다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[n8n이란 무엇인가?]]></title>
            <link>https://velog.io/@bell-ho/test</link>
            <guid>https://velog.io/@bell-ho/test</guid>
            <pubDate>Fri, 15 Nov 2024 07:34:50 GMT</pubDate>
            <description><![CDATA[<p>n8n은 <strong>오픈소스 워크플로우 자동화 도구</strong>로, 
다양한 API와 애플리케이션을 연결하여 작업을 자동화할 수 있는 시각적인 플랫폼입니다.</p>
<hr>
<h2 id="주요-특징">주요 특징</h2>
<ul>
<li><strong>노코드/로우코드</strong>: 프로그래밍 없이도 다양한 자동화 작업 구현 가능.</li>
<li><strong>확장성</strong>: 커스텀 노드 및 코드를 추가하여 복잡한 작업 처리.</li>
<li><strong>다양한 통합</strong>: 300개 이상의 애플리케이션 및 API와의 통합 지원.</li>
<li><strong>셀프 호스팅 가능</strong>: 온프레미스 또는 클라우드에서 실행 가능.</li>
</ul>
<hr>
<h2 id="주요-용도">주요 용도</h2>
<h3 id="1-데이터-통합">1. 데이터 통합</h3>
<ul>
<li>여러 애플리케이션 간 데이터를 자동으로 동기화.</li>
<li><strong>예</strong>: CRM, 이메일, 클라우드 스토리지 간 데이터 연결.</li>
</ul>
<h3 id="2-프로세스-자동화">2. 프로세스 자동화</h3>
<ul>
<li>이벤트 기반으로 자동화된 작업 실행.</li>
<li><strong>예</strong>: 신규 데이터 수집 후 자동 알림 전송.</li>
</ul>
<h3 id="3-api-호출-및-처리">3. API 호출 및 처리</h3>
<ul>
<li>REST 또는 GraphQL API와 상호작용 가능.</li>
<li><strong>예</strong>: 데이터를 수집하여 다른 API로 전달.</li>
</ul>
<h3 id="4-모니터링-및-알림">4. 모니터링 및 알림</h3>
<ul>
<li>오류나 특정 조건 발생 시 Slack, 이메일 등으로 알림 전송.</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 스케줄러 환경별 세팅]]></title>
            <link>https://velog.io/@bell-ho/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%9F%AC-%ED%99%98%EA%B2%BD%EB%B3%84-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@bell-ho/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%9F%AC-%ED%99%98%EA%B2%BD%EB%B3%84-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Wed, 29 May 2024 07:04:48 GMT</pubDate>
            <description><![CDATA[<p>로컬환경에서 스케줄러 실행 X</p>
<pre><code class="language-yaml">spring:
  config:
    activate:
      on-profile: dev</code></pre>
<pre><code class="language-java">@Component
@Profile(&quot;!dev&quot;) // dev가 아니면 실행
public class Scheduler {</code></pre>
<p>java -jar -Dspring.profiles.active=dev</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA 팁]]></title>
            <link>https://velog.io/@bell-ho/JPA-%ED%8C%81</link>
            <guid>https://velog.io/@bell-ho/JPA-%ED%8C%81</guid>
            <pubDate>Mon, 13 May 2024 07:36:45 GMT</pubDate>
            <description><![CDATA[<ol>
<li>엔티티 매핑 최적화</li>
</ol>
<p>엔티티 클래스와 데이터베이스 테이블 간의 매핑을 최적화하는 것은 JPA 성능 향상에 매우 중요합니다.</p>
<p>필드 매핑: 필요하지 않은 필드는 매핑하지 않도록 합니다. 
모든 데이터베이스 열을 엔티티 필드로 매핑하면 메모리 사용량과 성능에 영향을 미칩니다.</p>
<p>Fetch Type 설정: 연관 관계를 매핑할 때 지연 로딩(LAZY)을 기본으로 사용하고, 
즉시 로딩(EAGER)은 꼭 필요한 경우에만 사용합니다.
지연 로딩은 실제로 데이터가 필요할 때 로드되므로 성능이 향상됩니다.</p>
<pre><code class="language-java">@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;category_id&quot;)
    private Category category;
}</code></pre>
<ol start="2">
<li>Query Optimization</li>
</ol>
<p>JPQL 사용: 필요한 데이터만 선택하여 불필요한 데이터를 가져오지 않도록 합니다.</p>
<p>Pagination: 대량의 데이터를 처리할 때는 페이징 처리를 통해 한 번에 가져오는 데이터 양을 제한합니다.</p>
<pre><code class="language-java">public List&lt;Product&gt; findProducts(int page, int size) {
    return entityManager.createQuery(&quot;SELECT p FROM Product p&quot;, Product.class)
        .setFirstResult(page * size)
        .setMaxResults(size)
        .getResultList();
}</code></pre>
<ol start="3">
<li>2차 캐시 활용
JPA의 2차 캐시(Second Level Cache)를 활용하면 동일한 데이터를 여러 번 조회할 때 
데이터베이스 액세스를 줄일 수 있습니다. 
Hibernate에서는 ehcache, infinispan 등을 사용하여 2차 캐시를 설정할 수 있습니다.</li>
</ol>
<pre><code class="language-java">&lt;!-- persistence.xml --&gt;
&lt;property name=&quot;hibernate.cache.use_second_level_cache&quot; value=&quot;true&quot;/&gt;
&lt;property name=&quot;hibernate.cache.region.factory_class&quot; value=&quot;org.hibernate.cache.jcache.JCacheRegionFactory&quot;/&gt;
&lt;property name=&quot;hibernate.javax.cache.provider&quot; value=&quot;org.ehcache.jsr107.EhcacheCachingProvider&quot;/&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[AOP를 활용한 로그 추적 기능]]></title>
            <link>https://velog.io/@bell-ho/AOP%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8-%EC%B6%94%EC%A0%81-%EA%B8%B0%EB%8A%A5</link>
            <guid>https://velog.io/@bell-ho/AOP%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8-%EC%B6%94%EC%A0%81-%EA%B8%B0%EB%8A%A5</guid>
            <pubDate>Wed, 26 Apr 2023 05:28:32 GMT</pubDate>
            <description><![CDATA[<p>aop를 활용하여 메소드 실행중 슬로우 쿼리와 오류 발생시 해당 상황을 로그테이블에 저장하는 기능을 만들어 보았다.</p>
<pre><code class="language-java">@Aspect
@Component
public class LogTraceAspect {

    private static final long SLOW_QUERY_THRESHOLD = 1000; // 슬로우 쿼리 기준
    private final ThreadLocal&lt;Long&gt; startTime = new ThreadLocal&lt;&gt;();

    // 클래스 이름이 서비스인것을 추적
    @Pointcut(&quot;execution(* *..*Service.*(..))&quot;)
    private void allService() {}

    @Before(&quot;allService()&quot;)
    public void beforeMethod() {
        startTime.set(System.currentTimeMillis());
    }

    @After(&quot;allService()&quot;)
    public void afterMethod(JoinPoint joinPoint) {
        long endTime = System.currentTimeMillis();
        long elapsedTime = endTime - startTime.get();

        if (elapsedTime &gt; SLOW_QUERY_THRESHOLD) {
            logSave();
        }
    }

    @AfterThrowing(value = &quot;allService()&quot;, throwing = &quot;exception&quot;)
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        logSave();
    }
}</code></pre>
<p>Service 클래스에 있는 특정 기능에 sleep을 걸고 메소드를 실행하였다. log가 잘 저장되었을까</p>
<pre><code class="language-java">    public void delayed() {
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }</code></pre>
<p>엣 저장 실패</p>
<blockquote>
<p>Connection is read-only. Queries leading to data modification are not allowed</p>
</blockquote>
<p>성능 향상의 이유로 Service 클래스 상단에 @Transactional(readOnly = true) 어노테이션을 해놨는데 로그 저장 메소드를 이용하기 위해선 별도의 트랜잭션이 필요함</p>
<hr>
<p>해결 방법 : 로그 저장시 새로운 트랜잭션을 생성하고 현재 트랜잭션이 있으면 일시 중단하고 로그 저장이 완료되면 원래 트랜잭션 다시 시작</p>
<pre><code class="language-java">    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logSave() {
        // 로그 저장..
    }</code></pre>
<blockquote>
<p>@Transactional(propagation = Propagation.REQUIRES_NEW) 사용할 때 주의할 점</p>
</blockquote>
<ol>
<li>리소스 사용 증가 : 새로운 트랜잭션을 시작하려면 추가적인 리소스가 필요하여 성능에 영향을 줄 수 있음</li>
<li>트랜잭션 관리 복잡성 증가 : 두 개 이상의 트랜잭션이 동시에 발생하면 트랜잭션 관리가 복잡해질 수 있음</li>
</ol>
<p>내가 만든 로그 저장 같은 기능은 성능에 큰 영향을 주지 않을거 같음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next-Auth 소셜 로그인 [구글]]]></title>
            <link>https://velog.io/@bell-ho/Next-Auth-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%EA%B8%80</link>
            <guid>https://velog.io/@bell-ho/Next-Auth-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%EA%B8%80</guid>
            <pubDate>Mon, 13 Mar 2023 12:39:55 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/bell-ho/post/10bc4d9a-2721-4eb1-97ff-99cb3237e908/image.png" alt=""></p>
<p>NextJS 에서 next-auth를 이용하면 google , facebook , naver 같은 소셜 로그인 기능을 쉽게 만들 수 있습니다.</p>
<p>next 13버전, next-auth 4버전을 사용했습니다.</p>
<pre><code class="language-javascript">next-auth 사용시 pages/api/auth 폴더안에 [...nextauth].js 파일을 만들어야 합니다.

import NextAuth from &#39;next-auth&#39;;
import GoogleProvider from &#39;next-auth/providers/google&#39;;

const nextAuthOptions = (req, res) =&gt; {
  return {
    providers: [
      GoogleProvider({
        clientId: process.env.GOOGLE_CLIENT_ID,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
      }),
    ],
  };
};

const authHandler = (req, res) =&gt; {
  return NextAuth(req, res, nextAuthOptions(req, res));
};

export default authHandler;</code></pre>
<p>.env 파일을 이용해서 google 클라이언트 키와 시크릿키를 저장합니다.</p>
<p>구글 클라우드 플랫폼 이용하기
<a href="https://console.cloud.google.com/getting-started?hl=ko">https://console.cloud.google.com/getting-started?hl=ko</a></p>
<p>google API키와 시크릿키 얻는 방법은 생략하겠습니다.</p>
<pre><code class="language-javascript">로그인 화면에서 next-auth signIn 함수를 이용합니다

import { signIn } from &#39;next-auth/react&#39;;

const Login = () =&gt; {

  ... 생략

    return (
        &lt;Button onClick={() =&gt; signIn(&#39;google&#39;)}&gt;
              구글 로그인
        &lt;/Button&gt;
    );
}</code></pre>
<p>로그인 버튼을 누르면 ?
<img src="https://velog.velcdn.com/images/bell-ho/post/66fe948f-8a63-4a1f-9a43-820f7287c14d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSS 선택자]]></title>
            <link>https://velog.io/@bell-ho/CSS-%EC%84%A0%ED%83%9D%EC%9E%90</link>
            <guid>https://velog.io/@bell-ho/CSS-%EC%84%A0%ED%83%9D%EC%9E%90</guid>
            <pubDate>Mon, 16 Jan 2023 07:31:13 GMT</pubDate>
            <description><![CDATA[<p>띄어쓰기는 자손
<code>&gt;</code> 는 첫번째 자식</p>
<pre><code class="language-css">nav ul {

}

nav &gt; ul &gt; li &gt; a {

}</code></pre>
<p>인접 형제 선택자 A + B A 근접 B만
A 뒤에 따라오는 B는 전부 A ~ B</p>
<pre><code class="language-css">h1 + ul {

}

h2 ~ ul {

}</code></pre>
<p>자식 요소중 포커스에 걸리면</p>
<pre><code class="language-css">form:focus-within{

}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 in 연산자]]></title>
            <link>https://velog.io/@bell-ho/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-in-%EC%97%B0%EC%82%B0%EC%9E%90</link>
            <guid>https://velog.io/@bell-ho/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-in-%EC%97%B0%EC%82%B0%EC%9E%90</guid>
            <pubDate>Tue, 05 Jul 2022 15:16:29 GMT</pubDate>
            <description><![CDATA[<p>유투브 보다가 팁 발견</p>
<pre><code class="language-javascript">
for in 문 활용법

const person = { name:&#39;p1&#39;, age:1 , good:false};

for(const p in person){
    console.log(p);
  // name
  // age
  // good
}

객체 key를 뽑아내기 쉽다

이런것도 가능
if(&#39;name&#39; in person){
    true;
}
// true;</code></pre>
<p>출처 : <a href="https://www.youtube.com/watch?v=VgMJFzZQBjQ&amp;ab_channel=ZeroChoTV">제로초 : 잘 모르는 &quot;간단한&quot; 자바스크립트 문법 5가지</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 인라인 스타일의 단점]]></title>
            <link>https://velog.io/@bell-ho/React-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%98-%EB%8B%A8%EC%A0%90</link>
            <guid>https://velog.io/@bell-ho/React-%EC%9D%B8%EB%9D%BC%EC%9D%B8-%EC%8A%A4%ED%83%80%EC%9D%BC%EC%9D%98-%EB%8B%A8%EC%A0%90</guid>
            <pubDate>Sun, 29 May 2022 17:04:45 GMT</pubDate>
            <description><![CDATA[<p>style을 적용할 때 인라인 스타일을 이용할 때가 많았다 (<del>편하니까</del>)</p>
<pre><code class="language-html">&lt;div style={{ width:100px }}&gt;
    &lt;h1&gt;자바스크립트&lt;/h1&gt;      
&lt;/div&gt;</code></pre>
<p>이런식으로..</p>
<p>하지만 인라인식으로 작성하게 되면 불필요한 리랜더링을 하게 된다. <strong>이유는?</strong>
결론부터 말하면 {} === {} 가 false 이기 때문!
리액트의 버추얼돔이 검사를 하면서 어디가 달라졌는지 찾다가
이전 값과 비교했을때 다른 객체로 인식하기 때문에 리랜더링을 하게된다.
=&gt; 규모가 큰 웹이나 앱이면 성능이 느려짐.</p>
<h3 id="개선방법">개선방법</h3>
<ul>
<li>useMemo 사용하기<ul>
<li><pre><code class="language-javascript">const style = useMemo(() =&gt; ({ width: 100px }), []);</code></pre>
</li>
</ul>
</li>
<li>styled-components 사용하기<ul>
<li><pre><code class="language-javascript">const CustomDiv = styled.div`width:100px`; </code></pre>
</li>
</ul>
</li>
</ul>
<p>위의 방법을 통해 리랜더링 문제를 최적화 할 수 있다~</p>
]]></description>
        </item>
    </channel>
</rss>