<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>yoon_han0.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 18 Jun 2025 05:03:52 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>yoon_han0.log</title>
            <url>https://velog.velcdn.com/images/yoon_han0/profile/74fdaed0-d170-4929-8df0-8dae855e6913/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. yoon_han0.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yoon_han0" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring Security 회원가입 구현 및 DB 연결]]></title>
            <link>https://velog.io/@yoon_han0/Spring-Security-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84-%EB%B0%8F-DB-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@yoon_han0/Spring-Security-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84-%EB%B0%8F-DB-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Wed, 18 Jun 2025 05:03:52 GMT</pubDate>
            <description><![CDATA[<p>앞선 <a href="https://velog.io/@yoon_han0/Spring-Security-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">[블로그]</a>와 이어지는 글입니다.</p>
<p>이번에는 회원가입 로직을 구현해 보겠습니다.</p>
<ul>
<li>사용 스택<ul>
<li>h2 데이터베이스</li>
<li><del>Spring Data JPA</del> → mybatis</li>
</ul>
</li>
</ul>
<br />

<h2 id="1-db-연결">1. DB 연결</h2>
<p>회원가입 로직을 구현하기 전에 DB를 먼저 연결하겠습니다.</p>
<p>영상에서는 AWS에 DB 서버를 만들어서 사용하셨는데 저는 로컬에서만 간단하게 개발하고 싶어 h2 데이터베이스를 사용했습니다.
<a href="https://github.com/YoonHan0/hello-spring/blob/main/memo/DB%EC%97%B0%EA%B2%B0%EC%98%88%EC%A0%9C.md">[h2 데이터베이스 연결하는 방법]</a>
데이터베이스 연결하는 방법은 링크 참고 부탁드립니다.</p>
<br />

<h3 id="1️⃣-설정-추가하기">1️⃣ 설정 추가하기</h3>
<pre><code class="language-yml">/* application.yml */
spring:
  application:
    name: login

  h2:
    console:
      enabled: true
      path: /h2-console

  datasource:
    url: jdbc:h2:tcp://localhost/~/test
    driver-class-name: org.h2.Driver
    username: sa
    password:

mybatis:
  mapper-locations: classpath:/mapper/*.xml    # xml 파일이 있는 경로
  type-aliases-package: com.example.login.model # dto 클래스가 존재하는 경로</code></pre>
<pre><code class="language-gradle">/* build.gradle */
dependencies {
    ...
    implementation &#39;org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3&#39;
    runtimeOnly &#39;com.h2database:h2&#39;             // h2 Driver
}</code></pre>
<br />

<h3 id="2️⃣-securityconfig-클래스에서-회원가입-url-허용해주기">2️⃣ <code>SecurityConfig</code> 클래스에서 회원가입 url 허용해주기</h3>
<p>회원가입 api url (ex: ”/joinProc”)를 모든 사용자에게 허용해 줍니다.</p>
<pre><code class="language-java">/* SecuriryConfig */
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    RequestMatcher h2ConsoleMatcher = PathRequest.toH2Console();

    http
            .authorizeHttpRequests((auth) -&gt; auth
                    .requestMatchers(&quot;/&quot;, &quot;/login&quot;, &quot;/joinProc&quot;).permitAll()
                …
    …
}</code></pre>
<br />

<h3 id="3️⃣-mybatisconfig-클래스-설정-추가">3️⃣ <code>MyBatisConfig</code> 클래스 설정 추가</h3>
<pre><code class="language-java">/* MyBatisConfig 클래스 생성 */
@Configuration
@MapperScan(basePackages = &quot;com.example.login.mapper&quot;)
public class MyBatisConfig {
    // 비어있는 상태로 두면 됩니다.
}</code></pre>
<br />
<br />

<h2 id="2-비밀번호-암호화를-위한-security-config-추가">2. 비밀번호 암호화를 위한 Security Config 추가</h2>
<pre><code class="language-java">/* SecurityConfig.java */
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}</code></pre>
<p>Spring Security는 회원가입, 사용자 인증(로그인) 시 비밀번호에 대해 단방향 해시 암호화를 진행하여 저장되어 있는 비밀번호와 대조합니다.</p>
<p>Spring Security는 암호화를 위해 <code>BCryptPasswordEncoder</code> 클래스를 제공하고 해당 방식으로 처리할 것을 권장합니다.</p>
<br />
<br />

<h2 id="3-회원가입-로직">3. 회원가입 로직</h2>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/6001a18c-45f3-4d11-af99-8ef4f5153035/image.jpg" alt=""></p>
<p>프로젝트의 전반적인 구조는 <a href="https://www.devyummi.com/page?id=668bdd44b374ad837c3f9deb">[유미개발자님 블로그]</a>를 참고하여 개발하였습니다.</p>
<p>이 글에서 소스 코드를 줄줄이 적기에는 너무 내용이 길어지는 것 같아 핵심 코드나 공부한 코드들만 작성하도록 하겠습니다.😅</p>
<p>전체적인 소스 코드는 <a href="https://github.com/YoonHan0/login">[깃허브]</a>를 참고해 주시면 감사하겠습니다!</p>
<p><strong>1️⃣ <code>Controller, Service, Mapper</code> 클래스 및 인터페이스 생성하기</strong>
본 글의 핵심인 비밀번호 암호화에 대한 내용을 작성해야 하기 때문에 <code>Service</code>단 코드만 작성하겠습니다.</p>
<p>위의 <code>SecurityConfig</code> 클래스에서 Bean으로 등록한 <code>BCryptPasswordEncoder</code> 클래스를 가져와 DI 시켜주고</p>
<p><code>encode()</code> 메서드를 통해 비밀번호를 암호화시켜 DB에 저장하겠습니다.</p>
<pre><code class="language-java">/* JoinService */
@Service
public class JoinService {

    @Autowired
    private JoinMapper mapper;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public void joinProcess(UserInfo userInfo) throws Exception{

        // 동일한 계정이 존재하는지 체크하는 로직
        int count = mapper.countDuplicateId(userInfo);
        if(count &gt; 0) {
            return &quot;DUPLICATE_ID&quot;;
        }
        userInfo.setRole(&quot;ROLE_USER&quot;);
        userInfo.setPassword(bCryptPasswordEncoder.encode(userInfo.getPassword()));     // 비밀번호 암호화를 위한 로직 추가

        mapper.save(userInfo);
        return &quot;OK&quot;;
    }

}</code></pre>
<p>해시 암호화를 하여 DB에 값을 저장하게 되면 아래처럼 값이 들어가게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/47e905c0-90de-466a-b705-24f90568f294/image.png" alt=""></p>
<br />

<p><strong>2️⃣ <code>xml</code> 파일 생성하기</strong>
저는 <code>프로젝트/src/resources/mapper/*.xml</code> 경로로 생성하였습니다. <a href="https://github.com/YoonHan0/login">[깃 확인]</a> <br />
Mapper 기본 코드만 남겨놓겠습니다. 추가로는 필요하신 쿼리를 작성하시면 될 것 같습니다.</p>
<pre><code class="language-xml">/* JoinMapper */
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&gt;
&lt;mapper namespace=&quot;com.example.login.mapper.JoinMapper&quot;&gt;    // mapper 경로 맞춰주기

    /* 필요하신 쿼리 작성하시면 됩니다. */
&lt;/mapper&gt;</code></pre>
<br />
<br />

<h2 id="4-결과확인">4. 결과확인</h2>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/e37df4cf-3432-4bd7-bd99-7a6aff979f56/image.gif" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/b5ea1a53-bc3d-4373-af8d-3d23771d3807/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security 사용하기]]></title>
            <link>https://velog.io/@yoon_han0/Spring-Security-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoon_han0/Spring-Security-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 13 Jun 2025 07:18:27 GMT</pubDate>
            <description><![CDATA[<p>프론트 부분의 구조나 디자인에 대한 내용은 각자 원하시는대로 하시면 될 것 같고,
저는 디자인은 GPT, V0와 같은 AI 툴들을 사용해서 만들었습니다. (디자인은 자신이..😅)</p>
<p>UI가 필요하신 분들은 <a href="https://github.com/YoonHan0/login">[로그인 기능 구현]</a> 여기 깃 코드 사용하시면 될 것 같습니다.</p>
<p>Spring Security 공부를 위해 로그인 기능을 구현하는 것이기에 프론트보다는 백엔드에 초점을 맞추어 작성 하도록 하겠습니다.</p>
<p>프로젝트 세팅을 하지 않으신 분들은 <a href="https://velog.io/@yoon_han0/ReactSpringBoot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1">[React+SpringBoot 프로젝트 생성하기]</a> 블로그를 먼저 보고 와주시길 바랍니다.</p>
<h3 id="-spring-security란-">[ Spring Security란? ]</h3>
<p>Security를 사용하기 전에 간략하게 무엇인지를 알아보자면
<code>Spring Security</code>는 Spring 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크이다. 
<code>Spring Security</code>는 &#39;인증&#39;과 &#39;권한&#39;에 대한 부분을 Filter 흐름에 따라 처리하고 있다. Filter는 <code>Dispatcher Servlet</code>으로 가기 전에 적용되므로 가장 먼저 URL 요청을 받는다.</p>
<p><code>Spring Security</code>는 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있다.</p>
<ul>
<li><strong>인증(Authentication)</strong>: 해당 사용자가 본인이 맞는지를 확인하는 절차</li>
<li><strong>인가(Authorization)</strong>: 인증된 사용자가 요청한 자원에 접근 가능한지를 결정하는 절차 </li>
</ul>
<p><a href="https://mangkyu.tistory.com/76">참고</a></p>
<br />

<p>스프링은 버전에 따라 구현 방식이 조금씩 변경되는데 시큐리티의 경우 특히 세부 버전별로 구현 방법이 많이 다르기 때문에 버전마다 구현 특징을 확인해야 한다고 합니다.
<a href="https://www.devyummi.com/page?id=668bd7fe16014d6810ed85f7">[참고]</a>, <a href="https://github.com/spring-projects/spring-security/releases">[Spring Security GitHub Releases Note]</a></p>
<br />
<br />

<h3 id="1️⃣-spring-security-사용하기-의존성-추가">1️⃣ Spring Security 사용하기 (의존성 추가)</h3>
<p>Security를 사용하기 위해서는 <code>build.gradle</code> 파일에 의존성을 먼저 추가하여야 합니다.</p>
<pre><code class="language-gradle">dependencies {
    ...
    implementation &#39;org.springframework.boot:spring-boot-starter-security&#39;    // Spring Security
    ...
}</code></pre>
<h3 id="2️⃣-config-클래스-설정">2️⃣ Config 클래스 설정</h3>
<p>의존성을 추가하면 Spring Security를 사용할 수 있습니다.
Security를 사용하기 위한 <code>SecurityConfig</code> 클래스를 생성합니다.</p>
<p>사용하는 메서드명(<code>ex: filterChain</code>)은 원하시는 이름으로 정하고 
<strong>리턴 타입은 <code>SecurityFilterChain</code> 으로 하셔야 합니다.</strong></p>
<ul>
<li><p><strong>@Configuration</strong></p>
<ul>
<li>스프링 프레임워크에서 사용되는 어노테이션 중 하나로, 해당 클래스를 스프링의 설정 클래스로 지정하는 역할을 합니다.</li>
<li>해당 어노테이션이 적용된 클래스는 스프링의 설정 정보를 포함하고 있는 클래스로 간주됩니다.</li>
</ul>
</li>
<li><p><strong>@EnableWebSecurity</strong></p>
<ul>
<li>Spring Security를 사용하여 웹 보안을 구성할 때 사용하는 어노테이션입니다. 이 어노테이션을 사용하면 Spring Security와 관련된 구성을 할 수 있습니다.</li>
<li>Spring Security의 기능을 활성화하고, <del>주로 <code>WebSecurityConfigurerAdapter</code> 클래스를 상속받아 사용합니다.</del><pre><code class="language-java">/* 과거에는 이렇게 구현하였지만 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  ...
}
/* 
Spring Boot 3.0 이상
Spring Security 6 이상에서는 WebSecurityConfigurerAdapter는 사용 불가 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {
  ...
}</code></pre>
</li>
<li>해당 어노테이션을 사용하면 아래와 같은 작업을 수행할 수 있습니다.<ul>
<li>HttpSecurity 객체를 사용하여 HTTP 요청에 대한 보안 구성을 설정할 수 있습니다.<ul>
<li>WebSecurityConfigurerAdapter 클래스를 상속받은 설정 클래스를 등록할 수 있습니다.</li>
</ul>
</li>
</ul>
</li>
<li><a href="https://born2bedeveloper.tistory.com/75">[@EnabledWebSecurity  파헤치기]</a> → 관련된 내용 공부해서 추후에 정리해 보겠습니다~!</li>
</ul>
</li>
</ul>
<ul>
<li><strong>HttpSecurity http</strong><ul>
<li><code>Spring Security</code> 의 HTTP 보안 설정을 구성하기 위한 클래스입니다.</li>
<li>주로 URL 패턴에 따라 접근을 제한하거나, 인증 및 인가에 대한 규칙을 설정합니다.</li>
<li><code>WebSecurityConfigurerAdapter</code> 를 확장하여 사용하며, <code>configure(HttpSecurity http)</code> 메소드를 오버라이드하여 필요한 정보를 추가합니다.</li>
<li><code>http</code> 에서 사용하는 메서드들은 <strong>람다</strong> 형태로 사용하시면 됩니다.</li>
<li><a href="https://yujin-17.tistory.com/entry/Spring-Security-WebSecurity-HttpSecurity-%EB%91%90%EA%B0%80%EC%A7%80-%EC%A3%BC%EC%9A%94-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC">참고</a></li>
</ul>
</li>
</ul>
<br />
<br />

<pre><code class="language-java">@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // 인가
        http
                .authorizeHttpRequests((auth) -&gt; auth
                        .requestMatchers(&quot;/&quot;, &quot;/login&quot;).permitAll()
                        .requestMatchers(&quot;/getUserInfo&quot;).hasRole(&quot;ADMIN&quot;)
                        .anyRequest().authenticated()
                );
        // 인증     
        http
                .formLogin((auth) -&gt; auth.loginPage(&quot;/login&quot;)       // 권한이 없는 admin과 같은 경로로 사용자가 요청했을 때 오류 페이지가 아닌 작성한 &quot;경로&quot;로 Spring Security가 리다이랙트 시켜줌
                        .loginProcessingUrl(&quot;/loginProc&quot;)           // 해당 경로로 요청이 들어오면 Security가 로그인 처리를 진행
                        .defaultSuccessUrl(&quot;/login&quot;, true)
                        .permitAll()                                // 로그인 처리를 한다는게 permitAll() 권한을 준다는 것 같음
                );

        http
                .csrf((auth) -&gt; auth.disable());                    // POST방식으로 요청을 진행할 때 CSRF(사이트 위변조 방지 설정) 토큰을 보내야 로그인 처리를 가능하게 함, 이러한 설정은 Spring Security에서 설정되어 있음
                                                                    // 개발환경에서는 토큰을 사용하는 것이 불편하므로 disable 처리, 추후에 inable 처리하도록 수정

        return http.build();
    }
}</code></pre>
<br />
코드에 대한 설명을 하자면

<ul>
<li><code>requestMatchers(경로)</code>: &quot;경로&quot;에 대한 권한을 설정할 수 있다.</li>
<li><code>permitAll()</code>: 모든 사용자에게 접근 가능하도록 설정</li>
<li><code>hasRole(권한)</code>: &quot;권한&quot;이 있는 사용자만이 requestMatchers(경로)에 설정된 경로에 접근할 수 있다.</li>
<li><code>hasAnyRole(권한1, 권한2, ...)</code>: 여러 &quot;권한&quot;에 대해 접근을 제어할 수 있다.</li>
<li><code>anyRequest()</code>: 위에서 처리하지 못한 나머지 경로에 대한 권한 제어를 처리할 수 있음.</li>
<li><code>authenticated()</code>: 로그인한 사용자만이 접근할 수 있도록
<code>denyAll()</code>: 모든 사용자의 접근을 제한함</li>
</ul>
<p><code>requestMatchers()</code>를 이용해 경로에 대한 접근을 제한할 때 순서가 중요하다. 많은 권한에 대한 제한은 가장 마지막에 작성하는 것이 올바르다.</p>
<br />

<h4 id="🟩-spring-security를-이용한-인가-처리">🟩 Spring Security를 이용한 &quot;인가&quot; 처리</h4>
<pre><code class="language-java">http
                .authorizeHttpRequests((auth) -&gt; auth
                        .requestMatchers(&quot;/&quot;, &quot;/login&quot;).permitAll()
                        .requestMatchers(&quot;/getUserInfo&quot;).hasRole(&quot;ADMIN&quot;)
                        .anyRequest().authenticated()
                );</code></pre>
<ul>
<li><strong>requestMatchers(&quot;/&quot;, &quot;/login&quot;).permitAll()</strong>
사용자의 request가 <code>&quot;/&quot;</code>, <code>&quot;login&quot;</code> 경로일 경우 <code>permitAll()</code>(=모든 사용자가 접근이 가능하도록) 처리</li>
<li><strong>requestMatchers(&quot;/getUserInfo&quot;).hasRole(&quot;ADMIN&quot;)</strong>
사용자의 request가 <code>&quot;/getUserInfo&quot;</code> 경로일 경우 &quot;ADMIN&quot; 권한을 가진 사용자만 허용해준다.</li>
</ul>
<br />

<h4 id="🟩-spring-security를-이용한-인증-처리">🟩 Spring Security를 이용한 &quot;인증&quot; 처리</h4>
<pre><code class="language-java">http
                .formLogin((auth) -&gt; auth.loginPage(&quot;/login&quot;)       
                        .loginProcessingUrl(&quot;/loginProc&quot;)   
                        .defaultSuccessUrl(&quot;/login&quot;, true)
                        .permitAll()                         
                );</code></pre>
<ul>
<li><strong>formLogin()</strong>: 폼 기반 로그인 사용<ul>
<li>Spring Security에 폼 기반 로그인을 사용하겠다고 알려주는 설정</li>
<li>기본적으로 Spring Security는 자체 로그인 폼을 제공하는데, 이 설정을 통해 커스텀 로그인 페이지를 지정할 수 있습니다.</li>
</ul>
</li>
<li><strong>loginPage(&quot;/login&quot;)</strong><ul>
<li>사용자가 인증이 필요한 페이지(예: /admin)에 접근했을 때,
로그인되지 않은 상태라면 Spring Security가 자동으로 <code>/login</code> 페이지로 리다이렉트합니다.</li>
<li>즉, &quot;/login&quot;은 사용자가 로그인할 수 있도록 직접 만든 로그인 화면의 경로입니다.</li>
</ul>
</li>
<li><strong>loginProcessingUrl(&quot;/loginProc&quot;)</strong><ul>
<li>사용자가 <code>&#39;/loginProc</code> 요청을 보내면 Spring Security가 해당 요청을 가로채 로그인 처리를 수행</li>
</ul>
</li>
<li><strong>defaultSuccessUrl(&quot;/login&quot;, true)</strong><ul>
<li>로그인 인증이 성공하고 난 후, <code>Spring Security</code>가 성공 후 <code>&quot;/login&quot;</code> 경로로 리다이랙트를 시도함</li>
<li>파라미터로 사용되는 경로는 백엔드에서 실제로 존재하는 url 이어야 합니다. (존재하지 않는다면 404 에러가 발생할 수 있습니다..)</li>
<li>AJAX 기반 로그인 처리 방법으로 처리하게 되면 해당 코드는 작성이 필요 없다고 합니다.</li>
</ul>
</li>
</ul>
<br />

<p>이처럼 <code>Spring Security</code>를 이용해 <strong>&quot;인증&quot;, &quot;인가&quot;</strong>에 대한 처리를 할 수 있습니다.</p>
<br />

<p>저는 <a href="https://www.youtube.com/@xxxjjhhh">[개발자 유미 유튜브]</a> 를 참고하여 <code>Spring Security</code> 공부를 진행하고 있습니다.
영상에서는 SpringBoot 프로젝트만을 이용해서 알려주시기 때문에 제 코드와는 다른 부분이 있을 수 있습니다.</p>
<p>추가적인 이해나 설명이 더 필요하신 분들은 유튜브 들어가서 확인해 보시면 좋을 것 같습니다~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React+SpringBoot 프로젝트 생성]]></title>
            <link>https://velog.io/@yoon_han0/ReactSpringBoot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@yoon_han0/ReactSpringBoot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Wed, 11 Jun 2025 05:15:09 GMT</pubDate>
            <description><![CDATA[<p>SpringBoot를 간단히 공부를 해보고 ‘이제는 프로젝트를 개발하면서 부족한 부분들을 채워야 겠다’
라는 생각으로 React+SpringBoot를 이용한 프로젝트를 개발하려는데..</p>
<p>기획부터 DB설계, 디자인, 개발, 배포 등등.. 아직 혼자 프로젝트를 개발해 본적이 없는 저에겐
너무 막막하달까..</p>
<p>그래서 회사 선임분께 조언을 구하니 “처음부터 프로젝트 전부 다 개발하지말고 기능 하나씩 해봐~ 로그인 해보면 좋겠네”라고 해주셔서</p>
<p><del>역시.. 시니어는 다르군..</del></p>
<p>그래서~ 프로젝트 개발하기 전에 기능들을 하나씩 개발해보려 합니다~!</p>
<p>먼저 로그인 기능을 구현하면서 Spring Security 프레임워크를 공부해 보겠습니다!
<a href="https://github.com/YoonHan0/login">[구현중인 프로젝트 깃 주소]</a></p>
<h2 id="🌱-프로젝트-생성하기">🌱 프로젝트 생성하기</h2>
<p>시작이 반이다..</p>
<ul>
<li>React: <code>19버전</code></li>
<li>SpringBoot: <code>3.5.0</code></li>
<li>Java: <code>21</code></li>
<li>IDE: <code>VScode</code>, <code>IntelliJ</code></li>
</ul>
<br />

<h3 id="1️⃣-react-프로젝트-생성하기">1️⃣ React 프로젝트 생성하기</h3>
<p>리액트 프로젝트는 아시다시피 간단하게 생성이 가능합니다.
최근에는 npm 명령어말고 다른 방법으로도 생성한다고 하니 검색해 보시면 좋을 것 같습니다.</p>
<p>프로젝트를 생성할 경로에 가서 명령어를 쳐줍니다.
<code>npx create-react-app frontend</code></p>
<p>그랬더니 저는 이런 명령어가 떠서</p>
<pre><code>You are running `create-react-app` 5.0.1, which is behind the latest release (5.1.0).

We recommend always using the latest version of create-react-app if possible.

The latest instructions for creating a new app can be found here:
https://create-react-app.dev/docs/getting-started/</code></pre><p>확인해 보니 현재 사용 중인 create-react-app(CRA)의 버전이 최신이 아니라고 안내해 주는 것이라고 합니다.</p>
<p>그래서 아래 명령어로 프로젝트를 생성하였습니다.
<code>npx create-react-app@latest frontend</code></p>
<p>프로젝트를 생성한 후 <code>npm start</code>를 해서 올바르게 프로젝트가 동작하는지 확인합니다.</p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/00246109-0876-404e-a810-577e3c3849b1/image.png" alt=""></p>
<br />

<h3 id="2️⃣-springboot-프로젝트-생성하기">2️⃣ SpringBoot 프로젝트 생성하기</h3>
<p>다음으로 SpringBoot 프로젝트를 생성해 줍니다.</p>
<p>부트 프로젝트에 대한 설명을 잘해주신 블로거가 있어서 참고하시면 좋을 것 같습니다.
<a href="https://hel-p.tistory.com/25">[프로젝트 생성 참고]</a></p>
<p>프로젝트를 생성하고 설정 부분은 일전에 작성했던 내용을 참고하였습니다.
<a href="https://github.com/YoonHan0/hello-spring/blob/main/memo/BUILD.md">[프로젝트 설정 참고]</a></p>
<p>설정을 마치고 빌드 후 각자의 <code>Application.java</code> 파일을 실행하여 올바르게 실행되는지 확인합니다.</p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/97497cd2-7f34-4f99-8e7f-3472646bf3a9/image.png" alt=""></p>
<p>뭔가 잘못된 것처럼 보이지만 단순히 정적 리소스 경로에 index.html과 같은 파일이 없어서 나타나는 화면입니다. 걱정없이 기기</p>
<br />

<h3 id="3️⃣-cors-설정하기">3️⃣ CORS 설정하기</h3>
<p>React, SpringBoot 프로젝트를 각각 생성하였다면 두 프로젝트를 연동시켜야 합니다.</p>
<p>이때 반드시 넣어줘야 하는게 CORS 설정입니다.
하나의 로컬에서 두 개의 서버를 구동시킬 때 서로의 포트에 접근하기 위해서 설정을 해야한다..?
<a href="https://siyoonn.tistory.com/153">[CORS 설정하기 참고]</a></p>
<p>위 CORS 설정하기 참고 블로그에 4번 CORS 설정하기를 보면 <code>WebMvcConfigurer</code> 인터페이스를 구현하는 부분이 나오는데 해당 부분이 이해가 잘 가지 않아 GPT와 <a href="https://engineerinsight.tistory.com/79#%F0%9F%92%8B%20WebConfig%20%ED%8C%8C%EC%9D%BC%EC%9D%B4%EB%9E%80%3F-1">[WebMvcConfigurer 구현 참고]</a> 블로그를 참고하였습니다.</p>
<ul>
<li>+추가 이야기
<img src="https://velog.velcdn.com/images/yoon_han0/post/8008bf39-0b3a-4af0-bf03-a7bc3c4e517d/image.png" alt=""></li>
</ul>
<p>블로그를 참고하면서 예제를 하나씩 만들어보기 귀찮을 것 같은 분들을 위해 간략하게 CORS 설정하는 방법을 알려드리겠습니다.</p>
<br />

<p>먼저 frontend, backend 폴더 구조를 참고해 주세요~!</p>
<pre><code class="language-json"># backend
login/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── login/
│   │   │        ├── config
│   │   │        │    ├── WebConfig.java
│   │   │        ├── controller
│   │   │        │    ├── LoginController.java
│   │   │        ├── model
│   │   │        │    ├── UserInfo.java
│   │   │       └── LoginApplication.java
│   │   └── resources/
│   │       └── application.yml
├── build.gradle
├── settings.gradle</code></pre>
<pre><code class="language-json"># frontend
frontend/
├── node_modules
├── public
├── src
│   ├── components
│   │   ├── views
│   │   │   └── home
│   │   │        ├── MainHome.js
│   ├── App.js
├── package.json</code></pre>
<br />

<p>1.리액트 포트 설정: <code>3000</code>
리액트 앱이 3000 포트로 실행되도록 지정합니다.
기본적으로 리액트는 3000 포트를 사용해서 실행합니다. 다른 포트를 원할 경우 아래 설정으로 변경할 수 있습니다.</p>
<pre><code class="language-json">/* package.json */
&quot;scripts&quot;: {
    &quot;start&quot;: &quot;PORT=3000 react-scripts start&quot;,
      ...
}</code></pre>
<br />

<ol start="2">
<li>자바(SpringBoot) 포트 설정: <code>8085</code><pre><code class="language-yml"># application.yml
server:
port: 8085
servlet:
 context-path: /login  # 애플리케이션 기본 경로를 &#39;/login&#39;으로 고정, 모든 URL 경로가 &#39;/login&#39;으로 지정됨
</code></pre>
</li>
</ol>
<p>spring:
  application:
    name: login</p>
<pre><code>
&lt;br /&gt;

3. proxy 설정
찾아본 내용으로는 `package.json`에서 주석을 다는 방법이 따로 존재하진 않고, 아래처럼 `__...`과 같은 방식으로 주석을 처리한다고 하여 한 번 해봤습니다 :)
``` json
/* package.json */
&quot;__comment2&quot;: &quot;프론트 개발환경에서 API 요청을 백엔드(SpringBoot)로 우회(Proxy)시키기 위한 설정&quot;,
&quot;proxy&quot;: &quot;http://localhost:8085&quot;,</code></pre><br />

<ol start="4">
<li>CORS 설정</li>
</ol>
<p><strong>CORS(Cross-Origin Resource Sharing) 설정</strong>을 통해 프론트 <code>http://localhost:3000</code>에서 백엔드에 요청할 수 있도록 허용해 주었습니다.
해당 설정은 다른 도메인(포트)에서 API에 접근할 때 필요합니다.</p>
<p><code>WebConfig.java</code> 클래스 생성</p>
<pre><code class="language-java">/*
    SpringBoot는 기본적으로 @SpringBootApplication 어노테이션이 붙은 클래스가 위치한 패키지와 그 하위 패키지들을 스캔하여
    @Configuration, @Component, @Service, @Repository 등의 어노테이션이 붙은 클래스를 자동으로 감지하고 설정합니다.
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(&quot;/**&quot;)             // 모든 경로에 대해
                .allowedOrigins(&quot;http://localhost:3000&quot;)  // 허용할 출처(React 개발 서버)
                .allowedMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;)  // 허용할 HTTP 메서드
                .allowedHeaders(&quot;*&quot;)                             // 허용할 헤더
                .allowCredentials(true);                         // 쿠키, 인증 정보 허용 여부
    }
}</code></pre>
<br />

<ol start="5">
<li>코드 추가</li>
</ol>
<p><code>MainHome.js</code> 컴포넌트 생성</p>
<pre><code class="language-jsx">import React, { useEffect, useState } from &#39;react&#39;

const MainHome = () =&gt; {

    const [data, setData] = useState({});

    useEffect(() =&gt; {
        const fetchData = async () =&gt; {
            try {
                const response = await fetch(&#39;/login/getUserInfo&#39;);      // GET요청
                const data = await response.json();
                console.log(&quot;### 데이터 확인 ### \n&quot;, data);
                setData(data);
            } catch (error) {
                console.log(&quot;FETCH ERROR &quot;, error);
            }
        };
        fetchData();
    }, []);

    return (
        &lt;div&gt;
            &lt;h1&gt;등록된 회원정보&lt;/h1&gt;
            {data &amp;&amp; (
                &lt;div&gt;
                    &lt;p&gt;Email: {data.email}&lt;/p&gt;
                    &lt;p&gt;Name: {data.name}&lt;/p&gt;
                &lt;/div&gt;
            )}
        &lt;/div&gt;
    )
};

export default MainHome;</code></pre>
<p><code>MainHome 컴포넌트</code>를 호출하도록 <code>App.js</code> 수정</p>
<pre><code class="language-jsx">import &#39;./App.css&#39;;
import MainHome from &#39;./components/views/home/MainHome&#39;;


function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
        &lt;MainHome /&gt;      
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p><code>LoginController.java</code>, <code>UserInfo</code> 클래스 생성</p>
<pre><code class="language-java">package com.example.login.controller;

import com.example.login.model.UserInfo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
//@RequestMapping()
public class LoginController {

    @GetMapping(&quot;/getUserInfo&quot;)
    public Object getUserInfo() {
        UserInfo userInfo = new UserInfo();
        // ##### 예제 데이터 반환 #####
        userInfo.setEmail(&quot;YoonHan0@naver.com&quot;);
        userInfo.setName(&quot;한영윤&quot;);

        return userInfo;
    }
}</code></pre>
<br />

<ol start="6">
<li>결과확인
Boot 서버를 실행한 후 → React 서버를 실행하게 되면 결과를 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/yoon_han0/post/5bb122f3-8248-4dfe-be12-4a95fdaf90b5/image.png" alt=""></li>
</ol>
<br />
<br />
<br />

<p>아직 DB까지는 연결하지 않았지만 개발을 진행하면서 DB 연결은 
<a href="https://siyoonn.tistory.com/155">[DB연결 참고]</a>블로그의 3번 데이터베이스 생성 부분을 참고할 예정입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[7. JPA 사용하기]]></title>
            <link>https://velog.io/@yoon_han0/7.-JPA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoon_han0/7.-JPA-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Jun 2025 11:18:06 GMT</pubDate>
            <description><![CDATA[<p>DB연결까지 마무리되었으니 JPA를 사용하여 데이터를 <code>INSERT</code>, <code>SELECT</code> 할 수 있도록 구현하겠습니다.</p>
<h3 id="jpa-사용-전-member-테이블-생성">JPA 사용 전 Member 테이블 생성</h3>
<pre><code class="language-sql">/* MEMBER 테이블 생성하기 */
drop table if exists member CASCADE;
create table member
(
    id   bigint generated by default as identity,
    name varchar(255),
    primary key (id)
);</code></pre>
<img src="https://velog.velcdn.com/images/yoon_han0/post/24e918c9-d7ea-472a-93ad-19803cc78be2/image.png" width="60%" height="60%">

<p>쿼리를 실행란에서 실행하면 <code>MEMBER</code> 테이블이 생성됩니다.
쿼리 전체를 드레그한 후 <code>ctrl+Enter</code> 단축키로 쿼리 실행이 가능합니다.</p>
<br />

<h2 id="🚀-jpa">🚀 JPA</h2>
<ul>
<li><p>JPA는 기존의 반복적인 코드는 물론이고, 기본적인 SQL도 직접 만들어서 실행시켜 준다.</p>
</li>
<li><p>JPA를 사용하면 SQL과 데이터 중심의 설계에서 <strong>객체 중심의 설계</strong>로 패러다임을 전환할 수 있다.</p>
<pre><code class="language-java">/* JDBC Template 코드 */
@Override
public List&lt;Member&gt; findAll() {
    return jdbcTemplate.query(&quot;SELECT * FROM MEMBER&quot;, memberRowMapper());
}

/* -------------------- */

/* JPA 코드 */
// from절을 보면 테이블이 아닌 Entity 객체를 대상으로 사용
@Override
public List&lt;Member&gt; findAll() {
    return em.createQuery(&quot;SELECT m FROM Member m&quot;, Member.class).getResultList();
}</code></pre>
</li>
<li><p>개발 생산성을 크게 높일 수 있다.</p>
</li>
</ul>
<br />

<h2 id="👨💻-jpa를-사용해보자">👨‍💻 JPA를 사용해보자</h2>
<h3 id="1-buildgradle-파일에-jpa-라이브러리-추가">1. <code>build.gradle</code> 파일에 JPA 라이브러리 추가</h3>
<pre><code class="language-java">dependencies {
    ...
    // implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39;   주석 처리 
    implementation &#39;org.springframework.boot:spring-boot-starter-data-jpa&#39;
}</code></pre>
<p><code>spring-boot-starter-data-jpa</code>는 내부에 jdbc 관련 라이브러리를 포함하기 때문에 사용했던 jdbc 라이브러리는 제거해도 됩니다.</p>
<br />

<h3 id="2-applicationproperties-파일에-jpa-설정-추가">2. <code>application.properties</code> 파일에 JPA 설정 추가</h3>
<pre><code>...
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none</code></pre><ul>
<li><code>show-sql</code>: JPA가 생생하는 SQL을 출력합니다.</li>
<li><code>ddl-auto</code>: JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 <code>none</code>을 사용하여 해당 기능을 사용하지 않습니다.<ul>
<li><code>create</code>를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해줍니다.</li>
</ul>
</li>
</ul>
<br />

<h3 id="3-jpa-엔티티-맵핑">3. JPA 엔티티 맵핑</h3>
<ul>
<li><p>Member 클래스</p>
<pre><code class="language-java">  @Entity
  public class Member {

      @Id @GeneratedValue(strategy = GenerationType.IDENTITY)     // IDENTITY 전략: 디비에 값을 넣을 때 자동으로 채번을 해주는 (ex/auto Increament) 것을 말한다.
      private Long id;

      // @Column(name = &quot;userName&quot;) // 컬럼명이 userName이라면 이렇게 맵핑할 수 있음
      private String name;

      public Long getId() {
          return id;
      }

      public String getName() {
          return name;
      }

      public void setId(Long id) {
          this.id = id;
      }

      public void setName(String name) {
          this.name = name;
      }
  }</code></pre>
</li>
<li><p>JpaMemberRepository 클래스</p>
<ul>
<li><p>JPA는 <code>EntityManager</code>로 모두 동작을 함</p>
</li>
<li><p>gradle 파일에 jpa dependency 를 추가하게 되면 Spring이 현재 DB와 연결해서 <code>EntityManager</code>를 생성해줌</p>
</li>
<li><p>개발자는 생성된 <code>EntityManager</code>를 injection 받기만 하면 됨</p>
<pre><code class="language-java">    public class JpaMemberRepository implements MemberRepository {

      private final EntityManager em;
      /*
      * JPA는 EntityManager로 모두 동작을 함
      * gradle 파일에 jpa dependency 를 추가하게 되면 Spring이 현재 DB와 연결해서 EntityManager를 생성해줌
      * 우리는 생성된 EntityManager를 injection 받기만 하면 된다~!
      * */

      public JpaMemberRepository(EntityManager em) {
          this.em = em;
      }

      @Override
      public Member save(Member member) {
          em.persist(member);
          return member;
      }

      @Override
      public Optional&lt;Member&gt; findById(Long id) {
          Member member = em.find(Member.class, id);
          return Optional.ofNullable(member);
      }

      @Override
      public Optional&lt;Member&gt; findByName(String name) {
          List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m where m.name = :name&quot;, Member.class)
                  .setParameter(&quot;name&quot;, name)
                  .getResultList();

          return result.stream().findAny();
      }

      @Override
      public List&lt;Member&gt; findAll() {
          List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m&quot;, Member.class).getResultList();       // 테이블이 아닌 Entity 객체를 대상으로 사용
          return result;
      }
}</code></pre>
</li>
</ul>
</li>
</ul>
<br />

<h3 id="4-서비스-계층에-트랜잭션-추가">4. 서비스 계층에 트랜잭션 추가</h3>
<pre><code class="language-java">@Transactional
public class MemberService {
    ...
}</code></pre>
<ul>
<li><code>org.springframework.transaction.annotation.Transactional</code> 를 사용하자</li>
<li><strong>JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행하여야 한다.</strong></li>
</ul>
<br />

<h3 id="5-스프링-설정-파일-변경-springconfig">5. 스프링 설정 파일 변경 (<code>SpringConfig</code>)</h3>
<pre><code class="language-java">@Configuration
public class SpringConfig {

//    private DataSource dataSource;
//
//    @Autowired
//    public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; }

    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {
        this.em = em;
    }

    ...

    @Bean
    public MemberRepository memberRepository() {
//        return new MemoryMemberRepository();
//        return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }

    /* Spring이 실행되면 MemberService, MemberRepository를 bean으로 등록하고, 위에 작성된 것처럼 MemberService에 memberRepository를 주입해준다. */
}</code></pre>
<h3 id="6-결과화면">6. 결과화면</h3>
<p>위까지 마무리하게 되면 드디어 DB를 사용하여 데이터를 다룰 수 있게 됩니다.</p>
<ol>
<li>Spring 프로젝트를 실행하고</li>
<li>h2 데이터베이스도 실행합니다.</li>
</ol>
<h4 id="1️⃣-메인화면">1️⃣ 메인화면</h4>
<img src="https://velog.velcdn.com/images/yoon_han0/post/fb777e84-3566-4348-a7cb-363f3a9a1a54/image.png" width="60%" height="60%">

<h4 id="2️⃣-회원가입">2️⃣ 회원가입</h4>
<img src="https://velog.velcdn.com/images/yoon_han0/post/f70e5e73-928e-4c0d-beaa-76bd0def3b39/image.png" width="60%" height="60%">

<h4 id="3️⃣-회원목록">3️⃣ 회원목록</h4>
<img src="https://velog.velcdn.com/images/yoon_han0/post/ad5c2cdf-27ff-4347-8e68-fa930775f78f/image.png" width="60%" height="60%">
<img src="https://velog.velcdn.com/images/yoon_han0/post/09ed4bfd-77d1-43b0-9612-5da8fab44227/image.png" width="60%" height="60%">

<p>위에서 JPA 엔티티 <code>Member</code> 클래스를 만들면서</p>
<pre><code class="language-java">@Id @GeneratedValue(strategy = GenerationType.IDENTITY)     // IDENTITY 전략: 디비에 값을 넣을 때 자동으로 채번을 해주는 (ex/auto Increament) 것을 말한다.
private Long id;</code></pre>
<p><code>id</code> 필드에 <code>auto Increament</code> 설정을 하였기 때문에 자동으로 채번이 되는 것을 확인할 수 있습니다.</p>
<p>DB에도 데이터가 쌓이는 것을 확인할 수 있습니다.</p>
<img src="https://velog.velcdn.com/images/yoon_han0/post/abaa881c-524f-4e51-a1ba-feec9c23c355/image.png" width="60%" height="60%">


]]></description>
        </item>
        <item>
            <title><![CDATA[6. DB 연결하기]]></title>
            <link>https://velog.io/@yoon_han0/6.-DB-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yoon_han0/6.-DB-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Jun 2025 10:46:21 GMT</pubDate>
            <description><![CDATA[<p>앞서 개발한 회원관리 예제에 DB를 연결하는 과정을 보여드리겠습니다.
DB로는 H2 데이터베이스를 사용하도록 하겠습니다.</p>
<p>실제로 현업에서 로컬 테스트를 위해서 H2 데이터베이스를 사용한다고 하더라구요🙃</p>
<br />

<h2 id="🧑💻-h2-데이터베이스-설치">🧑‍💻 H2 데이터베이스 설치</h2>
<p>개발이나 테스트 용도로 가볍고 편리한 DB, 웹 화면 제공 <br />
<a href="https://www.h2database.com">다운로드</a> <br /></p>
<ul>
<li><p>다운로드 및 설치</p>
</li>
<li><p>h2 데이터베이스 버전은 스프링 부트 버전에 맞춘다.</p>
</li>
<li><p>다운로드한 h2 폴더 내부에 bin 폴더에 <code>h2.sh</code> 파일이 존재 (h2/bin/)</p>
</li>
<li><p>권한 주기: <code>chmod 755 h2.sh</code></p>
</li>
<li><p>실행: <code>./h2.sh</code></p>
<p>실행 시 발생할 수 있는 오류
  <img src="https://github.com/user-attachments/assets/f87c31f2-2eff-4fa0-bd21-43aec234e436" alt="Image"></p>
<p>오류 내용을 보면 Java 버전이 잘못됐다는 오류인데 (<code>55 = Java 11, 52 = Java 8</code>) <a href="https://www.inflearn.com/community/questions/53693/jar-%EC%9D%84-%EC%8B%A4%ED%96%89%ED%95%A0%EB%95%8C-%EC%97%90%EB%9F%AC%EA%B0%80-%EB%B0%9C%EC%83%9D%ED%95%B4%EC%9A%94?srsltid=AfmBOorsVl6Y_udIqSIE1hRH1FOW3HsIF4WFTxonhdCp5_5tOFf6B1d_">참고</a></p>
</li>
</ul>
<pre><code class="language-shell">    vi h2.sh    # shell 파일을 열어서

    # --- java -cp &quot;$dir/h2-2.3.232.jar... 구문 상단에 작성해야 합니다. ---
    export JAVA_HOME=실행할 때 사용하고 싶은 JAVA 버전의 경로    # ex) /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home    
    export PATH=$JAVA_HOME/bin:$PATH
    # ---------------------------------------------------------------</code></pre>
<p>쉘 파일을 닫고 다시 실행 <code>./h2.sh</code></p>
<ul>
<li><p>데이터베이스 파일 생성 방법</p>
<img width="483" alt="Image" src="https://github.com/user-attachments/assets/9731d059-f307-4f3d-8c93-33f261fe2f76" />

<ol>
<li><p>JDBC URL: <code>jdbc:h2:~/test</code> (최초 한번) → 연결</p>
</li>
<li><p><code>&#39;~/test.mv.db</code> 파일 생성 확인</p>
   <img width="651" alt="Image" src="https://github.com/user-attachments/assets/ffc2c9b7-b3bd-4d78-b034-9c3593539b18" />

<ul>
<li>이후부터는 JDBC URL 입력란에 <code>jdbc:h2: tcp://localhost/~/test</code>로 수정해서 접속</li>
</ul>
</li>
</ol>
</li>
</ul>
<br />
<br />

<h2 id="✅-h2-데이터베이스-사용하기">✅ H2 데이터베이스 사용하기</h2>
<h3 id="buildgradle-파일에-jdbc-h2-데이터베이스-관련-라이브러리-추가">build.gradle 파일에 jdbc, h2 데이터베이스 관련 라이브러리 추가</h3>
<pre><code class="language-java">dependencies {
    ...
    implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39; /* JAVA에서 DB랑 연결을 하기 위해서는 반드시 JDBC가 필요하다. */ 
    runtimeOnly &#39;com.h2database:h2&#39;     /* DB에서 필요한 클라이언트를 h2를 사용한다. */
}</code></pre>
<h3 id="스프링-부트-데이터베이스-연결-설정-추가">스프링 부트 데이터베이스 연결 설정 추가</h3>
<pre><code class="language-xml">  &lt;!-- resources/application.properties */ --&gt;
  spring.datasource.url=jdbc:h2:tcp://localhost/~/test    &lt;!-- spring.datasource.url=jdbc:h2:~/test(최초 한 번) --&gt;
  spring. datasource.driver-class-name=org.h2.Driver
  spring.datasource.username=sa</code></pre>
<p>DB 접속 정보를 Spring Boot에게 제공해서 Spring이 접속에 필요한 처리를 해주도록 하는 로직</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5. 회원관리 예제 개발]]></title>
            <link>https://velog.io/@yoon_han0/5.-%ED%9A%8C%EC%9B%90%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@yoon_han0/5.-%ED%9A%8C%EC%9B%90%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 18 Apr 2025 07:04:12 GMT</pubDate>
            <description><![CDATA[<h1 id="🧑💻-회원-관리-예제---웹-mvc-개발">🧑‍💻 회원 관리 예제 - 웹 MVC 개발</h1>
<br />

<p>공부한 내용을 바탕으로 간단한 예제를 만들어 보겠습니다 :) <br />
회원가입을 하고 회원 목록을 확인할 수 있는 간단한 프로그램입니다.</p>
<h2 id="🖥️-결과화면">🖥️ 결과화면</h2>
<h3 id="메인화면">메인화면</h3>
<p><img src="https://github.com/user-attachments/assets/cc59cdf5-03be-4a49-8014-dc91041f7868" alt="Image1"></p>
<h3 id="회원가입-화면">회원가입 화면</h3>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/44444c7f-ae18-462b-9816-869d43c59cd6/image.png" alt=""></p>
<h3 id="회원-목록-화면">회원 목록 화면</h3>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/ee8db48a-5509-49f3-aa9d-f80c9e091101/image.png" alt=""></p>
<br />

<hr>
<h2 id="⚙️-기능-구현">⚙️ 기능 구현</h2>
<h3 id="1--경로에-접근했을-때-출력될-메인-화면-개발">1. (&quot;/&quot;) 경로에 접근했을 때 출력될 메인 화면 개발</h3>
<p><img src="https://github.com/user-attachments/assets/cc59cdf5-03be-4a49-8014-dc91041f7868" alt="Image1"></p>
<ol>
<li><p><code>home.html</code> 파일 생성</p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;

&lt;div class=&quot;container&quot;&gt;
  &lt;div&gt;
      &lt;h1&gt;Hello Spring&lt;/h1&gt;
      &lt;p&gt;회원 기능&lt;/p&gt;
      &lt;p&gt;
          &lt;a href=&quot;/members/new&quot;&gt;회원 가입&lt;/a&gt;
          &lt;a href=&quot;/members&quot;&gt;회원 목록&lt;/a&gt;
      &lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt; &lt;!-- container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
</li>
<li><p>웹 브라우저에서 <code>localhost:8080</code>으로 요청이 왔을 때 맵핑될 메서드 구현</p>
<pre><code class="language-java">/* MemberController */
@Controller
public class MemberController {

 @GetMapping(&quot;/&quot;)
 public String home() {
     return &quot;home&quot;;
 }
}</code></pre>
</li>
</ol>
<hr>
<br />

<h3 id="2-membernew-경로에-접근했을-때-출력될-회원가입-화면-개발">2. (&quot;/member/new&quot;) 경로에 접근했을 때 출력될 회원가입 화면 개발</h3>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/44444c7f-ae18-462b-9816-869d43c59cd6/image.png" alt=""></p>
<ol>
<li><p>화면에 그려줄 파일 생성 <code>resourcces/templates/members/createMemberForm.html</code> 생성</p>
<pre><code class="language-html"> &lt;!-- ... --&gt;
 &lt;form action=&quot;/members/new&quot; method=&quot;post&quot;&gt;
     &lt;div class=&quot;form-group&quot;&gt;
         &lt;label for=&quot;name&quot;&gt;이름&lt;/label&gt;
         &lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot; placeholder=&quot;이름을 입력하세요.&quot;&gt;
     &lt;/div&gt;
     &lt;button type=&quot;submit&quot;&gt;등록&lt;/button&gt;
 &lt;/form&gt;
 &lt;!-- ... --&gt;</code></pre>
</li>
<li><p>form 태그에서 처리하는 데이터 받아줄 model 생성 <code>MemberForm</code> 생성</p>
<pre><code class="language-java"> public class MemberForm {
     private String name;        // 이름을 입력하면 해당 name 필드에 값이 저장되게 됩니다.

     public String getName() {
         return name;
     }
     public void setName(String name) {
         this.name = name;
     }
 }</code></pre>
</li>
<li><p><code>MemberController</code> 메서드 추가 </p>
<pre><code class="language-java">
@Controller
public class MemberController {

  private final MemberService memberService;

  @Autowired  // 의존성 주입 : DI, 연결하는 로직
  public MemberController(MemberService memberService) {
      this.memberService = memberService;
  }

  // `/members/new` 경로로 들어오게 되면 viewResolver가 방금 만든 createMemberForm.html을 화면에 그려줌
  @GetMapping(&quot;/members/new&quot;)
  public String createForm() {
      return &quot;members/createMemberForm&quot;; 
  }

  // createMemberForm 화면에서 &quot;등록&quot; 버튼 클릭 시 post 방식으로 호출되면서 create 메서드 호출
  // 파라미터로 MemberForm 클래스를 받으면서 html파일에서 submit한 name 데이터가 MemberForm 클래스의 name 필드로 받아짐
  @PostMapping(&quot;/members/new&quot;)
  public String create(MemberForm form) {
      Member member = new Member();
      member.setName(form.getName());

      System.out.println(form.getName()); // 입력한 데이터 확인
      memberService.join(member);

      return &quot;redirect:/&quot;;
  }
}</code></pre>
</li>
<li><p><code>MemberService</code> 메서드 구현</p>
<pre><code class="language-java">
/* Service는 비지니스적으로 함수명을 작성하던지 해야합니다. 
:. Service가 비지니스 처리를 하는 곳이기도 하고 추후에 어떤 문제가 생겨서 오류를 찾을 때도 찾기 편하기 때문에 */
@Service
public class MemberService {

   private final MemberRepository memberRepository;

   // 클래스 내에 생성자가 하나만 있으면 @Autowired 생략 가능(Spring 4.3 이상)
   public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
   }
   /**
   * &gt; 회원가입
   * 같은 이름이 있는 중복 회원X
   */
   public Long join(Member member) {
        // 요즘에는 null로 반환될 수 있을법한 로직에는 &quot;변수 != null&quot; 이렇게 처리하지 않고 Optional로 감싸서 처리
        // ex) Optional&lt;Member&gt; result = memberRepository.findByName(member.getName());
        validateDuplicateMember(member);    // 중복 회원 검증
        memberRepository.save(member);

        return member.getId();
   }
   /* 회원명으로 중복체크하는 메서드 */
   private void validateDuplicateMember(Member member) {
       // isPresent(): Optional이 값을 포함하고 있는지 확인만 하는 메서드, if인데 is로 오타가 나서 오류가 났었음..
       // ifPresent()는 Optional에 값이 존재하면 람다식을 실행하는 메서드이므로, 여기서 예외를 던지는 로직을 넣을 수 있습니다.
       memberRepository.findByName(member.getName())
             .ifPresent(m -&gt; {
                 throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
             });
   }
}</code></pre>
</li>
<li><p><code>MemberRepository</code> 생성 <br />
인터페이스를 활용하여 여러 클래스가 동일한 메서드를 구현하도록 강제하여 일관성을 유지할 수 있음</p>
<pre><code class="language-java">public interface MemberRepository {
    Member save(Member member);
    Optional&lt;Member&gt; findById(Long id);
    Optional&lt;Member&gt; findByName(String name);
    List&lt;Member&gt; findAll();
}</code></pre>
</li>
<li><p>구현부 <code>MemoryMemberRepository</code> 생성 <br />
데이터베이스를 연결하기 전이므로 메모리에 데이터를 저장하고 조회하는 방식으로 진행하겠습니다.</p>
<pre><code class="language-java">/* MemberRepository 인터페이스를 구현하는 클래스 */
@Repository
public class MemoryMemberRepository implements MemberRepository{

    private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;();    // 메모리 기반 데이터 저장소로 활용
    private static long sequence = 0L;

    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        return store.values().stream()
            .filter(member -&gt; member.getName().equals(name))
            .findAny();
    }
}</code></pre>
</li>
</ol>
<hr>
<br />

<h3 id="2-members-경로에-접근했을-때-출력될-회원목록-화면-개발">2. (&quot;/members&quot;) 경로에 접근했을 때 출력될 회원목록 화면 개발</h3>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/ee8db48a-5509-49f3-aa9d-f80c9e091101/image.png" alt=""></p>
<ol>
<li><p>화면에 회원 목록을 그려줄 파일 생성 <code>memberList.html</code></p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;

&lt;div class=&quot;container&quot;&gt;
    &lt;div&gt;
        &lt;table&gt;
            &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;#&lt;/th&gt;
                &lt;th&gt;이름&lt;/th&gt;
            &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
            &lt;tr th:each=&quot;member : ${members}&quot;&gt;   &lt;!-- thymeleaf 문법 --&gt;
                &lt;td th:text=&quot;${member.id}&quot;&gt;&lt;/td&gt;
                &lt;td th:text=&quot;${member.name}&quot;&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;/tbody&gt;
        &lt;/table&gt;
    &lt;/div&gt;
&lt;/div&gt; &lt;!-- container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
</li>
<li><p><code>MemberController</code>에 메서드 추가</p>
<pre><code class="language-java">@GetMapping(&quot;/members&quot;)      // home.html의 경로 &lt;a href=&quot;/members&quot;&gt;회원 목록&lt;/a&gt;
 public String list(Model model) {
     List&lt;Member&gt; members = memberService.findMembers();
     model.addAttribute(&quot;members&quot;, members);     // key: members, value: members(변수)

     return &quot;members/memberList&quot;;
 }</code></pre>
</li>
<li><p><code>MemberService</code> 클래스의 <code>findMembers()</code> 메서드 구현</p>
<pre><code class="language-java"> /* 전체 회원 조회 */
 public List&lt;Member&gt; findMembers() {
     return memberRepository.findAll();
 }</code></pre>
</li>
<li><p><code>MemoryMemberRepository</code> 클래스의 <code>findAll()</code> 메서드 구현</p>
<pre><code class="language-java"> @Override
 public List&lt;Member&gt; findAll() {
     return new ArrayList&lt;&gt;(store.values());     // 메모리에 저장된 데이터 반환 
 }</code></pre>
</li>
</ol>
<hr>
<br />

<p>여기까지 완료되면 예제 프로그램을 개발하게 됩니다 :) <br />
코드 중간중간에 나오는 <a href="https://github.com/YoonHan0/hello-spring/blob/main/memo/Bean1.md">의존성 주입</a>, <a href="https://github.com/YoonHan0/hello-spring/blob/main/memo/Bean1.md">@Controller, @Service, @Repository</a>, <a href="https://github.com/YoonHan0/hello-spring/blob/main/memo/interface.md">인터페이스</a>와 같은 내용들은 <br />
링크를 참고해서 공부해 주시면 감사하겠습니다 👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[4. Spring 웹 애플리케이션 구조]]></title>
            <link>https://velog.io/@yoon_han0/4.-Spring-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@yoon_han0/4.-Spring-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Fri, 18 Apr 2025 07:02:46 GMT</pubDate>
            <description><![CDATA[<h2 id="🌐-spring-웹-애플리케이션-구조">🌐 Spring 웹 애플리케이션 구조</h2>
<p><img width="563" alt="Image" src="https://github.com/user-attachments/assets/a2637fdb-8fb1-453b-837b-5e865056aed1" /> <br />
<small><em>이미지 출처: 인프런 강의(스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술)</em></small> <br /><br />
Spring을 사용한 일반적인 웹 애플리케이션은 <strong>역할별로 계층을 나누어 구조화</strong>되어 있습니다. <br />
이렇게 계층을 나누면 <strong>유지보수성이 좋아지고, 확장성이 뛰어나며, 코드의 가독성이 향상</strong>됩니다.</p>
<br />

<h2 id="🏗️-계층별-역할-정리">🏗️ 계층별 역할 정리</h2>
<h3 id="1-controller-컨트롤러">1. Controller (컨트롤러)</h3>
<p><strong>&quot;사용자 요청을 받아서 처리하는 역할&quot;</strong>  </p>
<ul>
<li>웹 MVC의 컨트롤러 역할, 사용자가 웹에서 요청을 보내면, 컨트롤러가 그 요청을 받아 적절한 로직을 호출</li>
<li><code>@Controller</code> 또는 <code>@RestController</code> 어노테이션을 사용</li>
<li><code>Service</code> 계층을 호출해서 필요한 로직을 수행한 후, 결과를 반환</li>
</ul>
<br />

<h3 id="2-service-서비스">2. Service (서비스)</h3>
<p><strong>&quot;비즈니스 로직 구현하는 핵심 계층&quot;</strong></p>
<ul>
<li>데이터를 조작하거나, 여러 개의 리포지토리를 호출하는 등 핵심적인 로직을 처리</li>
<li><code>@Service</code> 어노테이션을 사용</li>
<li><code>Controller</code>와 <code>Repository</code> 사이에서 중간 역할을 수행</li>
</ul>
<br />

<h3 id="3-repository-리포지토리">3. Repository (리포지토리)</h3>
<p><strong>&quot;데이터베이스 접근, 도메인 객체를 DB에 저장하고 관리하는 계층&quot;</strong></p>
<ul>
<li>데이터 저장, 조회, 수정, 삭제 등의 기능을 수행</li>
<li>JPA, MyBatis, JDBC 등의 기술을 사용할 수 있음</li>
<li><code>@Repository</code> 어노테이션을 사용 (Spring Data JPA를 사용하면 생략 가능)</li>
</ul>
<br />

<h3 id="4-domain-도메인">4. Domain (도메인)</h3>
<p><strong>&quot;비즈니스 도메인 객체&quot;</strong></p>
<ul>
<li>일반적으로 데이터베이스에 저장되는 회원, 주문, 상품, 쿠폰 등과 같은 개념</li>
<li>JPA를 사용한다면 <code>@Entity</code> 어노테이션을 사용하여 JPA가 관리하도록 설정 가능</li>
<li>도메인 객체는 비즈니스 규칙을 포함할 수도 있음 (예: 할인 정책)</li>
</ul>
<br />

<hr>
<br />

<p>위 계층을 기반으로 간단한 <strong>회원관리 예제</strong>를 만들어 보겠습니다.</p>
<p>개발을 하기 전 전체적인 프로젝트 구조는 아래와 같습니다 :)</p>
<br />

<h2 id="📁-프로젝트-구조">📁 프로젝트 구조</h2>
<pre><code>├── 📂 backend         
│   ├── 📂 src
│   │   ├── 📂 main
│   │   │   ├── 📂 java/com/hello/hello_spring  
│   │   │   │   ├── 📂 controller    # API 컨트롤러
│   │   │   │   │   ├── 📜 MemberController.java
│   │   │   │   │   ├── 📜 HomeController.java  
│   │   │   │   ├── 📂 service       # 비즈니스 로직
│   │   │   │   │   ├── 📜 MemberService.java  
│   │   │   │   ├── 📂 domain        # 비즈니스 도메인 객체
│   │   │   │   │   ├── 📜 Member.java
│   │   │   │   ├── 📂 repository    # 데이터 액세스 계층
│   │   │   │   │   ├── 📜 MemberRepository.java         # 인터페이스
│   │   │   │   │   ├── 📜 MemoryMemberRepository.java   # 구현체
│   │   │   │   ├── 📜 HelloSpringApplication.java  # Spring Boot 실행 파일
│   │   ├── 📂 test  # 테스트 코드
│   ├── 📜 build.gradle
│   │
│   ├── ...</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[3. 🌐 API 방식]]></title>
            <link>https://velog.io/@yoon_han0/3.-API-%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@yoon_han0/3.-API-%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Fri, 04 Apr 2025 02:31:53 GMT</pubDate>
            <description><![CDATA[<h1 id="api-방식">API 방식</h1>
<br />

<h2 id="⚙️-동작방식">⚙️ 동작방식</h2>
<p><img width="646" alt="Image" src="https://github.com/user-attachments/assets/b68250c4-11ae-4561-b68e-a4c3a45953cf" /> <br />
<small><em>이미지 출처: 인프런 강의(스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술)</em></small></p>
<ol>
<li>사용자가 웹 브라우저에서 <code>request</code>를 보냄</li>
<li>톰켓 서버가 <code>Controller</code>에 해당 <code>URL</code>이 있는지 확인</li>
<li>매핑된 메서드에 <code>@ResponseBody</code> 어노테이션이 붙어 있으면
 → <code>ViewResolver</code>가 아닌 <code>HttpMessageConverter</code>로 데이터 전송</li>
<li>반환되는 데이터 타입에 따라 처리 방식이 달라지게 되는데,<ul>
<li><strong>문자 형태</strong>이면<ul>
<li><code>StringConverter</code>가 처리 (StringHttpMessageConverter)</li>
<li><strong>문자 형태</strong> 그대로 웹 브라우저로 반환</li>
</ul>
</li>
<li><strong>객체 형태</strong>이면<ul>
<li><code>JsonConverter</code>가 처리 (MappingJackson2HttpMessageConverter)</li>
<li><strong>객체 → JSON</strong> 형태로 변경하여 웹 브라우저로 반환</li>
</ul>
</li>
</ul>
</li>
</ol>
<br />

<hr>
<h2 id="responsebody-사용">ResponseBody 사용</h2>
<ul>
<li><code>Http Response Body</code>에 <strong>직접 데이터를 반환</strong></li>
<li>템플릿을 사용하지 않고, <strong>데이터 자체를 응답</strong>하는 방식 (<code>ViewResolver</code> 대신에 <code>HttpMessageConverter</code>가 동작)</li>
<li>기본 문자처리: <code>StringHttpMessageConverter</code></li>
<li>기본 객체처리: <code>MappingJacson2HttpMessageConverter</code></li>
<li>byte 처리 등등 기타 여러 <code>HttpMessageConverter</code>가 등록되어 있음</li>
</ul>
<br />

<h3 id="📦-기본-동작하는-httpmessageconverters">📦 기본 동작하는 HttpMessageConverters</h3>
<table>
<thead>
<tr>
<th>타입</th>
<th>Converter</th>
</tr>
</thead>
<tbody><tr>
<td><code>String</code></td>
<td><code>StringHttpMessageConverter</code></td>
</tr>
<tr>
<td><code>Object</code> (ex. DTO)</td>
<td><code>MappingJackson2HttpMessageConverter</code></td>
</tr>
<tr>
<td>기타 바이너리 등</td>
<td>필요 시 추가 등록 가능 (<code>ByteArrayHttpMessageConverter</code> 등)</td>
</tr>
</tbody></table>
<br />

<hr>
<h2 id="🛠️-테스트-방법">🛠️ 테스트 방법</h2>
<ol>
<li>Controller에 코드 추가(HelloController.java)<pre><code class="language-java">/* 1. API 방식 (반환 형식이 문자일 때) */
@GetMapping(&quot;hello-string&quot;)
@ResponseBody       // HTML이 아닌 HTTP에서 Head/Body의 그 body 부분에 내가 직접 값을 넣어주겠다~ 라는 의미
public String helloString(@RequestParam(&quot;name&quot;) String name) {
 return &quot;hello &quot; + name;
}
</code></pre>
</li>
</ol>
<p>/* 2. API 방식 (반환 형식이 JSON일 때) */
@GetMapping(&quot;hello-api&quot;)
@ResponseBody
public Hello helloApi(@RequestParam(&quot;name&quot;) String name) {
    Hello hello = new Hello();
    hello.setName(name);
    return hello;
}</p>
<p>static public class Hello {
    private String name;</p>
<pre><code>public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}</code></pre><p>}</p>
<pre><code>2. 프로젝트 실행 후, 반환 형식이 문자일 때를 먼저 테스트 해보자면 `http://localhost:8080/hello-string?name=원하는이름` 호출
3. 결과확인
![Image](https://github.com/user-attachments/assets/420db34c-feff-46c5-a562-d693f329916a)

4. 반환 형식이 객체일 때 테스트를 해보면, `http://localhost:8080/hello-api?name=원하는이름` 호출
5. 결과확인
![Image](https://github.com/user-attachments/assets/af003b62-9fa6-4030-a4cf-d50d24bbdb25)

&lt;br /&gt;

### ❌ 테스트 중 발생했던 오류
Spring 강의에서는 아래와 같이 코드를 작성했었는데 *( class 앞에 public 없이 )*

``` java
/* hello.hello_spring.controller.HelloController.java */

static class Hello {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}</code></pre><p><code>hello</code> 객체가 반환되는 부분에서 실행은 되지만<br />
<code>Class &#39;Hello&#39; is exposed outside its defined visibility scope</code>와 같은 오류가 발생할 수 있다.</p>
<br />

<h4 id="💡-오류가-발생하는-이유는">💡 오류가 발생하는 이유는</h4>
<p>Spring이 객체를 JSON으로 변환할 때 클래스의 접근 제한자를 엄격하게 체크하기 때문이라고 한다.</p>
<p>Spring에서 <code>@ResponseBody</code>를 사용하면 객체(<code>Hello</code>)를 JSON으로 변환하는 과정이 필요한데, <br />
이 변환을 담당하는게 위에서 말한 <strong>Jackson</strong>이라는 라이브러리이다.</p>
<br />

<p>Jackson은 객체를 JSON으로 변환할 때,</p>
<ol>
<li>기본 생성자가 존재하는지 확인</li>
<li>필드에 접근할 수 있는 Getter 메서드가 있는지 확인</li>
<li>클래스가 직렬화 가능하고, 접근할 수 있는 범위인지 확인을 한다.</li>
</ol>
<br />

<p>Spring은 기본적으로 객체를 반환할 때 JSON 형태로 반환한다고 한다. <em>xml형태도 있지만</em></p>
<br />
<br />

<hr>
<h2 id="💭-같이-공부해-볼-개념">💭 같이 공부해 볼 개념</h2>
<h3 id="✅-json이란">✅ JSON이란?</h3>
<p><strong>J</strong>ava <strong>S</strong>cript <strong>O</strong>bject <strong>N</strong>otation의 약자이다. <br />
데이터를 쉽게 <strong>교환</strong>하고 <strong>저장</strong>하기 위한 텍스트 기반의 데이터 교환 표준이다.</p>
<h4 id="기본-형태">기본 형태</h4>
<pre><code class="language-xml">{ key : value }</code></pre>
<p>JSON의 형태는 키(key), 값(value)의 쌍으로 이루어진 구조이다. <a href="https://codingazua.tistory.com/4">참고</a></p>
<br />


<h3 id="✅-자바빈-프로퍼티란">✅ 자바빈 프로퍼티란?</h3>
<p>Spring 프레임워크에서는 자바빈 프로퍼티를 활용하여 객체의 상태를 캡슐화하고, 쉽게 접근하고 조작할 수 있는 기능을 제공한다. <a href="https://jjangadadcodingdiary.tistory.com/entry/Spring-%EC%9E%90%EB%B0%94%EB%B9%88-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0Property%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%ED%99%9C%EC%9A%A9-%EB%B0%A9%EB%B2%95">참고</a></p>
<p><strong>자바빈 프로퍼티는</strong> <br/></p>
<ul>
<li><p>객체의 필드에 접근하기 위한 Getter, Setter 메서드를 통칭하는 용어이다.</p>
</li>
<li><p>Spring에서는 자바빈 프로퍼티를 활용하여 객체의 상태를 캡슐화하고, 외부에서 안전하게 필드에 접근하고 조작할 수 있도록 지원한다.</p>
</li>
<li><p>자바빈 프로퍼티의 역할로는</p>
<ul>
<li>캡슐화</li>
<li>접근 제어</li>
<li>데이터 바인딩</li>
</ul>
<br />


</li>
</ul>
<h3 id="✅-jsonconverter-mappingjackson2httpmessageconverter는-같은-것일까">✅ JsonConverter? MappingJackson2HttpMessageConverter?는 같은 것일까?</h3>
<ul>
<li><code>JsonConverter</code>는 일반적인 개념 또는 약칭이고,</li>
<li><code>MappingJackson2HttpMessageConverter</code>는 Spring이 실제로 사용하는 구현체 클래스입니다.</li>
<li><code>StringConverter</code>와 <code>StringHttpMessageConverter</code>도 같은 개념입니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2. 🧑‍💻 MVC와 템플릿 엔진]]></title>
            <link>https://velog.io/@yoon_han0/Spring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B02</link>
            <guid>https://velog.io/@yoon_han0/Spring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B02</guid>
            <pubDate>Fri, 04 Apr 2025 01:50:34 GMT</pubDate>
            <description><![CDATA[<h1 id="mvc와-템플릿-엔진">MVC와 템플릿 엔진</h1>
<p><strong>MVC</strong>는 <code>Model</code>, <code>View</code>, <code>Controller</code>의 약자로, 애플리케이션을 구성하는 주요 역할을 분리한 아키텍처 패턴입니다.</p>
<br />

<h2 id="패턴을-가지고-역할을-나누는-이유">패턴을 가지고 역할을 나누는 이유</h2>
<ul>
<li><p><strong>관심사의 분리(Separation of Concerns, SoC)</strong></p>
<p>  각각의 역할을 독립적으로 나누어, 코드 간의 영향도를 줄이는 것이 핵심입니다. <br /><br /></p>
</li>
</ul>
<h4 id="예를-들어">예를 들어</h4>
<ul>
<li>UI(화면 표시) → 사용자 인터페이스 관련 코드만 다룸</li>
<li>비즈니스 로직 → 데이터 처리, 계산, 규칙 적용 등 핵심 로직 담당</li>
<li>데이터 접근 → DB와의 연결 및 데이터 저장 처리 <br /><br /></li>
</ul>
<h4 id="✅-장점">✅ 장점</h4>
<ul>
<li>유지보수성 증가</li>
<li>재사용성 증가</li>
<li>테스트 용이성 증가 ...</li>
</ul>
<p><br /><br /></p>
<h2 id="⚙️-동작방식">⚙️ 동작방식</h2>
<p><img width="910" alt="Image" src="https://github.com/user-attachments/assets/7da00d12-3b4d-4d8c-a640-5fa6a4fd3fcd" /> <br />
<small><em>이미지 출처: 인프런 강의(스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술)</em></small></p>
<br />

<ol>
<li>사용자가 웹 브라우저에서 <code>localhost:8080/hello-mvc</code>와 같은 요청을 보냄  </li>
<li><strong>톰캣</strong>이 요청을 받고, <code>Controller</code>에 매핑된 경로가 있는지 확인  </li>
<li>✅ 경로가 있다면 → <code>return &quot;템플릿 이름&quot;</code>으로 반환  </li>
<li><strong>ViewResolver</strong>가 해당 템플릿 파일을 찾아 HTML로 변환  </li>
<li>변환된 HTML이 브라우저에 출력됨</li>
</ol>
<br />

<h2 id="🛠️-테스트-방법">🛠️ 테스트 방법</h2>
<ol>
<li>파일 생성(hello-template.html)<pre><code class="language-html">&lt;!-- src/main/resources/templages/ 아래에 파일 생성 --&gt;
&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
 &lt;title&gt;Hello Template&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;hello &#39;+ ${name}&quot;&gt;hello Empty&lt;/p&gt;    &lt;!-- 해당 코드는 Thymeleaf 문법인데 아래 참고 항목 링크 확인 부탁드립니다. --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
</li>
<li><code>controller</code> 패키지 생성 및 파일 생성
```java
/*<ol>
<li>프로젝트 아래에 controller 패키지 생성</li>
<li>&#39;HelloController&#39;라는 이름으로 클래스 생성</li>
</ol>
</li>
</ol>
<p>*/</p>
<pre><code>3. `Controller`에 코드 추가(HelloController.java)
```java
/* 2. 템플릿 엔진 방식 */
@GetMapping(&quot;hello-mvc&quot;)
public String helloMvc(@RequestParam(value = &quot;name&quot;, required = true) String name, Model model) {
    model.addAttribute(&quot;name&quot;, name);
    return &quot;hello-template&quot;;
}</code></pre><ol start="4">
<li>브라우저에서 <code>localhost:8080/hello-mvc?name=원하는이름</code> 호출</li>
<li>결과확인 <br />
<img src="https://github.com/user-attachments/assets/7125b06b-9cc0-4a7d-beac-53fcf6854172" alt="Image"></li>
</ol>
<br />
<br />
<br />

<blockquote>
<p>참고
<a href="https://github.com/YoonHan0/hello-spring/blob/main/memo/thymeleaf-basics.md">Thymeleaf 사용법 정리</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[1. 🌱 Spring 공부하기]]></title>
            <link>https://velog.io/@yoon_han0/Spring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B01</link>
            <guid>https://velog.io/@yoon_han0/Spring-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B01</guid>
            <pubDate>Thu, 03 Apr 2025 09:00:04 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-공부를-시작하게된-eu">Spring 공부를 시작하게된 EU</h2>
<p>서비스 회사에서 근무하면서 유지보수 업무를 주로 맡다 보니, 점점 전체적인 흐름을 읽는 능력이 부족해지고 있다는 걸 느끼게 되었습니다.🥲</p>
<p>이런 고민을 해결하기 위해, 직접 하나의 프로젝트를 개발해보면 좋겠다는 생각이 들어 시작하게 되었습니다.
하나씩 차근차근, 우선 Spring부터 공부해보려고 합니다! 😊</p>
<p>정확히는 SpringBoot 프로젝트를 생성하여 개발 및 공부를 진행할 것인데
SpringBoot도 결국 Spring을 손쉽게 사용할 수 있게 해주는 도구이고, 본질은 Spring 프레임워크이기 때문에 Spring 공부하기로 제목을 지었습니다.</p>
<p><a href="https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8">&quot;인프런 강의&quot;</a>를 참고해서 작성하였습니다 :)</p>
<p>먼저 프로젝트를 만들고, 설정하는 것부터 시작하는데</p>
<p>제가 강의를 들으며 <a href="https://github.com/YoonHan0/hello-spring/blob/main/README.md#spring-project-%EC%8B%9C%EC%9E%91">&quot;깃허브_Spring Project 시작&quot;</a>에 정리한 내용이 있으니 같이 공부하실 분들은 참고해서 설정 부탁드립니다</p>
<p>세팅을 마치고 나면,
먼저 Spring 웹 개발 기초에 대해서 공부하게 됩니다.</p>
<br />
<br />

<h2 id="스프링-웹-개발은-크게-3가지로-나눌-수-있는데">스프링 웹 개발은 크게 3가지로 나눌 수 있는데</h2>
<ul>
<li><strong>정적컨텐츠 (Static Content)</strong></li>
<li><strong>MVC와 템플릿 엔진 (Spring MVC + Template Engine)</strong></li>
<li><strong>API</strong></li>
</ul>
<br />


<p>정적컨텐츠 방식 먼저 공부를 해보자면</p>
<br />

<h2 id="🚀-static-content">🚀 Static Content</h2>
<p>말그대로 정적인 페이지를 사용자에게 보여주는 방식이다.</p>
<br/>

<h3 id="⚙️-동작방식">⚙️ 동작방식</h3>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/4ba33a74-801c-4679-9d15-02d6ff35b529/image.png" alt=""></p>
<p><em>이미지 출처: 인프런 강의(스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술)</em></p>
<br />

<p>웹 브라우저에서 <code>localhost:8080/hello-static.html</code>과 같은 요청이 오면 톰캣이 먼저 받습니다.</p>
<p>톰캣은 해당 요청이 <code>Controller</code>에 매핑된 경로인지 확인합니다.</p>
<ul>
<li><strong>✅ 경로가 존재하면,</strong> 해당 경로에 맞는 처리를 반환합니다. (뒤에서 학습할 내용이므로 자세한 설명은 뒤에서 하도록 하겠습니다.)</li>
<li><strong>❌ 경로가 존재하지 않으면,</strong> <code>resources/static/</code> 디렉터리에서 요청된 파일을 찾습니다. <a href="https://docs.spring.io/spring-boot/3.4-SNAPSHOT/reference/web/reactive.html#web.reactive.webflux.static-content">Spring 공식문서</a></li>
</ul>
<br />

<h2 id="🛠️-테스트-방법">🛠️ 테스트 방법</h2>
<ol>
<li>파일생성(hello-static.html)<pre><code class="language-html">&lt;!-- src/main/resources/static/ 아래의 경로에 파일 생성 --&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;ko&quot;&gt;
&lt;head&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
 &lt;title&gt;static content&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
정적 컨텐츠입니다.
&lt;/body&gt;
&lt;/html&gt;</code></pre>
</li>
<li>프로젝트 실행</li>
<li><code>localhost:8080/hello-static.html</code> 경로로 이동</li>
<li>결과확인
<img src="https://velog.velcdn.com/images/yoon_han0/post/74e42bfa-432d-4610-8deb-1fae30ffd4fa/image.png" alt=""></li>
</ol>
<br />

<h2 id="언제-사용할까">언제 사용할까?</h2>
<ul>
<li>단순한 웹 페이지 (예: 소개 페이지, 공지사항, 문서 등)를 제공할 때</li>
<li>프론트엔드에서 자체적으로 동작하는 정적 리소스 (JS, CSS)를 제공할 때</li>
</ul>
<br />
<br />
<br />

<blockquote>
<p>썸네일 이미지 출처: <a href="https://spring.io/">스프링 공식 홈페이지</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[일반 함수 vs 화살표 함수]]></title>
            <link>https://velog.io/@yoon_han0/%EC%9D%BC%EB%B0%98-%ED%95%A8%EC%88%98-vs-%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@yoon_han0/%EC%9D%BC%EB%B0%98-%ED%95%A8%EC%88%98-vs-%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 25 Oct 2024 09:01:04 GMT</pubDate>
            <description><![CDATA[<p>평소와 같이 개발을 하다가 </p>
<p>Uncaught TypeError: Cannot read properties of undefined (reading &#39;setState&#39;) ....
이런 에러를 만나게 되었다..?</p>
<p>undefined의 property를 읽을 수 없다는 거 같은데... 
오류가 나는 코드를 보니 this.setState({ … }); 처리하는 부분이 있었고, 이 부분에서 this가 컴포넌트를 가르키고 있지 않는 것을 확인할 수 있었다 </p>
<p>this.setState({…}) 를 할 때 this 가 컴포넌트를 가르키고 있지 않았던 것</p>
<p><strong>아래 코드 부분입니다 !</strong>
<img src="https://velog.velcdn.com/images/yoon_han0/post/7b5ca8d0-2ceb-46d7-ac32-701a44801c58/image.png" alt=""></p>
<p>위의 코드처럼 객체의 메서드로 일반 함수를 만들어 this를 찍어서 사용하면 그 객체(=getMainButtons)를 가르키게 된다.</p>
<br />
<br />

<h3 id="해결방법은">해결방법은</h3>
<p>일반 함수 형태로 선언되어 있는 <code>getMainButtons()</code> 를 <strong>화살표 함수 형태</strong>로 만들어주면 된다.
<img src="https://velog.velcdn.com/images/yoon_han0/post/17f6bc4f-ddad-4043-9b6f-c1b9c9b3c086/image.png" alt=""></p>
<p>가려져 있지만..</p>
<pre><code class="language-javaScript">getMainButtons() { … }    -&gt;    getMainButtons: () =&gt; { … }
// 이렇게 수정하면 해결이 됩니다. </code></pre>
<br />


<p><code>this</code>에 대해서 어렴풋이 알고는 있었지만 확실히 알아보기 위해서 예시를 만들어서 테스트 해보았습니다.</p>
<br />
<br />


<h3 id="일반-함수와-화살표-함수가-가리키는-this는-어떻게-다를까">일반 함수와 화살표 함수가 가리키는 this는 어떻게 다를까?</h3>
<p>이 부분을 알아보려면 <code>켄텍스트</code> 라는 개념을 알아야 합니다.</p>
<br />

<ul>
<li><h4 id="컨텍스트-or-실행컨텍스트란">컨텍스트 or 실행컨텍스트란?</h4>
  프로그래밍에서 특정 코드 블록이나 함수가 실행될 때, 그 코드가 어떤 환경에서 실행되고 있는지를 나타내는 개념.</li>
</ul>
<h6 id="참고-httpsheycodingtistorycom86">참고... <a href="https://heycoding.tistory.com/86">https://heycoding.tistory.com/86</a></h6>
<br />

<h4 id="함수-컨텍스트">함수 컨텍스트</h4>
<p>함수가 호출될 때 생성되는 컨텍스트. </p>
<ul>
<li><strong>일반 함수의 경우</strong>, 호출된 방식에 따라 this가 다르게 설정됨.
<br /> 객체의 메서드로 호출되면 <code>this</code>는 그 객체를 가르키고, 일반 함수로 호출되면 전역 객체를 가리키거나 undefined가 됨.</li>
</ul>
<pre><code class="language-javascript">/* ----------- 객체의 메서드로 호출되는 예시 --------- */
const person = {
  name: &#39;Han0&#39;,
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

// 메서드로 호출
person.greet(); // 출력: Hello, my name is Han0</code></pre>
<br />

<ul>
<li><strong>화살표 함수의 경우</strong>, 자신이 정의된 위치의 컨텍스트를 그대로 사용. 화살표 함수가 정의된 곳의 this를 참조함.
<br /> 화살표 함수(=greet)가 정의된 곳(=person)의 this를 참조하게 됩니다.</li>
</ul>
<pre><code class="language-javascript">/* ----------- 객체의 메서드로 호출되는 예시 --------- */
this.name = &#39;한영&#39;
const person = {
  name: &#39;YoonHan0&#39;,
  greet: () =&gt; {
    console.log(`Hello, my name is ${this.name}`);
  }
};

// 메서드로 호출
person.greet(); // 출력: Hello, my name is 한영</code></pre>
<br />
<br />

<p>아래에는 일반 함수, 화살표 함수를 비교하기 위해 만들어본 예시입니다 !</p>
<pre><code class="language-javascript">import React, { Component } from &#39;react&#39;;

class MyComponent extends Component {
  constructor(props) {
    super(props);

    // 일반 함수 바인딩
    this.handleIncrement = this.handleIncrement.bind(this);
    /* bind 하지 않으면
    Uncaught TypeError: Cannot read properties of undefined (reading &#39;setState&#39;) .... 에러 발생
     */
  }
  state = {
    count: 0,
  }

  // 일반 함수
  // 일반 함수는 호출되는 컨텍스트에 따라 this가 결정됨
  handleIncrement() {
    // 여기서 this는 undefined가 될 수 있습니다.
    console.log(this); // undefined (strict mode에서)
    this.setState({ count: this.state.count + 1 });
  }

  // 화살표 함수
  // 화살표 함수는 자신이 정의된 컨텍스트의 this를 그대로 사용
  handleDecrement = () =&gt; {
    // 여기서 this는 MyComponent 인스턴스를 가리킵니다.
    console.log(this); // MyComponent { ... }
    this.setState({ count: this.state.count - 1 });
  };

  // 위의 일반 함수, 화살표 함수를 테스트하는 함수
  testFunc = () =&gt; {
    this.name = &#39;한영&#39;;
    const person = {
      name: &#39;Han0&#39;,
      greet: function () {
        console.log(`Hello, my name is ${this.name}`);
      }
      /*
      greet: () =&gt; {
        console.log(`Hello, my name is ${this.name}`);
      }
      */
    };
    person.greet();
  }

  render() {
    return (
      &lt;div&gt;
        &lt;h1&gt;Count: {this.state.count}&lt;/h1&gt;
        &lt;button onClick={this.handleIncrement}&gt;Increment&lt;/button&gt;
        &lt;button onClick={this.handleDecrement}&gt;Decrement&lt;/button&gt;
        &lt;button onClick={this.testFunc}&gt;HELLO&lt;/button&gt;
      &lt;/div&gt;
    );
  }
}

export default MyComponent;
</code></pre>
<br />
<br />
<br />

<blockquote>
<h6 id="이미지-출처">이미지 출처</h6>
</blockquote>
<h6 id="httpsvelogiorlacksals96cs-javascript-this"><a href="https://velog.io/@rlacksals96/CS-Javascript-This">https://velog.io/@rlacksals96/CS-Javascript-This</a></h6>
]]></description>
        </item>
        <item>
            <title><![CDATA[500, 1000건 이상 Bulk 처리]]></title>
            <link>https://velog.io/@yoon_han0/500-1000%EA%B1%B4-%EC%9D%B4%EC%83%81-Bulk-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@yoon_han0/500-1000%EA%B1%B4-%EC%9D%B4%EC%83%81-Bulk-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Wed, 09 Oct 2024 02:27:18 GMT</pubDate>
            <description><![CDATA[<p>회사 업무를 하던 중 속도 개선 건을 맡게되었다..🥲
하나 하나 다 이해하고 작업해야 하는 속도 개선 건이 조금은 싫지만..</p>
<p>이번 건은 꽤 간단하게 해결할 수 있었어서 해결 방법을 간략하게 정리해 보겠습니다!</p>
<br>

<p>우선, 간략하게 로직 먼저 알려드리자면</p>
<p>권한 설정을 하는 기능에서 사원 여러 명을 선택한 후, 권한을 일괄 등록하는 기능을 구현하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/2fd677fa-0b96-4d45-9342-0c4ec4f87a12/image.png" alt=""></p>
<ol>
<li>사원정보를 넘겨받고</li>
<li>해당 사원이 권한 관리테이블 (이하 Table A) 에 존재하는지 확인</li>
<li>존재한다면 Table A에 UPDATE / 존재하지 않는다면 Table A에 INSERT</li>
</ol>
<br />
<br />

<h3 id="✅-기존-로직">✅ 기존 로직</h3>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/0fed1515-1393-490b-a42d-6fa7131f09fb/image.png" alt=""></p>
<p>사원정보(ex/5000명)를 한 건씩 API를 호출하여 존재하는지 확인하고 
UPDATE or INSERT 처리</p>
<ul>
<li>문제점
사원 정보가 늘어나면 늘어날수록 속도 저하가 심해짐</li>
</ul>
<br />
<br />

<h3 id="✅-개선방안">✅ 개선방안</h3>
<ul>
<li>개선방안 1
<img src="https://velog.velcdn.com/images/yoon_han0/post/9da07af0-9da6-4399-b223-639ba997df47/image.png" alt=""></li>
</ul>
<ol>
<li>사원정보(ex/5000명)을 List 형태로 한 번에 넘겨서</li>
<li>for문을 돌면서 한 건씩 권한 확인</li>
<li>확인 후 UPDATE or INSERT 처리</li>
</ol>
<br />
<br />

<ul>
<li>개선방안 2
<img src="https://velog.velcdn.com/images/yoon_han0/post/94da80f4-e427-4200-8e7c-ec3483c842e4/image.png" alt=""></li>
</ul>
<ol>
<li>사원정보(ex/5000명)을 List 형태로 한 번에 넘겨서</li>
<li><code>Bulk Duplicate</code> 사용하여 한 번에 처리 (Bulk Duplicate 가 올바른 용어인지는 잘 모르겠습니다..ㅎ)</li>
</ol>
<br />
<br />


<h3 id="✅-사용한-해결방법">✅ 사용한 해결방법</h3>
<p>개선방안 2번으로 해결을 하였습니다.</p>
<pre><code class="language-xml">&lt;!-- 
    empList: 사원코드, 권한정보들이 담긴 데이터 
--&gt;
INSERT INTO TABLE_A
            (
                &lt;!-- 
                INSERT or UPDATE 할 컬럼,
                여기 있는 컬럼들과 UPDATE문에 해당하는 부분에서 차이가 나는 컬럼이 해당 테이블에 식별자가 되어 테이블에 동일한 데이터가 존재한다면 UPDATE
                존재하지 않는다면 INSERT 하게 됩니다!
                이 예시에서는 EMPLOYEE_CODE가 식별자가 됨 
                --&gt;
                EMPLOYEE_CODE,
                A1,
                A2,
                A3,
                A4,
                A5
            )
            VALUES
            &lt;foreach collection=&quot;empList&quot; item=&quot;item&quot; separator=&quot;,&quot;&gt;
                (              
                      #{item.empCode},
                      #{a1},
                      #{a2},
                      #{a3},
                      #{a4},
                      #{a5}
                )
            &lt;/foreach&gt;
            ON DUPLICATE KEY UPDATE
                &lt;!-- TABLE_A에 동일한 값(사원코드)이 존재할 때 UPDATE 하는 부분 --&gt;
                A1 = #{a1}
                A2 = #{a2}
                A3 = #{a3}
                A4 = #{a4}
                A5 = #{a5}</code></pre>
<br />
<br />

<p>사원수를 6000명 정도로 했을 때 위 방법으로도 시간이 많이 단축되었지만</p>
<p>다른 방법이 있나 찾다보니 1,000건, 10,000건 이상정도 되는 데이터를 <code>Bulk INSERT</code> 할 때는 <code>일정 건수로 분할하여 삽입</code> 하는 방법도 있다고 하더라고요</p>
<p>그래서 최종 해결방안은 !</p>
<h3 id="✅-500건씩-쪼개서-bulk-duplicate-하는-방법">✅ 500건씩 쪼개서 Bulk Duplicate 하는 방법</h3>
<br>

<ul>
<li>batchSize 만큼 쪼갠 후, 배열로 리턴해주는 메서드<pre><code class="language-java">public &lt;T&gt; List&lt;List&lt;T&gt;&gt; partitionList(List&lt;T&gt; list, int batchSize) {
    List&lt;List&lt;T&gt;&gt; partitions = new ArrayList&lt;&gt;();
    for (int i = 0; i &lt; list.size(); i += batchSize) {
        int endIndex = Math.min(i + batchSize, list.size());
        partitions.add(list.subList(i, endIndex));
    }
    return partitions;
}</code></pre>
</li>
</ul>
<br>

<ul>
<li>500건 이상일 때 아래처럼 처리<pre><code class="language-java">// param.getList() -&gt; 사원리스트 로 수정
// RSelectEmp -&gt; 사원정보를 담은 모델 클래스
</code></pre>
</li>
</ul>
<p>// 사원리스트가 500건 이상일 때 | 500개 쪼개어 Bulk Duplicate 
if(param.getList().size() &gt; 500) {<br>    List&lt;List<RSelectEmp>&gt; empList = this.partitionList(param.getList(), 500);</p>
<pre><code>for(int i=0; i&lt;empList.size(); i++) {
    param.setList(empList.get(i));
    mapper.bulkDuplicate(param);
}</code></pre><p>} else {
    mapper.bulkDuplicate(param);<br>}</p>
<p>```</p>
<br />
<br />

<h3 id="결과확인">결과확인</h3>
<table>
<thead>
<tr>
<th>코드 작성 방식</th>
<th>실행 시간</th>
</tr>
</thead>
<tbody><tr>
<td>6000건 한 번에 처리</td>
<td>1.704s</td>
</tr>
<tr>
<td>500건씩 나누어 처리</td>
<td>0.828s</td>
</tr>
</tbody></table>
<br />
<br />



<p><span style="color: rgb(128, 128, 128);">* 테스트결과는 컴퓨터 사양에 따라 달라질 수 있습니다.</span></p>
<p><span style="color: rgb(128, 128, 128);">* bulk insert를 이용하여 10,000건 이상 삽입 시 성능이 좋지 않을경우에는 위 방법처럼 일정 건수로 분할하여 삽입하거나 엑셀파일로 export한 후 DBMS에서 엑셀파일을 읽는 방식으로 진행하는 방안도 존재합니다. </span></p>
<br />
<br />

<blockquote>
<h6 id="이미지-출처">이미지 출처</h6>
</blockquote>
<h6 id="황정민아저씨---httpswwwdigitaltodaycokrnewsarticleviewhtmlidxno69324">황정민아저씨 - <a href="https://www.digitaltoday.co.kr/news/articleView.html?idxno=69324">https://www.digitaltoday.co.kr/news/articleView.html?idxno=69324</a></h6>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Bean? 미스터 빈..?]]></title>
            <link>https://velog.io/@yoon_han0/Spring-Bean-%EB%AF%B8%EC%8A%A4%ED%84%B0-%EB%B9%88</link>
            <guid>https://velog.io/@yoon_han0/Spring-Bean-%EB%AF%B8%EC%8A%A4%ED%84%B0-%EB%B9%88</guid>
            <pubDate>Mon, 12 Aug 2024 00:26:31 GMT</pubDate>
            <description><![CDATA[<p>Spring을 공부하면서 IoC, DI, DL에 대해서 알게 된 내용을 정리하였습니다 :)</p>
<p>개념 정리를 하고, 실제로 Bean을 등록해 보겠습니다 !</p>
<p align="center"><img src="https://velog.velcdn.com/images/yoon_han0/post/d7423911-de08-4704-8825-bdf1f2c74d05/image.jpeg" width="60%" height="100%"></p>


<h3 id="개념정리">개념정리</h3>
<ul>
<li>IoC란?</li>
<li><em>IoC(Inversion of Control)*</em>이란 <code>제어의 역전</code>이라고 하며, <strong>객체의 생성</strong>, <strong>생명주기의 관리</strong>까지 모든 객체에 대한 제어권이 개발자에게 있는 것이 아닌 프레임워크(ex/ Spring)에 있다는 개념이다.
<img src="https://velog.velcdn.com/images/yoon_han0/post/5834919a-e67a-4285-9f48-4bb1d069e535/image.png" alt=""></li>
</ul>
<ol>
<li><p>IoC가 아닌 경우
 개발 시 필요할 때마다 <code>새로운 객체를 생성해서 사용함</code></p>
</li>
<li><p>IoC인 경우
<code>자주 사용하는 객체를 미리 만들어두고</code> 필요할 때마다 가져와서 사용함</p>
</li>
</ol>
<br />

<h4 id="ioc를-사용하는-이유">IoC를 사용하는 이유</h4>
<p>새로운 객체를 생성하고 해제하는 작업 ( = 메모리에 데이터를 올려 사용하고 내리고하는 작업 )은 시간이 오래 걸릴 수 있다.</p>
<p>때문에 자주 사용하는 객체를 미리 만들어두고 필요할 때마다 가져다 사용하면 성능 향상에 도움이 된다.</p>
<br />

<h4 id="이러한-ioc를-구현하기-위해서-dl-di-개념이-나오게-되는데">이러한 IoC를 구현하기 위해서 DL, DI 개념이 나오게 되는데,</h4>
<img src="https://velog.velcdn.com/images/yoon_han0/post/1bcb6a47-cd1e-43e1-8b86-5cc8cbf4c76f/image.jpeg" width="80%" height="100%">

<ul>
<li><p><code>DL (Dependency Lookup)</code> ( 의존성 검색 )
컨테이너(객체를 관리하는)에서 개발자에게 객체를 전달하는 기능</p>
</li>
<li><p><code>DI (Dependency Injection)</code> ( 의존성 주입 )
객체들은 따로 따로 존재하는 것이 아닌 서로 서로 의존 관계를 가지고 있다라는 개념 ( 소스 상에서의 &quot;참조&quot; )</p>
</li>
</ul>
<h4 id="ioc-관련-용어-정리">Ioc 관련 용어 정리</h4>
<ul>
<li>Managed Bean ( = Bean, Bean 객체 )<ul>
<li>Spring Container에 의해 관리되는 객체</li>
<li>스프링 설정 파일( <code>src/main/resoruces</code> ) 에 등록되어 사용</li>
<li>자동 등록 기능 사용 가능 ( = Component Auto Scanning, 어노테이션 )</li>
</ul>
</li>
</ul>
<br />


<ul>
<li>Spring Container<ul>
<li>관리되어지는 빈이 모여 있는 곳</li>
<li>IoC Container, Spring Context, Application Context 등으로 불림</li>
</ul>
</li>
</ul>
<br />
<br />

<hr>
<h3 id="실제로-bean을-등록해서-사용해-보겠습니다">실제로 Bean을 등록해서 사용해 보겠습니다</h3>
<br />

<p>아래와 같은 순서로 진행하려고 합니다.</p>
<blockquote>
<p>이클립스에서 진행합니다 !</p>
</blockquote>
<ul>
<li>Maven Project 만들기</li>
<li>객체를 이용하여 예제 만들기</li>
<li>Bean 객체를 등록하여 예제 만들기</li>
</ul>
<br />

<h3 id="1-maven-project-만들기">1. Maven Project 만들기</h3>
<ol>
<li>Dynamic Web Project 생성</li>
</ol>
<ul>
<li>Project Name: <code>beanRegister-practice</code></li>
<li>Project 구조<pre><code class="language-xml">src/main/java
src/main/resources
src/test/java
src/test/resources</code></pre>
</li>
</ul>
<ol start="2">
<li>Maven 프로젝트로 변경
프로젝트에서 오른쪽 마우스 &gt; <code>Configure</code> &gt; <code>Convert to Maven Project</code></li>
</ol>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/f5caa7a1-cb56-4c21-a6bf-590b2b80fd81/image.png" alt=""></p>
<p align="center"><img src="https://velog.velcdn.com/images/yoon_han0/post/cf2e6a13-7973-4ba8-bc95-3ebf0fcd0a21/image.png" width="60%" height="100%"></p>

<p><img src="https://velog.velcdn.com/images/yoon_han0/post/8b5bf1d6-7c25-4f44-a694-f41d2470b521/image.png" alt=""></p>
<br />
<br />

<h3 id="2-패키지-생성-및-dependency-추가">2. 패키지 생성 및 dependency 추가</h3>
<ul>
<li>src/main/java 에 패키지 추가 ( ex/ <code>kr.co.acomp</code> )</li>
<li>Maven Project 로 변경 후 생성된 <code>pom.xml</code> 파일에 dependency 추가</li>
<li>패키지 내에 클래스 생성<ul>
<li>HelloDAO.java</li>
<li>HelloMain.java</li>
</ul>
</li>
</ul>
<p align="center"><img src="https://velog.velcdn.com/images/yoon_han0/post/2743c123-ba2f-4cc6-92b8-f1b302e13b20/image.png" width="60%" height="100%"></p>

<pre><code class="language-xml">&lt;!-- pom.xml --&gt;
&lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot; xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
  &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
  &lt;groupId&gt;kr.co.acomp&lt;/groupId&gt;
  &lt;artifactId&gt;beanRegister-practice&lt;/artifactId&gt;
  &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
  &lt;packaging&gt;war&lt;/packaging&gt;
  &lt;build&gt;
    &lt;plugins&gt;
      &lt;plugin&gt;
        &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
        &lt;version&gt;3.8.1&lt;/version&gt;
        &lt;configuration&gt;
          &lt;release&gt;17&lt;/release&gt;
        &lt;/configuration&gt;
      &lt;/plugin&gt;
      &lt;plugin&gt;
        &lt;artifactId&gt;maven-war-plugin&lt;/artifactId&gt;
        &lt;version&gt;3.2.3&lt;/version&gt;
      &lt;/plugin&gt;
    &lt;/plugins&gt;
  &lt;/build&gt;

  &lt;dependencies&gt;
       &lt;!-- spring-core --&gt;
      &lt;dependency&gt;
      &lt;groupId&gt;org.springframework&lt;/groupId&gt;
      &lt;artifactId&gt;spring-core&lt;/artifactId&gt;
      &lt;version&gt;5.3.5&lt;/version&gt;
    &lt;/dependency&gt;
      &lt;!-- spring-context --&gt;
      &lt;dependency&gt;
      &lt;groupId&gt;org.springframework&lt;/groupId&gt;
      &lt;artifactId&gt;spring-context&lt;/artifactId&gt;
      &lt;version&gt;5.3.5&lt;/version&gt;
    &lt;/dependency&gt;
  &lt;/dependencies&gt;

&lt;/project&gt;</code></pre>
<br />
<br />


<h3 id="3-객체를-이용한-예제">3. 객체를 이용한 예제</h3>
<pre><code class="language-java">
/* HelloDAO.java */
package kr.co.acomp.hello;

public class HelloDAO {

    public int addTwoNumber(int a, int b) {
        return a + b;
    }
}</code></pre>
<pre><code class="language-java">
/* HelloMain.java */
package kr.co.acomp.hello;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloMain {

    public static void main(String[] args) {

        /* [] 객체를 생성해서 사용하는 방식 */
        HelloDAO dao = new HelloDAO();
        int result = dao.addTwoNumber(3, 5);

        System.out.println(result);        // 결과 8 출력
    }
}</code></pre>
<h3 id="4-bean을-등록한-후-예제-만들기">4. Bean을 등록한 후 예제 만들기</h3>
<ul>
<li>src/main/resources 에 <code>spring-context.xml</code> 생성</li>
<li>Bean 등록</li>
<li><code>HelloMain.java</code> 소스 수정</li>
</ul>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&gt;

&lt;!-- [] id: bean id, class: Bean으로 등록할 클래스 파일 --&gt;
    &lt;bean id=&quot;helloDAO&quot; class=&quot;kr.co.acomp.hello.HelloDAO&quot; /&gt;

&lt;/beans&gt;</code></pre>
<pre><code class="language-java">package kr.co.acomp.hello;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloMain {

    public static void main(String[] args) {

        // parameter (Bean 설정파일이름)
        /* [] Bean 컨테이너, Spring container가 로딩됨(생성), Bean이 생성이 됨 */
        AbstractApplicationContext ctx =  
                new ClassPathXmlApplicationContext(&quot;/spring-context.xml&quot;);

        // parameter (Bean ID, Class Type)
        // getBean()이 Dependency Lookup을 지원, 매번 생성하는 것이 아닌 기존에 있는(=Spring Container에 있는) 객체를 전달, 훨씬 가벼움
        HelloDAO dao = ctx.getBean(&quot;helloDAO&quot;, HelloDAO.class);
        int result = dao.addTwoNumber(3, 5);

        System.out.println(result);        // 결과 8 출력
    }
    // 기존에는 &quot;}&quot; 이후에 객체가 사라지게 되는데 DL을 하게 되면 객체가 사라지는 것이 아니라 컨테이너로 반환되는 것임 -&gt; 성능 향상
}
</code></pre>
<br />
<br />

<h3 id="마치며">마치며</h3>
<p>Spring Container에 Bean을 등록하여 사용하면
객체가 필요할 때마다 생성해서 사용하는 것이 아닌 Container에 존재하는 객체를 가지고 와서 사용하므로 성능 측면에서 상당한 이점이 있다고 합니다.</p>
<p>요청이 빈번한 Web Application 에서는 컨테이너를 사용하지 않으면 관리가 힘들다고 하니 개발하시면서 꼭 사용해 보시면 좋겠습니다 !</p>
<br />
<br />


<blockquote>
<h6 id="이미지-출처">이미지 출처</h6>
</blockquote>
<h6 id="심슨---httpsblognavercomsysl9556221225617255">심슨 - <a href="https://blog.naver.com/sysl9556/221225617255">https://blog.naver.com/sysl9556/221225617255</a></h6>
]]></description>
        </item>
        <item>
            <title><![CDATA[부동소수점 너 뭔데]]></title>
            <link>https://velog.io/@yoon_han0/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90</link>
            <guid>https://velog.io/@yoon_han0/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90</guid>
            <pubDate>Sun, 09 Jun 2024 08:36:30 GMT</pubDate>
            <description><![CDATA[<p>저는 회사에서 회계, 예산 프로그램을 담당하고 있어서 금액을 계산하는 작업이 다수인데</p>
<p>가끔 &#39;분명히 맞는데 왜 이상하게 나오지..?&#39; 싶을 때가 있어서 찾아보다 알게된 내용들입니다!</p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/3089994f-469e-4681-a902-9a28bf45178e/image.jpeg" width="80%" height="100%" alt="img" align="center"></img></p>
<div style="font-size: 80%; color: gray; text-align: right;">출처: 구글이미지
</div>


<h3 id="📒부동소수점과-고정소수점">📒부동소수점과 고정소수점</h3>
<ul>
<li><p><strong>부동소수점:</strong> 좌표 등 약간의 오차가 중요하지 않은 데이터에서 사용</p>
<ul>
<li>모든 숫자를 1.xxxxxx 형식으로 나타낸다.</li>
<li>첫 번째 비트는 양수, 음수를 구분하는데 사용한다.</li>
<li>그 다음 8비트로 소수점이 몇 칸 움직일 지를 나타낸다. (127과의 차이)</li>
<li>나머지 23자리에 소수점이 움직인 결과에서 소수점 뒤로 오는 부분들을 채워넣는다.</li>
</ul>
</li>
<li><p><strong>고정소수점:</strong> 금액 등 정확한 값이 필요한 경우에 사용</p>
<ul>
<li>bit를 반으로 나눠서 절반은 정수, 절반은 소수 부분에 할당한다.</li>
<li>정수 부분이 큰 숫자, 소수 부분이 정밀한 숫자를 나타낼 수 없다.</li>
<li>정수 부분을 늘리면 소수 부분이 줄어들고, 소수 부분을 늘리면 정수 부분이 줄어들게 된다.</li>
</ul>
</li>
</ul>
<br>

<h3 id="✅-부동소수점-방식에서-오차가-발생하는-이유">✅ 부동소수점 방식에서 오차가 발생하는 이유</h3>
<p><code>0.1 + 1.1 == 1.2 =&gt; false</code> 인 이유</p>
<p>컴퓨터가 숫자를 저장할 때 한 숫자당 32bit의 공간을 마련함</p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/b996feb7-d4bd-464d-88f1-6abb23a8e5ee/image.png" width="100%" height="100%" alt="img" align="center"></img></p>
<div style="font-size: 80%; color: gray; text-align: right;">출처: https://www.youtube.com/watch?v=-GsrYvZoAdA (코딩애플)
</div>

<p>소수를 저장할 때 (IEEE 형식)</p>
<ul>
<li>첫 번째칸은 음수, 양수 여부</li>
<li>8칸은 정수 부분을 2진법으로 변환해서 저장</li>
<li>나머지 23칸에 소수 부분을 2진법으로 변환해서 저장</li>
</ul>
<br>

<p>일반적으로 0.125와 같은 숫자를 2진수로 변환하면 0.001 과 같이 깔끔하게 딱 떨어져서 변환 가능하지만</p>
<p>0.1 과 같이 깔끔하게 변환되지 않고 0.000110011001001 …. 과 같이 1001이 무한히 이어지는 순환소수가 되는 경우가 있는데</p>
<p>숫자를 저장할 때 32bit를 넘어가면 뒷 부분을 자르고 메모리에 숫자를 저장하게 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/1aceaf90-4a34-47aa-a7f1-5aff0be3a982/image.png" width="100%" height="100%" alt="img" align="center"></img></p>
<div style="font-size: 80%; color: gray; text-align: right;">출처: https://www.youtube.com/watch?v=-GsrYvZoAdA (코딩애플)
</div>

<p>-&gt; 그래서 자른 부분만큼 오차가 발생함</p>
<br>

<h3 id="✅-해결방법">✅ 해결방법</h3>
<ol>
<li><strong>소수를 정수로 변환해서 계산</strong><ul>
<li>10ⁿ(n=소수점 최대 길이) 만큼 곱해줘서 정수로 만들고, 계산 후 마지막에 다시 10ⁿ만큼 나누는 방법입니다.
<img src="https://velog.velcdn.com/images/yoon_han0/post/9104f160-0fa9-4089-babe-6fc81e899f61/image.png" width="60%" height="80%" alt="img" align="center"></img></li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>decimal.js 라이브러리 사용</strong></p>
<pre><code class="language-javascript"> // npm decimal 을 통한 라이브러리 설치

 let number = 0.1;
 let transNumber = new Decimal(number).add(1.1).toString();
 let errorNumber = number + 1.1;
 console.log(&quot;&gt; 라이브러리 사용해서 부동소수점 확인 &quot;, transNumber == 1.2);
 console.log(&quot;&gt; 일반 연산 부동소수점 확인 &quot;, errorNumber == 1.2);</code></pre>
<p><img src="https://velog.velcdn.com/images/yoon_han0/post/e91d8788-5188-4bb0-82bc-c12c00d5c19d/image.png" width="60%" height="100%" alt="img" align="center"></img></p>
</li>
<li><p>double 자료형 사용 -&gt; 메모리 공간을 2배 차지함(64bit)</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로시저 제거를 통해 알게된 내용]]></title>
            <link>https://velog.io/@yoon_han0/%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80-%EC%A0%9C%EA%B1%B0%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EA%B2%8C%EB%90%9C-%EB%82%B4%EC%9A%A9</link>
            <guid>https://velog.io/@yoon_han0/%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80-%EC%A0%9C%EA%B1%B0%EB%A5%BC-%ED%86%B5%ED%95%B4-%EC%95%8C%EA%B2%8C%EB%90%9C-%EB%82%B4%EC%9A%A9</guid>
            <pubDate>Sun, 09 Jun 2024 07:39:05 GMT</pubDate>
            <description><![CDATA[<p>회사에서 기존에 사용되던 프로시저 레거시들을 해체해서 mybatis에서 
사용되는 형태로 수정하는 작업을 하게 되었다..</p>
<p>학교에서 전공 수업으로 이론만 배우고 간단한 실습만 해봤던 프로시저들..🙃</p>
<img src="https://velog.velcdn.com/images/yoon_han0/post/03f0bafc-6a16-4767-be3f-fb044b0440b5/image.jpeg" height="100%" width="100%" align="center">    

<p style="text-align:right; font-size:70%; margin-top: -15px; color: gray">출처: 구글이미지(https://bart-park.tistory.com/1)</p>

</br>

<p>작업하면서 모르는 내용이 많아 공부하면서 조금씩 정리해봤습니다 !</p>
</br>
</br>

<h3 id="프로시저-procedure">프로시저 (PROCEDURE)</h3>
<ul>
<li>특정 작업을 수행하는 SQL BLOCK.</li>
<li>쿼리문을 하나의 메서드 형식으로 만들고 어떤 동작을 일괄적으로 처리하는 용도로 쓰인다.</li>
</ul>
<blockquote>
<p>👨‍💻 프로시저를 사용하는 이유
레거시인 만큼 기존에 회사에서 일을 하셨던 분들의 의도를 완전히 파악할 수는 없겠지만 프로시저의 장점과 관련이 있을 것 같습니다 !</p>
</blockquote>
<ol>
<li>하나의 요청으로 여러 SQL문을 실행시킬 수 있기때문에 네트워크 부하를 줄일 수 있다.</li>
<li>API처럼 여러 애플리케이션과 공유가 가능하다 </li>
<li>특정한 기능을 변경 시 프로시저만 변경하면 되기에 기능 변경이 편리하다.</li>
</ol>
<br> 

<h5 id="🧐-그러면-이런-장점들이-있는데-왜-제거해야-할까">🧐 그러면 이런 장점들이 있는데 왜 제거해야 할까?</h5>
<ul>
<li><p>프로시저가 앱의 어디에 사용되는 지 확인이 어렵기에 유지보수가 어렵다.</p>
<ul>
<li>프로시저 내부의 동작을 확인하기 어려워 유지보수에 어려움이 있음.</li>
</ul>
</li>
<li><p>문자나 숫자 연산에 프로시저를 사용하면 오히려 C나 JAVA보다 처리 성능이 느리다.</p>
</li>
</ul>
<h5 id="이러한-단점이-존재해서-제거를-하게되는-것-같습니다-😟">이러한 단점이 존재해서 제거를 하게되는 것 같습니다. 😟</h5>
</br>


<h3 id="✅-cursor">✅ cursor</h3>
<ul>
<li><strong>커서란?</strong>
특정 SQL 문장을 처리한 결과를 담고 있는 영역을 가리키는 일종의 포인터입니다. 
커서를 사용하면 처리된 SQL 문장의 결과 집합에 접근할 수 있습니다.</li>
</ul>
<br>

<p>ex&gt; 커서 정의</p>
<pre><code class="language-sql">DECLARE CURSOR_NAME_EX1 CURSOR FOR
        SELECT CODE, NAME
        FROM TABLE_A 
        WHERE A.CO_CODE = V_CO_CODE
        AND A.EMP_CODE = V_EMP_CODE; </code></pre>
<p>커서를 실행하면 해당 SELECT 쿼리가 실행되어 결과 집합이 메모리에 로드됩니다.
그 후 커서는 결과 집합을 가리키는 위치를 관리하면서 각 행을 차례로 가져옵니다.
각 행을 가져올 때마다, 커서는 그 행에 대한 데이터에 접근할 수 있게 됩니다.</p>
<br>

<ul>
<li><p><strong>동작하는 방식 (사용하는 방식)</strong></p>
<pre><code class="language-sql">OPEN CURSOR_NAME_EX1;

  CUR_LOOP : LOOP
  FETCH CURSOR_NAME_EX1 INTO CODE, NAME; 
  …
  ….
</code></pre>
</li>
</ul>
<p>END LOOP;
CLOSE      CURSOR_NAME_EX1;</p>
<pre><code>


커서가 결과 집합의 한 행씩 접근하여 원하는(작성한) 로직을 처리할 수 있다.

&lt;br&gt;

---

### ✅ select into

CODE, NAME 컬럼을 가지고 있는 TABLE_A가 기존에 존재한다고 했을 때

|CODE|NAME|
|:-----------|:-----------|
|code1|name1|
|code2|name2|
|code3|name3|
|...|...|

``` sql
-- 새로운 테이블 생성 및 데이터 복사
SELECT *
INTO TABLE_A_COPY
FROM TABLE_A;

-- TABLE_A_COPY 테이블의 내용 확인
SELECT * FROM TABLE_A_COPY;

CODE | NAME
----------------------------
code1 | name1
code2 | name2
code3 | name3
…
…</code></pre><br>

<hr>
<h3 id="✅-set----">✅ set = ‘ … ’</h3>
<p>프로시저 내에서 정의한 지역변수에 원하는 값을 할당할 수 있습니다.</p>
<p><code>SET</code> 이라는 명령어를 사용하여 변수에 값을 할당
<code>ex&gt; SET MY_NAME = ‘Yoon Han Young’;</code></p>
<br>

<hr>
<h3 id="✅--의-의미">✅ || 의 의미</h3>
<p>|| 연산자는 문자열을 연결하는 연산자</p>
<pre><code class="language-sql">DECLARE V_FIRST_NAME VARCHAR(50) = ‘Yoon’;
DECLARE V_LAST_NAME VARCHAR(50) = ‘Han Young’;
DECLARE V_FULL_NAME VARCHAR(100);

SET V_FULL_NAME = V_FIRST_NAME || &#39; &#39; || V_LAST_NAME;
PRINT V_FULL_NAME;    — 결과값 Yoon Han Young</code></pre>
<hr>
<h3 id="✅-활용">✅ 활용</h3>
<p>커서 내부의 SELECT 결과 집합이 아래와 같을 때</p>
<img src="https://velog.velcdn.com/images/yoon_han0/post/327dd26c-bf4d-4b87-a7d2-2425d7917ac2/image.png" height="100%" width="40%" align="center">  

<br>

<p><code>cursor</code>와 <code>SET</code>, <code>|| 연산자</code>를 이용하여 원하는 변수에 문자를 중첩시킬 수 있습니다.</p>
<pre><code class="language-sql">ex&gt;
DECLARE V_ACCT_CD    VARCHAR(10);    -- 변수 생성

-- 커서 정의
DECLARE CURSOR_NAME_EX1 CURSOR FOR
        SELECT CODE, NAME
        FROM TABLE_A 
        WHERE A.CO_CODE = V_CO_CODE
        AND A.EMP_CODE = V_EMP_CODE;

-- 커서 open
OPEN CURSOR_NAME_EX1;

    CUR_LOOP : LOOP
    FETCH CURSOR_NAME_EX1 INTO CODE, NAME; 

    -- 문자열 중첩
    SET V_CODE = ‘and TABLE_A.CODE in (‘’’ || CODE || ‘’’)’;
    …
    …

END LOOP;
CLOSE      CURSOR_NAME_EX1;



/* 
V_CODE 에는 &#39;and TABLE_A.CODE in (‘code1’, ‘code2’, ‘code3’, …)&#39; 
이라는 문자열이 쌓이게 됨
*/</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[환경설정 시 헷갈리는 부분 / 오류나는 부분]]></title>
            <link>https://velog.io/@yoon_han0/%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-%EC%8B%9C-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%98%A4%EB%A5%98%EB%82%98%EB%8A%94-%EB%B6%80%EB%B6%84</link>
            <guid>https://velog.io/@yoon_han0/%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-%EC%8B%9C-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%98%A4%EB%A5%98%EB%82%98%EB%8A%94-%EB%B6%80%EB%B6%84</guid>
            <pubDate>Wed, 27 Sep 2023 03:41:57 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>[MAC]Homebrew 설치 (맥북 M2 PRO)
<a href="https://velog.io/@mouse0429/MACHomebrew-%EC%84%A4%EC%B9%98-%EB%A7%A5%EB%B6%81-M2-PRO">https://velog.io/@mouse0429/MACHomebrew-%EC%84%A4%EC%B9%98-%EB%A7%A5%EB%B6%81-M2-PRO</a></p>
</li>
<li><p>Homebrew로 JDK 설치
<a href="https://parkjh7764.tistory.com/196">https://parkjh7764.tistory.com/196</a></p>
</li>
<li><p>mac 설치 차단 시
<a href="https://kwaziimom.tistory.com/738">https://kwaziimom.tistory.com/738</a></p>
</li>
<li><p>이클립스 git clone
<a href="https://hgko1207.github.io/2020/05/18/eclipse-git-clone/">https://hgko1207.github.io/2020/05/18/eclipse-git-clone/</a></p>
</li>
<li><p>node 다운로드
<a href="https://nodejs.org/download/release/v16.20.2/">https://nodejs.org/download/release/v16.20.2/</a>
<code>node-v16.20.2.pkg</code></p>
</li>
</ul>
<ul>
<li><p>yarn 설치
<a href="https://jerryjerryjerry.tistory.com/80">https://jerryjerryjerry.tistory.com/80</a></p>
</li>
<li><p>vscode 폰트
<a href="https://velog.io/@woo0_hooo/vscode-%EC%97%90-%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9%ED%95%B4%EC%84%9C-%EC%BD%94%EB%94%A9%ED%95%98%EA%B3%A0-%EC%8B%B6%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0">https://velog.io/@woo0_hooo/vscode-%EC%97%90-%ED%8F%B0%ED%8A%B8-%EC%A0%81%EC%9A%A9%ED%95%B4%EC%84%9C-%EC%BD%94%EB%94%A9%ED%95%98%EA%B3%A0-%EC%8B%B6%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0</a></p>
</li>
<li><p>이클립스 Tomcat 설정
<a href="https://dev-handbook.tistory.com/32">https://dev-handbook.tistory.com/32</a></p>
</li>
<li><p>이클립스 Tomcat 환경 만들기
<a href="https://cepojyze.tistory.com/19">https://cepojyze.tistory.com/19</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 초기 화면 뜯어보기 / 기초 학습]]></title>
            <link>https://velog.io/@yoon_han0/React-%EC%B4%88%EA%B8%B0-%ED%99%94%EB%A9%B4-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@yoon_han0/React-%EC%B4%88%EA%B8%B0-%ED%99%94%EB%A9%B4-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 12 Sep 2023 14:26:59 GMT</pubDate>
            <description><![CDATA[<h2 id="컴포넌트-파일-파헤치기">컴포넌트 파일 파헤치기!</h2>
<br />

<h3 id="1-appjs">1. App.js</h3>
<p>컴포넌트에 해당하는 코드인 <code>App.js</code> 를 확인해보면</p>
<pre><code class="language-javascript">import React, { Component } from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;

class App extends Component {
  render() {
    return (
      &lt;div className=&quot;App&quot;&gt;
        &lt;header className=&quot;App-header&quot;&gt;
          &lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&gt;
          &lt;h1 className=&quot;App-title&quot;&gt;Welcome to React&lt;/h1&gt;
        &lt;/header&gt;
        &lt;p className=&quot;App-intro&quot;&gt;
          To get started, edit &lt;code&gt;src/App.js&lt;/code&gt; and save to reload.
        &lt;/p&gt;
      &lt;/div&gt;
    );
  }
}

export default App;</code></pre>
<p>이렇게 구성이 되어있다.</p>
<p><br /><br /></p>
<p>코드를 순서대로 살펴보면</p>
<pre><code class="language-javascript">import React, { Component } from &#39;react&#39;;
import logo from &#39;./logo.svg&#39;;
import &#39;./App.css&#39;;</code></pre>
<p><code>import</code> 한다는 것은 무엇을 불러온다는 뜻이다.
이렇게, <code>import</code> 를 한다는 것은 우리가 <strong>webpack</strong> 을 사용하기에 가능한 작업이다. 이렇게 불러오고나면 나중에 프로젝트를 빌드하게 됐을 때 webpack 에서 파일의 확장자에 따라 다른 작업을 하게 되는데, </p>
<ul>
<li><strong>CSS 파일</strong>은 프로젝트에서 사용한 프로젝트를 <strong>한 파일에 모두 결합해주는 작업을 진행</strong>한다.</li>
<li><strong>JS 파일</strong>은 모든 코드들이 제대로 로딩되게끔 <strong>순서를 설정하고 하나의 파일로 합쳐주는 작업</strong>을 진행</li>
<li><strong>svg 와 같은 사전에 따로 설정을 하지 않은 확장자</strong>의 경우, 그냥 파일로서 불러온 다음에 나중에 <strong>특정 경로에 사본을 만들어 주고</strong>, <strong>해당 사본의 경로를 텍스트로 받아오게 된다.</strong></li>
</ul>
<p><br /><br /></p>
<h3 id="2-indexjs">2. index.js</h3>
<pre><code class="language-javascript">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import registerServiceWorker from &#39;./registerServiceWorker&#39;;

ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;root&#39;));
registerServiceWorker();
</code></pre>
<p>우리가 만든 <code>App.js</code> 를 <code>import</code> 를 이용해서 불러와 준다.</p>
<br />


<p>그리고 브라우저 상에 우리의 <strong>React Component</strong> 를 보여주기 위해서는 <code>ReactDOM.render</code> 함수를 사용한다. <strong>첫 번째 파라미터는 렌더링 할 결과물</strong>이고, <strong>두 번째 파라미터는 컴포넌트를 어떤 DOM 에 그릴지를 정한다.</strong></p>
<br />

<p>코드를 보면 id 가 root 인 DOM 을 찾아서 그리도록 되어 있는데 해당 DOM 은 <strong>public/index.html</strong> 파일에서 찾을 수 있다.</p>
<pre><code class="language-javascript">// public/index.html
&lt;div id=&quot;root&quot;&gt;&lt;/div&gt;</code></pre>
<p>해당 DOM을 찾아서 렌더링을 한다.</p>
<p><br /><br /></p>
<h3 id="3-jsx">3. JSX</h3>
<p>HTML 과 비슷한 문법으로 작성하면 이를 <code>React.createElement</code> 를 사용하는 자바스크립트 형태로 변환시켜준다.</p>
<br />

<ul>
<li>JSX 안에 자바스크립트 값 사용하기<pre><code class="language-javascript">import React, { Component } from &#39;react&#39;;
</code></pre>
</li>
</ul>
<p>class App extends Component {
  render() {
    const name = &#39;react&#39;;
    return (
      <div className="App">
        Hello {name}!
      </div>
    );
  }
}</p>
<p>export default App;</p>
<pre><code>
&lt;br /&gt;

- 조건부 렌더링
``` javascript
import React, { Component } from &#39;react&#39;;

class App extends Component {

  printHandler = () =&gt; (&lt;div&gt;맞다야아아~!@~!@&lt;/div&gt;)

  render() {
    const name = &quot;react!&quot;;
    return (
      &lt;div className=&quot;App&quot;&gt;
        {/* 주석은 이렇게 한다아! */}
        Hello {name}
        {
          1 + 1 === 2
          ? (&lt;div&gt;맞아요!&lt;/div&gt;)
          : (&lt;div&gt;틀려요!!!!&lt;/div&gt;)
        }
        {
          1 + 1 === 2 &amp;&amp; this.printHandler()
        }
      &lt;/div&gt;
    );
  }
}

export default App;</code></pre><p>간단한 조건들은 JSX 안에서 처리해도 되지만 복잡한 조건들은 웬만하면 JSX 밖에서 로직을 작성하는 것이 좋다!</p>
<p>왜냐하면 render() 함수가 렌더링될 때마다 함수가 생성되기 때문에 성능에 영향을 미칠 수 있다!</p>
<br />

<hr>
<br />
<br />

<h2 id="기초-학습">기초 학습</h2>
<br />

<ul>
<li><strong>ref 를 사용하는 이유</strong> <br />
React 에서는 일반적으로 직접 document.xxx 를 이용하여 DOM 에 직접 접근하는 것을 권장하지 않는다. 왜냐하면 React 는 Virtual DOM 을 사용하여 DOM 조적을 추상화하고 컴포넌트 상태를 업데이트하며 UI 를 관리하는데, 직접 DOM 에 접근하면 거기서 나오는 이점이 사라지기 때문이다.<br />
그래서 특정 DOM 요소에 접근해야 할 경우 `ref` 속성을 사용하면 된다.



</li>
</ul>
<pre><code class="language-javascript">class TestComponent extends Component {
    constructor(props) {
        super(props);
        this.inputRef = React.createRef(); // React.createRef()로 생성
    }

    render() {
           return(
            &lt;input type=&quot;text&quot; ref={this.inputRef} /&gt;;
        )
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript]]></title>
            <link>https://velog.io/@yoon_han0/JavaScript</link>
            <guid>https://velog.io/@yoon_han0/JavaScript</guid>
            <pubDate>Tue, 12 Sep 2023 01:36:28 GMT</pubDate>
            <description><![CDATA[<h2 id="forin과-forof의-사용법">for...in과 for...of의 사용법</h2>
<br />

<h3 id="1-forin-의-사용방법">1. for...in 의 사용방법</h3>
<ul>
<li>for...in 사용법 - Object
<img src="https://velog.velcdn.com/images/yoon_han0/post/1f96acd5-f8f6-4b05-bc25-cbf7c46aab4e/image.png" alt="">
위의 예제처럼 <code>obj1</code> 라는 객체를 생성하여 <code>for...in</code> 문법을 사용하면 <code>temp</code> 로 <strong>key 값</strong>에 접근할 수 있고, <code>obj1[temp]</code> 와 같은 대괄호 표기법을 이용하여 <strong>value 값</strong>에 접근할 수 있다.</li>
</ul>
<br />

<ul>
<li>for...in 사용법 - Array
<img src="https://velog.velcdn.com/images/yoon_han0/post/7ef3203d-92cf-40ec-aa6a-51d584015b9c/image.png" alt="">
위의 예제처럼 <code>arr1</code> 이라는 배열을 생성하여 <code>for...in</code> 문법을 사용하면 <code>temp</code> 로 배열의 <strong>index</strong>에 접근할 수 있고, <code>arr1[temp]</code> 를 이용하여 배열의 <strong>value</strong> 에 접근할 수 있다.</li>
</ul>
<br />

<h3 id="2-forof-의-사용방법">2. for...of 의 사용방법</h3>
<ul>
<li>for...of 의 사용법 - Object
<img src="https://velog.velcdn.com/images/yoon_han0/post/557a909e-982e-4ad2-8222-d9e76ac9d74e/image.png" alt="">
이처럼 <code>for...of</code> 는 iterable(순회 가능한) 객체만을 사용할 수 있다.</li>
</ul>
<br />

<ul>
<li>for...of 의 사용법 - Array
<img src="https://velog.velcdn.com/images/yoon_han0/post/e1a291be-e659-4e9e-9516-675d053f896d/image.png" alt="">
<code>arr1</code> 이라는 배열을 생성한 후 <code>for...of</code> 문법을 사용하여 배열을 순회하며 <strong>value</strong> 값에 접근할 수 있다.</li>
</ul>
<p><br /><br /></p>
<h2 id="대괄호-표기법">대괄호 표기법</h2>
<p>JavaScript 에서 객체의 속성에 접근하거나 값을 설정할 때 사용하는 방법 중 하나이다.</p>
<br />

<ul>
<li>객체 속성에 접근하는 방법<pre><code class="language-javascript">const obj = {
name: &quot;Yoon&quot;,
age: 26,
}
</code></pre>
</li>
</ul>
<p>const propertyName = &quot;name&quot;;
console.log(obj[propertyName]);        // Yoon</p>
<pre><code>
&lt;br /&gt;

- 객체의 속성 값을 설정하는 방법
``` javascript
const obj = {};
const propertyName = &quot;name&quot;;
obj[propertyName] = &quot;Yoon&quot;;

console.log(obj.name);        // Yoon</code></pre><p><br /><br /></p>
<h2 id="화살표-함수와-일반-함수의-차이점this-바인딩-호이스팅">화살표 함수와 일반 함수의 차이점(this 바인딩, 호이스팅)</h2>
<br />

<h3 id="this-바인딩">this 바인딩</h3>
<ul>
<li>화살표 함수
화살표 함수는 자체적인 <code>this</code> 를 가지지 않고, 함수가 정의된 스코프의 <code>this</code> 를 상속 받는다. 이를 &quot;렉시컬 스코프 바인딩&quot;이라고 한다. 따라서 화살표 함수 내에서 <code>this</code> 를 사용하면 함수를 정의한 컨텍스트의 <code>this</code> 를 참조하게 된다.</li>
</ul>
<h3 id="호이스팅">호이스팅</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[React-Virtual DOM]]></title>
            <link>https://velog.io/@yoon_han0/React</link>
            <guid>https://velog.io/@yoon_han0/React</guid>
            <pubDate>Mon, 11 Sep 2023 08:49:18 GMT</pubDate>
            <description><![CDATA[<p>참고사이트</p>
<ul>
<li><a href="https://velopert.com/3612">https://velopert.com/3612</a></li>
</ul>
<h3 id="react-virtual-dom-왜-쓰냐">React Virtual DOM 왜 쓰냐?!</h3>
<p>&#39;데이터가 바뀌면 그냥 뷰를 날려버리고 새로 만들어버리면 어떨까?!&#39; React가 시작하게 된 생각이다.</p>
<p>그렇지만 브라우저가 게임 엔진도 아니고, DOM 기반으로 동작하는 이 페이지는 그때 그때 새로 뷰를 만들어버리고 하면 성능에 엄청난 문제가 생긴다!</p>
<p>그래서 Virtual DOM을 사용한다!!!</p>
<br />

<h3 id="virtual-dom">Virtual DOM</h3>
<p>Virtual DOM은 가상의 DOM이다. 변화가 일어나면, 실제로 브라우저의 DOM 에 새로운 것을 넣는 것이 아니라, JS로 이루어진 가상 DOM 에 한 번 렌더링하고, 기존의 DOM 과 비교를 한 다음에 변화가 필요한 곳에만 업데이트를 해주는 것이다.</p>
<p>Virtual DOM 은 DOM 변화를 최소화 시켜주는 역할을 한다. 이 횟수를 최소화 시키는 것은 성능적으로 매우 중요한 이슈이다!!!</p>
<br />

<p>*3rd party libary</p>
<ul>
<li>프로그래밍에서 3rd party란 프로그래밍을 도와주는 <code>pulg_in</code> 이나 <code>libary</code> 를 만드는 회사를 말한다.</li>
<li>서드 파티 라이브러리란 | 개인 개발자나 프로젝트 팀, 혹은 업체 등에서 개발하는 라이브러리를 말한다.</li>
<li>리액트에는 공식적인 라이브러리는 없지만 3rd party libary 가 존재한다.</li>
</ul>
<br />

<h4 id="리액트의-몰랐던-장점">리액트의 몰랐던 장점...?</h4>
<p>React 라이브러리는 뷰 쪽만 관리하게 되고, 나머지 기능은 3rd party 라이브러리가 담당하게 함으로서, React는 리액트 라이브러리로서 더욱 성숙해질 수가 있을 것이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[yarn install 시에 에러]]></title>
            <link>https://velog.io/@yoon_han0/yarn-install-%EC%8B%9C%EC%97%90-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@yoon_han0/yarn-install-%EC%8B%9C%EC%97%90-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Mon, 11 Sep 2023 02:37:48 GMT</pubDate>
            <description><![CDATA[<h3 id="yarn-or-yarn-install을-쳤는데-error-incorrect-data-check-at-에러가-뜰-때">Yarn or yarn install을 쳤는데 &quot;Error: incorrect data check at...&quot; 에러가 뜰 때</h3>
<pre><code class="language-bash"># nvm 다운로드
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash

# nvm 다운로드 확인
nvm list

# 에러가 뜬다면
export NVM_DIR=&quot;$HOME/.nvm&quot;  
[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;&amp; \. &quot;$NVM_DIR/nvm.sh&quot;
[ -s &quot;$NVM_DIR/bash_completion&quot; ] &amp;&amp; \. &quot;$NVM_DIR/bash_completion&quot;

# 순서대로 입력해서 다시 nvm list 확인
nvm list

# 원하는 버전의 nvm 설치
nvm install 16.20.2

# 확인
nvm list

# 기존의 yarn 삭제
npm uninstall -g yarn

# yarn 설치
npm install -g yarn

# 작업 경로로 가서 node-modules 삭제
rm -rf node_modules

# yarn or yarn install!!!!!!!!!!
yarn install</code></pre>
]]></description>
        </item>
    </channel>
</rss>