<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>m_yn.log</title>
        <link>https://velog.io/</link>
        <description>기록하는 사람</description>
        <lastBuildDate>Tue, 21 Mar 2023 07:38:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>m_yn.log</title>
            <url>https://velog.velcdn.com/images/m_yn/profile/d9c1ac8f-b02c-43c4-a8d4-da22c5b4192d/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. m_yn.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/m_yn" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Spring Security과 OAuth2 - 소셜로그인(1)]]></title>
            <link>https://velog.io/@m_yn/Spring-Security%EA%B3%BC-OAuth2-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B81</link>
            <guid>https://velog.io/@m_yn/Spring-Security%EA%B3%BC-OAuth2-%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B81</guid>
            <pubDate>Tue, 21 Mar 2023 07:38:29 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-security">Spring Security</h3>
<p>스프링 시큐리티는 스프링 기반의 어플리케이션에서 <code>보안과 인증(Authentication)</code>을 담당하는 프레임워크다. 인증, 권한부여, 보안, 세션관리 등의 기능을 제공하며 웹 애플리케이션 뿐만 아니라 서비스 계층에서의 보안기능을 구현하는 데 사용될 수 있다. 다양한 인증방식을 지원하는데 종류로는 기본적으로 폼 로그인 방식과 <code>OAuth</code>, <code>OpenID Connect</code>, <code>LDAP</code>, <code>Kerberos</code> 등의 다른 인증방식도 지원한다.</p>
<p>인증과 권한 부여를 위해 <code>Authentication(인증)</code>과 <code>Authorization(권한 부여)</code> 개념을 사용한다. Authentication은 주체의 신원을 확인하는 것이며, 이를 통해 해당 주체가 자원에 접근할 권한이 있는지 확인한다. Authorization은 인증된 주체가 특정 자원에 대해 어떤 권한을 가지고 있는지 확인하는 것이다. </p>
<p>스프링 시큐리티와 OAuth2를 활용하여 소셜로그인을 구현해보자.</p>
<h3 id="구글-서비스-등록">구글 서비스 등록</h3>
<ol>
<li><p>구글 클라우드 플랫폼 주소(<a href="https://console.cloud.google.com)%EB%A1%9C">https://console.cloud.google.com)로</a> 이동하여 새 프로젝트를 만든 후, <code>API 및 서비스</code> 카테고리로 이동하여 사용자 인증정보를 클릭한다.</p>
</li>
<li><p><code>사용자 인증 정보 만들기</code> - <code>OAuth 클라이언트 ID</code> - <code>동의 화면 구성</code></p>
</li>
<li><p><code>OAuth 동의 화면</code> - 어플리케이션 이름, 지원이메일, API범위(email, profile, openid) 설정</p>
</li>
<li><p><code>OAuth 클라이언트 ID 만들기</code> - 웹애플리케이션 설정 - 이름설정</p>
</li>
<li><p><code>승인된 리디렉션 URI</code>설정
 스프링 부트2 버전의 시큐리티에서는 기본적으로 {도메인}/login/oauth2/code/{소셜서비스코드}로 리다이렉트 URL을 지원하고 있다.
 <code>http://localhost:8080/login/oauth2/code/google</code>
 현재는 개발단계이므로 localhost로 등록하지만, 배포하게 되면 추가로 주소를 추가해야한다.</p>
</li>
<li><p>모두 설정해준 뒤 돌아오면, 클라이언트ID와 보안비밀번호를 조회할 수 있다.</p>
</li>
</ol>
<h3 id="프로젝트-설정">프로젝트 설정</h3>
<p><strong>application-oauth.properties 파일 구성</strong></p>
<p>준비물은 위의 단계에서 조회된 클라이언트 ID와 클라이언트 보안 비밀번호다.
resources 디렉토리에 <code>application-oauth.properties</code> 파일을 생성해주자.</p>
<pre><code class="language-java">spring.security.oauth2.client.registration.google.client-id=클라이언트 ID
spring.security.oauth2.client.registration.google.client-secret=클라이언트 보안 비밀번호
spring.security.oauth2.client.registration.google.scope=profile,email
</code></pre>
<p><code>application-xxx.propertis</code> 파일은 &#39;xxx&#39;라는 이름의 프로파일을 지정하여 각각 다른 설정을 사용할 수 있다.
<code>프로파일</code>이란 애플리케이션을 실행할 때 사용하는 설정의 집합으로, 실행 환경에 따라 설정을 달리하여 관리할 수 있다. 
<code>appication-oauth.properties</code> 파일은 스프링부트 애플리케이션에서 OAuth 2.0 인증을 구현할 때 사용하는 프로퍼티 파일이다. 이 파일에는 OAuth 2.0 인증을 위한 클라이언트 ID, 클라이언트 시크릿, 리다이렉트 URI 등의 설정 정보가 포함된다. 이 파일을 사용하여 OAuth 2.0인증을 구현하면, 소셜 미디어 서비스에서 제공하는 OAuth 2.0 인증 API를 사용하여 사용자 인증을 처리할 수 있다. 
이제 application-properties 파일에서 application-oauth.properties를 포함하도록 구성해야 한다.
<code>spring.profiles.include=oauth</code> 이렇게 코드를 추가로 작성해주면, 이 설정값을 사용할 수 있게된다.</p>
<p><strong>.gitignore 등록</strong>
클라이언트 ID와 보안 비밀은 보안이 중요한 정보들이다. 외부에 노출될 경우 언제든 개인정보를 가져갈 수 있는 취약점이 될 수 있으므로 .gitignore에 다음과 같이 추가하여 깃허브에 커밋되지 않도록 한다.</p>
<h3 id="프로젝트-구현">프로젝트 구현</h3>
<p><strong>User Entity 작성</strong></p>
<pre><code class="language-java">@Getter
@NoArgsConstructor
@Entity
public class User extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Column
    private String picture;

    @Enumerated(EnumType.STRING)
    @Column
    private Role role;

    @Builder
    public User(String name, String email, String picture, Role role) {
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }

    public User update(String name, String picture) {
        this.name = name;
        this.picture = picture;

        return this;
    }

    public String getRoleKey() {
        return this.role.getKey();
    }

}</code></pre>
<p>위 코드에서 User 엔티티는 데이터베이스에 저장되는 User 테이블과 매핑된다.
Id와 컬럼들을 정의해주는 것을 알 수 있는데, 그 중 Role은 사용자의 역할을 나타내며 열거형 타입을 데이터베이스에서 문자열로 저장하도록 지정되어있다. 기본적으로는 int로 된 숫자가 저장되는데, 문자열로 저장하도록 별도로 설정해준 것이다.</p>
<p><strong>Role 클래스 작성</strong>
각 사용자의 권한을 관리할 Enum 클래스 Role을 생성하여 작성하자.</p>
<pre><code class="language-java">@Getter
@RequiredArgsConstructor
public enum Role {

    GUEST(&quot;ROLE_GUEST&quot;, &quot;손님&quot;),
    USER(&quot;ROLE_USER&quot;, &quot;일반 사용자&quot;);

    private final String key;
    private final String title;
}</code></pre>
<p>스프링 시큐리티에서는 권한 코드에 항상 <code>ROLE_</code>이 앞에 있어야만 한다.
GUEST와 USER 두 가지 상수로 정의하여 각각의 값을 가지고 있다.</p>
<p>이 클래스에서 두개의 final 필드인 key와 title은, 각각 사용자의 역할을 나타내는 문자열(&quot;ROLE_GUEST&quot;, &quot;ROLE_USER&quot;)과 해당 역할의 한글 제목(&quot;손님&quot;, &quot;일반 사용자&quot;)을 나타낸다.</p>
<p><strong>UserRepository 생성</strong></p>
<pre><code class="language-java">public interface UserRepository extends JpaRepository&lt;User, Long&gt; {
    Optional&lt;User&gt; findByEamil(String email);
}</code></pre>
<p><code>findByEamil</code> : 소셜로그인으로 반환되는 값 중 email을 통해 이미 생성된 사용자인지 처음 가입하는 사용자인지 판단하기 위한 메소드</p>
<h3 id="스프링-시큐리티-설정">스프링 시큐리티 설정</h3>
<p><strong>의존성 추가</strong></p>
<p>build.gradle에 스프링 시큐리티 관련 의존성 추가</p>
<pre><code class="language-java">  implementation(&#39;org.springframework.boot:spring-boot-starter-oauth2-client&#39;)</code></pre>
<p><code>spring-boot-starter-oauth2-client</code> 의존성을 추가하여 스프링 부트 애플리케이션에서 OAuth2 서비스 제공자와 연동 후 로그인 및 사용자 인증 과정을 처리하도록 한다. 이 의존성은 스프링 시큐리티와 연동하여 사용할 수 있으며, OAuth2 클라이언트의 기능을 쉽게 사용할 수 있도록 필요한 라이브러리들을 자동으로 추가해준다.
<br></p>
<p><strong>OAuth 라이브러리를 이용한 소셜로그인 코드 작성</strong>
소셜로그인에 관한 설정 코드를 작성하는 클래스를 생성하는 것이므로, 설정 정보를 관리하기 위한 패키지인 config 패키지를 생성한다.
생성한 config 패키지 안에, 인증관련 설정파일을 다룰 패키지 auth를 생성한 후 설정클래스를 생성하여 아래와 같이 작성해준다.</p>
<pre><code class="language-java">@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable().headers().frameOptions().disable()
                .and()
                    .authorizeRequests().antMatchers(&quot;/&quot;, &quot;/css/**&quot;, &quot;/images/**&quot;, &quot;/js/**&quot;, &quot;/h2-console/**&quot;)
                    .permitAll()
                    .antMatchers(&quot;/api/v1/**&quot;).hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout().logoutSuccessUrl(&quot;/&quot;)
                .and()
                .oauth2Login()
                .userInfoEndpoint().userService(customOAuth2UserService);
    }
}
</code></pre>
<p>위의 코드에 관해 잠시 설명하자면</p>
<p>이 클래스는 <code>WebSecurityConfigurerAdapter</code> 클래스를 상속받아 구현현하는데, WebSecurityConfigurerAdapter 클래스는 스프링 시큐리티 구성을 위한 필수적인 메소드를 제공하는 추상 클래스다.</p>
<p><strong><code>@EnableWebSecurity</code></strong> 는 스프링 시큐리티를 활성화 시키기 위한 어노테이션이다.
<strong><code>HttpSecurity</code></strong>는 스프링 시큐리티에서 HTTP 요청에 대한 보안 처리를 구성하는 클래스다. 이 클래스를 사용하면 인증과 권한부여에 대한 설정을 지정할 수 있다.
HttpSecurity 는 WebSecurityConfigurerAdapter 클래스를 상속하여 configure(HttpSecurity http)메소드를 재정의하여 사용한다.</p>
<pre><code class="language-java">// WebSecurityConfigurerAdapter 
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        this.disableLocalConfigureAuthenticationBldr = true;
    }</code></pre>
<p><strong><code>.csrf().disable().headers().frameOptions().disable()</code></strong>
: csrf 보호기능과 frameOptions기능을 사용하지 않도록 설정하는 코드. h2-console 화면을 사용하기 위해 해당 옵션들을 disable로 설정한다.</p>
<p><strong><code>authorizeRequests()</code></strong> 특정한 경로에 특정한 권한을 가진 사용자만 접근할 수 있도록 설정한다.  URL별 권한 관리를 설정하는 옵션의 시작점이다.</p>
<p><strong><code>.antMatchers()</code></strong>은 권한관리를 지정하는 옵션으로 URL, HTTP 메소드별로 관리가 가능하다. <code>.permitAll()</code> 메소드로 끝나는 경로에 있는 url은 무조건 접근을 허용하도록 설정한 것이고, <code>hasRole()</code>로 끝나는 경로에 있는 url은 권한을 가진 사람만 접근을 허용하도록 설정한 것이다.</p>
<p><strong><code>anyRequest</code></strong>는 natMatchers에서 설정되지 않은 나머지 URL을 의미한다. <code>.anyRequest.authenticated()</code>는 즉 나머지 URL은 모두 인증된 사용자들에게만 허용하도록 설정한 것이다.</p>
<p><strong><code>.logout().logoutSuccessUrl(&quot;/&quot;)</code></strong> 은 로그아웃에 대한 설정인데, 로그아웃에 성공했다면 &quot;/&quot; URL 주소로 이동된다.</p>
<p><strong><code>oauth2Login</code></strong> : OAuth 2.0 기반 로그인 기능에 대한 여러 설정한다.
<strong><code>userInfoEndpoint</code></strong> : OAuth 2.0 로그인으로 인증한 사용자 정보를 가져오는 엔드포인트를 설정들을 담당한다.
<strong><code>userService</code></strong> : 소셜로그인 성공 시 가져온 사온자 정보를 처리할 UserService 인터페이스의 구현체를 등록한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Java Collection - stream API]]></title>
            <link>https://velog.io/@m_yn/Java-Collection-stream-API</link>
            <guid>https://velog.io/@m_yn/Java-Collection-stream-API</guid>
            <pubDate>Mon, 20 Mar 2023 13:47:28 GMT</pubDate>
            <description><![CDATA[<h3 id="stream-이란">Stream() 이란?</h3>
<p>Java 8버전부터 Collection 인터페이스에 추가된 API
stream은 Collection 객체를 스트림으로 변환하여 데이터를 처리하는 다양한 연산을 수행하도록 도와주며 컬렉션, 배열 등의 데이터 소스로부터 요소를 추출하고 처리하는 기능을 제공한다. 
Stream에서 연산은 파이프라인을 통해 처리된다. 이는 여러개의 중간 연산과 최종연산이 순차적으로 연결된 것을 의미하는데, 스트림 파이프라인을 통해 코드를 간결하게 유지할 수 있다.</p>
<h3 id="중간연산">중간연산</h3>
<p>중간연산은 여러번 사용될 수 있는데, 이 단계에서 자주 사용하는 메소드는 다음과 같다.</p>
<ul>
<li><strong><code>filter()</code></strong> : 조건에 따라 요소를 필터링하여 만족하는 요소만 Stream으로 반환된다. 스트림에서 짝수만 반환하는 예시는 다음과 같다.<pre><code class="language-java">List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5);
Stream&lt;Integer&gt; stream = list.stream();
Stream&lt;Integer&gt; evenStream = stream.filter(x -&gt; x % 2 == 0);</code></pre>
</li>
</ul>
<p>위 코드에서 filter() 메소드는 각 요소 x에 대해 x % 2 == 0의 조건을 검사한 후, 만족하는 요소만을 선택하여 evenStream에 반환한다.</p>
<hr>

<ul>
<li><strong><code>map()</code></strong> : 스트림의 각 요소를 변환하는데, 입력값을 받아서 변환된 출력값을 반환한다. <pre><code class="language-java">List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5);
Stream&lt;Integer&gt; stream = list.stream();
Stream&lt;Integer&gt; squaredStream = stream.map(x -&gt; x * x);
</code></pre>
</li>
</ul>
<p>// 출력값 : 1, 4, 9, 16, 25</p>
<pre><code>
위 코드에서 스트림의 map 메소드를 사용하여 각 요소를 제곱한 결과를 새로운 Stream으로 반환한다. 이 때 람다식 `x -&gt; x * x`는 각 요소를 제곱하는 함수이다.

&lt;hr&gt;

* **`sorted()`** : 스트림의 요소를 정렬하여 새로운 스트림을 반환한다.
기본적으로는 `Comparable` 인터페이스를 구현한 객체에 대해서 오름차순으로 정렬한다. `Comparator`를 인자로 받는 오버로딩된 메소드를 사용하여 정렬방법으 지정할 수도 있다.
```java
List&lt;String&gt; list = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;cherry&quot;, &quot;date&quot;);
Stream&lt;String&gt; stream = list.stream();

Stream&lt;String&gt; sortedStream = stream.sorted();
sortedStream.forEach(System.out::println);

// 출력 결과: apple, banana, cherry, date</code></pre><p>위 코드에서는 String 타입의 요소를 가진 리스트에서 스트림을 생성하고 Sorted() 메소드를 호출하여 알파벳순으로 정렬된 스트림을 반환하고 출력한다.</p>
<hr>

<ul>
<li><strong><code>distinct()</code></strong> : 스트림의 요소 중에서 중복된 요소를 제거하여 새로운 Stream을 반환하며 중복을 판단할 때 equals() 메소드를 사용한다.<pre><code class="language-java">List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 2, 4, 3, 5);
Stream&lt;Integer&gt; stream = list.stream();
</code></pre>
</li>
</ul>
<p>Stream<Integer> distinctStream = stream.distinct();
distinctStream.forEach(System.out::println);</p>
<p>// 출력 결과: 1, 2, 3, 4, 5</p>
<pre><code>
위 코드에서는 Integer타입의 요소를 가진 리스트에서 스트림을 생성하고, distinct() 메소드를 호출하여 중복된 요소를 제거한 Stream을 반환하고 출력한다. 

&lt;hr&gt;

* **`limit()`** : 스트림 요소 중에서 처음부터 지정한 개수만큼 요소를 가져와 새로운 스트림을 반환한다.
```java
List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5);
Stream&lt;Integer&gt; stream = list.stream();

Stream&lt;Integer&gt; limitedStream = stream.limit(3);
limitedStream.forEach(System.out::println);

// 출력 결과: 1, 2, 3
</code></pre><p>위 코드에서는 limit() 메소드를 호출하여 처음 3개의 요소만을 가진 스트림을 반환하고 출력한다.</p>
<hr>

<ul>
<li><strong><code>flatMap()</code></strong> : 스트림의 각 요소들을 다른 스트림으로 변환한 후, 모든 스트림을 하나의 스트림으로 연결하여 반환한다.<pre><code class="language-java">List&lt;String&gt; list1 = Arrays.asList(&quot;java&quot;, &quot;python&quot;);
List&lt;String&gt; list2 = Arrays.asList(&quot;stream&quot;, &quot;lambda&quot;);
Stream&lt;List&lt;String&gt;&gt; streamOfList = Stream.of(list1, list2);
</code></pre>
</li>
</ul>
<p>Stream<String> flatMapStream = streamOfList.flatMap(list -&gt; list.stream());
flatMapStream.forEach(System.out::println);</p>
<p>// 출력 결과: java, python, stream, lambda</p>
<pre><code>
위 코드에서는 각각 두개의 문자열 리스트를 가진 스트림을 생성하고 flatMap() 메소드를 호출하여 각 리스트의 문자열 요소들을 하나의 스트림으로 연결 후 반환하고 출력한다. 


### 최종연산
최종연산은 스트림의 요소들을 처리하여 최종 결과를 반환하는 연산인데, 최종 연산을 수행한 후에는 더이상 스트림을 다룰 수 없다.

**1. 요소의 출력 : forEach()**
스트림의 각 요소를 소비하여 출력한다.
```java
List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
    .forEach(x -&gt; System.out.print(x + &quot; &quot;)); 

// 출력 결과: 1 2 3 4 5
</code></pre><p><strong>2. 요소의 소모 : reduce()</strong>
스트림의 요소를 하나씩 처리하여 최종적으로 하나의 결과값을 반환한다.</p>
<pre><code class="language-java">List&lt;Integer&gt; list = Arrays.asList(1, 2, 3, 4, 5);
Optional&lt;Integer&gt; sum = list.stream().reduce((x, y) -&gt; x + y);
System.out.println(sum.get()); // 출력 결과: 15</code></pre>
<p><strong>3. 요소의 검색 : findFirst(), findAny()</strong>
findFisrt(), findAny()는 스트림에서 요소를 검색하는 용도로 사용된다.</p>
<ul>
<li><strong><code>findFirst()</code></strong> 메소드는 스트림에서 첫 번째 요소를 반환하거나, 스트림이 비어있다면 <code>Optional.empty()</code>를 반환하며, 일반적으로 정렬된 스트림에서 첫번째 요소를 찾는 데 사용된다.<pre><code class="language-java">List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;);
Optional&lt;String&gt; firstA = strings.stream().filter(s -&gt; s.startsWith(&quot;a&quot;)).findFirst();
System.out.println(firstA.orElse(&quot;No strings found&quot;));
</code></pre>
</li>
</ul>
<p>//출력 결과 : apple</p>
<pre><code>위 코드는 filter() 메소드를 사용하여 문자열 리스트에서 &#39;a&#39;로 시작하는 첫 번째 문자열을 찾기 위해 스트림을 필터링한 후, findFirst() 메소드를 통해 결과를 반환한다.

&lt;hr&gt;

* **`findAny()`** 메소드는 스트림에서 임의의 요소를 반환한다. 마찬가지로 스트림이 비어있다면 `Optional.empty()`를 반환하며 병렬처리된 스트림에서 사용되어 스트림의 모든 요소 중 하나를 찾을 때 유용하다.
```java
List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;);
Optional&lt;String&gt; anyA = strings.parallelStream().filter(s -&gt; s.contains(&quot;a&quot;)).findAny();
System.out.println(anyA.orElse(&quot;No strings found&quot;));

//출력 결과 : banana
</code></pre><p>위 코드에서 출력되는 결과는 &#39;banana&#39; 또는 &#39;avocado&#39; 문자열 중 하나가 출력된다. findAll() 메소드는 임의의 요소를 반환하기 때문이다.
<br></p>
<p><strong>4. 요소의 검사 : anyMatch(), allMatch(), noneMatch()</strong>
anyMatch(), allMatch(), noneMatch() 메소드는 모두 스트림의 요소를 조건식과 비교하여 boolean 값을 반환한다.</p>
<ul>
<li><strong><code>anyMatch()</code></strong> 메소드는 만족하는 스트림의 요소가 하나라도 있는지 검사한다. 조건식을 만족하는 요소가 적어도 하나가 있다면 true를 반환하고 그렇지 않으면 false를 반환한다.<pre><code class="language-java">List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;);
boolean anyContainsA = strings.stream().anyMatch(s -&gt; s.contains(&quot;a&quot;));
System.out.println(&quot;Any string contains &#39;a&#39;: &quot; + anyContainsA);
</code></pre>
</li>
</ul>
<p>//출력 결과 : Any string contains &#39;a&#39;: true</p>
<pre><code>위 코드는 문자열 리스트에서 &#39;a&#39;를 포함하는 요소가 하나 이상이 있는지 확인하는 예시다.

&lt;hr&gt;

* **`allMatch()`** 메소드는 조건식을 모든 스트림 요소에 대해 검사한다. 모든 요소가 조건식을 만족한다면 true를, 그렇지 않으면 false를 반환한다.
```java
List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;, &quot;cherry&quot;);
boolean allStartsWithA = strings.stream().allMatch(s -&gt; s.startsWith(&quot;a&quot;));
System.out.println(&quot;All strings start with &#39;a&#39;: &quot; + allStartsWithA)

//출력 결과 : Any string contains &#39;a&#39;: false</code></pre><p>위 코드에서는 각 요소 모두가 a를 포함하고 있지 않기 때문에 false를 반환한다.</p>
<hr>


<ul>
<li><strong><code>noneMatch()</code></strong> 메소드는 모든 스트림 요소가 조건식을 만족하지 않는지 검사한다. 모든 요소가 조건식을 만족하지 않으면 true, 하나라도 만족한다면 false를 반환한다.<pre><code class="language-java">List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;, &quot;cherry&quot;);
boolean noneContainsZ = strings.stream().noneMatch(s -&gt; s.contains(&quot;z&quot;));
System.out.println(&quot;No string contains &#39;z&#39;: &quot; + noneContainsZ);
</code></pre>
</li>
</ul>
<p>//출력 결과 : Any string contains &#39;z&#39;: true  </p>
<pre><code>각 요소 모두가 &#39;z&#39;라는 문자열을 포함하고 있지 않으므로 반환되는 결과는 true 이다.
&lt;br&gt;

**5. 요소의 통계 : count(), min(), max()**

* **count()** 메소드는 스트림의 요소 수를 반환한다.
```java
List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;, &quot;cherry&quot;);
long count = strings.stream().count();
System.out.println(&quot;Number of strings: &quot; + count);

// 출력 결과 : Number of strings: 4</code></pre><p>위 코드에서 각  요소의 갯수는 4개 이므로 반환되는 값은 4다.</p>
<hr>

<ul>
<li><strong><code>min()</code></strong> 메소드는 스트림의 최소값을 반환한다.<pre><code class="language-java">List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional&lt;Integer&gt; min = numbers.stream().min(Integer::compareTo);
System.out.println(&quot;Minimum number: &quot; + min.get());
</code></pre>
</li>
</ul>
<p>//출력 결과 : Minimum number: 1</p>
<pre><code>
&lt;hr&gt;

* **`max()`** 메소드는 스트림의 최대값을 반환한다.
```java
List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5);
Optional&lt;Integer&gt; max = numbers.stream().max(Integer::compareTo);
System.out.println(&quot;Maximum number: &quot; + max.get());

//출력 결과 : Maximum number: 5</code></pre><br>

<p><strong>6. 요소의 연산 : sum(), average()</strong></p>
<ul>
<li><strong><code>sum()</code></strong> 메소드는 스트림 요소들의 합을 반환한다. 요소들은 반드시 숫자형 데이터여야 한다.<pre><code class="language-java">List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().mapToInt(Integer::intValue).sum();
System.out.println(&quot;Sum of numbers: &quot; + sum);
</code></pre>
</li>
</ul>
<p>//출력 결과 : Sum of numbers: 15</p>
<pre><code>
&lt;hr&gt;

* **`average()`** 메소드는 스트림 요소들의 평균값을 반환한다. 역시 모두 숫자형 데이터여야 한다.
```java
List&lt;Integer&gt; numbers = Arrays.asList(1, 2, 3, 4, 5);
OptionalDouble avg = numbers.stream().mapToInt(Integer::intValue).average();
System.out.println(&quot;Average of numbers: &quot; + avg.getAsDouble());

//출력 결과 : Average of numbers: 3.0</code></pre><br>

<p><strong>7. 요소의 수집 : collect()</strong>
스트림의 요소를 수집하는 용도로 사용된다. 스트림의 요소를 수집해서 다른 컬렉션에 담거나 문자열로 변환하는 등의 작업을 할 수 있다.</p>
<pre><code class="language-java">List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;, &quot;cherry&quot;);
List&lt;String&gt; longStrings = strings.stream()
                                  .filter(s -&gt; s.length() &gt;= 5)
                                  .collect(Collectors.toList());
System.out.println(&quot;Long strings: &quot; + longStrings);

//출력 결과 : [apple, banana, avocado, cherry]</code></pre>
<p>위 코드는 collect() 메소드를 사용해서 문자열 리스트에서 길이가 5 이상인 요소들만 골라서 새로운 리스트를 수집하는 예시다.
<br></p>
<p>스트림 요소를 문자열로 결합할 수도 있다.</p>
<pre><code class="language-java">List&lt;String&gt; strings = Arrays.asList(&quot;apple&quot;, &quot;banana&quot;, &quot;avocado&quot;, &quot;cherry&quot;);
String joinedString = strings.stream()
                             .collect(Collectors.joining(&quot;, &quot;));
System.out.println(&quot;Joined string: &quot; + joinedString);

//출력 결과 : oined string: apple, banana, avocado, cherry</code></pre>
<p>위 코드는 문자열 리스트의 모든 요소를 쉼표로 구분한 문자열로 결합하는 예시다.</p>
<p>이처럼 collect() 메소드는 스트림의 요소를 수집하는 다양한 방법을 제공한다. 위의 예시 말고도 <code>Collectors 클래스</code>에서 제공하는 메소드들을 이용하여 원하는 수집 방식을 선택하여 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA Auditing]]></title>
            <link>https://velog.io/@m_yn/JPA-Auditing</link>
            <guid>https://velog.io/@m_yn/JPA-Auditing</guid>
            <pubDate>Sun, 19 Mar 2023 12:44:05 GMT</pubDate>
            <description><![CDATA[<h3 id="jpa-auditing이란">JPA Auditing이란?</h3>
<p>생성일, 수정일 같은 중요한 정보를 기록하는 코드가 모든 테이블과 서비스 메소드에 반복적으로 작성된다면, 귀찮고 자칫 코드가 지저분해질 수 있다.
이런 문제를 해결하고자 JPA Auditing을 사용해 보려고 한다.</p>
<p>JPA Auditing은 JPA를 사용하여 엔티티의 생성일, 수정일 같은 정보를 자동으로 기록하고 추적하는 기능인데, 일반적으로 Spring Data JPA와 함께 사용된다.</p>
<p>JPA Auditing을 구련하려면 엔티티에 <code>@EntityListeners</code> 어노테이션을 사용하여 리스너를 등록해야 한다. 이 리스너는 엔티티의 상태가 변경될 때 마다 콜백 메소드를 호출하여 적절한 정보를 추적하고 기록한다.</p>
<h3 id="jpa-auditing-적용">JPA Auditing 적용</h3>
<p>JPA Auditing 기능을 사용하기 위해 다음과 BaseTime엔티티를 생성한다.</p>
<pre><code class="language-java">@Getter
@EnableJpaAuditing
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {

    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}</code></pre>
<p>잠시 위의 코드를 설명해보자면</p>
<p><strong><code>@EnableJpaAuditing</code></strong>
JPA Auditing 어노테이션들을 모두 활성화 시키는 어노테이션</p>
<p><strong><code>@MappedSuperClass</code></strong>
JPA 어노테이션. 엔티티에서 공통적으로 사용하는 필드를 정의하는 추상 클래스임을 나타낸다. 즉, JPA Entity 클래스들이 BaseTimeEntity를 상속할 경우 필드들(createdDate, modifiedDate)도 컬럼으로 인식하도록 한다.</p>
<p><strong><code>@EntityListeners(AuditingEntityListener.class)</code></strong>
JPA Auditing을 구현하기 위한 어노테이션 중 하나로, BaseEntity 클래스에서 리스너를 사용하도록 설정한다. </p>
<p><strong><code>@CreatedDate</code></strong>
Entity가 생성되어 저장될 때 시간이 자동으로 저장된다.</p>
<p><strong><code>@LastModifiedDate</code></strong>
조회된 Entity의 값을 변경할 때 시간이 자동으로 저장된다.</p>
<br>

<h3 id="jpa-auditing-테스트-코드-작성">JPA Auditing 테스트 코드 작성</h3>
<p>위와 같이 실제 구현 코드를 완성시켰으니, 이제 제대로 작동하는지  테스트를 해보자.</p>
<pre><code class="language-java">package com.myshop.domain.posts;

import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.time.LocalDateTime;
import java.util.List;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest // 별다른 설정 없이 이 어노테이션을 사용할 경우 H2 데이터베이스를 자동으로 실행해줌
public class PostsRepositoryTest {

    @Autowired
    PostsRepository postsRepository;

    @After
    public void cleanup() {
        postsRepository.deleteAll();
    }

    @Test
    public void BaseTimeEntityTest() {

        //given
        LocalDateTime now = LocalDateTime.of(2023,3,19,0,0,0);
        postsRepository.save(Posts.builder()
                .title(&quot;생성일 제목&quot;)
                .content(&quot;생성일 내용&quot;)
                .author(&quot;테스트~&quot;)
                .build()
        );

        //when
        List&lt;Posts&gt; postsLists = postsRepository.findAll();

        //then
        Posts posts = postsLists.get(0);

        System.out.println(&quot;&gt;&gt;&gt;&gt; createdDate=&quot; + posts.getCreatedDate()+&quot;, modifiedDate=&quot; + posts.getModifiedDate());

        assertThat(posts.getCreatedDate()).isAfter(now);
        assertThat(posts.getModifiedDate()).isAfter(now);
    }

}
</code></pre>
<p>위 테스트 코드에서는 현재 시점의 LocalDateTime 정보를 생성한다.
생성된 정보는 save 메소드로 객체를 저장할 때 BaseEntity 클래스의 생성일(createdDate)과 수정일(modifiedDate) 정보가 자동으로 기록된다.</p>
<p>그 후, findAll() 메소드로 저장된 리스트들을 모두 불러온 뒤, 첫번째 엔티티 객체를 선택하여 posts 변수에 저장한 후 결과를 출력한다.</p>
<p>마지막으로 Posts 엔티티 객체의 생성일과 수정일이 테스트시점의 LocalDateTime 정보(now) 이후임을 검증한다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/78a33582-2e84-4926-99bc-ac9c6eaf7294/image.png" alt=""></p>
<p>테스트코드를 실행해보면 콘솔창에 createdDate와 modifiedDate가 조회된다!</p>
<blockquote>
<p>참고 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스                                                                                                             </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP ResponseEntity]]></title>
            <link>https://velog.io/@m_yn/HTTP-ResponseEntity</link>
            <guid>https://velog.io/@m_yn/HTTP-ResponseEntity</guid>
            <pubDate>Sun, 19 Mar 2023 05:17:09 GMT</pubDate>
            <description><![CDATA[<h3 id="responseentity란">ResponseEntity란?</h3>
<p>ResponseEntity 클래스는 HTTP 응답(Response)을 나타내는 객체로, 반환된 HTTP 상태코드와 헤더, 응답 본문 등을 포함한다.
ResponseEntity는 밑의 사진에서 보이는 것처럼 <code>HttpEntity</code>를 상속받았기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/168fb6e5-60e9-49e4-9460-af82427a1ca2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/c6500064-d1ae-44c4-9ebc-ea5538191051/image.png" alt=""></p>
<p>바로 위의 사진은 HTTP 클래스인데, 헤더와 바디를 멤버필드로 포함하고 있다.<br>즉 ResponseEntity는 HttpEntity 클래스의 멤버필드 headers, body와 자체적으로 갖고 있는 멤버필드 status(상태코드)를 포함하여 <code>HttpStatus</code>, <code>HttpHeaders</code>, <code>HttpBody</code>를 변수로 갖게 된다.</p>
<p>일반적으로 <code>@Controller</code>나 <code>@RestController</code>, <code>@RequestMapping</code> 메소드를 사용하여 HTTP 요청에 대한 응답을 반환할 때 사용한다.</p>
<p>예를 들어 HTTP 상태코드가 200인 응답을 반환하려면 다음과 같이 ResponseEntity를 사용할 수 있다.</p>
<pre><code class="language-java">@GetMapping(&quot;/hello&quot;)
public ResponseEntity&lt;String&gt; hello() {
    String message = &quot;Hello, World!&quot;;
    return ResponseEntity.ok(message);
}</code></pre>
<p>위 코드에서 <code>ResponseEntity.ok(message)</code>는 HTTP 상태코드 200과 &quot;Hello, World!&quot; 메세지를 가진 ResponseEntity 객체를 생성하여 반환한다.</p>
<br>
또한 예외가 발생한 경우에도 ResponseEntity를 사용하여 예외에 대한 적절한 HTTP 상태코드와 메세지를 반환할 수 있다. 아래 코드를 보자.

<pre><code class="language-java">@GetMapping(&quot;/users/{id}&quot;)
public ResponseEntity&lt;User&gt; getUserById(@PathVariable Long id) {
    User user = userRepository.findById(id)
        .orElseThrow(() -&gt; new ResourceNotFoundException(&quot;User not found with id: &quot; + id));
    return ResponseEntity.ok(user);
}

@ResponseStatus(HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}</code></pre>
<p>위 코드는 ReponseEntity를 사용하여 404 Not Found 에러를 반환하는 코드다. 
맨 처음으로 데이터베이스에서 해당 id값을 가진 사용자 정보를 조회하는 메소드가 보이는데, 이 때 <code>findById()</code> 메소드는 Spring Data JPA에서 제공하는 메소드중 하나로 결과를 <code>Optional</code> 타입으로 반환 한다. </p>
<blockquote>
<p><strong><code>Optional</code></strong>은 값이 없을 수도 있는 객체를 다룰 때 사용하는 클래스다. 이 타입을 사용하면 null 체크를 하지 않아도 되는 장점이 있다.
<code>Optional.empty()</code> : 조회된 엔티티가 없는 경우 반환되는 객체
<code>orElseThrow()</code> : 예외를 발생시키는 메소드
<code>orElse()</code> : 기본값을 반환하는 메소드</p>
</blockquote>
<p>따라서 위 코드에서는 userRepository.findById(id)를 호출하여 조회한 Optional 객체가 값이 없다면, <code>orElseThrow</code> 메소드가 <code>ResourceNotFoundException</code> 예외를 발생시킨다.
즉, &quot;User not found with id: {id}&quot;라는 메세지를 가진 <code>ResourceNotFoundException</code> 예외가 발생하여 HTTP 상태코드 404를 반환한다.</p>
<p>ResourceNotFoundException은 HTTP 요청에 대한 예외처리를 위한 사용자 정의 예외클래스다. 
<code>@ResponseStatus(HttpStatus.NOT_FOUND)</code> 어노테이션은 해당 예외가 발생했을 때, HTTP 응답 상태코드를 <code>404 NOT FOUND</code>로 설정해준다. 이는 REST API에서 자주 사용되는 응답 상태코드 중 하나로 클라이언트가 요청한 값이 존재하지 않는 경우에 사용된다.</p>
<h3 id="responseentity-customize">ResponseEntity Customize</h3>
<p>ResponseEntity는 상태코드와 헤더, 바디 중 원하는 것만 선택해서 반환할 수 있다는 큰 장점이 있다.</p>
<p>몇가지 예시를 들어보자</p>
<p><strong>1. 상태코드와 헤더는 그대로 유지, 본문만 변경하고자 할 때</strong></p>
<pre><code class="language-java">// 원래의 ResponseEntity 객체
ResponseEntity&lt;String&gt; responseEntity = ResponseEntity
    .status(HttpStatus.OK)
    .header(&quot;My-Header&quot;, &quot;value&quot;)
    .body(&quot;original body&quot;);

// 상태 코드와 헤더는 그대로 유지하고, 본문만 변경한 새로운 ResponseEntity 객체
ResponseEntity&lt;String&gt; newResponseEntity = ResponseEntity
    .status(responseEntity.getStatusCode())
    .headers(responseEntity.getHeaders())
    .body(&quot;new body&quot;);</code></pre>
<p>위의 코드에서, newResponseEntity 객체는 responseEntity 객체와 상태코드와 헤더가 동일하고, 본문만 &quot;new body&quot;로 변경된 새로운 객체가 된다.
<br>
<strong>2. 바디만 반환하는 경우</strong></p>
<pre><code class="language-java">@GetMapping(&quot;/body&quot;)
public ResponseEntity&lt;String&gt; getBody() {
    return ResponseEntity.ok(&quot;response body&quot;);
}</code></pre>
<p>위 코드에서 ResponseEntity 객체 생성 시, 생성자의 두번째 파라미터로 반환할 바디의 내용을 넘겨주면 된다.
<br></p>
<p><strong>3. 상태코드만 반환하는 경우</strong>
게시글의 삭제같은 경우처럼 반환타입이 따로 없는 경우가 있다. 
이런 경우에는 상태코드만 반환해줄 수 있는데, 코드는 다음과 같다.</p>
<pre><code class="language-java">@DeleteMapping(&quot;/resources/{id}&quot;)
public ResponseEntity&lt;Void&gt; deleteResource(@PathVariable Long id) {
    boolean success = resourceService.deleteResource(id);
    if (success) {
        return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
    } else {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
    }
}</code></pre>
<p>위 코드는 게시글이 무사히 삭제된 경우와, 그렇지 않을 경우에 각각 다른 상태코드를 반환한다.
만약 삭제가 성공했다면 <code>HttpStatus.NO_CONTENT</code> 상태코드와 빈 바디를 가진 ResponseEntity를 반환한다.
반면 삭제에 실패했다면 <code>HttpStatus.NOT_FOUND</code> 상태코드와 함께 빈 바디를 가진 ResponseEntity를 반환한다.</p>
<h3 id="responseentity-methods">ResponseEntity Methods</h3>
<p>ResponseEntity의 메소드들은 서버에서 생성된 HTTP 응답을 생성하기 위한 다양한 방법을 제공해준다. 그 중 자주 사용되는 몇가지 메소드들을 살펴보자</p>
<p><code>ok()</code> : HTTP 상태코드 200를 반환
<code>notFound()</code> :  HTTP 상태코드 404를 반환
<code>getBody()</code> : HTTP 응답 바디에 대한 내용을 반환
<code>getHeaders()</code> : HTTP 응답 헤더에 대한 내용을 반환
<code>getStatusCode()</code> : HTTP 응답 상태코드를 반환한다. 200은 &quot;OK&quot;, 404는 &quot;Created&quot;, 404는 &quot;Not Found&quot; 등의 의미를 갖고 있다.`</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringBoot JPA 설정하기]]></title>
            <link>https://velog.io/@m_yn/SpringBoot-JPA-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@m_yn/SpringBoot-JPA-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 18 Mar 2023 13:22:20 GMT</pubDate>
            <description><![CDATA[<p>지금까지는 Oracle, MySql 등 관계형 데이터베이스를  Mybatis와 같은 SQL 매퍼를 이용해서 데이터베이스의 쿼리를 작성해왔다.
이번에는 <code>ORM(Object Relational Mapping)</code>을 구현하는 자바표준 기술인 <code>JPA(Java Persistence API)</code>를 이용해서 데이터를 관리하고자 한다.</p>
<h3 id="orm">ORM</h3>
<p>ORM은 객체지향 프로그래밍 언어에서 사용되는 객체와 관계형 데이터베이스 사이의 데이터 변환 기술이다. ORM은 객체 지향적인 방식으로 데이터를 관리할 수 있도록 해주며, 객체 간의 관계를 강조하여 데이터를 저장하고 검색하는 것을 가능하게 해준다.</p>
<h3 id="jpa">JPA</h3>
<p>JPA는 ORM을 구현하는 자바 표준 기술이다. 애플리케이션 개발자가 객체를 사용하여 데이터를 저장하고 검색할 수 있도록 해주며 데이터베이스와의 통신, SQL 생성 및 실행, 객체와 테이블 간의 매핑, 캐시 및 트랜잭션 관리 등을 처리한다. 즉 객체를 사용하여 데이터베이스와 상호작용을 하도록 도와주므로 개발자는 데이터베이스와의 상호작용에 구체적인 구현을 신경쓰지 않아도 된다.</p>
<p>인터페이스인 JPA를 사용하기 위해서는 구현체가 필요한데 대표적으로는 <code>Hibernate</code> 등이 있다. 하지만 스프링에서 JPA를 사용할 때는 이 구현체들을 직접 다루지 않고, 구현체들을 좀더 쉽게 사용하고자 추상화시킨 Spring Data Jpa라는 모듈을 이용하여 JPA 기술을 다룬다.</p>
<h3 id="spring-boot에-spring-data-jpa-적용하기">Spring Boot에 Spring Data Jpa 적용하기</h3>
<ol>
<li>build.gradle에 의존성을 등록한다.</li>
</ol>
<pre><code class="language-java">dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    testImplementation &#39;org.springframework.boot:spring-boot-starter-test&#39;
    implementation(&#39;org.projectlombok:lombok:1.18.24&#39;)

    implementation(&#39;org.springframework.boot:spring-boot-starter-data-jpa:2.7.7&#39;)
    implementation(&#39;com.h2database:h2:2.1.214&#39;)
}</code></pre>
<p><code>h2database</code> : 인메모리 관계형 데이트베이스인 h2database를 사용하면, 별도의 데이터베이스 서버를 설치하거나 구성할 필요 없이 애플리케이션에서 데이터베이스를 사용할 수 있다. 인메모리 데이터베이스를 사용할 경우 데이터베이스가 메모리 상에 존재하기 때문에 매우 빠르게 데이터를 처리할 수 있는 장점이 있다. </p>
<ol start="2">
<li>도메인 클래스 작성<pre><code class="language-java">package com.myshop.web.domain.posts;
</code></pre>
</li>
</ol>
<p>import lombok.Builder;
import lombok.NoArgsConstructor;</p>
<p>import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Column;</p>
<p>@Entity
@NoArgsConstructor
public class Posts {</p>
<pre><code>@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(length = 500, nullable = false)
private String title;

@Column(columnDefinition =  &quot;TEXT&quot;, nullable = false)
private String content;

private String author;

@Builder
public Posts(String title, String content, String author) {
    this.title = title;
    this.content = content;
    this.author = author;
}</code></pre><p>}</p>
<p>```</p>
<p>위 코드는 JPA를 사용하여 게시글을 관리하기 위한 Posts 엔티티 클래스다. 주요 어노테이션 및 코드 설명을 간단히 해보자면</p>
<p><strong><code>@Entity</code></strong> : 이 클래스가 JPA Entity임을 나타낸다. JPA Entity는 데이터베이스의 <code>테이블</code>과 매핑된다.</p>
<p><strong><code>@NoArgsConstructor</code></strong> : lombok 어노테이션으로 파라미터가 없는 기본 생성자를 자동으로 생성해준다. </p>
<p><strong><code>@Id</code></strong> : 해당 필드가 엔티티의 기본키(pk)임을 나타낸다.</p>
<p><strong><code>@GeneratedValue</code></strong> : 기본키의 값을 자동으로 생성하기 위한 방법을 지정한다. <code>GenerationType.IDENTITY</code> 옵션을 추가해야만 <code>auto_increment</code>가 된다. </p>
<p><strong><code>@Column</code></strong> : 필드가 데이터베이스의 컬럼과 매핑될 때 사용한다. <code>length</code> 속성은 문자열의 길이를 지정할 수 있으며(문자열의 경우 varchar(255)가 기본값이다. 사이즈를 늘리고 싶을 때 사용한다.) <code>nullable</code> 속성은 null값을 허용하는지 여부를 지정한다. <code>columnDefinition</code> 속성은 컬럼의 데이터 타입이나 기타 속성을 직접 지정할 수 있다.</p>
<p><strong><code>@Builder</code></strong> lombok 어노테이션으로 해당 클래스를 빌더 패턴을 사용하여 객체로 생성할 수 있도록 해준다. 생성자 상단에 선언 시, 생성자에 포함된 필드만 빌더에 포함된다.</p>
<br>

<h3 id="entity-클래스에-setter를-사용하지-않는-이유">Entity 클래스에 Setter를 사용하지 않는 이유</h3>
<p>여태 getter와 setter를 세트처럼 같이 사용해왔는데, Entity 클래스에는 Setter 메소드를 절대 만들지 않는다고 한다. 이유가 뭘까?</p>
<p>JPA에서는 객체의 상태를 변경할 때 객체의 생성자를 통해 초기화 하거나, 메소드를 추가하여 값을 변경한다. 이는 객체지향 프로그래밍의 캡슐화 원칙을 지키기 위해서다. 만약 setter 메소드를 사용하여 객체의 상태를 변경한다면, JPA는 이를 감지하지 못하게 되는 경우가 발생한다. JPA는 객체를 관리할 때 객체의 상태변화를 추적하여 적절한 SQL 쿼리를 생성하기 때문이다.</p>
<blockquote>
<p>참고 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스  </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot - 테스트코드 작성]]></title>
            <link>https://velog.io/@m_yn/egiljen1</link>
            <guid>https://velog.io/@m_yn/egiljen1</guid>
            <pubDate>Sat, 18 Mar 2023 07:49:20 GMT</pubDate>
            <description><![CDATA[<h3 id="테스트코드를-왜-작성하나">테스트코드를 왜 작성하나?</h3>
<p>테스트코드를 작성하지 않는다면 오류가 발생하는 순간부터, 수정하고 반영하기 까지 계속 톰캣을 내렸다가 다시 실행하는 것을 반복한다.
테스트 코드를 작성하면, 이런 문제가 해결되는 것과 더불어 여러가지 장점이 있기 때문에 이번 프로젝트를 진행하면서 테스트 코드를 사용할 생각이다.
그럼 테스트코드를 작성하는 것의 여러가지 장점에 대해 살펴보자</p>
<ol>
<li><p>코드의 신뢰성 향상
테스트 코드는 어떤 상황에서도 코드가 잘 동작하는지를 검증한다. 때문에, 코드의 버그를 미리 찾아내고 수정함으로써 코드의 신뢰성을 높일 수 있다.</p>
</li>
<li><p>리팩토링의 안전성 향상
코드를 리팩토링하면서 기능이 망가지지 않도록 하기 위해서는 테스트 코드가 필요하다. 리팩토링 과정에서 기능이 망가지지 않도록 코드를 안전하게 수정할 수 있다.</p>
</li>
<li><p>문서화 역할
테스트코드는 개발자들이 작성한 코드를 설명해주는 문서역할을 할 수 있다. 테스트 코드를 작성하면서 코드가 어떻게 동작하는지에 대한 이해를 높일 수 있기 때문이다.</p>
</li>
</ol>
<h3 id="테스트-코드-작성">테스트 코드 작성</h3>
<p>다양한 테스트코드 프레임워크가 있지만, 그중 자바용인 JUnit을 사용할 계획이다.</p>
<p>우선 간단한 API를 만들자.</p>
<pre><code class="language-java">package com.myshop.web;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MyShopController {

    @GetMapping(&quot;/shop&quot;)
    public String myShop() {
        return &quot;myShop&quot;;
    }
}
</code></pre>
<p>위에 쓰여진 코드를 잠깐 설명해보자면</p>
<p><code>@RestController</code> : 스프링 프레임워크에서 제공하는 어노테이션 중 하나로, 웹 애플리케이션에서 <code>Restful</code>  웹 서비스를 개발할 때 사용한다. 이 어노테이션을 사용하면 해당 컨트롤러 클래스가 RESTful 웹서비스를 처리하는 컨트롤러임을 지정할 수 있다.</p>
<p><code>@GetMapping</code> : HTTP GET 요청을 처리하기 위한 메서드를 지정하는 어노테이션이다.</p>
<p>이제 작성한 코드가 제대로 작동하는지 테스트를 해보자.
<img src="https://velog.velcdn.com/images/m_yn/post/7db7e31f-264a-4584-92db-edfef40551cf/image.png" alt=""></p>
<p>src 폴더 밑에는 main과 test 디렉토리가 존재하고있는데, 테스트 코드를 작성하기 위해 test 디렉토리에 main디렉토리에서 생성했던 패키지를 그대로 다시 생성하면 된다.</p>
<p>콘트롤러 이름은 기존 컨트롤러 이름 뒤에 Test를 붙여서 테스트용 컨트롤러임을 명시한다.</p>
<pre><code class="language-java">package myshop.web;

import com.myshop.web.MyShopController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = MyShopController.class)
public class MyShopControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void hello가_리턴된다() throws Exception {
        String myShop = &quot;myShop&quot;;

        mvc.perform(get(&quot;/shop&quot;))
                .andExpect(status().isOk())
                .andExpect(content().string(myShop));
    }
}
</code></pre>
<p><strong><code>@RunWith(SpringRunner.class)</code></strong>
JUnit을 확장하여 스프링 테스트를 수행하는 방법을 지정하는 어노테이션이다.
테스트클래스 실행시 <code>실행자(runner)</code>로 사용되는데, 스프링 컨텍스트를 로드하고 테스트에서 사용할 빈(bean)을 생성 및 주입(inject)하는 등의 스프링 테스트 환경을 설정한다. 위의 코드는 SpringRunner라는 스프링 실행자를 사용하여 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 한다.</p>
<p><strong><code>@WebMvcTest(controllers = MyShopController.class)</code></strong>
여러 스프링 테스트 어노테이션 중, Web(MVC)에 집중할 수 있는 어노테이션이다. 테스트할 컨트롤러만 로드되므로 더 빠르게 테스트를 수행할수 있다. @WebMvcTest 의 <code>controllers</code> 속성은 테스트할 컨트롤러 클래스를 작성한다. 
<br></p>
<pre><code class="language-java">@Autowired
    private MockMvc mvc;</code></pre>
<p>MockMvc는 스프링 MVC테스트에서 사용되는 클래스로, 스프링 애플리케이션의 HTTP 요청 및 응답을 테스트하기 위한 모의 객체를 생성한다. <code>@Autowired</code> 어노테이션을 통해 의존성을 주입하여 사용할 수 있다.
요약하면 위 코드는 HTTP GET, POST 등에 대한 API 테스트를 할 수 있도록 @AutoWired 어노테이션을 통해 의존성을 주입한 것이다.</p>
<pre><code class="language-java">mvc.perform(get(&quot;/shop&quot;))
                .andExpect(status().isOk())
                .andExpect(content().string(myshop));</code></pre>
<p>위에서 보이는 perform, andExpect는 Spring MVC Test 프레임워크에서 제공하는 메소드들인데, 좀 더 자세히 알아보도록 하자.</p>
<p><strong><code>mvc.perform(get(&quot;/shop&quot;))</code></strong>
MockMvc 객체의 <code>perform()</code> 메소드를 사용하여 &quot;/shop&quot; 주소로 HTTP GET 요청을 보낸다.</p>
<p><strong><code>andExpect(status().isOk())</code></strong>
<code>andExpect()</code> 메소드를 사용하여 응답 상태코드가 200 OK인지 검증한다.
즉, HTTP 요청이 성공적으로 처리되어 200 상태코드를 반환하는지를 검증하는데, Boolean 값을 반환하며 상태코드가 200인 경우에만 테스트가 통과된다.</p>
<p><strong><code>andExpect(content().string(hello))</code></strong>
HTTP 응답의 본문(content)을 검증하는 역할을 한다.
&#39;string(hello)&#39; 는 본문의 내용이 &#39;hello&#39; 문자열과 일치하는지를 검증한다. Boolean값을 반환하며 본문이 &#39;hello&#39; 문자열과 일치하는 경우에만 테스트가 통과된다.</p>
<br>
이제 테스트 코드를 실행해보자. 테스트 메소드 왼쪽에 화살표 모양 아이콘을 클릭한 후 Run 'my_shop이_리턴된다()' 를 클릭하면 테스트가 실행된다.

<p><img src="https://velog.velcdn.com/images/m_yn/post/268ec964-6329-4f47-a749-f5479b4473fe/image.png" alt=""></p>
<p>무사히 실행이 완료되면 Tests passed에 초록색 체크아이콘이 생긴다.</p>
<blockquote>
<p>참고 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스  </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[환경설정] build.gradle 스프링부트 프로젝트로 변경]]></title>
            <link>https://velog.io/@m_yn/build.gradle-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A1%9C-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@m_yn/build.gradle-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A1%9C-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Sat, 18 Mar 2023 03:41:15 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 유형을 gradle로 하여 자바 프로젝트를 생성한 후, SpringBoot 프로젝트로 변경하는 방법을 알아보고 정리하고자 한다.</p>
<p>gradle.build 파일을 열어서 코드를 학인해보면 다음과 같다.</p>
<pre><code class="language-java">plugins {
    id &#39;java&#39;
}

group &#39;com.myshop&#39;
version &#39;1.0-SNAPSHOT&#39;

repositories {
    mavenCentral()
}

dependencies {
    testImplementation &#39;org.junit.jupiter:junit-jupiter-api:5.8.1&#39;
    testRuntimeOnly &#39;org.junit.jupiter:junit-jupiter-engine:5.8.1&#39;
}

test {
    useJUnitPlatform()
}</code></pre>
<p>간단하게 작성되어있는 기본 코드에, 플러그인 의존성 관리를 위한 설정 코드를 작성해주자.</p>
<pre><code class="language-java">buildscript {
    ext {
        springBootVersion = &#39;2.1.7.RELEASE&#39;
    }
    Repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath(&quot;org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}&quot;)
    }
}</code></pre>
<p><code>ext</code> : build.gradle에서 사용하는 전역변수를 설정하겠다는 의미인데, ext 블록은 gradle 빌드 스크립트 전체에서 사용할 수 있는 추가 속성을 정의하기 위해 사용되는 특수한 블록이다. *<em>&#39;SpringBootVersion&#39; *</em> 라는 전역변수를 정의하고 그 값을 &#39;2.1.7.RELEASE&#39;로 설정하는 Groovy 스크립트다. 
즉, <code>spring-boot-gradle-plugin</code> 라는 스프링 부트 그레이들 플러그인의 2.1.7 RELEASE를 의존성으로 받겠다는 의미이다.</p>
<p><code>Repositories</code> : 각종 의존성(라이브러리)들을 어떤 원격 저장소에서 받을지 정한다. </p>
<BR>


<p>다음은 앞서 선언한 플러그인 의존성들을 적용할 것인지를 결정하는 코드다.</p>
<pre><code class="language-java">    apply plugin: &#39;java&#39;
    apply plugin: &#39;eclipse&#39;
    apply plugin: &#39;org.springframewokr.boot&#39;
    apply plugin: &#39;io.spring.dependency-management&#39;</code></pre>
<p><code>apply plugin: java</code> : Java 플러그인을 적용하는 코드. Java 소스 코드를 컴파일하고 빌드할 수 있도록 도와준다.
<code>apply plugin: eclipse</code> : 이클립스에서 프로젝트를 가져올 수 있도록 프로젝트 파일을 생성한다.
<code>apply plugin: org.springframework.boot</code> : Spring Boot 애플리케이션을 빌드하기 위한 다양한 작업을 수행한다.
<code>apply plugin: io.spring.dependency-management</code> : Spring Boot 프로젝트에서 의존성 관리를 위해 사용되는 플러그인을 적용하는 코드다. 이 플러그인은 Maven 중앙저장소에서 의존성을 관리하며, 버전충돌을 방지하고 라이브러리의 버전을 쉽게 업그레이드 할 수 있도록 도와준다.  </p>
<pre><code class="language-java">  dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    testCompileOnly(&#39;org.springframework.boot:spring-boot-starter-test&#39;)
}</code></pre>
<p>마지막으로 프로젝트 개발에 필요한 의존성들을 선언해준다.
처음엔 compile(&#39;org.springframework.boot:spring-boot-starter-web&#39;) 로 작성했지만, <code>No candidates found for method call</code> 메세지가 뜨면서 작동을 하지 않았다. 이유를 알아보니 Gradle 4.x 버전 이상부터는 <code>implementation</code> 이나 <code>api</code> 키워드를 대신 사용하도록 권장된다고 하여 바꿔준 것이다.</p>
<p>이렇게 개발에 필요한 빌드설정을 마쳤다. 모두 적용한 전체 코드는 이렇다.</p>
<pre><code class="language-java"> buildscript {
    ext {
        springBootVersion = &#39;2.1.7.RELEASE&#39;
    }
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath(&quot;org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}&quot;)
    }
}

apply plugin: &#39;java&#39;
apply plugin: &#39;eclipse&#39;
apply plugin: &#39;org.springframework.boot&#39;
apply plugin: &#39;io.spring.dependency-management&#39;

group &#39;com.myshop&#39;
version &#39;1.0-SNAPSHOT&#39;
sourceCompatibility = 11

repositories {
    mavenCentral()
}

dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
    testCompileOnly(&#39;org.springframework.boot:spring-boot-starter-test&#39;)
}

test {
    useJUnitPlatform()
}     </code></pre>
<p>이제 Reload Gradle Project를 클릭하여 프로젝트에 반영해주면 된다.
완료되면 우측의 Gradle 탭으로 들어가서 의존성들이 잘 받아졌는지 확인!</p>
<blockquote>
<p>참고 : 스프링 부트와 AWS로 혼자 구현하는 웹서비스</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Microsoft SQL Server T-SQL]]></title>
            <link>https://velog.io/@m_yn/Microsoft-SQL-Server-T-SQL</link>
            <guid>https://velog.io/@m_yn/Microsoft-SQL-Server-T-SQL</guid>
            <pubDate>Sun, 12 Mar 2023 07:41:50 GMT</pubDate>
            <description><![CDATA[<h3 id="rdbms">RDBMS?</h3>
<p>RDBMS는 데이터를 관리하고 저장하는 데 사용되는 소프트웨어다.
데이터를 테이블로 구성하고 각 테이블은 다른 테이블과 관계를 맺음으로써 생기는 관계에 의해 데이터를 구성하고 관리할 수 있다.</p>
<p>RDMS는 Oracle, MySQL, PostgreSQL 등 여러 종류가 있는데, 그 중 SQL Server에 대해 알아보려고 한다.</p>
<h3 id="sql-server">SQL Server?</h3>
<p>SQL Server는 Microsoft Corporation<code>(MSSQL)</code>에서 개발한 <code>관계형 데이터베이스 관리 시스템(RDBMS)</code>이다. SQL Server는 데이터를 저장하고 관리하며, 클라이언트 응용 프로그램과 데이터베이스 서버 간의 통신을 관리한다.</p>
<p>MSSQL은 다른 RDBMS에서는 지원하지 않는 함수들도 제공한다.</p>
<h3 id="t-sql">T-SQL</h3>
<p><code>T-SQL(Transact-SQL)</code> 라고 불리는 프로그래밍 언어는, SQL-Server에서 사용된다.
T-SQL은 SQL문법을 기반으로 한 확장된 형태의 언어인데, 데이터베이스 시스템의 <code>데이터 관리, 데이터 조작, 저장 프로시저, 트리거, 함수</code> 등을 정의하고 사용할 수 이쓴 강력한 기능을 제공한다. 다른 데이터베이스의 SQL과 문법적으로 굉장히 비슷하며, 마이크로소프트만의 기능이 있다.</p>
<p>다음은 T-SQL 에서만 지원하는 함수들이다.
추후에 자세히 다루겠지만, 지금은 간략하게 함수명과 어떠한 용도로 사용되는지만 작성하겠다.
<br></p>
<blockquote>
<p>T-SQL에서 제공하는 함수들</p>
</blockquote>
<p>*<em>FORMAT() *</em>
지정된 형식의 문자열을 반환한다.</p>
<p><strong>TRY_CONVERT()</strong>
데이터 형식을 변환하며, 변환에 실패하면 NULL을 반환한다.</p>
<p><strong>CONCAT_WS()</strong>여러개의 문자열을 합칠 때 사용한다. 구분자를 지정하여 문자열을 연결할 수 있다.</p>
<p><strong>STRING_AGG()</strong>
그룹화된 열의 값을 연결하여 문자열로 반환한다.</p>
<p><strong>TRANSLATE()</strong>
문자열에서 지정된 문자를 다른 문자로 바꾼다.</p>
<p><strong>IIF()</strong>
조건식에 따라 다른 값을 반환한다.</p>
<p><strong>CHOOSE()</strong>
인덱스에 해당하는 값을 반환한다.</p>
<p><strong>JSON_VALUE()</strong>
JSON 문자열에서 지정된 속성의 값을 반환한다.</p>
<p><strong>STRING_SPLIT()</strong>
문자열을 지정된 구분자로 분리하여 결과집합으로 반환한다.</p>
<p><strong>COMPRESS()</strong>
데이터를 압축한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터베이스 - 날짜 & 시간 함수]]></title>
            <link>https://velog.io/@m_yn/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%82%A0%EC%A7%9C-%EC%8B%9C%EA%B0%84-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@m_yn/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%82%A0%EC%A7%9C-%EC%8B%9C%EA%B0%84-%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 10 Mar 2023 06:06:53 GMT</pubDate>
            <description><![CDATA[<p>MariaDB는 다양한 날짜와 시간과 관련된 함수를 제공한다.
주로 사용되는 함수들을 살펴보자.</p>
<blockquote>
<p>now(), curdate()</p>
</blockquote>
<p><strong>now()</strong> : 현재 날짜와 시간을 알려준다.</p>
<pre><code class="language-sql">select now();</code></pre>
<p>위 쿼리는 <code>2023-03-10 14:29:36</code> 로 반환된다.</p>
<p><strong>curdate()</strong> : 현재의 날짜를 반환한다.</p>
<pre><code class="language-sql">select curdate();</code></pre>
<p>위 쿼리는 <code>2023-03-10</code>을 반환한다.</p>
<blockquote>
<p>년/월/일/시간 추출</p>
</blockquote>
<p>현재 시간에서 혹은 데이터베이스에 저장된 Date 타입의 데이터에서, 년도만 추출하고 싶거나 월만 추출하는 등 원하는 부분만 추출하여 조회할 수 있다.</p>
<ol>
<li>연도 추출</li>
</ol>
<pre><code class="language-sql">select year(now());</code></pre>
<ol start="2">
<li>월 추출</li>
</ol>
<pre><code class="language-sql">select month(now*());</code></pre>
<ol start="3">
<li><p>일 추출</p>
<pre><code class="language-sql">select date(now());</code></pre>
</li>
<li><p>시간</p>
<pre><code class="language-sql">select time(now());</code></pre>
</li>
</ol>
<p>time을 사용하는 경우 12:20:00 형태로 반환된다.
여기서 시, 분, 초 단위로 추출하고 싶다면 time대신 각각 hour, minute, second 를 작성하면 된다.</p>
<blockquote>
<p>DATE_ADD(), DATE_SUB()</p>
</blockquote>
<p>날짜에 연/월/일을 더하거나 뺄 수 있다.</p>
<p><code>DATE_ADD</code>는 날짜에서 지정한 수 만큼 더한다. </p>
<pre><code class="language-sql">select now() as 현재시간, date_add(now(), interval 1 day) as addDate;
</code></pre>
<p>위 코드를 간단하게 풀어보면, 현재날짜(<code>now()</code>)에서 하루(<code>1 day</code>)를 더한 데이터가 조회된다.
한눈에 비교할 수 있도록 현재시간도 추가하여 조회한 결과는 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/79ed088f-ed3e-4f10-a746-c34c009a996c/image.png" alt=""></p>
<p>쿼리에서 하루를 증가시키도록 작성했기 때문에, 현재 날짜와 add_date() 함수를 적용시킨 날짜를 비교해 보면 연도와 월은 같지만 일은 함수에서 지정해준 대로 1이 증가되어 있다. </p>
<p>하루 말고 10일을 증가시키고 싶다면 add_date 함수에서 수치만 조정해주면 된다. == <code>10 days</code> </p>
<p>일 단위 말고도 월단위/연단위 증가도 모두 가능하다.
<strong>월단위</strong> : <code>date_add(now(), interval 1 month)</code>
<strong>년단위</strong> : <code>date_add(now(), interval 2 year)</code></p>
<p>변경할 대상은 현재 날짜 말고도 date 형식이면 모두 올 수 있다.</p>
<pre><code class="language-sql">select date_add(&#39;2023-02-01&#39;, interval 1 day);
select cu_name, cu_regdate 가입일자, date_add(cu_regdate, interval 1 day) add_date from my_customer;</code></pre>
<p>&#39;2023-02-01&#39;이 첫번째 인자로 들어간 경우 조회하면 2023-02-02 가 반환된다.
두번째줄은 테이블에서 유저이름, 가입일자 컬럼과 add_date 함수로 가입일자에서 하루씩 증가시키도록 작성했다.
date_add 함수의 첫번째 인자로 데이터베이스의 데이터타입 컬럼을 넘긴것이다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/a242376b-de44-4ed4-9d9e-ae88e01baf90/image.png" alt=""></p>
<p>두번재줄 쿼리를 돌린 결과다. 기존 가입 날짜와 add_date 컬럼을 비교해 보면 하루씩 증가가 된것을 볼 수 있다.</p>
<p><code>date_sub</code>는 날짜에서 지정한 수 만큼 빼는 것이다.</p>
<pre><code class="language-sql">select date_sub(now(), interval 1 month);</code></pre>
<p>간단한 예시로 현재 날짜에서 한달을 빼도록 작성했다.
결과는 밑의 사진과 같이 나온다
<img src="https://velog.velcdn.com/images/m_yn/post/dc98769f-1c90-4583-9b04-233e6cd855f4/image.png" alt=""></p>
<p>사진을 보면 알 수 있듯이, 더하냐, 빼냐 그 차이일 뿐 date_add 함수와 작성하는 방법은 똑같다. </p>
<h4 id="예시">예시)</h4>
<p>유저 테이블에서 가입한지 3개월 이내인 고객의 이름을 조회</p>
<pre><code class="language-sql">select cu_name, cu_regdate from my_customer
where cu_regdate &gt;= DATE_SUB(CURDATE(), INTERVAL 3 MONTH);</code></pre>
<p>위 쿼리는 date_sub함수로 현재날짜에서 3개월을 뺀 날짜보다 같거나 큰 가입일자에 해당하는 유저를 반환하는 쿼리다.
현재날짜가 2023년 3월 10일이면, date_sub 함수에서 반환되는 날짜는 2022년 12월 10일이다.</p>
<p>조회조건인 유저의 가입일자가 2022년 12월 10일보다 큰 경우를 조회하는거니까 가입일자가 2022년 12월 10일부터 현재날짜까지 포함된 모든 유저의 이름을 반환한다.</p>
<blockquote>
<p>DATEDIFF(), TIMEDIFF()</p>
</blockquote>
<p><strong>DATEDIFF</strong> : 두 날짜의 차이를 조회한다. </p>
<pre><code class="language-sql">select datediff(&#39;2022-10-12&#39;, &#39;2022-10-10&#39;);</code></pre>
<p>두 날짜의 차이는 2일 이므로 쿼리 조회 시 2 라는 결과가 반환된다.
이 때 날짜 차이는 왼쪽을 기준으로 계산하는데, 왼쪽의 날짜가 오른쪽 날짜보다 크다면 양수를 반환하고, 적다면 음수로 반환된다.</p>
<pre><code class="language-sql">select datediff(&#39;2022-10-12&#39;, &#39;2022-10-14&#39;);</code></pre>
<p>위 코드는 똑같이 2일 차이지만 2가 아닌 -2 를 반환한다. 왼쪽 날짜가 더 적으므로 음수로 반환되는 것이다.
<br></p>
<p><strong>TIMEDIFF</strong> : 두 시간의 차이를 조회한다.</p>
<pre><code class="language-sql">select timediff(&#39;10:00:00&#39;, &#39;09:00:00&#39;);</code></pre>
<p>두 시간을 비교하면 조회되는 결과는 <code>01:00:00</code> 이다.
하지만 기준이 되는 앞의 시간이 뒤의 시간보다 늦다면 결과는 날짜를 비교하는 DATEDIFF() 함수와는 다른 형식으로 조회된다.</p>
<pre><code>select timediff(&#39;10:00:00&#39;, &#39;11:00:00&#39;);</code></pre><p>왼쪽 시간이 오른쪽 시간보다 1시간이 늦다. 예상했던 조회결과는 <code>-01:00:00</code> 이었지만, 실제로 조회된 결과는 <code>23:00:00</code> 이다.</p>
<p>반환되는 값의 형식은 &#39;HH:MM:SS&#39; 인데, 이때 시간 값이 음수일 경우에 음수 부호 대신 하루를 나타내는 시간인 24를 더하여 양수 값으로 반환한다. 그래서 -1 에서 24를 더한 23:00:00 이 반환된다.</p>
<blockquote>
<p>DATE_FORMAT()</p>
</blockquote>
<p>DATE_FORMAT()은 날짜와 시간을 원하는 형식으로 지정할 수 있다.
DATE_FORMAT() 함수 밑의 밑의 구문과 같이 사용된다.</p>
<p><code>DATE_FORMAT(date, format);</code></p>
<p><code>date</code>는 날짜 형식(date, timestamp, datetime)의 데이터이며, <code>format</code>은 변환하고자 하는 날짜 형식을 지정하는 문자열이다. </p>
<pre><code class="language-sql">SELECT DATE_FORMAT(NOW(), &#39;%Y-%m-%d %H:%i:%s&#39;);</code></pre>
<p>위 쿼리의 결과는 <code>2023-03-10 10:30:00</code> 로 현재 날짜를 지정한 형식으로 반환한다.</p>
<p><strong>&lt;날짜 FORMAT 코드&gt;</strong></p>
<p><code>%Y</code> : 4자리 연도 (예: 2023)
<code>%y</code> : 2자리 연도 (예: 23)
<code>%m</code> : 월 (01부터 12까지)
<code>%c</code> : 월 (1부터 12까지)
<code>%d</code> : 일 (01부터 31까지)
<code>%e</code> : 일 (1부터 31까지)
<code>%H</code> : 24시간 형식으로 표시한 시 (00부터 23까지)
<code>%h</code> : 12시간 형식으로 표시한 시 (01부터 12까지)
<code>%i</code> : 분 (00부터 59까지)
<code>%s</code> : 초 (00부터 59까지)
<code>%p</code> : AM 또는 PM (대문자)</p>
<br>

<p><strong>&lt;구분자를 사용한 날짜 FORMAT 형식&gt;</strong></p>
<p><code>-</code> : 하이픈(-)
<code>/</code> : 슬래시(/)
<code>.</code> : 마침표(.)
<code>:</code> : 콜론(:)
<code></code>  : 공백</p>
<h4 id="예시-1">예시)</h4>
<pre><code class="language-sql">SELECT DATE_FORMAT(&#39;2023-03-10&#39;, &#39;%d-%m-%Y&#39;);</code></pre>
<p>위 코드는 &#39;2023-03-10&#39; 을, 오른쪽의 날짜 포맷 형식에 맞춰서 반환될 것이다.
&#39;2023-03-10&#39; 은 <code>&#39;%Y-%m-%d&#39;</code> 형식으로 되어있는데, 오른쪽의 날짜 포맷형식으로 바꾸면 조회되는 데이터는 <code>&#39;10-03-2023&#39;</code> 이다.</p>
<pre><code class="language-sql">SELECT DATE_FORMAT(&#39;2023-03-10 14:30:45&#39;, &#39;%Y년 %c월 %e일 %p %h시 %i분 %s초&#39;);</code></pre>
<p>그럼 위의 쿼리는  어떻게 변경되어 나타날까?
구분자를 이용하여 날짜와 시간을 구분하는 대신 년,월,일,시,분,초로 형태를 지정하였으므로 조회되는 값은 아래와 같다.</p>
<p><code>2023년 3월 10일 오후 2시 30분 45초</code> 와 같은 형식으로 조회될 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터의 NULL 처리]]></title>
            <link>https://velog.io/@m_yn/%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-NULL-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@m_yn/%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%9D%98-NULL-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Thu, 09 Mar 2023 08:59:37 GMT</pubDate>
            <description><![CDATA[<p>MariaDB에서 조회된 데이터가 Null인 경우 처리하는 방법은 다양하다. 
null인 경우 다른 값으로 대체할 수도 있고, 아예 조회가 되지 않게 처리할 수 있다.</p>
<blockquote>
<p>IFNULL()</p>
</blockquote>
<p>IFNULL() 함수는 첫번째 인자값이 Null 인 경우, <code>두번째 인자값</code>을 반환한다. 
따라서 조회된 데이터가 Null인 경우, IFNULL 함수를 사용하여 다른 값을 반환할 수 있다. </p>
<h4 id="예시">예시)</h4>
<p><img src="https://velog.velcdn.com/images/m_yn/post/873bdeb6-5a52-47c0-a4e0-8080115cc328/image.png" alt=""></p>
<p>위 사진에서 확인할 수 있듯이 유재석 유저는 email을 입력하지 않아 null로 표시되어 있다.</p>
<pre><code class="language-sql">select * from cu_name, IFNULL(cu_email, &#39;이메일없음&#39;) from my_customer;</code></pre>
<p>위와 같이 IFNULL을 사용하여 <code>첫번째 인자값</code>인 이메일 컬럼의 값이 null인 경우, <code>두번째 인자값</code>인 &#39;입력안함&#39; 으로 치환되어 표시되게 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/2be07ce2-18e8-4c20-b214-5446769559af/image.png" alt=""></p>
<p>쿼리를 실행한 결과 위 사진같이 <code>&lt;null&gt;</code>로 표시됐던 데이터가 &#39;이메일없음&#39; 으로 치환되어 반환됐다.</p>
<blockquote>
<p>COALESCE()</p>
</blockquote>
<p>COALESCE() 함수는 여러개의 인자값을 받을 수 있다.
여러개의 인자를 받고 그 중 Null이 아닌 값을 반환한다.
<img src="https://velog.velcdn.com/images/m_yn/post/515d984e-b395-4267-8f7a-312a5da6ee29/image.png" alt=""></p>
<p>위 사진을 보면 null값이 많이 보이는 유저 테이블이 보인다. </p>
<pre><code class="language-sql">select cu_name, cu_phone, cu_address, coalesce(cu_phone, cu_address, &#39;2번째대체값&#39;) from my_customer;</code></pre>
<p>조회할 항목으로 coalesce를 추가해주면서 인자로 연락처, 주소, &#39;2번째대체값&#39; 으로 지정해줬다. 그 후 쿼리를 실행시키면 반환되는 테이블은 아래 사진과 같다.
<img src="https://velog.velcdn.com/images/m_yn/post/8f0c1af5-9fd9-4fc7-bfae-89c3656276f9/image.png" alt="">
맨 끝 컬럼에 coalesce 컬럼이 추가되면서, 전달받은 인자값 중 Null이 아닌 값이 표시된다.
고옹백 유저와 강태공 유저는 coalesce 인자로 전달된 cu_phone, cu_address 값이 모두 Null 이므로 마지막인자인 &#39;두번째대체값&#39; 이 채워진다.
강호동 유저는 cu_phone null값이지만 cu_address 컬럼에는 값이 있으므로 cu_address 값이 표시된다.</p>
<blockquote>
<p>IS NULL / IS NOT NULL</p>
</blockquote>
<ol>
<li>IS NULL
 조회되는 데이터가 null값인지 검사한다. 이 구문을 통해 지정한 컬럼이 null값인 경우에만 조회가 되도록 처리할 수 있다.<pre><code class="language-sql"> select * from my_customer where cu_phone is null;</code></pre>
</li>
</ol>
<p>위의 쿼리를 실행하면 cu_phone이 null값인 행들만 조회된다.
<img src="https://velog.velcdn.com/images/m_yn/post/64154dbe-5e98-4412-b16e-cc829fdfe034/image.png" alt=""></p>
<ol start="2">
<li>IS NOT NULL
조회하려는 데이터가 null값이 아닌지 검사한다. 이 구문을 통해 지정한 컬럼이 null값인 경우는 조회가 되지 않도록 처리할 수 있다.<pre><code class="language-sql"> select * from my_customer where cu_email is not null;</code></pre>
</li>
</ol>
<p>위의 쿼리를 실행하면 cu_email 이 null값이 아닌 행들만 조회된다.
<img src="https://velog.velcdn.com/images/m_yn/post/5b94fcb2-147a-4329-80a9-86eea0994d48/image.png" alt=""></p>
<blockquote>
<p>IF함수 사용</p>
</blockquote>
<p>조건문을 사용하여 조회된 데이터가 Null인 경우 다른 값을 출력하거나, 다른 처리를 할 수 있다.
mariaDB 에서 IF함수는 다음과 같이 작성할 수 있다.</p>
<pre><code class="language-sql">select IF(조건, 참일때 값, 거짓일때 값)</code></pre>
<p>위의 구조에서 조건은 비교할 조건을 의미한다. 참일 때 값은 조건이 참인 경우 반환할 값, 거짓일 때 값은 조건이 거짓인 경우 반환할 값이다.</p>
<pre><code class="language-sql">select IF(cu_phone is null, &#39;null값&#39;, cu_phone) as cu_phone
from my_customer;</code></pre>
<p>위 쿼리를 풀어보자면 cu_phone 컬럼의 값이 null인 경우(true)에는 &#39;null값&#39;을 반환하고, cu_phone 컬럼의 값이 null이 아닌 경우(false)에는 해당 행의 cu_phone의 값을 그대로 반환한다는 뜻이다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/9f10f649-4bbd-4222-ac6c-91e3b406b791/image.png" alt=""></p>
<p>위의 쿼리를 실행시켜서 조회해보면, 7,8 행은 연락처 컬럼이 null이 아니라서 연락처 값을 그대로 반환, 9~11 행은 연락처 컬럼이 null이라서 IF함수에서 지정한 &#39;null값&#39; 이라는 문자열이 대체되어 조회된다.    </p>
<p>IF함수는 null 값을 처리하는 것 말고도 다양한 경우에서 사용할 수 있을 것 같다. 다음에 기회가 된다면 IF함수로 처리할 수 있는 여러가지 경우를 터득하여 포스팅 할 계획이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[서브쿼리]]></title>
            <link>https://velog.io/@m_yn/%EC%84%9C%EB%B8%8C%EC%BF%BC%EB%A6%AC</link>
            <guid>https://velog.io/@m_yn/%EC%84%9C%EB%B8%8C%EC%BF%BC%EB%A6%AC</guid>
            <pubDate>Thu, 09 Mar 2023 06:57:11 GMT</pubDate>
            <description><![CDATA[<p>서브쿼리를 사용해보긴 했지만, 거의 WHERE문에서 사용했었다. SELECT문과 FROM문은 봐도 헷갈리는 부분이 있어서 이참에 서브쿼리에 대해 종합적으로 정리해보려고 한다.</p>
<h3 id="서브쿼리란">서브쿼리란?</h3>
<p><code>서브쿼리</code>는 말그대로 메인쿼리에서 서브로 사용하는 또 다른 쿼리문이다. 메인쿼리에 종속적이며 서브쿼리가 사용되는 목적은 메인쿼리에서 최종적으로 필요한 데이터를 조회하기 위한 수단으로 사용된다.</p>
<p>서브쿼리는 더 복잡한 조건을 만족하는 검색이나 연산을 할 때 유용하므로 자주쓰이고 있다. </p>
<h3 id="서브쿼리의-종류">서브쿼리의 종류</h3>
<blockquote>
<p>중첩 서브쿼리(Nested Subquery)</p>
</blockquote>
<p><code>WHERE절</code>에서 사용하는 쿼리로, <code>서브쿼리 연산자</code>를 사용하여 메인쿼리와 서브쿼리의 데이터를 비교하는 방식이다.</p>
<p>서브쿼리 연산자는 크게 <code>비교 연산자</code>와 <code>EXISTS</code> 연산자로 나눌 수 있다.</p>
<h4 id="1-비교연산자">1. 비교연산자</h4>
<p>비교 연산자는 서브쿼리의 결과값을 사용하여 메인쿼리의 조건식을 만족하는 데이터를 조회한다. 
비교 연산자에는 대소비교 연산자(&gt;, &lt;, &gt;=, &lt;=), 등호연산자(=, &lt;&gt;) 등이 있다.
비교연산자를 활용하여 서브쿼리를 작성할 때는 서브쿼리로 조회되는 결과가 반드시 1건 이하여야 한다. == <code>단일행 서브쿼리</code></p>
<h5 id="예시">예시)</h5>
<p>주문가격이 만원 이상인 고객들의 정보를 조회해보자</p>
<p>서브쿼리 없이 쿼리를 짠다면 밑의 코드처럼 될 것이다.</p>
<pre><code class="language-sql">SELECT id
FROM 주문테이블
WHERE 주문가격 &gt;= 10000;

SELECT id, 이름, 이메일
FROM 고객테이블
WHERE id in (위에서 실행한 쿼리의 결과값)</code></pre>
<p>이렇게 두번에 걸쳐서 먼저 조건을 충족하는 고객id를 조회하는 쿼리를 작성 한 후에, 새로운 쿼리로 위의 쿼리에서 받은 조회결과를 조건으로 만족하는 름, 이메일을 다시 조회한다.</p>
<p>쿼리를 두번에 걸쳐서 짜줘야 해서 복잡하다. 
서브쿼리를 사용하면 위의 두 개의 쿼리를 하나의 쿼리로 사용이 가능하다.</p>
<pre><code class="language-sql">SELECT id, 이름, 이메일
FROM 고객테이블
WHERE id IN (
  SELECT 고객id
  FROM 주문테이블
  WHERE 주문가격 &gt;= 10000
);</code></pre>
<p>위 코드는 서브쿼리를 활용하여 처음에 따로 각각 작성했던 코드를 합친거랑 똑같은 결과가 조회된다.</p>
<p>메인쿼리의 조회 조건인 where절에서 ( ) 괄호안에 조건으로 사용할 데이터를 조회하는 서브쿼리를 작성해주면 된다.</p>
<h4 id="2-exists-연산자">2. EXISTS 연산자</h4>
<p>EXISTS 연산자는 서브쿼리의 결과값이 존재 하는지 여부를 확인하여 메인쿼리의 조건식을 만족하는 데이터를 조회한다. 만약 결과값이 존재한다면 true를 반환하고, 존재하지 않으면 false를 반환한다.
EXISTS 연산자는 NOT EXISTS 연산자와 함께 사용하며, 서브쿼리의 결과값이 존재하지 않는 데이터를 조회할 수도 있다.</p>
<h4 id="예시-1">예시)</h4>
<p>고객 테이블에서 존재하는 이메일을 사용하는 주문이 있는지 확인하는 쿼리</p>
<pre><code class="language-sql">SELECT *
FROM 고객테이블
WHERE EXISTS (
  SELECT *
  FROM 주문테이블
  WHERE 고객테이블.id = 주문테이블.고객id
    AND 고객테이블.이메일 = 주문테이블.이메일
);
</code></pre>
<p>위의 쿼리를 실행하면, 먼저 where절에 있는 exists 연산자로 평가될 서브쿼리가 먼저 실행된다. 
만약 서브쿼리의 안에서 조회되는 행이 있다면(고객의 아이디와 일치하는 이메일을 사용하는 주문건수가 있다면), 즉 조회한 결과가 true 라면 해당 행을 모두 반환한다.
이 때 반환되는 행들은 여러행이 될 수 있다. == <code>다중 서브쿼리</code>
만약 exists를 not exists로 바꾼다면 exists와 반대로 주문내역이 없는 고객정보가 담긴 행만 반환하게 된다.
<br></p>
<blockquote>
<p>인라인 뷰(Inline View)</p>
</blockquote>
<p>FROM절에서 사용하는 서브쿼리.
FROM절에는 조회할 테이블을 작성하는데, FROM절에서 서브쿼리를 사용한다면 서브쿼리에 의해 선택된 결과집합은 하나의 테이블로써 사용되므로 반드시 이름(별칭)이 필요하다.</p>
<h4 id="예시-2">예시)</h4>
<p>주문을 한 고객중에서 가장 많은 금액을 결제한 정보를 조회</p>
<pre><code class="language-sql">select main.*
from (
    select mc.*, sum(order_price) as 총주문금액
    from my_order o
    join my_customer mc on o.cu_id = mc.cu_id
    group by mc.cu_id
    order by 총주문금액 desc
    limit 1
     ) as main;
</code></pre>
<p>from절에 오는 서브쿼리에서는 조건을 만족하는 고객의 정보를 조회하는 쿼리문에 들어간다. 조회된 결과셋은 테이블로써 사용되므로 main 이라는 별칭이 들어간다.</p>
<p>메인쿼리에서 조회되는 * 는 모든 정보를 의미하는데, from절에서 조회되는 컬럼들에 한해서 조회가 가능하다. </p>
<p>근데 생각해보니, 저 예시는 from절 서브쿼리를 사용하지 않고 join으로도 조회가 가능한 것 같아서 쿼리를 짜보았다.</p>
<pre><code class="language-sql">select m.*, sum(o.order_price) 총주문금액 from my_customer as m
join my_order o on m.cu_id = o.cu_id
group by m.cu_id
order by 총주문금액 desc
limit 1;</code></pre>
<p>from절에 서브쿼리를 작성하지 않고 고객테이블과, 주문테이블을 조인하여 결과를 돌려봐도 결과는 똑같다.</p>
<p>동일한 결과를 반환한다면 인라인뷰와 join을 사용하는 방식과의 차이점은 뭘까?</p>
<p>*<em>FROM절 서브쿼리 *</em>
FROM절에서 서브쿼리를 사용하는 방식은 서브쿼리 결과를 <code>새로운 가상 테이블</code> 만들어서 사용하는 방식이다.
따라서 서브쿼리가 실행될 때마다 해당 서브쿼리를 계산하고, 이를 메모리에 저장한 후에 다시 이를 이용해서 새로운 가상테이블을 만들어서 사용한다.</p>
<ol>
<li>서브쿼리 계산</li>
<li>결과값 메모리에 저장</li>
<li>새로운 가상테이블을 만들어서 사용<U>
이 방식은 간단하지만 서브쿼리의 결과가 크거나 복잡할 경우에는 성능 이슈가 발생할 수 있다.</u>

</li>
</ol>
<p><strong>JOIN</strong>
JOIN을 사용하는 방식은 두개 이상의 테이블을 합쳐서 결과를 가져오는 방식이다. 
이 방식은 조인할 테이블이 많을수록 더 복잡해질 수 있지만, FROM절에 서브쿼리를 사용하는 것 보다 성능면에서 더 좋다. </p>
<p><strong>결론</strong>
따라서, 일반적으로 데이터 양이 많거나 복잡한 쿼리의 경우에는 JOIN 방식을 사용하는 것이 더 좋다. </p>
<br>


<blockquote>
<p>스칼라 서브쿼리(Scalar Subquery)</p>
</blockquote>
<p>SELECT절에서 사용하는 서브쿼리다.
스칼라는 <code>하나의 수치만으로 완전히 표시되는 값</code> 이라는 단어의 뜻 그대로 하나의 행만 반환하고, 일치하는 값이 없다면 Null을 반환한다.</p>
<p>서브쿼리로 조회된 결과를 조회할 컬럼으로 사용한다.</p>
<h4 id="예시-3">예시)</h4>
<p>가장 많이 주문한 고객의 이름과, 주문횟수 조회</p>
<pre><code class="language-sql">SELECT 
  이름, 
  (SELECT COUNT(*) FROM 주문테이블 WHERE 고객id = 고객테이블.id) AS 주문횟수 
FROM 
  고객테이블 
ORDER BY 
  주문횟수 DESC 
LIMIT 1</code></pre>
<p>위의 쿼리에서는 count함수로 주문횟수를 같이 조회하기 위해 select 절에 서브쿼리를 사용한 경우다.</p>
<p>다음은 스칼라 서브쿼리를 사용하지 않고 Join을 이용하여 같은 결과값을 조회하는 쿼리다.</p>
<pre><code class="language-sql">select cu_name, count(*) 주문횟수 from my_order o
join my_customer mc on o.cu_id = mc.cu_id
group by mc.cu_id
order by 주문횟수 desc limit 1;</code></pre>
<p>두 쿼리의 결과값은 동일하지만, 첫번째 쿼리는 스칼라 서브쿼리를 사용하여 각 고객의 주문횟수를 구하고, 그 결과를 각각의 행에 추가한다.
두번째 쿼리는 join을 사용하여 고객별 주문 수를 구한다.
조회하려는 데이터의 규모가 작다면 스칼라 서브쿼리나 join 중 어느것을 사용해도 큰 상관이 없지만, 대부분의 경우 더 효율적이고 빠른 join을 사용하는것이 더 권장된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript - 싱글스레드와 비동기처리]]></title>
            <link>https://velog.io/@m_yn/JavaScript-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC%EC%99%80-%EC%8B%B1%EA%B8%80%EC%93%B0%EB%A0%88%EB%93%9C</link>
            <guid>https://velog.io/@m_yn/JavaScript-%EB%B9%84%EB%8F%99%EA%B8%B0%EC%B2%98%EB%A6%AC%EC%99%80-%EC%8B%B1%EA%B8%80%EC%93%B0%EB%A0%88%EB%93%9C</guid>
            <pubDate>Thu, 09 Mar 2023 00:28:24 GMT</pubDate>
            <description><![CDATA[<p>업무에 들어감에 있어서 자바스크립트의 핵심적이고 기본적인 개념에 대해 이해가 부족한 것 같아서 대표적인 특징인 비동기처리에 대해 먼저 알아보려고 한다.</p>
<p>자바스크립트의 대표적인 특징으로는 비동기처리, 병렬처리, 동시성 등.. 들어보긴 했지만, 당연히 되는 거라고 생각을 해왔다. 근데 자바스크립트는 <code>싱글쓰레드</code>로 동작을 하는 언어다. </p>
<p>싱글쓰레드는 자바스크립트 엔진에서 관리하는 하나의 호출스택(call stack)과 하나의 메모리힙(memory heap)만을 사용한다는 것을 의미한다.</p>
<blockquote>
<p><code>자바스크립트 엔진</code> 코드를 해석하고 실행하는 역할을 수행. 대표적으로 v8 엔진이 사용되며, 코드의 문법검사, 바이트코드로의 컴파일, 메모리관리, 콜스택의 구현 등을 수행한다.
<code>호출스택</code> 함수가 호출되면서 스택으로 쌓이는 곳
<code>메모리힙</code> 메모리 할당을 담당하는 곳</p>
</blockquote>
<p>함수 호출 시 해당 함수의 실행 컨텍스트를 호출 스택에 쌓아 올리고, 함수가 반환되면 해당 실행 컨테스트를 스택에서 제거한다. </p>
<p>이렇듯 실행중인 함수를 쌓아두는 곳인 호출스택이 한개이기 때문에, 동시에 하나의 작업만 가능하다. 근데 어떻게 비동기 처리가 가능하게 된 걸까?<img src="https://velog.velcdn.com/images/m_yn/post/2224c22b-2b9b-44ab-b597-5d2dd1e4b595/image.png" alt=""></p>
<p>위의 사진에서 보면, 자바스크립트 엔진인 V8이 갖고있는 하나의 콜스택과 메모리힙이 보이고, 웹브라우저에서 제공하는 <code>Web API</code>가 있다는 것을 알 수 있다.
Web API는 자바스크립트에서 제공되지 않는 브라우저의 기능을 활용할 수 있도록 제공되는 <strong>비동기처리</strong> <strong>API</strong> 인데, 대표적으로 <code>setTimeout</code>, <code>ajax</code>, <code>setInterval</code> 등의 함수가 포함된다.</p>
<p>자바스크립트에서는 일반적으로 <code>콜백함수</code>를 사용하여 비동기작업을 처리한다. 대표적인 예로 setTimeout 함수를 사용하면, 일정 시간이 지난 후에 콜백 함수가 호출된다.</p>
<p>이러한 콜백함수는 호출 스택에 직접 추가되지 않고, Web API에서 <code>비동기적인 처리</code>를 마친 후 <code>콜백큐</code>에 저장된다. 이 콜백큐는 호출스택과는 별도의 큐로서, 콜백 함수들이 대기하고 있다.</p>
<p>Web API가 처리하는 비동기 작업과 콜백함수가 콜백큐에 추가되는 과정은 예로 다음과 같다.</p>
<pre><code class="language-javascript">console.log(&#39;aaa&#39;);

const button = document.querySelector(&#39;button&#39;);
button.addEventListener(&#39;click&#39;, () =&gt; {
  console.log(&#39;bbb&#39;);
});

console.log(&#39;ccc&#39;);

// 출력순서 : aaa, ccc</code></pre>
<p>위 코드는 버튼 클릭이벤트를 처리하는 이벤트 핸들러를 등록하고, 버튼을 클릭하여 이벤트가 발생하면 콜백함수를 실행하는 코드다. 이 때 Web API가 하는 역할을 알아보자.</p>
<ul>
<li>이벤트핸들러 등록 : <code>addEventListener</code> 메소드를 사용하여 버튼클릭 이벤트 핸들러를 등록</li>
<li>이벤트 대기 : 이벤트가 발생할 때 까지 대기</li>
<li>콜백 함수 추가 : 이벤트가 발생하면 콜백 함수를 콜백큐에 추가</li>
</ul>
<p>위의 코드에서 aaa, ccc만 출력되는 이유는 아직 클릭이벤트가 발생하지 않았기 때문이다. 단지 Web Api는 버튼클릭 이벤트 핸들러를 등록해놓고, 클릭이벤트가 발생하면 그때 콜백함수를 콜백큐에 넘겨준다.</p>
<p>그럼 비동기처리의 결과로 콜백큐에 저장되어있는 콜백함수는 어떻게 실행되는걸까?</p>
<p>사진에서 보이는 <code>event loop</code>는 여기서 등장하게 되는데, 콜스택과 콜백큐를 모니터링하며 콜스택이 비어있을 때 콜백큐에서 콜백함수를 꺼내어 실행한다. 이벤트 루프는 계속해서 호출 스택과 콜백큐를 감시하면서 반복적으로 실행된다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[join]]></title>
            <link>https://velog.io/@m_yn/join</link>
            <guid>https://velog.io/@m_yn/join</guid>
            <pubDate>Wed, 08 Mar 2023 08:11:28 GMT</pubDate>
            <description><![CDATA[<h3 id="join">join</h3>
<p>데이터들은 하나의 테이블에 모두 저장되는게 아니라, 데이터의 성격에 따라 여러 테이블에 나뉘어서 저장된다. 데이터의 성격에 따라 다른 테이블에 나뉘어 저장된다 해도, 테이블끼리 서로 유기적으로 연관되어 있는 경우가 대부분이다.</p>
<p>테이블별로 분리되어 있는 데이터들을 연결해서 하나의 셋으로 출력해야 할 때 쓰는 구문이 <code>Join</code> 이다.</p>
<p>만약 주문 테이블과 리뷰 테이블이 있다고 가정해보자.</p>
<p>주문내역에는 존재하지만, 리뷰를 작성하지 않아서 리뷰테이블에는 없는 데이터가 있을 수 있다. </p>
<p>이 때 주문 테이블과 리뷰 테이블을 Join하여 데이터를 조회할 때 리뷰가 없는 주문건도 조회를 할지, 아니면 리뷰가 작성된 주문건만 조회할 지 선택해야 한다. </p>
<p>join에는 여러 종류가 있는데, 출력하고자 하는 형식에 따라 join 종류 중 필요한 join 구문을 사용해야 한다.</p>
<blockquote>
<ol>
<li>inner join</li>
</ol>
</blockquote>
<p>inner join은 교집합 연산과 같다. 조회하려는 컬럼이 조인하려는 테이블 모두에 공통적으로 존재하는 데이터만 조인하여 출력한다.</p>
<p>20개의 주문건수가 존재하는 주문테이블과, 12개의 리뷰내역이 존재하는 테이블 두 개를 inner join 으로 조회하는 구문은 이렇다.</p>
<pre><code class="language-sql">select o.order_state, o.order_price, r.review_content, r.review_score as 점수 from my_order o
left outer join my_review r
on o.order_id = r.order_id;</code></pre>
<p><img src="https://velog.velcdn.com/images/m_yn/post/d8620ce6-4f48-4be3-8bba-b3868374e641/image.png" alt=""></p>
<p>20행의 주문테이블과, 12행의 리뷰테이블을 left join 으로 묶어서 조회한 결과 리뷰내역이 존재하는 12개의 행들만 조회가 된다. </p>
<p>만약 여기서 리뷰가 쓰여지지 않은 주문내역도 같이 조회를 하고싶으면 어떻게 할까? </p>
<p>inner 를 left outer로 바꿔주면 된다.</p>
<blockquote>
<p>left outer join</p>
</blockquote>
<p>left outer join의 용도로 생각해볼 수 있는 것 중 하나는 다음과 같다. </p>
<p>만약 상품을 정렬하는 기능 중 &#39;리뷰많은순&#39; 으로 정렬을 해야한다고 가정해보자. 그 중 어제 등록된 상품은 아직 주문이 들어오지 않아 리뷰개수가 0일 것이다. </p>
<p>하지만 리뷰많은순 정렬을 클릭했을 때는 리뷰개수가 0인 상품들도 나와야 한다. 등록된 리뷰가 없다고 상품 목록에서 제외를 시키는것은 안되는데, 만약 이때 inner join을 이용하여 상품 목록을 보여주게 된다면 리뷰개수가 0인 상품들은 조회가 되지 않을것이다.</p>
<p>이때 사용하는 조인이 left outer join이다.</p>
<p>위에 사진은 inner join 으로 12개의 행이 조회되는 것을 볼 수 있다.</p>
<pre><code class="language-sql">select count(*) from my_order as o
left outer join my_review r
on o.order_id = r.order_id;</code></pre>
<p>이렇게 left outer 조인으로 바꿔주게 된다면 count 컬럼의 값은 inner join의 결과로 집계됐던 12행이 아닌 20행일 것이다. 리뷰가 0개인 것들도 조회를 해주는 것이다.</p>
<p>더 추가적으로 설명하자면 left outer join은 왼쪽에 오는 행이 <code>기준</code>이 된다. 
위의 코드로 설명해보면 주문 테이블을 기준으로 리뷰 테이블을 조인한다. 조회 결과는 기준이 되는 왼쪽테이블의 모든 행과 오른쪽 테이블의 매칭되는 행을 조인하게되는데, 만약 오른쪽 테이블에서 매칭되는 행이 없으면 NULL 값이 반환된다. </p>
<p>left outer join과 반대되는 <code>right outer join</code> 은, 오른쪽 컬럼이 기준컬럼이 되어 오른쪽 테이블의 모든 행과 왼쪽 테이블의 매칭되는 행을 조인하게 된다.</p>
<blockquote>
<p>Full outer join</p>
</blockquote>
<p>full outer join은 기준이 되는 테이블이 없다. 상품테이블과 리뷰테이블을 조인하여 조회할 때 서로 매칭되는 값이 없어도 모두 출력해주는 <code>합집합</code>과 비슷하다. A테이블에만 존재하는 행 + B테이블에만 존재하는 행 + A,B 에 모두 존재하는 행(교집합)을 모두 출력할 때 full outer join을 사용한다.(모든 행이 조건에 관계없이 결합)</p>
<p>현재 사용중인 mariaDB는 Full outer join을 지원하지 않는다. mariaDB에서 full outer join을 사용하려면 <code>union</code>을 이용하여 조회해야 한다.</p>
<pre><code class="language-sql">select pro_name, pro_price, review_content, review_score from my_products as p
left outer join my_review r
on p.pro_id = r.pro_id
union
select pro_name, pro_price, review_content, review_score from my_products as p
right outer join my_review r
on p.pro_id = r.pro_id;</code></pre>
<p>full outer join은 결국 left outer join과 right outer join을 합친 것이기 때문에 두 조인 구문을 union을 이용해서 합쳐주면 결합한 모든 행들이 조회가 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Group by]]></title>
            <link>https://velog.io/@m_yn/Group-by</link>
            <guid>https://velog.io/@m_yn/Group-by</guid>
            <pubDate>Wed, 08 Mar 2023 06:32:06 GMT</pubDate>
            <description><![CDATA[<h3 id="group-by-구문">Group by 구문</h3>
<p>SQL에서 사용되는 중요한 구문 중 하나.
데이터를 그룹화하여 집계 함수를 사용하여 <code>통계 정보</code>를 생성하는 데 사용된다.
<img src="https://velog.velcdn.com/images/m_yn/post/127d91fe-8eec-480f-9889-44a6bbd41a21/image.png" alt=""></p>
<p>위 사진은 주문 테이블을 전체 조회한 결과다.
이 중에서 cu_id(고객id)가 1인 경우에만 조회하려면 다음과 같이 쿼리를 작성할 수 있다.</p>
<pre><code class="language-sql">select * from my_order where cu_id = 1;</code></pre>
<p>그럼 아래 사진처럼 cu_id가 1인 행들만 조회가 된다.</p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/1d605205-7362-4a4c-83b4-94fd45bdb64b/image.png" alt=""></p>
<p>여기서 만약, 고객별로 총 구매개수를 알고싶으면 어떻게 해야할까? cu_id(고객id) 컬럼을 그룹화하여 주문수량의 총 개수를 구하면 된다. 그럴때 사용하는 구문이 Group by절이다.</p>
<p>그룹함수는 <code>집계함수</code>와 같이 사용된다. 집계함수는 그룹화된 각 컬럼 데이터들의 통계를 내기 위해 사용되는 함수다.</p>
<ol>
<li>sum : 값을 모두 합산하여 반환한다.</li>
<li>avg : 평균값을 반환한다.</li>
<li>count : 열의 값을 모두 세어 반환한다.</li>
<li>max : 가장 큰 값을 반환한다.</li>
</ol>
<br>

<p>group by절과 집계함수를 같이 사용하여 고객별로 총 구매수량을 구하는 함수는 아래와 같다.</p>
<pre><code class="language-sql">select cu_id, sum(order_quantity) from my_order group by cu_id;</code></pre>
<p><img src="https://velog.velcdn.com/images/m_yn/post/d7087c10-684e-49a4-b73d-9437988e0f7c/image.png" alt=""></p>
<p>총 구매수량을 알기 위해 sum 집계함수를 사용했다. 만약 총 구매수량이 아니라  주문 횟수를 구하고 싶다면 sum 대신 count를 넣어서 조회하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인텔리제이 + DB 연동하기]]></title>
            <link>https://velog.io/@m_yn/%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4-DB-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@m_yn/%EC%9D%B8%ED%85%94%EB%A6%AC%EC%A0%9C%EC%9D%B4-DB-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 12 Feb 2023 12:35:26 GMT</pubDate>
            <description><![CDATA[<p>학원에서는 이클립스 + oracle을 사용했었다.
오라클 홈페이지 가서 프로그램을 깔고, cmd 창에서 이것저것 설정을 해주었던 기억이 있는데, 현재 회사에서 사용중인 인텔리제이에 db를 연결해볼까? 수업때처럼 사용하고자 하는 데이터베이스 프로그램을 설치하는 것 부터 시작해서 복잡한 과정을 거쳐야하는 걸까? 데이터베이스는 앞으로 계속 사용할 것이므로 바로 시도해봤다.</p>
<p>결론은 생각보다 간편하게 설치를 완료했다.
프로그램을 os에 맞게 설치하는 번거로운 과정도 없었다.
현재 맥os를 사용중인데 맥은 맥os용 패키지 관리자인 홈브류(Homebrew)라는 어플리케이션이 있다. 맥os로 프로그래밍을 하는 개발자에게는 거의 필수적인 도구이다. 필요한 도구나 시스템 패키지들을 설치하는 데 사용된다.</p>
<p>나는 아주 유용한 이 홈브류 어플리케이션을 통해 mysql 데이터베이스를 쉽게 설치하였다.</p>
<ol>
<li><p>Homebrew 업데이트
<code>$ brew update</code></p>
</li>
<li><p>my sql 설치
<code>$ brew install mysql</code></p>
</li>
</ol>
<p>설치가 무사히 완료되면 mysql_secure_intallation 문구가 출력된다!
이제 실행단계로 넘어가서 비밀번호를 설정해보자.</p>
<ol start="3">
<li><p><code>mysql -u root -p</code> 명령어 입력
password를 입력하는 문구가 출력되는데 비번을 설정하지 않았으면 아무것도 입력하지 않고 엔터를 치면 된다.</p>
</li>
<li><p><code>user mysql</code> 입력</p>
</li>
<li><p><code>select host, user, authentication_string from user;</code>
위의 명령어를 입력하면 현재 패스워드 설정과 관련된 내용 리스트가 출력된다.</p>
</li>
<li><p><code>alter user &#39;root&#39;@&#39;localhost&#39; identified with mysql_native_password by &#39;new_password_you_want&#39;</code>
유저정보를 수정하겠다는 명령어로 &#39;new_password_you_want&#39; 부분에 원하는 비밀번호를 입력한다.</p>
</li>
</ol>
<p>이제 터미널에서 명령어로 설치하고 설정해줘야 하는 단계는 완료됐다.
본격적으로 인텔리제이로 넘어가서 DB와 연결해주면 된다.</p>
<ol start="7">
<li><p>인텔리제이 - database - new(+) - mysql - 계정정보 입력
테스트 커넥션으로 접속테스트를 해본 후 성공하면 커넥션으로 연결해주자</p>
</li>
<li><p>연동에 성공했으니 server object - 쿼리콘솔을 클릭하여 쿼리콘솔창을 연 후에 원하는 쿼리를 입력하여 실행!</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Mustache]]></title>
            <link>https://velog.io/@m_yn/%EA%B0%9D%EC%B2%B4%EC%99%80-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@m_yn/%EA%B0%9D%EC%B2%B4%EC%99%80-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Sun, 12 Feb 2023 12:01:57 GMT</pubDate>
            <description><![CDATA[<p>JSP와 Vue.js로 화면 영역을 개발한 경험이 있지만, Mustache는 처음 다뤄보는 엔진이므로 궁굼해져서 작성하게 되었다.</p>
<h3 id="mustache란">Mustache란?</h3>
<p>스프링과 같은 웹 프레임워크에서 사용되는 템플릿 엔진중 하나.
Mustache는 간단하고 직관적인 구문을 사용하여 HTML, XML, JSON 등 다양한 형식의 템플릿을 작성할 수 있다.</p>
<p>Mustache 템플릿에서는 변수, 반복문, 조건문 등의 기본적인 로직을 작성할 수 있으며 HTML 태그나 자바스크립트 코드와 같은 부분을 제외한 일반 텍스트를 그대로 출력하므로, 코드의 가독성을 높이는 장점이 있다.</p>
<p>또한 수많은 언어를 지원하는 가장 심플한 템플릿 엔진으로 루비, 자바스크립트, 파이썬, PHP, 자바, 펄, Go, ASP 등 현존하는 대부분 언어를 지원하고 있다. 그래서 자바에서 사용될 때는 서버 템플릿 엔진으로, 자바스크립트에서 사용될 때는 클라이언트 템플릿 엔진으로 모두 사용할 수 있다.</p>
<p>자바 진영에서는 jsp, Thymeleaf 등의 서버 템플릿 엔진이 있는데, jsp는 이미 활용을 많이 해봤고 Thymeleaf는 아직 나에게는 문법이 다소 어려울 것 같아서 다른 템플릿 엔진보다 심플한 머스테치가 적당할 것 같다.</p>
<h3 id="mustache-플러그인-설치">Mustache 플러그인 설치</h3>
<p><img src="https://velog.velcdn.com/images/m_yn/post/ab1d3c70-1039-4ecc-9af7-a05387bc967b/image.png" alt=""></p>
<p>인텔리제이 Plugins - Marketplace로 이동하여 Handlebars/Mustache를 선택 후 설치한다.</p>
<h3 id="기본-페이지-만들기">기본 페이지 만들기</h3>
<ol>
<li><p>설치 후 가장먼저 해야 할 것은 build.gradle에 의존성을 등록하는 것이다.</p>
<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-mustache&#39;</code></pre>
</li>
<li><p>그 다음으로 index.mustache를 src/main/resources/templates에 생성한다. 이 경로에 머스테치 파일을 두면 스프링 부트에서 자동으로 로딩한다.</p>
</li>
<li><p>index.mustache 코드를  채워주자</p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
 &lt;title&gt;스프링 부트 웹서비스&lt;/title&gt;
 &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
</code></pre>
</li>
</ol>
<body>
    <h1>스프링 웹서비스!</h1>
</body>
</html>
```

<ol start="4">
<li><p>URL 매핑하기
매핑할 컨트롤러를 생성하여 다음과  같이 코드를 적어주자.</p>
<pre><code class="language-java">@Controller
public class indexController {

 @GetMapping(&quot;/&quot;)
 public String index() {
     return &quot;index&quot;;
 }
}</code></pre>
</li>
</ol>
<h3 id="test-코드-작성">Test 코드 작성</h3>
<p>여기까지 코드가 완성 되었으니, 이번에도  테스트 코드로 검증을 해보자.</p>
<pre><code class="language-java">@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class IndexControllerTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    public void mainPageLoading() {
        //when
        String body = this.restTemplate.getForObject(&quot;/&quot;, String.class);

        //then
        assertThat(body).contains(&quot;스프링 웹서비스&quot;);
    }
}</code></pre>
<p>위 코드에서는 랜덤포트를 할당받고, <code>TestRestTemplate</code>을 사용하여 요청을 보내고 응답을 받는다. 그 후 <code>assertThat</code>을 사용하여 응답 body에 &quot;스프링 웹서비스&quot; 라는 문자열이 포함되어 있는지 검증한 후, 포함된다면 성공하고 포함되지 않는다면 실패하는 테스트 코드다.</p>
<p>TestRestTemplate의 <code>getForObject</code> 메소드는 HTTP GET 요청을 보내고 해당 URL의 응답을 객체로 반환한다. 위 코드에서는 &#39;/&#39; 경로에 GET 요청을 보내고, 반환된 응답을 String 형식으로 받아서 body 변수에 저장하게 된다.</p>
<p>참고로 body를 콘솔창에 출력해 보면 다음과 같다.
<img src="https://velog.velcdn.com/images/m_yn/post/8241bfda-a1ec-4358-a055-073941d491f7/image.png" alt=""></p>
<br>

<p>localhost:8080 경로로 접속해보면 화면에서도 잘 뜨는 것이 확인된다.
<img src="https://velog.velcdn.com/images/m_yn/post/78113153-df1f-4235-977c-666e85a35e9b/image.png" alt=""></p>
<blockquote>
<p>참고 : 스프링 부트와 AWS로 혼자 구현하는 웹 서비스  </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[오류해결] While resolving: @vue/eslint-config-standard@6.1.0]]></title>
            <link>https://velog.io/@m_yn/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-While-resolving-vueeslint-config-standard6.1.0</link>
            <guid>https://velog.io/@m_yn/%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0-While-resolving-vueeslint-config-standard6.1.0</guid>
            <pubDate>Thu, 09 Feb 2023 04:41:45 GMT</pubDate>
            <description><![CDATA[<p>Vue.js 프로젝트를 진행하던 중 생긴 예상치 못한 오류
ajax를 사용하기 위해 axios를 설치하려 로컬터미널에 <code>npm install axios</code> 명령어를 실행하였다. 하지만 설치가 되지 않고 오류 메세지들이.....</p>
<p>``
 ERR! code ERESOLVE
npm ERR! ERESOLVE could not resolve
npm ERR! 
npm ERR! While resolving: @vue/eslint-config-standard@6.1.0
npm ERR! Found: <a href="mailto:eslint-plugin-vue@8.7.1">eslint-plugin-vue@8.7.1</a>
npm ERR! node_modules/eslint-plugin-vue
npm ERR!   dev eslint-plugin-vue@&quot;^8.0.3&quot; from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer eslint-plugin-vue@&quot;^7.0.0&quot; from @vue/eslint-config-standard@6.1.0
npm ERR! node_modules/@vue/eslint-config-standard
npm ERR!   dev @vue/eslint-config-standard@&quot;^6.1.0&quot; from the root project
npm ERR! 
npm ERR! Conflicting peer dependency: <a href="mailto:eslint-plugin-vue@7.20.0">eslint-plugin-vue@7.20.0</a>
npm ERR! node_modules/eslint-plugin-vue
npm ERR!   peer eslint-plugin-vue@&quot;^7.0.0&quot; from @vue/eslint-config-standard@6.1.0
npm ERR!   node_modules/@vue/eslint-config-standard
npm ERR!     dev @vue/eslint-config-standard@&quot;^6.1.0&quot; from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! See /Users/경로/.npm/eresolve-report.txt for a full report.</p>
<p>npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/경로/.npm/_logs/2023-02-09T04_09_30_005Z-debug-0.log
minyounglee@Mins-MacBook-Pro vuestagram % npm install axios
npm ERR! code ETARGET
npm ERR! notarget No matching version found for @vue/eslint-config-standard@^8.0.3.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn&#39;t exist.</p>
<p>npm ERR! A complete log of this run can be found in:
npm ERR!     /경로/.npm/_logs/2023-02-09T04_16_30_419Z-debug-0.log
``</p>
<p>종속성을 해결할 수 없어서 발생한 오류.
package.json 파일을 확인하니 <code>@vue/eslint-config-standard</code> 버전이 6.1.0 버전으로 되어있는데 설치된 모듈과 호환되지 않는 피어 종속성이 있는 것 같다.</p>
<p>해결방법
&quot;eslint-plugin-vue&quot;: &quot;^7.20.0&quot;
eslint의 플러그인 버전을 위와 같이 수정해 준 뒤 다시 설치를 시도한 결과 성공적으로 설치되었다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Vue.js 3 프로젝트 생성]]></title>
            <link>https://velog.io/@m_yn/Vue.js-3-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@m_yn/Vue.js-3-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Wed, 08 Feb 2023 13:55:02 GMT</pubDate>
            <description><![CDATA[<p>Vue.js 가 초면인 상황에서 공부를 시작하였다.
프로젝트를 새로 생성 및 설정하면서 느꼈던 것은 역시 난 코드를 작성하는 것 보다 환경설정이 훨씬 어렵다는 것!</p>
<p>이번에 프로젝트를 생성 한 방법도 정석은 아니다. 
앞으로 여러 Vue.js 3         프로젝트를 생성하면서 숙련된다면 추후에 수정할 예정이다.</p>
<p>우선 내가 프로젝트를 생성한 방법을 작성해보자...</p>
<ol>
<li>프로젝트를 생성할 디렉토리 만들기</li>
<li>개발할 프레임워크 상단메뉴 File-Open 클릭하여 생성한 디렉토리 불러오기
(내가 쓰는 프레임워크는 인텔리제이!)</li>
<li>로컬 터미널에서 node.js 버전 확인 
<code>node -v</code>
확인되지 않다면 아래 링크로 접속하여 설치해주자. 
<a href="https://nodejs.org/ko/download/">https://nodejs.org/ko/download/</a></li>
</ol>
<ul>
<li>node.js는 npm을 사용하기 위해 필요하다.</li>
<li>npm은 노드 패키지 매니저의 약자로 패키지를 관리하는 곳이다.</li>
</ul>
<ol start="4">
<li>vue/cli 설치
<code>npm install -g @vue/cli</code></li>
</ol>
<p>위의 네 과정으로 vue 프로젝트를 생성하기 위한 준비는 끝났다.
이제 본격적으로 vue 프로젝트를 생성 해보자!</p>
<p><code>vue create 프로젝트명</code>
그 후 사용하는 버전을 설정해 주면 vue 프로젝트 생성이 완료된다.</p>
<p><code>cd 프로젝트명</code> 를 입력하여 생성된 프로젝트로 경로 이동을 한 후
<code>npm run serve</code> 명령어를 입력하여 프로젝트를 실행해 준다.</p>
<p>마지막으로 프로젝트가 무사히 실행된 것을 확인하려면 터미널창에 실행 성공 후 띄워진  localhost 주소로 접속하여 확인하면 된다.</p>
<p>이렇게 개발환경 세팅은 의외로 간단하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 프로젝트 환경설정, 오류해결]]></title>
            <link>https://velog.io/@m_yn/Spring-Boot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0</link>
            <guid>https://velog.io/@m_yn/Spring-Boot-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-%EC%98%A4%EB%A5%98%ED%95%B4%EA%B2%B0</guid>
            <pubDate>Thu, 22 Dec 2022 14:25:07 GMT</pubDate>
            <description><![CDATA[<p>팀 프로젝트에서 Spring &amp; maven으로 프로젝트를 생성하여 사용해봤으니 개인 프로젝트는  Spring Boot &amp; gradle 프로젝트로 진행하려고 한다.</p>
<p>Tool : Spring Tool Suite 3
Project : Gradle Project
Spring Boot : 2.3.7
Language : Java
Java : 16</p>
<br>

<p><strong>1. 프로젝트 생성</strong></p>
<p><a href="https://start.spring.io">https://start.spring.io</a>  페이지로 접속하면 쉽게 프로젝트를 생성할 수 있다.
<br></p>
<p>*<em>2. STS에서 생성한 프로젝트 Import *</em></p>
<p>프로젝트를 Import로 가져오는건 성공했지만, 예상대로 프로젝트에 빨간 x표시가 떴다. </p>
<p>알아보니 내가 사용하고 있는 JDK 버전은 11버전이였고, 프로젝트에서 요구하는 버전은 16버전 이였다. </p>
<p>JDK 16버전 설치를 한 뒤 Build Path에서 버전 재설정, 자바 컴파일러 재설정을 마쳐주니 x표시가 깔끔하게 사라졌다!
<br></p>
<p><strong>3. 실행하기</strong></p>
<p><img src="https://velog.velcdn.com/images/m_yn/post/39dfd811-49ca-4c9a-9d0e-8d7747010ffc/image.png" alt=""></p>
<p>Run으로 실행을 돌려본 결과 콘솔창에 메세지들이 많이 뜬다!
하지만 한가지 거슬리는게 있으니.. INFO 속에 끼어든 WARN 메세지..</p>
<pre><code class="language-Description:">
Web server failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that&#39;s listening on port 8080 or configure this application to listen on another port.</code></pre>
<p>8080은 이미 사용중인 포트번호라는 오류같다.
그럼 포트번호만 바꿔주도록 하자!</p>
<p>application.properties 파일을 연 다음에</p>
<pre><code>#port change
server.port=8082</code></pre><p>코드를 추가하도록 하자.
저장하고 난 후 다시 돌려보니 거슬리던 WARN 메세지가 사라졌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[네트워크(4) 파이프라인 & 쿠키]]></title>
            <link>https://velog.io/@m_yn/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC4-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%BF%A0%ED%82%A4</link>
            <guid>https://velog.io/@m_yn/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC4-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8-%EC%BF%A0%ED%82%A4</guid>
            <pubDate>Thu, 15 Dec 2022 08:53:56 GMT</pubDate>
            <description><![CDATA[<p>초기의 HTTP 버전은 HTTP 통신을 한번 할 때마다 TCP에 의해 연결과 종료를 해야했다.
당시의 통신에서는 작은 사이즈의 텍스트를 보내는 정도였기 때문에 별다른 문제는 없었다.
그러나 HTTP가 널리 보급되어가면서 다량의 이미지를 포함한 문서 등이 늘어나게 됐다.
하나의 HTML에 여러 이미지가 포함되어 있는 경우 브라우저를 사용해서 리퀘스트를 하면 HTML 문서에 포함되어 있는 이미지를 획득하기 위해서 여러 리퀘스트를 송신해야 했다. 매번 리퀘스트를 보낼때마다 TCP 연결과 종료를 반복하게 되니 통신량도 늘어나게 됐다. </p>
<blockquote>
<p>지속연결(Persistent Connections)</p>
</blockquote>
<p>HTTP/1.1과 일부 HTTP/1.0에서는 TCP연결 문제를 해결하기 위해 지속연결을 고안하였다.
지속연결은 어느 한 쪽이** 명시적으로 연결을 종료하지 않는 이상 TCP연결을 계속 유지**하는 특징이 있다. 즉 한번의 연결로 여러번의 리퀘스트를 요청하고 리스폰스를 받을 수 있게 되었다. 
TCP커넥션의 연결과 종료를 반복하는 오버헤드를 줄이게 되고, 서버에 대한 과부하가 경감되며 리퀘스트/리스폰스가 빠르게 이루어진다는 장점을 갖고 있다.</p>
<blockquote>
<p>파이프라인화</p>
</blockquote>
<p>지속연결은 여러 리퀘스트를 보낼 수 있도록 <code>파이프라인(HTTP pipelining)</code>화를 가능하게 한다. 이전에는 리퀘스트 송신 후에 리스폰스를 수신할 때까지 기다린 뒤에 그다음 리퀘스트를 송신했던 것을 리스폰스를 기다리지 않고 바로 다음 리퀘스트를 연속적으로 보낼 수 있게 된 것이다. </p>
<blockquote>
<p>쿠키</p>
</blockquote>
<p>HTTP는 stateless 프로토콜이기 때문에, 과거에 교환했었던 리퀘스트와 리스폰스의 상태를 관리하지 않는다. 과거의 상태를 근거로 해서 현재 리퀘스트를 처리하는게 불가능하다.  이러한 특징들로 서버의 CPU나 메모리같은 리소스의 소비를 줄일수 있는 장점도 있다. 
하지만 큰 단점도 존재하게 되는데 어떤 사이트에서 로그인을 한 뒤에 잠시 다른 사이트를 방문했다가 돌아왔을 때 로그인이 유지되지가 않아 불편함을 유발하는 점이다. 이러한 단점을 보완하기 위해 <code>쿠키</code>라는 시스템이 도입됐다. </p>
<p>쿠키는 서버에서 클라이언트로 리퀘스트에 대한 <strong>리스폰스를 보낼 때 쿠키를 발행하여 같이 송신</strong>한다. 리스폰스를 받은 클라이언트는 리스폰스로 보내진 <code>Set-Cookie</code>라는 헤더필드에 의해 쿠키를 보존하게 된다. </p>
<p>그 다음 재차 통신을 하게 될 때 클라이언트는 <strong>리퀘스트에 쿠키를 붙여서 송신하게 되면, 서버에서는 쿠키를 확인해서 어느 클라이언트가 접속했는지 확인하고 서버상의 기록을 확인해 이전 상태를 알 수 있게된다.</strong></p>
<br>
<br>
    1. 리퀘스트메세지(쿠키를 갖고 있지 않은 상태)

<p>GET /reader/HTTP/1.1
Host: <a href="http://www.youngjin.com">www.youngjin.com</a></p>
 <br>



<ol start="2">
<li>리스폰스(서버가 쿠키를 발행하여 송신)</li>
</ol>
<p>HTTP /1.1 2000 OK
DATE : The. 12 Jul 2012 07:12:20 GMT
Server: Apache
&lt;Set-Cookie: sid=1342077140226724; // 내용생략&gt;</p>
<br>

<ol start="3">
<li>리퀘스트(보관하고 있던 쿠키를 자동 송신)</li>
</ol>
<p>GET /image/HTTP/1.1
 Host: <a href="http://www.youngjin.com">www.youngjin.com</a>
Cookie: sid=1342077140226724</p>
<br>
<br>

<p>참고 : 그림으로 배우는 HTTP &amp; Network Basic
참고 : 성공과 실패를 결정하는 1%의 네트워크 원리</p>
]]></description>
        </item>
    </channel>
</rss>