<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>stable_root.log</title>
        <link>https://velog.io/</link>
        <description>왕왕왕초보</description>
        <lastBuildDate>Thu, 02 Feb 2023 15:48:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>stable_root.log</title>
            <url>https://velog.velcdn.com/images/jg-konkuk/profile/49d7bfd8-cd01-41a2-acb7-d3236dba7241/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. stable_root.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jg-konkuk" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[언리얼5] 에셋 만들기(모델링 모드)]]></title>
            <link>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-%EA%B1%B4%EB%AC%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-%EA%B1%B4%EB%AC%BC-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 02 Feb 2023 15:48:32 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="개요">개요</h2>
<ul>
<li>웬만하면 퀵셀, 언리얼 마켓플레이스에서 완성된 에셋을 이용하여 건물을 만들어보려 했으나 생각보다 무료인 에셋들 중 맞는 것을 구하기 힘들었고 퀵셀의 타일을 이용하여 새로 만들기로 하였다.</li>
</ul>
<hr>
<h2 id="퀵셀의-타일">퀵셀의 타일</h2>
<ul>
<li><p>퀵셀의 타일을 이용하면 쉽게 외벽을 만들 수 있으나 문제가 있다.
그냥 사용하면 싱크가 맞지 않는다는 점이다.</p>
</li>
<li><p>이를 위해 머터리얼의 타일링을 조절할 수도 있겠지만 더 좋은 방법이 있다.
모델링의 UVs에서 XFormUV를 이용하여 타일링을 내가 원하는대로 쉽게 조작할 수 있다.</p>
</li>
<li><p>UVs의 다른 기능들을 이용하면 조금은 사실적인 묘사가 가능하다. 이것은 다음 영상을 참고하자</p>
</li>
</ul>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=alSlMVyq5rc&amp;t=186s">파괴된 에셋 생성: 언리얼 엔진과 함께한 &#39;90일&#39; | 튜토리얼 | 퀵셀</a></p>
</blockquote>
<hr>
<h2 id="건물-만들기">건물 만들기</h2>
<ul>
<li>나는 위의 영상에서 만든 사실적인 에셋이 아닌 간단하게 게임의 분위기와 어울리는 에셋만 만들면 된다.</li>
</ul>
<blockquote>
<p>에셋을 만들기 전에 기본이 되는 에셋은 변경되면 안되므로 복사해서 다른 곳에 저장시켜서 그 에셋으로 작업하도록 하자.</p>
</blockquote>
<ul>
<li><p>일단 원하는 기본 토대를 만들고 원하는 머터리얼을 퀵셀에서 입힌다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/03b0b1bc-d029-4628-88a9-3408181bd290/image.png" alt=""></p>
</li>
<li><p>그냥 쓰기엔 어색하니 위에서 언급한 <strong>모델링 모드 - UVs - XFormUV</strong>를 이용하여 조절해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/16f7ee1d-1cd9-49dd-9967-5eecf1b78efd/image.png" alt=""></p>
</li>
</ul>
<p>※ 한 면만 선택하는 것보다 모든 면을 선택하여 모든 면을 한번에 조절하는 것이 더 자연스럽다.</p>
<ul>
<li><p>자신이 원하는 모양이 나올 때까지 찾아서 조절해주면 된다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/ffcf3409-ff4a-46c2-94cc-4dc64aac8468/image.png" alt=""></p>
</li>
<li><p>삼인칭 콘텐츠 팩의 캐릭터를 이용하거나 실제 플레이 뷰를 보면서 수정해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/18bddcc9-091e-41e8-8013-9161361554da/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c84f7745-52ef-44e1-83c9-aa882c4f774b/image.png" alt=""></p>
</li>
<li><p>지금까지도 나쁘진 않지만 너무 어색할 수 있기 때문에 중간에 패턴을 넣기로 하였다
<img src="https://velog.velcdn.com/images/jg-konkuk/post/52033bdc-8772-4a1e-817f-6c02abf165be/image.png" alt=""></p>
</li>
<li><p>모델링 모드의 PolyCut을 이용하여 중간에 다른 에셋이 들어갈 수 있도록 공간을 만들고 Merge로 새로운 에셋을 만들어낸다.</p>
<blockquote>
<p>병합하기 전에 어울리는 머터리얼을 넣고 병합하도록 하자.</p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/ab7cc54f-0bab-4fe7-9d0c-970e3a946842/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/15c8b788-f5b4-4d63-a699-21975c8936ec/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/8b4169d0-0fe9-493a-96a6-4c0ad773aa21/image.png" alt=""></p>
<ul>
<li>다른 팀원분이 만들어주신 에셋으로 창고 안쪽에서 잠겨있는 문을 만들었다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/f00ba94d-afcb-4e46-9b4b-9dd2befd99ba/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/35217525-1c6d-4c68-9915-fbf73715c57a/image.png" alt=""></p>
<ul>
<li>구글에 이미지를 찾아서 참고하여 제작하면 훨씬 자연스러운 건물을 완성할 수 있다.<blockquote>
<p><a href="https://free3d.com/ko/3d-model/wooden-shed-6-2026.html">참고한 이미지</a></p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="사실적인-콘크리트-바닥-만들기">사실적인 콘크리트 바닥 만들기</h2>
<ul>
<li>퀵셀의 계단 에셋을 사용하다가 계단은 있지만 계단과 어울리는 바닥은 내가 만들어주어야 하는 경우가 발생하였다.
이로 인해 모델링 모드의 Displace모드 이용이 불가피해졌다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c98e0573-85f6-4c63-80fa-7d9fd246252d/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c3f421ef-ee75-4a2a-9039-671c40238203/image.png" alt=""></li>
<li>알맞은 텍스쳐는 구했지만 이질감이 든다.</li>
</ul>
<ol>
<li><p>먼저 <strong>UV</strong>를 생성해주어야한다.
<strong>모델링 모드 - UVs - Unwrap</strong> 을 이용하면 자동으로 생성해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/40bb9cc4-8b38-4b95-9091-6b1f68801308/image.png" alt=""></p>
</li>
<li><p>그다음 <strong>Deform - Displace</strong>로 자신이 원하는만큼 조절해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/f99d8efb-ec47-4993-9840-d36963063341/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>이렇게 울퉁불퉁하여 조금 더 사실적으로 보인다.</li>
</ul>
<ol start="3">
<li>모서리는 다른 기능을 이용하여 다듬어주어야 한다.
원하는 모서리에 복사한 에셋을 맞닿아서 놓는다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/ff6d98e2-7ccf-4b40-9283-e38c53940ab8/image.png" alt=""></li>
</ol>
<ul>
<li>깍을 에셋과 복사한 에셋을 순서대로 지정해준뒤 <strong>PolyModel - MshBool</strong> 을 실행한 후 수락해주면 복사한 에셋은 사라지고 모서리가 깍인것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/bdcae7b1-9c0d-4833-b6dd-ee9cc5784e28/image.png" alt=""></li>
</ul>
<ol start="4">
<li>마지막으로 이 에셋의 메모리 사용량을 줄여주는 <strong>MeshOps - Simplfy</strong>를 사용하여 필요 없는 지오메트리를 없애준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c0181e94-724a-4d9a-b83e-60bde6648378/image.png" alt=""></li>
</ol>
<ul>
<li>타깃 퍼센티지를 25%정도로 해도 필요한 디테일이 모두 남아있는걸 확인할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-8. AOP]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-8.-AOP</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-8.-AOP</guid>
            <pubDate>Tue, 31 Jan 2023 08:50:16 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="aop가-필요한-상황">AOP가 필요한 상황</h2>
<ul>
<li>AOP는 c언어의 포인터라고 불리는만큼 이해하기 어려운 내용이다.</li>
<li>그러나 AOP가 필요한 상황을 이해하면 쉬울 수 있다.</li>
</ul>
<ol>
<li><p>모든 메소드의 호출 시간을 측정하고 싶을 때
간단하게 함수의 시작 부분과 끝 부분에 시간을 재는 방법이 있을 것이다.</p>
</li>
<li><p>공통 관심 사항 vs 핵심 관심 사항</p>
</li>
<li><p>회원 가입 시간, 회원 조회 시간을 측정하고 싶다면?</p>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/96f91b66-1bd1-4fde-9b23-f44380c74d59/image.png" alt=""></p>
<blockquote>
<p>try 구문 실행 후 finally 구문이 실행된다.</p>
</blockquote>
</li>
</ol>
<ul>
<li>문제점
시간 측정 기능은 핵심 관심 사항이 아닌 공통 관심 사항이다.
핵심 비즈니스의 로직과 섞여 유지보수가 어렵다.
시간 측정 로직을 변경할 때 모든 로직을 찾아가면서 변경해야 한다.</li>
</ul>
<hr>
<h2 id="aop-적용">AOP 적용</h2>
<ul>
<li>AOP : Aspect Oriented Programming</li>
<li>공통 관심 사항과 핵심 관심 사항을 분리한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/6d995a84-2f5c-4e53-990b-ee594f723e52/image.png" alt=""></li>
<li>aop라는 새로운 패키지에 TimeTraceAop 클래스 생성 후 다음 코드 넣기<pre><code class="language-java">package hello.hellospring.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
</code></pre>
</li>
</ul>
<p>@Component
@Aspect
public class TimeTraceAop {
    @Around(&quot;execution(* hello.hellospring..*(..))&quot;)
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println(&quot;START: &quot; + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;END: &quot; + joinPoint.toString()+ &quot; &quot; + timeMs + &quot;ms&quot;);
        }
    }
}</p>
<pre><code>- joinPoint.proceed()
 다음 메소드로 진행시켜주는 함수
- joinPoint.toString()
 현재 메소드의 이름을 출력해준다.
- 위 코드와 같이 @Component로 해주어도 되지만 Aop를 사용한다는 것을 가시적으로 보여주기 위해 스프링 빈에 직접 등록한다.(등록 시 @Component 제거)
 -&gt;springconfig에 다음 코드 추가
```java
    @Bean
    public TimeTraceAop timeTraceAop(){
        return new TimeTraceAop();
    }</code></pre><ul>
<li><p>@Around
Aop를 적용하는 범위 지정
위 코드에서는 hello.hellospring 패키지에 있는 전범위를 포함한것</p>
</li>
<li><p>해결
핵심 관심사항과 공통 관심 사항을 분리하였다.
시간 측정 로직을 별도의 공통 로직으로 만들었다.
핵심 관심 사항을 깔끔하게 유지 가능하다.
원하는 적용 대상을 선택할 수 있다.(@Around)</p>
</li>
</ul>
<hr>
<ul>
<li><p>AOP 적용 전 의존관계
<img src="https://velog.velcdn.com/images/jg-konkuk/post/0d624f1f-7f91-4731-8d6b-7f45d78dd4ea/image.png" alt=""></p>
</li>
<li><p>AOP 적용 후 의존관계
<img src="https://velog.velcdn.com/images/jg-konkuk/post/5000baec-cc67-49ea-85fd-f061efc3cfdd/image.png" alt=""></p>
</li>
<li><p>AOP가 프록시(가짜) memberService를 만들어내어 먼저 실행되도록 조작한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/75470daa-4f2f-4b99-a201-5fbf262236cd/image.png" alt=""></p>
</li>
</ul>
<hr>
<ul>
<li>김영한 님의 스프링 완전 정복 로드맵의 첫번때 단계가 끝났다.
다음 강의부터는 유료강의이기 때문에 개인노트에 정리를 하겠다.</li>
</ul>
<blockquote>
<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">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</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-7. 스프링 DB 접근 기술 (2)]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-7.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A0-2</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-7.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A0-2</guid>
            <pubDate>Tue, 31 Jan 2023 07:58:16 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="스프링-jdbctemplate">스프링 JdbcTemplate</h2>
<ul>
<li>스프링에서 제공하는 이 라이브러리는 JDBC API에서 본 반복적인 코드를 대부분 제거해준다.</li>
<li>그러나 SQL은 직접 작성해야 한다.</li>
<li>현재 실무에서도 종종 사용한다.</li>
<li>JdbcTemplateMemberRepository 라는 새로운 repository를 만들고 다음 코드를 넣는다.<pre><code>package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcTemplateMemberRepository(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName(&quot;member&quot;).usingGeneratedKeyColumns(&quot;id&quot;);
Map&lt;String, Object&gt; parameters = new HashMap&lt;&gt;();
parameters.put(&quot;name&quot;, member.getName());
Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
@Override
public Optional&lt;Member&gt; findById(Long id) {
List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where id 
= ?&quot;, memberRowMapper(), id);
return result.stream().findAny();
}
@Override
public List&lt;Member&gt; findAll() {
return jdbcTemplate.query(&quot;select * from member&quot;, memberRowMapper());
}
@Override
public Optional&lt;Member&gt; findByName(String name) {
List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where 
name = ?&quot;, memberRowMapper(), name);
return result.stream().findAny();
}
private RowMapper&lt;Member&gt; memberRowMapper() {
return (rs, rowNum) -&gt; {
Member member = new Member();
member.setId(rs.getLong(&quot;id&quot;));
member.setName(rs.getString(&quot;name&quot;));
return member;
};
}
}</code></pre></li>
<li>생성자가 한개라면 @Autowired 생략이 가능하다.(위 코드에서도 생략 가능)</li>
<li>jdbcMemberRepository와 비교를 해보면 findById 함수가 memberRowMapper를 이용하여 두줄만에 짜여진 것을 볼 수 있다.</li>
<li>save는 jdbcTemplate을 인자로 받는 SimpleJdbcInsert를 이용하여 query를 짤 필요 없이 쉽게 구현이 되었다.</li>
<li>현재는 자세한 설명 없이 전체적인 흐름만 보았지만 추후에 jdbcTemplate의 다양한 기능들을 배울 예정이다.</li>
</ul>
<hr>
<ul>
<li>구현한 jdbcTemplate 연결(다음 코드 변경)<pre><code class="language-java">  @Bean
  public MemberRepository memberRepository(){
      //return new MemoryMemberRepository();
      //return new JdbcMemberRepository(dataSource);
      return new JdbcTemplateMemberRepository(dataSource);
  }</code></pre>
</li>
<li>테스트 파일을 돌려보면 오류 없이 잘 작동한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/abda1e44-b639-4148-8ae7-b24a6ed61263/image.png" alt=""></li>
</ul>
<hr>
<h2 id="jpa">JPA</h2>
<ul>
<li>JPA는 기존의 반복 코드는 물론, 기본적인 SQL도 만들어주기 때문에 개발 생산성을 크게 향상시킬 수 있다.</li>
<li>JPA는 Spring 만큼 기술적인 깊이도 깊고 스프링 자체 환경에서 JPA를 기본으로 하는 경우가 많기 때문에 배우면 도움이 될 것이다.
(JPA 또한 굉장히 방대하다.)</li>
</ul>
<hr>
<ul>
<li><p>JPA를 사용하기 위해 build.gradle에 다음 이미지처럼  ---data-jpa를 추가하고 gradle refresh 버튼을 눌러준다.(코끼리 버튼)
<img src="https://velog.velcdn.com/images/jg-konkuk/post/d17b09ed-c3c0-4836-ab30-564937cc9964/image.png" alt=""></p>
</li>
<li><p>application.properties에 다음을 추가한다.</p>
<pre><code class="language-java">spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none</code></pre>
</li>
<li><p>show-sql=true
jpa가 날린 코드들을 볼 수 있게 해준다.</p>
</li>
<li><p>hibernate.ddl-auto=none
jpa는 회원이라는 객체를 인식하여 자동으로 테이블을 만들어주는데 우리는 만들어놓은 테이블을 이용할 것이기 때문에 위 기능을 꺼준다.</p>
</li>
<li><p>JPA는 결국 인터페이스와 같아서 여러 업체에서 구현체를 만들어 연결을 시키는 형태라고 볼 수 있다.
우리는 JPA의 hibernate 기능을 쓸 것이다.</p>
</li>
</ul>
<hr>
<ul>
<li><p>도메인 파일의 Member 클래스에 @Entity를 붙여 이 클래스는 JPA가 관리한다라는 표시를 해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c793733c-aad0-44a7-8dcd-39a37071637a/image.png" alt=""></p>
</li>
<li><p>DB에 insert into member(name) values(&#39;spring1&#39;); 를 실행할 때 id값을 우리가 설정하는 것이 아닌 DB에서 자동으로 생성해주는데 이것을 IDENTITY 전략이라고 한다.</p>
</li>
<li><blockquote>
<p>id 위쪽에 이 기능을 추가해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/493a8a8c-2449-45e8-93bf-2e99306e0a77/image.png" alt=""></p>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li>JPA는 EntityManager로 모든 것을 관리한다.</li>
<li>JPA를 이용한 새로운 repository JpaMemberRepository 를 생성하고 다음 코드를 넣는다.<pre><code class="language-java">package hello.hellospring.repository;
</code></pre>
</li>
</ul>
<p>import hello.hellospring.domain.Member;</p>
<p>import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;</p>
<p>public class JpaMemberRepository implements MemberRepository{</p>
<pre><code>private final EntityManager em;

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() {
    return em.createQuery(&quot;select m from Member m&quot;, Member.class)
            .getResultList();
}</code></pre><p>}</p>
<pre><code>&gt; 단축키 팁
 windows 기준 ctrl+alt+n을 누르면 하나의 라인으로 합쳐진다.
 ex) 
 ```java
 List&lt;Member&gt; result = parallel;
  return result
  -&gt;
  return parallel;</code></pre><p>※ JPA를 사용할 때 항상 @Transactional이 있어야 한다.
-&gt; MemberService에 @Transactional을 넣어준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/96cd93bd-938a-4ebb-8e07-aef4e73bbde7/image.png" alt=""></p>
<ul>
<li>SpringConfig에 JpaMemberRepository 를 연결시켜 준다.
원래 있던 DataSource 대신 EntityManager를 만들어 넣어준다.</li>
</ul>
<pre><code class="language-java">package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private EntityManager em;

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

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);
        //return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }
}</code></pre>
<ul>
<li>확인하기 위해 MemberServiceIntegrationTest의 회원가입 부분만 실행시켜 본다.</li>
<li>@Transactional은 실행 후 롤백시켜주므로 @Commit을 대신 넣어주면 DB에 값이 추가되는 것을 확인할 수 있다.</li>
</ul>
<hr>
<h2 id="스프링-데이터-jpa">스프링 데이터 JPA</h2>
<ul>
<li>스프링 데이터 JPA를 사용하면 리포지토리에 구현 클래스 없이 인터ㅔ이스 만으로 개발을 할 수 있게 된다.</li>
</ul>
<p>※ 스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 도와주는 도구이기 때문에 반드시 JPA 선행학습이 필요하다.</p>
<hr>
<ul>
<li>리포지토리에 SpringDataJpaMemberRepository 인터페이스를 생성하고 다음 코드를 넣는다.<pre><code class="language-java">package hello.hellospring.repository;
</code></pre>
</li>
</ul>
<p>import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;</p>
<p>import java.util.Optional;</p>
<p>public interface SpringDataJpaMemberRepository extends JpaRepository&lt;Member, Long&gt;, MemberRepository {</p>
<pre><code>@Override
Optional&lt;Member&gt; findByName(String name);</code></pre><p>}</p>
<pre><code>&gt; 인터페이스가 다른 인터페이스를 가져올 때는 implements가 아닌 extends이다.

- 스프링이 JpaRepository를 발견하면 spring bean에 자동으로 등록해준다.
 -&gt; 구현체를 만들어서 등록해준다.

- springconfig 파일을 다음과 같이 변경해준다.
```java
package hello.hellospring;

import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    private final MemberRepository memberRepository;

    public SpringConfig(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService()
    {
        return new MemberService(memberRepository);
    }

}</code></pre><ul>
<li><p>서비스 통합테스트를 돌려보면 정상적으로 작동이 되는 것을 확인할 수 있다.</p>
</li>
<li><p>기본적인 save(), findAll() 함수 등은 스프링 데이터 JPA에서 제공을 해주기 때문에 따로 짤 필요가 없다.</p>
</li>
<li><p>위와 같은 공통적인 부분은 쓸 수 있지만 비지니스 모델이 다른 경우 등에는 따로 써야한다.
이것은 스프링 데이터 JPA 리포지토리에 findByName을 통해 확인할 수 있다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/e9c81682-23e5-4077-a8dc-efd45cb229a4/image.png" alt=""></p>
</li>
<li><p>실무에서는 대부분 JPA, 스프링 데이터 JPA, Querydsl(복잡한 동적 쿼리 처리 라이브러리) 의 조합으로 사용한다.</p>
</li>
<li><p>이 조합으로 해결하기 어려운 쿼리는 JPA가 제공하는 네이티브 쿼리를 사용하거나, 스프링 JdbcTemplate를 사용하면 된다.</p>
</li>
</ul>
<blockquote>
<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">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</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-6. 스프링 DB 접근 기술 (1)]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-6.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A0-1</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-6.-%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A0-1</guid>
            <pubDate>Sun, 29 Jan 2023 08:56:49 GMT</pubDate>
            <description><![CDATA[<hr>
<ul>
<li>이전까지는 메모리에 저장해 자바를 종료하면 데이터도 날라가버렸다.</li>
<li>이번 장에서 데이터베이스를 다루는 법을 배울 것이다.</li>
<li>다음 5가지를 순차적으로 배우게 된다.</li>
</ul>
<ol>
<li>데이터베이스 설치</li>
<li>데이터베이스 SQL과 애플리케이션 서버를 연결하는데에 필요한 기술인 jdbc를 배운다.
-&gt; 순수 jdbc로 20년전 개발하기 힘들었던 환경을 경험한다.</li>
<li>스프링에서 jdbctemplate를 제공하여 보다 편한 jdbc 개발이 가능하다.</li>
<li>SQL을 직접 짤 필요 없이 JPA를 이용하여 보다 편리하게 개발이 가능하다.</li>
<li>JPA를 더욱 편리하게 개발하기 위해 감싼 기술인 스프링 데이터 JPA를 배운다.</li>
</ol>
<hr>
<h2 id="h2-데이터베이스-설치">H2 데이터베이스 설치</h2>
<ul>
<li>실무로 자주 쓰이는 mysql 계열의 데이터베이스이다.</li>
<li>교육용으로 사용하기에 좋다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/605209b4-a2e7-4eea-be4f-8ca9a6b57e85/image.png" alt=""></li>
<li>위 화면에서 연결 버튼을 누르면 자동으로 데이터베이스가 생성되는데 나는 다음과 같은 오류가 발생하였다.<blockquote>
<p>Database &quot;C:/Users/사용자명/test&quot; not found, either pre-create it or allow remote database creation (not recommended in secure environments)</p>
</blockquote>
</li>
<li>이는 해당 폴더에 test 파일이 없어서 생긴 문제로 해당 경로에 빈 텍스트 파일을 생성하고 파일명을 확장자 포함 &#39;test.mv.db&#39; 로 바꿔주고 다시 연결을 시도한다.</li>
<li>이후 성공적으로 데이터베이스가 접속되었다면 JDBC URL을 바꿔서 접근해주어야한다.<blockquote>
<p>jdbc:h2:tcp://localhost/~/test</p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/16859f1d-5854-4c63-94be-010a16857ded/image.png" alt=""></p>
<ul>
<li><p>다음 코드를 넣고 실행시켜 왼쪽에 MEMBER 파일이 생성되었는지 확인한다.</p>
<blockquote>
<p>drop table if exists member CASCADE;
create table member
(
id bigint generated by default as identity,
name varchar(255),
primary key (id)
);</p>
</blockquote>
</li>
<li><p>다음 코드를 넣고 or member파일을 클릭하고 한번 더 실행한다.</p>
<blockquote>
<p>SELECT * FROM MEMBER </p>
</blockquote>
</li>
<li><p>확인하기 위해 다음 코드를 넣고 member 파일을 확인해본다.</p>
<blockquote>
<p>insert into member(name) values(&#39;spring2&#39;)</p>
</blockquote>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/82967d0b-720a-479a-b32b-353fa5cfaef8/image.png" alt=""></p>
<ul>
<li>데이터베이스 또한 intelliJ를 통해 관리할 수 있다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/da160a9b-079b-4f51-929c-2831cf5caf57/image.png" alt=""></li>
</ul>
<hr>
<h2 id="순수-jdbc">순수 JDBC</h2>
<ul>
<li><p>옛날에 사용하던 기술이므로 완전히 익힐 필요없이 인지 정도만 하면 된다.</p>
</li>
<li><p>build.gradle 파일에 다음 코드를 추가한다.</p>
<pre><code class="language-java">implementation &#39;org.springframework.boot:spring-boot-starter-jdbc&#39;
runtimeOnly &#39;com.h2database:h2&#39;</code></pre>
</li>
<li><p>resources/application.properties에 데이터베이스 연결 설정을 추가한다.</p>
<pre><code class="language-java">spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver</code></pre>
</li>
<li><p>이때 오류가 뜨는데 오른쪽에 코끼리 버튼을 누르거나 오른쪽 gradle탭에서 새로고침 모양을 눌러준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/1d5565c1-73a0-4ea1-8aab-395d6741b8d4/image.png" alt=""></p>
<blockquote>
<p>※ 스프링부트 2.4 버전 이상은 다음 코드를 추가로 넣어주어야 한다.
spring.datasource.username=sa</p>
</blockquote>
</li>
<li><p>새로운 구현체인 JdbcMemberRepository 를 만들어주고 다음 코드를 넣는다.</p>
</li>
<li><p>정신건강을 위해 복붙하고 참고만 하도록 하자</p>
<blockquote>
<pre><code class="language-java">package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = &quot;insert into member(name) values(?)&quot;;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
} else {
throw new SQLException(&quot;id 조회 실패&quot;);
}
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional&lt;Member&gt; findById(Long id) {
String sql = &quot;select * from member where id = ?&quot;;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong(&quot;id&quot;));
member.setName(rs.getString(&quot;name&quot;));
return Optional.of(member);
} else {
return Optional.empty();
}
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public List&lt;Member&gt; findAll() {
String sql = &quot;select * from member&quot;;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
List&lt;Member&gt; members = new ArrayList&lt;&gt;();
while(rs.next()) {
Member member = new Member();
member.setId(rs.getLong(&quot;id&quot;));
member.setName(rs.getString(&quot;name&quot;));
members.add(member);
}
return members;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional&lt;Member&gt; findByName(String name) {
String sql = &quot;select * from member where name = ?&quot;;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
rs = pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong(&quot;id&quot;));
member.setName(rs.getString(&quot;name&quot;));
return Optional.of(member);
}
return Optional.empty();
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
{
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
close(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}</code></pre>
</blockquote>
<pre><code></code></pre></li>
<li><p>코드에 대한 자세한 설명은 강의를 참고하도록 하자..</p>
</li>
</ul>
<blockquote>
<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/dashboard">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/dashboard</a></p>
</blockquote>
<hr>
<ul>
<li><p>이제 우리가 가상 시나리오를 설정하여 구현체를 따로 만들어준 이유를 알 수 있다.</p>
</li>
<li><p>SpringConfig에서 MemberRepository에게 반환하는 값을 변경해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/feedce9a-5b87-4c0f-bd45-33251e136380/image.png" alt=""></p>
</li>
<li><p>또한 위쪽에 다음 코드를 추가해준다.</p>
<pre><code class="language-java">  private final DataSource dataSource;

  @Autowired
  public SpringConfig(DataSource dataSource) {
      this.dataSource = dataSource;
  }</code></pre>
</li>
<li><p>이렇게 @Configuration만 만져주면 데이터베이스와 연결된다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/44099116-a01e-4fc0-8fde-11e6c3a256fb/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/46668d80-c91a-4c10-93b1-e8a10bdb83cd/image.png" alt=""></p>
</li>
<li><p><strong>이렇게 구현체를 쉽게 바꾸는 객체지향적인 기술을 스프링이 지원해주기 때문에 스프링을 사용한다.</strong>
<img src="https://velog.velcdn.com/images/jg-konkuk/post/fefb0e5b-308d-4466-bb42-b04c4ba00612/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/609f60fc-3ac8-4cf0-80ec-197152628ce3/image.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="스프링-통합-테스트">스프링 통합 테스트</h2>
<ul>
<li><p>아래 코드 넣기</p>
<pre><code class="language-java">package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
  @Autowired MemberService memberService;
  @Autowired MemberRepository memberRepository;
  @Test
  public void 회원가입() throws Exception {
      //Given
      Member member = new Member();
      member.setName(&quot;hello&quot;);
      //When
      Long saveId = memberService.join(member);
      //Then
      Member findMember = memberRepository.findById(saveId).get();
      assertEquals(member.getName(), findMember.getName());
  }
  @Test
  public void 중복_회원_예외() throws Exception {
      //Given
      Member member1 = new Member();
      member1.setName(&quot;spring&quot;);
      Member member2 = new Member();
      member2.setName(&quot;spring&quot;);
      //When
      memberService.join(member1);
      IllegalStateException e = assertThrows(IllegalStateException.class,
              () -&gt; memberService.join(member2));//예외가 발생해야 한다.
      assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
  }
}</code></pre>
</li>
<li><p>@SpringBootTest
스프링 컨테이너와 테스트를 함께 실행한다.
-&gt; 스프링 서버가 동작하면서 테스트를 실행한다.</p>
</li>
<li><p>@Transactional
테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다.
이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지
않는다
-&gt; 따로 DB를 초기화하는 코드가 필요없게 된다.</p>
</li>
</ul>
<hr>
<ul>
<li>기존에 했던 MemberServiceTest와의 차이점
스프링 서버를 동작하지 않으므로 빠르게 실행된다.
웬만하면 스프링 서버 동작 없이(스프링 통합 테스트) 하는 것이 좋은 테스트일 가능성이 높다.</li>
</ul>
<blockquote>
<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/dashboard">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/dashboard</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 시작]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@jg-konkuk/Spring-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Sun, 29 Jan 2023 08:41:04 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="시작">시작</h2>
<ul>
<li>실무와 가장 가까운 언어이면서 백엔드에서 군림하고 있다고 볼 수 있는 spring을 공부하기로 하였다.</li>
<li>그냥 공부하기엔 막막하고 포기할 수 있으므로 인프런 강의(김영한 님)을 통해 공부하기로 하였다.</li>
<li>김영한 님의 2가지 로드맵을 통하여 거대한 spring을 차근차근 익혀나가기로 하였다.</li>
</ul>
<hr>
<h2 id="계획">계획</h2>
<h3 id="첫번째-스프링-완전-정복-로드맵은-다음과-같다">첫번째 스프링 완전 정복 로드맵은 다음과 같다.</h3>
<ol>
<li>스프링 입문</li>
<li>스프링 핵심 원리</li>
<li><strong>스프링 웹 MVC</strong></li>
<li>스프링 DB 데이터 접근 기술</li>
<li>실전! 스프링 부트</li>
</ol>
<ul>
<li>요새 개발 추이가 웹에서 하는 경우가 많기 때문에 웹 MVC가 중요하다.</li>
</ul>
<hr>
<h3 id="첫주차-계획간단한-웹-애플리케이션-개발은-다음과-같다">첫주차 계획(간단한 웹 애플리케이션 개발)은 다음과 같다.</h3>
<ol>
<li>스프링 프로젝트 생성</li>
<li>스프링 부트로 웹 서버 실행</li>
<li>회원 도메인 개발</li>
<li>웹 MVC 개발</li>
<li>DB 연동 - JDBC, JPA, 스프링 데이터 JPA</li>
<li>테스트 케이스 작성</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-5. 회원 관리 예제 - 웹 MVC 개발]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-5.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%9B%B9-MVC-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-5.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%9B%B9-MVC-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Sun, 29 Jan 2023 06:45:59 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="홈-화면-추가">홈 화면 추가</h2>
<ul>
<li>컨트롤러 파일에 HomeController 클래스 추가</li>
<li>아래 코드 삽입<pre><code class="language-java">package hello.hellospring.controller;
</code></pre>
</li>
</ul>
<p>import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;</p>
<p>@Controller
public class HomeController {</p>
<pre><code>@GetMapping(&quot;/&quot;)
public String home(){
    return &quot;home&quot;;
}</code></pre><p>}</p>
<pre><code>
- &#39;/&#39;에 대해서 @GetMapping한 것은 제일 상위 주소, 즉 **localhost:8080**에 대한 mapping이다.
- resource - templates에 home.html 파일 추가
- 아래 코드 삽입
```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><p><img src="https://velog.velcdn.com/images/jg-konkuk/post/824917eb-ee9a-4368-8bfa-7bbbd253beeb/image.png" alt=""></p>
<ul>
<li>원래 static 파일인 index.html이 있었지만 컨트롤러의 우선순위가 더 높기 때문에 mapping이 되어있는 home.html이 출력되었다.</li>
</ul>
<hr>
<h2 id="등록">등록</h2>
<ul>
<li><p>/members/new 에 대한 템플릿 작성</p>
</li>
<li><p>member controller에 다음 코드 추가</p>
<pre><code class="language-java">@GetMapping(&quot;/members/new&quot;)
  public String createFrom(){
      return &quot;members/createMemberForm&quot;;
  }</code></pre>
</li>
<li><p>templates에 members디렉토리 생성, 그리고 createMemberForm.html 파일 생성</p>
</li>
<li><p>다음 코드 삽입</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;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;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
</li>
<li><p>controller 파일에 MemberForm 클래스 생성, 다음 코드 삽입</p>
<pre><code class="language-java">package hello.hellospring.controller;
</code></pre>
</li>
</ul>
<p>public class MemberForm {
    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>- MemberController 에 다음 코드 추가
```java
    @PostMapping(&quot;/members/new&quot;)
    public String create(MemberForm form) {
        Member member = new Member();
        member.setName(form.getName());

        memberService.join(member);

        return &quot;redirect:/&quot;;
    }</code></pre><p><img src="https://velog.velcdn.com/images/jg-konkuk/post/54b0df4a-53e6-4b5d-bd9d-5613477a57eb/image.png" alt=""></p>
<ul>
<li><p>정상적으로 진행하였다면 등록버튼을 눌렀을때 오류페이지가 뜨던 전과 달리 처음화면으로 돌아가는 것을 볼 수 있다.</p>
</li>
<li><p>html 설명
placeholder는 아무것도 입력하지 않았을때 들어가는 값이다.
등록버튼을 누르면 form 태그 안에 적혀있는 것처럼 &quot;/members/new&quot;에 &quot;post&quot;방식으로 id와 name이 넘어가게 된다.</p>
</li>
<li><p>이어서 MemberController에서 @PostMapping(&quot;/members/new&quot;)로 mapping되어있는 create가 호출되어 실행된다.</p>
</li>
<li><p>이때 spring은 인자 MemberForm 에 접근하여 setname을 통해 private인 name을 설정한다.</p>
</li>
</ul>
<hr>
<h2 id="조회">조회</h2>
<ul>
<li><p>/members 에 대한 템플릿 작성</p>
</li>
<li><p>MemberController 에 다음 코드 추가</p>
<pre><code class="language-java">  @GetMapping(&quot;/members&quot;)
  public String list(Model model){
      List&lt;Member&gt; members = memberService.findMembers();
      model.addAttribute(&quot;members&quot;, members);
      return &quot;members/memberList&quot;;
  }</code></pre>
</li>
<li><p>members파일에 memberList.html 템플릿 추가</p>
</li>
<li><p>다음 코드 삽입</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;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>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/c9f249a7-46f0-418a-a8ab-66fac37440a0/image.png" alt=""></p>
</li>
<li><p>/members 에 접속하게 되면 모든 회원(member)들을 리스트 형태로 model에 붙여주고 html 에서 each를 통해 각각의 member들에 대해 표 형태로 나타낸다.</p>
</li>
</ul>
<blockquote>
<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/dashboard">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/dashboard</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-4. 스프링 빈과 의존관계]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-4.-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-4.-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</guid>
            <pubDate>Sat, 28 Jan 2023 08:23:02 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="컴포넌트-스캔과-자동-의존-관계-설정">컴포넌트 스캔과 자동 의존 관계 설정</h2>
<ul>
<li>스프링 빈을 등록하는 첫번째 방법(자동)</li>
<li>실제 html과 우리가 작업했던 서버 설정들을 이어주기 위해선 멤버 컨트롤러가 필요하다.</li>
<li>controller 파일에 MemberController 클래스 생성 후 아래 코드 삽입<pre><code class="language-java">package hello.hellospring.controller;
</code></pre>
</li>
</ul>
<p>import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;</p>
<p>@Controller
public class MemberController {</p>
<pre><code>private final MemberService memberService;

@Autowired
public MemberController(MemberService memberService) {
    this.memberService = memberService;
}</code></pre><p>}</p>
<pre><code>- 멤버 컨트롤러를 통해 멤버 서비스를 가져오는데 멤버 서비스는 다른 여러 군데에서 사용할 수 있으므로 new로 매번 생성할 필요 없이 하나만 생성해주면 된다.
- 연결 방법
1. 생성자(constructor)로 memberservice의 생성자를 만들어준다.
 (window 기준 alt+insert 단축키 이용)
 ![](https://velog.velcdn.com/images/jg-konkuk/post/273996ef-9d86-4886-b5c4-063eeeaa5899/image.png)

2. @Autowired
 스프링 컨테이너에 있는 Member Service, 즉 service파일의 MemberService와 생성자의 memberService와 연결시켜준다.

3. 이것만으로는 memberservice를 찾지 못하여 오류가 발생한다.
 컨트롤러 같은 경우는 @controller 등의 **annotation**으로 스프링이 찾을 수 있지만 memberservice는 순수한 자바 코드로 이루어진 클래스이기 때문에 스프링이 찾을 수가 없다.
 **따라서 MemberService 클래스 위에 @Service 라는 annotation을 넣어준다.**
 ![](https://velog.velcdn.com/images/jg-konkuk/post/102af4c2-a8a4-4475-9973-6f7f0a4a9af9/image.png)

4. 같은 원리로 구현체인 MemoryMemberRepository 클래스 위에 @Repository를 넣어준다.
![](https://velog.velcdn.com/images/jg-konkuk/post/f6d64e79-169d-4ba4-a470-21a8ee5f0a35/image.png)

5. MemberController와 MemberService를 이어주었으니 MemberService와 MemoryMemberRepository도 똑같이 @Autowired로 이어준다.

- 이 과정이 스프링 빈을 등록하는 2가지 방법 중에서 한가지인 **컴포넌트 스캔** 방식이다. (@Autowired와 같은 annotation 이용)
![](https://velog.velcdn.com/images/jg-konkuk/post/4306c26c-cb3f-429b-99e7-42b2d88cb1b7/image.jpeg)

- @Controller, @Service, @Repository를 살펴보면 그 속에 @Component가 붙어있는 것을 확인할 수 있다.
![](https://velog.velcdn.com/images/jg-konkuk/post/49a0d98b-99f7-44c5-ac91-c48faf382660/image.png)

※ @Component 에노테이션이 있으면 스프링 빈으로 자동 등록 된다.

---
## 자바 코드로 직접 스프링 빈 등록하기
- 스프링 빈을 등록하는 2번째 방법(수동)
- 우선 이전 방법으로 설정했던 annotation을 전부 지운다.(membercontroller 제외)
---
- hello.hellospring 패키지에 SpringConfig 클래스 생성 후 아래 코드 삽입
```java
package hello.hellospring;

import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}
</code></pre><ul>
<li>스프링이 @Configuration을 인식하면 @Bean 다음에 있는 클래스들을 스프링 빈으로 등록한다.
※ 컨트롤러는 수동이 아닌 자동으로 등록해야함.</li>
</ul>
<hr>
<h2 id="참고">참고</h2>
<ul>
<li>DI에는 필드 주입, setter 주입, 생성자 주입 세가지 방법이 있다.
우리는 이 중에서 <strong>생성자 주입</strong>을 사용하였다.</li>
<li>필드 주입, setter 주입은 몇가지 문제가 있고 의존관계가 실행중에 동적으로 변하는 경우는 거의 없으므로 생성자 주입을 권장한다.</li>
<li>실무에서는 주로 정형화된 컨트롤러, 서비스, 리포지토리 같은 코드는 컴포넌트 스캔을 사용한다.
<strong>그리고 정형화 되지 않거나, 상황에 따라 구현 클래스를 변경해야 하면 설정을 통해 스프링 빈으로 등록한다.</strong></li>
</ul>
<p>=&gt; 현재 가상 시나리오는 &#39;저장소가 정해지지 않아 바뀔 수 있으므로 구현체를 통하여 추후에 바꿀 수 있도록 설계&#39;하였기 때문에 수동으로 스프링 빈으로 등록하여 간단하게 바꿀 수 있도록 설계한다.</p>
<p>※ 스프링 컨테이너, DI 관련된 자세한 내용은 스프링 핵심 원리 강의에서 설명한다.</p>
<blockquote>
<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/dashboard">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/dashboard</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-3. 회원 관리 예제 - 백엔드 개발 2]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-3.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-2</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-3.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-2</guid>
            <pubDate>Wed, 25 Jan 2023 12:03:44 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="회원-서비스-개발">회원 서비스 개발</h2>
<ul>
<li>도메인과 리포지토리를 이용하여 비즈니스 로직 구현</li>
<li>src - main - java 에 service 패키지 생성 후 MemberService 클래스 생성</li>
<li>아래 코드 붙여넣기<pre><code class="language-java">package hello.hellospring.service;
</code></pre>
</li>
</ul>
<p>import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;</p>
<p>import java.util.List;
import java.util.Optional;</p>
<p>public class MemberService {</p>
<pre><code>private final MemberRepository memberRepository = new MemoryMemberRepository();

/**
 * 회원 가입
 */
public Long join(Member member){
    // 조건 : 같은 이름이 있는 중복 회원X
    validateDuplicateMember(member); // 중복회원 검증
    memberRepository.save(member);
    return member.getId();
}

private void validateDuplicateMember(Member member) {
    Optional&lt;Member&gt; result = memberRepository.findByName(member.getName());
    result.ifPresent(m -&gt;{
        throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
        // 같은 이름이 존재하면 위 문구 출력
    });
}

/**
 * 전체 회원 조회
 */
public List&lt;Member&gt; findMembers(){
    return memberRepository.findAll();
}

public Optional&lt;Member&gt; findOne(Long memberId){
    return memberRepository.findById(memberId);
}</code></pre><p>}</p>
<pre><code>
- Optional을 쓰는 이유 : Optional을 이용하면 if(null==?) 등의 다양한 메소드들을 활용할 수 있다.
&gt; 단축키
 shift+f6 : 단어를 한번에 바꿀 수 있다.
 ctrl+alt+shift+t : 해당 블록을 함수화 시킨다.(window)

---
## 회원 서비스 테스트
- test 파일에서 따로 package를 만들고 class를 만들 필요 없이 자동으로 만들어주는 단축키가 있다.
&gt; ctrl + shift + t
![](https://velog.velcdn.com/images/jg-konkuk/post/46cee11f-8a9f-4795-84a4-956cc590a206/image.png)

- create new test를 누르면 창이 뜬다.
- test할 함수를 선택하면 자동으로 test 파일을 생성해준다.
- test 함수는 빌드에 포함되지 않으므로 한글사용이 가능하다.
- test는 기본적으로 다음과 같은 단계로 구분된다.
 given : 어떠한 상황이 주어진다.
 when : 이것이 실행되었을 때
 then : 이것이 나와야한다.

```java
package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemoryMemberRepository;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;

class MemberServiceTest {

    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach(){
        memberRepository.clearStore();
    }
    @Test
    void join() {
        // given
        Member member = new Member();
        member.setName(&quot;hello&quot;);

        // when
        Long saveId = memberService.join(member);

        // then
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외(){
        // given
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);

        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);

        // when
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class, () -&gt; memberService.join(member2));
        // 2번째 인자를 실행했을 때 1번째 인자의 오류가 발생해야함
        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
        // 발생한 오류의 메시지 체크

        /*
        // try catch 이용하여 중복 검사가 동작하는지 체크
        try {
            memberService.join(member2);
            fail(&quot;예외가 발생해야 합니다.&quot;);
        }catch (IllegalStateException e){
            assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
        }
*/

        // then
    }

    @Test
    void findMembers() {
    }

    @Test
    void findOne() {
    }
}</code></pre><ul>
<li><p>그러나 memberService에서 repository를 new로 생성하고
테스트 파일에서 따로 repository를 new로 생성하는 오류가 있다.
-&gt; static이기 때문에 결국은 같은 저장공간을 가르키지만 static이 아니라면 오류가 발생한다.</p>
</li>
<li><p>따라서 service의 다음 코드를 바꿔주어야한다.</p>
</li>
</ul>
<pre><code class="language-java">private final MemberRepository memberRepository = new MemoryMemberRepository();</code></pre>
<p>=&gt;</p>
<pre><code class="language-java">private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }</code></pre>
<ul>
<li>service test의 코드 또한 바꿔 준다.</li>
</ul>
<pre><code class="language-java">    MemberService memberService = new MemberService();
    MemoryMemberRepository memberRepository = new MemoryMemberRepository();</code></pre>
<p>=&gt;</p>
<pre><code class="language-java">    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach // 각 테스트 실행하기 전에 실행
    public void beforeEach(){
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }</code></pre>
<hr>
<blockquote>
<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">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</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-3. 회원 관리 예제 - 백엔드 개발 1]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-3.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-1</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-3.-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C-1</guid>
            <pubDate>Wed, 25 Jan 2023 10:56:51 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="비즈니스-요구사항-정리">비즈니스 요구사항 정리</h2>
<ul>
<li><p>데이터 : 회원ID, 이름</p>
</li>
<li><p>기능 : 회원 등록, 조회</p>
</li>
<li><p>가상의 시나리오 : 데이터 저장소가 선정되지 않음</p>
</li>
<li><p>웹 애플리케이션 계층 구조
<img src="https://velog.velcdn.com/images/jg-konkuk/post/bc1e8512-41d8-49c8-af7f-92daa4bd52a1/image.png" alt=""></p>
</li>
<li><p>컨트롤러 : 웹 MVC의 컨트롤러 역할</p>
</li>
<li><p>서비스 : 핵심 비즈니스 로직 구현(회원은 중복가입 불가 등의 로직)</p>
</li>
<li><p>리포지토리 : 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리</p>
</li>
<li><p>도메인 : 비즈니스 도메인 객체
ex) 회원, 주문, 쿠폰 등 주로 데이터베이스에 저장하고 관리됨</p>
</li>
<li><p>클래스 의존관계
<img src="https://velog.velcdn.com/images/jg-konkuk/post/95c71b82-2c68-44f1-b17f-f6de79259570/image.png" alt=""></p>
</li>
<li><p>리포지토리는 interface로 설계 -&gt; 아직 데이터 저장소가 선정되지 않았기 때문(interface는 구현체(구현 클래스)를 바꿔끼울 수 있다.)</p>
</li>
<li><p>구현체로 가벼운 메모리 기반의 데이터 저장소 사용</p>
</li>
</ul>
<hr>
<h2 id="회원-도메인과-리포지토리-만들기">회원 도메인과 리포지토리 만들기</h2>
<ul>
<li>hello.hellospring에 domain이라는 이름의 package 생성</li>
<li>그 안에 Member라는 class 생성</li>
<li>id, name 데이터 생성, getter와 setter 생성<pre><code class="language-java">package hello.hellospring.domain;
</code></pre>
</li>
</ul>
<p>public class Member {</p>
<pre><code>private Long id;
private String name;

public Long getId() {
    return id;
}

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

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}</code></pre><p>}</p>
<pre><code>
---
- hello.hellospring에 repository이라는 이름의 package 생성
- 그 안에 MemberRepository 라는 interface 생성
- 아래 코드 붙여넣기
```java
package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import java.util.List;
import java.util.Optional;

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><hr>
<ul>
<li>구현체 만들기</li>
<li>repository package에 MemoryMemberRepository라는 class 생성</li>
<li>아래 코드 기입<pre><code class="language-java">package hello.hellospring.repository;
</code></pre>
</li>
</ul>
<p>import hello.hellospring.domain.Member;</p>
<p>import java.util.*;</p>
<p>public class MemoryMemberRepository implements MemberRepository{</p>
<pre><code>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; findById(Long id) {
    return Optional.ofNullable(store.get(id));

}

@Override
public Optional&lt;Member&gt; findByName(String name) {
    return store.values().stream()
            .filter(member -&gt; member.getName().equals(name))
            .findAny();
    // stream : store에 저장된 데이터를 loop로 쭉 훑는다.
    // filter : store에 저장된 name과 현재 들어온 name을 찾는다.
    // findAny : 한개 이상 찾았을때(중복이 있을때)
    // 중복을 찾았다면 이미 있는 데이터를 optional로 반환한다.
}

@Override
public List&lt;Member&gt; findAll() {
    return new ArrayList&lt;&gt;(store.values());
    // 반환형태가 List이므로 new로 list 생성
    // store.values() 하면 store의 모든 value 반환
}</code></pre><p>}</p>
<pre><code>
---
## 회원 리포지토리 테스트 케이스 작성
- 테스트할 때 자바의 main 메서드 또는 웹 어플리케이션의 컨트롤러를 통해서 해당 기능을 실행한다.
- 이러한 방법은 오래걸리고, 여러 테스트를 한번에 실행하기 어렵다는 단점이 있다.
- 따라서 JUnit이라는 프레임워크로 테스트를 실행해서 문제를 해결한다.
- test - java - hello.hellospring에 repository 라는 package 생성
- 그 안에 MemoryMemberRepositoryTest 라는 class 생성
- 아래 코드 붙여넣기

```java
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

class MemoryMemberRepositoryTest {

    MemoryMemberRepository repository = new MemoryMemberRepository();

    @AfterEach
    public void afterEach(){
        repository.clearStore();
    }
    @Test
    public void save() {
        Member member = new Member();
        member.setName(&quot;spring&quot;);

        repository.save(member);

        Member result = repository.findById(member.getId()).get();
        assertThat(member).isEqualTo(result);
        // repository에 제대로 저장이 되는지 확인
        // 성공 시 아무것도 출력 x, 실패 시 경고 문구 출력
    }

    @Test
    public void findByName(){
        Member member1=new Member();
        member1.setName(&quot;spring1&quot;);
        repository.save(member1);

        Member member2=new Member();
        member2.setName(&quot;spring2&quot;);
        repository.save(member2);

        Member result = repository.findByName(&quot;spring1&quot;).get();

        assertThat(result).isEqualTo(member1);
        // 만약 findByName에 spring2를 넣으면 member1과 다르기 때문에 오류가 뜰 것
    }

    @Test
    public void findAll(){
        Member member1=new Member();
        member1.setName(&quot;spring1&quot;);
        repository.save(member1);

        Member member2=new Member();
        member2.setName(&quot;spring2&quot;);
        repository.save(member2);

        List&lt;Member&gt; result = repository.findAll();

        assertThat(result.size()).isEqualTo(2);
        // repository에 2개의 member가 저장되어있으므로 findAll()을 하면 2개가 나와야한다.
    }
}</code></pre><ul>
<li><p>Test의 순서는 보장되지 않음
-&gt; 객체 중복 주의, Test끼리 의존 관계가 보장되지 않는다.
-&gt; 하나의 테스트가 끝날때마다 repository를 초기화 시켜주어야한다.
-&gt; @AfterEach 사용</p>
</li>
<li><p>MemoryMemberRepository 클래스에 다음 코드 추가</p>
<pre><code class="language-java">public void clearStore(){
      store.clear();
      // store를 싹 비운다.
  }</code></pre>
</li>
</ul>
<hr>
<blockquote>
<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">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</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-2. 스프링 웹 개발 기초]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-2.-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-2.-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9B%B9-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Wed, 25 Jan 2023 09:43:00 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="웹-개발-종류">웹 개발 종류</h2>
<ul>
<li><p>정적 컨텐츠
서버의 동작 없이 그대로 파일을 그대로 출력</p>
</li>
<li><p><strong>MVC와 템플릿 엔진</strong>
jhp, php등의 템플릿 엔진을 통해 html을 변형하여 출력
요새 개발 트렌드</p>
</li>
<li><p>API
<strong>안드로이드 또는 아이폰</strong> 등을 개발할 때에 html이 아닌 json 데이터 구조 포맷으로 개발함
또는 html 전달이 필요없는 서버끼리의 통신 간에 사용</p>
</li>
</ul>
<hr>
<h2 id="정적-컨텐츠-개발">정적 컨텐츠 개발</h2>
<blockquote>
<p><a href="https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content">https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/html/spring-boot-features.html#boot-features-spring-mvc-static-content</a></p>
</blockquote>
<ul>
<li>스프링 부트 사이트에서는 정적 컨텐츠 개발 방법을 다음과 같이 안내하고 있다.</li>
<li>src - main - resources - static 파일에 아무 html 파일을 넣고 내용을 작성하면 그대로 출력이 된다.</li>
<li>별도의 서버 프로그래밍을 할 수 없다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/5dffc607-40fc-4b66-b484-40b76a4b21a6/image.png" alt=""></p>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;static content&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
정적 컨텐츠 입니다.
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/14fd60d4-56c8-4232-8c0e-cdedc477a344/image.png" alt=""></p>
<ul>
<li>정적 컨텐츠 작동 구조
<img src="https://velog.velcdn.com/images/jg-konkuk/post/43961382-82db-4f8b-b0bb-b995c6bb203b/image.png" alt=""></li>
</ul>
<ol>
<li>웹 브라우저가 톰켓 서버에게 요청한다.</li>
<li>톰켓 서버에서 hello-static 관련 컨트롤러가 있는지 스프링 컨테이너에게 신호를 보낸다 -&gt; 컨트롤러가 우선순위가 더 높다.</li>
<li>관련 컨트롤러가 없으므로 resources: static에서 해당 파일을 찾는다.</li>
<li>그대로 반환한다.</li>
</ol>
<hr>
<h2 id="mvc와-템플릿-엔진-개발">MVC와 템플릿 엔진 개발</h2>
<ul>
<li><p>MVC : Model, View, Controller</p>
</li>
<li><p>옛날에는 view에 모든 프로그래밍을 설계한 model1 방식이지만 지금은 MVC와 같은 model2 방식으로 개발한다.</p>
</li>
<li><p>분야를 분리하여 유지, 보수에 더 용이하게 하기 위함이다.
-&gt; view는 프론트(보이는 것), model과 controller는 백(내부)</p>
</li>
<li><p>thymeleaf의 장점 : 서버 구동 없이 html을 주소창에 입력하면 바로 볼 수 있다.(view 영역)</p>
</li>
<li><p>controller 파일의 hellocontroller 파일에 다음을 추가한다.</p>
<pre><code class="language-java">@GetMapping(&quot;hello-mvc&quot;)
  public String helloMvc(@RequestParam(&quot;name&quot;) String name, Model model){
      model.addAttribute(&quot;name&quot;,name);
      return &quot;hello-template&quot;;
  }</code></pre>
</li>
<li><p>resource - templates 파일에 hello-template.html을 생성하고 다음 내용을 작성한다.</p>
<pre><code class="language-html">&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;hello &#39; + ${name}&quot;&gt;hello! empty&lt;/p&gt;
&lt;/body&gt;</code></pre>
</li>
<li><p>이때 이 템플릿은 서버 구동 없이 확인가능</p>
</li>
<li><p>copy path - absolute path 로 주소창을 붙여넣기</p>
</li>
<li><p>여기서 _loalhost:8080/hello-mvc_로 접속하면 에러페이지가 뜬다.</p>
</li>
<li><p>이유는 name을 받아오는데 required 값이 기본적으로 true로 설정되어 있어서 주소창에 추가로 <strong>?name=spring!</strong>을 넣어주어야한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/84640549-250c-4d6c-9ba6-74f1d7e1740e/image.png" alt=""></p>
</li>
<li><p>MVC, 템플릿 엔진 작동 구조
<img src="https://velog.velcdn.com/images/jg-konkuk/post/43af8bcd-d1b9-4563-aba5-5913e70fd3d3/image.png" alt=""></p>
</li>
</ul>
<ol>
<li>웹 브라우저가 톰켓 서버에게 요청한다.</li>
<li>톰켓 서버에서 hello-static 관련 컨트롤러가 있는지 스프링 컨테이너에게 신호를 보낸다</li>
<li>관련 컨트롤러가 있으므로 그 컨트롤러를 따라간다.</li>
<li>return은 hello-template이고 model에는 spring!이라는 값이 들어있는 name이 담겨 전송된다.</li>
<li>스프링은 viewResolver를 통해 템플릿으로 넘긴다.</li>
<li>model에 담긴 값에 따라 변환 후 웹 브라우저로 넘긴다.</li>
</ol>
<hr>
<h2 id="api-엔진-개발">API 엔진 개발</h2>
<ul>
<li><p>controller 에 다음 코드 추가</p>
<pre><code class="language-java">@GetMapping(&quot;hello-spring&quot;)
  @ResponseBody // http 의 body 부분에 아래 데이터를 직접 넣겠다는 의미, ※ html의 body 아님
  public String helloString(@RequestParam(&quot;name&quot;) String name){
      return &quot;hello &quot;+name;</code></pre>
</li>
<li><p>view 없이 바로 변환된 값이 전송된다.</p>
</li>
<li><p>결과는 MVC, 템플릿 방식과 같지만 소스 부분이 다르다.</p>
</li>
<li><p>MVC, 템플릿 방식 소스
<img src="https://velog.velcdn.com/images/jg-konkuk/post/63142ead-00e2-4c40-aa27-e08ff2d493f1/image.png" alt=""></p>
</li>
<li><p>API 방식 소스
<img src="https://velog.velcdn.com/images/jg-konkuk/post/14b28217-5714-415b-8992-82815c717410/image.png" alt=""></p>
</li>
</ul>
<hr>
<ul>
<li><p>위 예시는 MVC와의 차이를 보여주기 위한 코드일 뿐이다.</p>
</li>
<li><p>컨트롤러에 아래의 코드를 추가한다.</p>
<pre><code class="language-java">@GetMapping(&quot;hello-api&quot;)
  @ResponseBody
  public Hello helloApi(@RequestParam(&quot;name&quot;) String name){
      Hello hello = new Hello();
      hello.setName(name);
      return hello;
  }

  static class Hello{
      private String name;

      public String getName() {
          return name;
      }

      public void setName(String name) {
          this.name = name;
      }
  }</code></pre>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/b6bd70d0-c9b4-415f-a55d-63efee32dad5/image.png" alt=""></p>
</li>
<li><p>실행하면 위와 같이 데이터 형식으로 나오는데 이것은 json형식이다.
(json : key와 value로 이루어진 데이터 구조)</p>
</li>
</ul>
<p>=&gt; @ResponseBody를 사용하고 객체를 반환하면 json형식으로 반환하게 된다.</p>
<ul>
<li>API 작동 구조(@ResponseBody)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/56953598-c7d9-43b7-8513-f39b5556b180/image.png" alt=""></p>
<ol>
<li>웹 브라우저가 톰켓 서버에게 요청한다.</li>
<li>톰켓 서버에서 hello-static 관련 컨트롤러가 있는지 스프링 컨테이너에게 신호를 보낸다</li>
<li>관련 컨트롤러가 있으므로 그 컨트롤러를 따라간다.</li>
<li>@ResponseBody에 의해 HttpMessageConverter로 넘어간다.</li>
<li>객체이므로 JsonConverter로 인해 json형식으로 변환된다.
(※ 현재 spring은 객체가 들어오면 json으로 바꾸는 것이 default이다.)</li>
<li>웹브라우저로 보내진다.</li>
</ol>
<p>※ 기본 객체처리 라이브러리 : MappingJackson2HttpMessageConverter</p>
<hr>
<blockquote>
<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">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</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[언리얼5] 5. 제단 제작]]></title>
            <link>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-5.-%EC%A0%9C%EB%8B%A8-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-5.-%EC%A0%9C%EB%8B%A8-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Wed, 18 Jan 2023 13:52:38 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="컨셉">컨셉</h2>
<ul>
<li>광신도들은 나무, 뿌리를 숭배하므로 제단이 나무 뿌리쪽에 위치하도록 설계하였다.</li>
</ul>
<hr>
<h2 id="개발-진행">개발 진행</h2>
<h3 id="1-제단-들어갈-공간-만들기">1. 제단 들어갈 공간 만들기</h3>
<ul>
<li>나무 뿌리쪽에 제단을 배치하기 위해 땅 속을 파고들어야 했다.</li>
<li>동굴을 만드는 법을 고민하다가 아예 땅을 매몰시키고 그 위를 덮기로 하였다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/74c05cfc-6510-4860-a468-141463612bab/image.png" alt=""></p>
<h3 id="2-제단-틀-만들기">2. 제단 틀 만들기</h3>
<ul>
<li><p>중간중간에 랜드스케이프를 계속 수정하면 비효율적이므로 다른 공간에 제단을 미리 만들기로 하였다.</p>
</li>
<li><p>제단 입구는 언뜻 보기에 평범해 보인다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/51e959bc-8794-4474-a89e-68b3a5f51d0b/image.png" alt=""></p>
<ul>
<li>제단 내부는 다음과 같이 설계하였다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/8ec622bd-fd12-451f-a329-c1cbe3ee03df/image.png" alt=""></p>
<ul>
<li>비밀번호를 입력하고 계단을 따라 내려가면 바닥에 퍼즐이 있는 무언가가 있다.</li>
</ul>
<h3 id="3-머터리일-입히기-및-위치-이동">3. 머터리일 입히기 및 위치 이동</h3>
<ul>
<li>게임의 엔딩을 보는곳이 제단인 만큼 엔딩을 보는 장소로 갈때에 긴장감을 느낄 수 있도록 조금 더 길게 설계하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/ecbd8bda-9080-4bde-abd4-218c5ff071e0/image.png" alt=""></li>
</ul>
<h3 id="4-메가스캔-에셋-수정">4. 메가스캔 에셋 수정</h3>
<ul>
<li><p>메가스캔의 에셋은 완성본이자 미완성본이라고 할 수 있는데 그 이유는 벽이나 계단같은 에셋들이 모든 면이 설계되어 있지 않기 때문이다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/5e96c4bd-5bab-4acc-b614-4194c5ec481d/image.png" alt=""></p>
</li>
<li><p>따라서 내가 직접 안쪽을 설계해주어야했다.(물론 다른 편한방법이 존재할 수 있지만 나는 찾지 못하였다.)</p>
</li>
<li><p>메가스캔의 에셋들은 폴리그룹이 보이는 면만 설정되어 직사각형의 형태가 아니다.
문이 열리는 블루프린트를 삽입할 것이기 때문에 모든 방향에서 보이도록 만들어야했다.
따라서 완벽히 수복할 수는 없지만 최대한 직사각형으로 만들어 이질감을 없애려고 하였다.</p>
</li>
<li><p>모델링모드 - PolyModel - <strong>PolyEd</strong> 를 이용하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/cbd0b795-227f-4124-a8ac-297189bafab8/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/bc42511c-c9a1-465c-9263-d4e9198928e2/image.png" alt=""></p>
</li>
<li><p>추가로 <strong>HFill</strong>을 사용하여 비어있는 구멍들을 채워준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/2fadfab1-74a8-468a-86db-fe57c21eb931/image.png" alt=""></p>
</li>
<li><p>성공적으로 직사각형에 가깝게 변형시켰다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/31557c76-6c19-4918-b7d6-6f99323dcb61/image.png" alt=""></p>
</li>
<li><p>부작용으로 앞쪽이 약간 찌그러졌다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/8a05865b-e230-4334-a99d-b9ab950076bb/image.png" alt=""></p>
</li>
</ul>
<h3 id="5-구덩이-메꾸기">5. 구덩이 메꾸기</h3>
<ul>
<li>다른 랜드스케이프를 생성하여 구덩이를 메꾸었다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/a5158c31-5d8e-4baa-a3f5-5d734f5f0548/image.png" alt=""></li>
</ul>
<h3 id="6-제단-횃불-에셋-만들기">6. 제단 횃불 에셋 만들기</h3>
<ul>
<li><p>불이 나오는 제단 횃불을 구할 수 없었고 퀵셀의 다른 에셋을 이용하여 만들기로 하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/836e8b61-6920-48ff-9fb1-5371d4207be9/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/93ecacba-daa1-44f7-8d9f-a61daf8c35ae/image.png" alt=""></p>
</li>
<li><p>기둥의 윗부분들 횃불을 받쳐주는 용도로 사용하기로 하였다.</p>
</li>
<li><p>불을 담고 있는 철조망(?)이 있으면 좋겠지만 구하기 힘들것이라 판단하여 아쉬운대로 윗부분에 불이 들어갈 공간을 동그랗게 만들어 주기로 하였다.</p>
</li>
<li><p>모델링 모드 - PolyModel - <strong>MshBool</strong> 을 이용할 것이다.</p>
</li>
</ul>
<ol>
<li><p>먼저 모델링 모드로 적당한 크기의 구를 생성해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/9cad9003-6a74-4a1d-8b3b-e4fda5da9b69/image.png" alt=""></p>
</li>
<li><p>만들어준 구에 기둥의 머터리얼을 그대로 넣어준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/786c38be-b697-4574-9eaa-aaf7e631d4cd/image.png" alt=""></p>
</li>
<li><p>구의 위치를 조정해준 다음 기둥을 먼저 찍고 shift로 구를 찍어준다.(순서 중요)</p>
</li>
<li><p>그 다음 <strong>MshBool</strong> 을 실행한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/d8c94696-468b-4078-86d7-ccc619aaebb5/image.png" alt=""></p>
</li>
<li><p>내가 원하는 모양이 만들어졌으므로 불을 추가해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/9ff00a2b-2cc7-40b4-8e69-c03ecac5ffe9/image.png" alt=""></p>
</li>
<li><p>완성
<img src="https://velog.velcdn.com/images/jg-konkuk/post/ea74d8f7-cf38-48d3-8f63-e838c7c02c74/image.png" alt=""></p>
</li>
</ol>
<h3 id="7-지하-2층으로-내려가는-비밀-공간-만들기">7. 지하 2층으로 내려가는 비밀 공간 만들기</h3>
<ul>
<li>엔딩을 보기 위해 조각상에 빈 부분을 끼워놓으면 지하2층으로 가는 비밀 공간이 생긴다.</li>
<li>이를 위해 바닥에 구멍을 뚫고 그 구멍을 다른 에셋으로 채워넣는다.</li>
<li>이 과정에서 바닥은 울퉁불퉁하기 때문에 자연스럽게 표현하기 위해 모델링 모드의 <strong>TriEd</strong> 을 이용하여 조정해준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/8a4ba432-f5ff-4fee-b9cb-d5c94e95e68a/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/2c310cc1-befa-4749-a8c1-d33d47e317d2/image.png" alt=""></li>
</ul>
<h3 id="8-지하-2층-계단-만들기">8. 지하 2층 계단 만들기</h3>
<ul>
<li>모델링 모드의 stair로 원하는 모양의 계단을 생성하고 <strong>XFormUV</strong> 으로 자연스러운 머터리얼 모양을 잡는다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/f6dd12c2-1ab8-4c96-bcf3-b613e582fa57/image.png" alt=""></li>
</ul>
<h3 id="9-나선-계단-만들기">9. 나선 계단 만들기</h3>
<ul>
<li><p>게임의 엔딩인 만큼 가볍게 만들수 없었고 나선 계단을 만들어 긴장감을 높이기로 하였다.</p>
</li>
<li><p>액터 배치 - 지오메트리의 <strong>나선 계단 브러시</strong> 를 활용하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/1831f414-799b-4f1f-a21a-6e2fe8694a7d/image.png" alt=""></p>
</li>
<li><p>벽은 원통형 실린더를 생성하고 모델링 모드를 이용해 안쪽은 파주고 문쪽은 뚫어주었다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/a555e513-92e8-4ed4-8e77-132d030fa8ce/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/0f736b71-3747-44d3-a625-4079bc7a8bb8/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/8e3492f9-2345-499d-a05e-14708b1adef7/image.png" alt=""></p>
</li>
<li><p>이때 cut through로 반대쪽도 똑같이 뚫려버리기때문에 폴리컷을 하기 전 에셋을 이름이 다른 에셋으로 복사해주고 반으로 자른다음 합쳐서 뚫린부분을 메꾸어주었다.</p>
</li>
<li><p>외벽까지 같은 방법으로 생성해주면 완성된다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/231dd7fb-005d-4be8-9a9c-273bd94a2cd9/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/930e0750-8b30-45d6-a6ac-c335d613a096/image.png" alt=""></p>
</li>
<li><p>신비한 느낌을 주기 위해 고급스러운 머터리얼을 채택했다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/8dd85e4a-acad-485e-a6ce-1e4c8bd2d557/image.png" alt=""></p>
</li>
</ul>
<h3 id="10-지하-2층-제작">10. 지하 2층 제작</h3>
<ul>
<li><p>숭배의 대상이 뿌리를 표현하기 위해 바닥을 뿌리들이 얽혀있는 머터리얼을 사용했고 섬의 핵심 코어로 꽃을 배치하였다.</p>
</li>
<li><p>조금 더 디테일하게 작업할 수도 있겠지만 시간 부족으로 가볍게 설치만 하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/56c16f28-22ec-4e9c-bb4c-57537a5ffd35/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[언리얼5] 4. 숲 제작]]></title>
            <link>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-4.-%EC%88%B2-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-4.-%EC%88%B2-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Wed, 18 Jan 2023 13:44:56 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="컨셉">컨셉</h2>
<ul>
<li>이 게임에는 신앙을 숭배하는 악당(?)들이 있는데 이들은 나무, 뿌리를 숭배한다.</li>
<li>따라서 섬에 거대한 나무를 배치하고 이 거대한 나무를 기준으로 가까운 곳에 있는 나무일수록 큰 나무들을 배치하여 무언가 신비로운 힘이 있음을 암시하도록 설계하였다.</li>
</ul>
<hr>
<h2 id="개발-진행">개발 진행</h2>
<h3 id="1-감시-초소">1. 감시 초소</h3>
<ul>
<li>숲 속에서 총을 얻을 수 있는 장소인 감시 초소를 만들었다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/e564e804-cad5-46b7-9576-d13c7597c2f0/image.png" alt=""></li>
</ul>
<h3 id="2-나무-추가">2. 나무 추가</h3>
<ul>
<li>컨셉이 숲이기 때문에 많은 나무들을 설치해야하는데 지금 사용하는 나무는 마켓플레이스 에셋이기 때문에 많은 성능을 차지할 수 있다. 따라서 적당히 하늘을 덮는 정도로만 깔고 나머지는 퀵셀의 나무 기둥 에셋들을 가져와 깔기로 하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/e2b23c70-2b4b-4c17-8b84-2dddf5191299/image.png" alt=""></li>
</ul>
<h3 id="3-아이템-총-추가">3. 아이템 (총) 추가</h3>
<ul>
<li>게임의 엔딩에 영향을 주는 아이템은 총을 배치하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/ea415160-ec88-4d97-8371-1ef6bbaf4c97/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[언리얼5] 3. 스프공장 제작]]></title>
            <link>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-3.-%EC%8A%A4%ED%94%84%EA%B3%B5%EC%9E%A5-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-3.-%EC%8A%A4%ED%94%84%EA%B3%B5%EC%9E%A5-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Wed, 18 Jan 2023 13:39:08 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="컨셉">컨셉</h2>
<ul>
<li>이 게임은 오픈월드로 자유롭게 돌아다닐 수 있지만 예상 플레이타임을 30분으로 설정하였기 때문에 넓으면 곤란하다.</li>
<li>따라서 스프공장을 가까운 곳에 배치하여 농장 다음으로 플레이어가 이동할 수 있도록 유도하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/95942603-a07c-4212-817e-66a31a7e57d4/image.png" alt=""></li>
</ul>
<hr>
<h2 id="개발-진행">개발 진행</h2>
<ul>
<li>먼저 대략적인 크기를 설정하였고 어울리는 벽 에셋들을 찾기로 하였다.</li>
<li>실사적인 방향으로 개발하기로 하였기에 외부는 배틀그라운드의 건물들을 참고하고, 내부는 실제 공장의 설계도를 참고하면 더욱 현실적으로 나타낼 수 있을 것이라 생각하였다.</li>
</ul>
<h3 id="1-전체적인-틀-구성">1. 전체적인 틀 구성</h3>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/f569025e-c604-4cc8-aec9-d019813193fc/image.png" alt=""></p>
<h3 id="2-공장-천장-골조-제작">2. 공장 천장 골조 제작</h3>
<ul>
<li>공장하면 떠오르는 것이 커다란 지붕을 받쳐주는 천장 골조라고 생각하여 메가스캔의 에셋을 이용해 구현해 보았다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/5a923380-3c96-4f5b-95e1-1465c1de363c/image.png" alt=""></li>
<li>이때 이 골조는 따로 구할 수가 없어서 선반 같은 에셋을 활용하였다.
이용한 에셋은 이것이다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/f77ff19b-53e0-4085-a3a8-93a55f378d3f/image.png" alt=""></li>
<li>원래는 이러한 철제 선반이였지만 모델링모드의 PlnCut, Pattern 등을 활용하여 천장 골조를 구현할 수 있었다.</li>
</ul>
<h3 id="3-건물-외벽-정리-및-내부-공간-분리">3. 건물 외벽 정리 및 내부 공간 분리</h3>
<ul>
<li>건물에 머터리얼을 입히고 내부 공간을 분리하였다. 이제 에셋들을 적절하게 배치하는 일만 남았다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/e4c36203-f50a-454b-8e35-a5aa16869480/image.png" alt=""></li>
</ul>
<h3 id="4-에셋-배치">4. 에셋 배치</h3>
<ul>
<li><p>공장은 에픽게임즈의 마켓플레이스에서 무료에셋 &#39;FactoryEnvironmentCollect&#39; 의 도움 덕에 좋은 에셋들을 많이 활용할 수 있었다.</p>
</li>
<li><p>스프공장이라는 컨셉에 맞게 에셋을 활용하고자 실제 공장 내부 사진을 참고하여 배치하였다.</p>
</li>
</ul>
<p>&lt;참고 사진&gt;
<img src="https://velog.velcdn.com/images/jg-konkuk/post/65df7e8b-6c76-4613-9518-8e4f40ae5bc4/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/71a632d2-4f65-4685-81d5-cf02487fc809/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/a2f3177d-4cb4-498b-abfc-d70c7be9f3e3/image.png" alt=""></p>
<ul>
<li>이밖에 구글의 여러 사진들을 이용하여 에셋들을 배치하였다.</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/43580d67-761d-413c-8fdf-facb9170ae93/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/285b1995-e357-4d24-9ac4-da0090ff9406/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/74a5f1ba-bbce-4dd9-9f0c-ef9d31e8b607/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/07c6b58e-5ec9-4dcc-9bb2-9bd27bcc5589/image.png" alt=""></p>
<h3 id="5-공장-배경-모습">5. 공장 배경 모습</h3>
<ul>
<li>완성된 공장의 전체적인 모습이다
<img src="https://velog.velcdn.com/images/jg-konkuk/post/60b6e2b6-bd89-4005-9af4-02a72dc023b3/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[언리얼5] 2. 농장 제작]]></title>
            <link>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-2.-%EB%86%8D%EC%9E%A5-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-2.-%EB%86%8D%EC%9E%A5-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Wed, 18 Jan 2023 13:32:41 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="컨셉">컨셉</h2>
<ul>
<li>플레이어는 농장에 있는 창고 안에서 갇힌 채로 시작된다.</li>
<li>이 게임은 겉보기엔 평화롭지만 실상은 약간 공포적인 분위기로 농장은 인간이 사육되는 곳(?) 같은 분위기를 연출해야한다.</li>
<li>따라서 농장은 울타리로 갇혀있으며 낮은 지형에 위치시켜 언제든 감시당할 수 있는 느낌을 표현하려 하였다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/0bb9cb1b-43a8-417a-ae66-5f6b790cbaab/image.png" alt=""></p>
<hr>
<h2 id="개발-진행">개발 진행</h2>
<ul>
<li>최대한 무료, 퀵셀 콘텐츠를 사용하려 하였으나 마땅한 콘텐츠가 없어서 위와 같이 전체적인 틀만 만들었다.</li>
</ul>
<h3 id="1-창고-외관-및-울타리-배치">1. 창고 외관 및 울타리 배치</h3>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/1b9b7f08-f7b3-4cbf-8aab-6951ed485690/image.png" alt=""></p>
<h3 id="2-창고-안-에셋-배치">2. 창고 안 에셋 배치</h3>
<ul>
<li>좋은 무료 에셋을 구했기에 어울리는 에셋들을 배치할 수 있었다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/8b1cbf77-7f89-4b44-8de9-805afd303f59/image.PNG" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/4adf6421-7436-4551-a270-8924bb2eb809/image.PNG" alt=""></p>
<h3 id="3-농장-에셋-배치">3. 농장 에셋 배치</h3>
<ul>
<li>다양한 에셋들을 이용하여 농장을 꾸몄다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/75ed09fa-323f-4194-9b94-bcb2e83483ee/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[언리얼5] 1. 랜드스케이프를 이용하여 지형 생성하기]]></title>
            <link>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-1.-%EB%9E%9C%EB%93%9C%EC%8A%A4%EC%BC%80%EC%9D%B4%ED%94%84%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%A7%80%ED%98%95-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jg-konkuk/%EC%96%B8%EB%A6%AC%EC%96%BC5-1.-%EB%9E%9C%EB%93%9C%EC%8A%A4%EC%BC%80%EC%9D%B4%ED%94%84%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%98%EC%97%AC-%EC%A7%80%ED%98%95-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 18 Jan 2023 13:23:20 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="1-랜드매스-이용">1. 랜드매스 이용</h2>
<ul>
<li><p>프로젝트를 만들었다면 지형을 만들어야한다.
기본으로 생성된 지형들은 모두 지우고 <strong>랜드매스 플러그인</strong>으로 새로운 지형을 만들 것이다.</p>
</li>
<li><p>아래에 CG맨님의 랜드매스 영상을 올릴 것이나 주의점이 있다.
※ 랜드매스 플러그인이 언리얼4 기반이여서 그런건지 <strong>MaterialOnly 브러쉬</strong>가 제대로 동작되지 않는다.
따라서 나는 지형에 텍스쳐를 입히는 용도로만 사용하였다.</p>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=MEkJcJw7R3E">한국어 튜토리얼! 왕초보도 10분 만에 게임 속 랜덤한 산, 강 지형 만들기! (ft. 랜드매스 플러그인)</a></p>
</blockquote>
<ul>
<li>랜드스케이프 모드의 스컬프팅, 스무드, 플래튼 등을 활용하여 지형을 만들어준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/e60c6cff-4932-41ec-a645-64e5bb627b71/image.png" alt=""></p>
</li>
</ul>
<hr>
<h2 id="2-바다-추가">2. 바다 추가</h2>
<ul>
<li><p>섬에서 탈출하는 것이 최종목표이기 때문에 가장자리 부분을 파서 섬을 표현했고 노이즈를 통하여 자연스럽게 표현되도록 시도하였다.</p>
</li>
<li><p>섬에서 탈출하는 컨셉이기 때문에 바다가 필요한 것은 당연하다.
water 플러그인을 이용하여 Water Body Custom을 이용하여 물이 섬주변을 꽉 채우도록 설정하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/78e47b50-c42d-4018-96a3-541ad03fc4fe/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/9bd654df-5637-4c0e-a233-0663a18b22e8/image.png" alt=""></p>
</li>
<li><p>평화로운 분위기가 기반이기에 <strong>화창한 날씨와 잔잔한 물결</strong>이 잘 어울린다고 보았다.</p>
</li>
</ul>
<hr>
<h2 id="3-맵-콜리전-추가">3. 맵 콜리전 추가</h2>
<ul>
<li>맵 밖으로 나가지 못하도록 맵에 콜리전 영역을 설정해준다.</li>
<li>액터 배치의 <strong>BlockingVolume</strong> 을 이용하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/087d4d00-49db-4b87-b3a1-1efb67e1feef/image.png" alt="">
<img src="https://velog.velcdn.com/images/jg-konkuk/post/d8bdc994-d123-429e-9651-03cae529ed4b/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unreal5 이용한 게임 프로젝트]]></title>
            <link>https://velog.io/@jg-konkuk/Unreal5-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B2%8C%EC%9E%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@jg-konkuk/Unreal5-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B2%8C%EC%9E%84-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Wed, 18 Jan 2023 13:08:10 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="개요">개요</h2>
<ul>
<li>교내 게임제작동아리를 통하여 게임개발 프로젝트에 참여하여 게임 개발은 어떨지 경험해보기로 하였다.</li>
<li>언리얼을 한번도 사용하지 않았음에도 불구하고 공부하면서 진행할 수 있다는 독려에 겁먹지 않고 시작하게 되었다.</li>
<li>총 인원은 4명이며 섬에서 퍼즐을 풀며 탈출을 해야하는 <strong>스토리탈출게임</strong>이다.</li>
<li>4개의 파트로 <strong>캐릭터와 상호작용</strong>, <strong>맵디자인</strong>, <strong>데이터베이스와 UI</strong>, <strong>퍼즐제작</strong> 으로 나누어 각자 하나씩 맡기로 하였다.</li>
<li>이 중에서 나는 <strong>맵디자인</strong>을 맡았다.</li>
</ul>
<hr>
<h2 id="공부">공부</h2>
<ul>
<li><p>유튜브에 많은 자료가 있었는데 그 중 러셀님이 만드신 <strong>[러셀과 함께하는 시작해요 언리얼]</strong>으로 시작하였다.</p>
<blockquote>
<p><a href="https://www.youtube.com/watch?v=eT-uLb-tVIM&amp;t=1s">[러셀과 함께하는 시작해요 언리얼] 1주차: 언리얼 엔진 시작하기</a></p>
</blockquote>
</li>
<li><p>맵디자인에 필요한 최소공부량은 위 영상의 1주차~3주차 까지로 부담되지 않는 학습량이다.</p>
</li>
<li><p>이 정도만으로도 시작하기에는 충분하지만 언리얼5가 출시된지 얼마 안되었기에 모르는 부분을 구글링해도 나오지 않을 확률이 크다.</p>
</li>
</ul>
<hr>
<h2 id="최적화">최적화</h2>
<ul>
<li><p>유니티의 경우 대부분 2D로 제작하고 쯔꾸르적인 묘사로 최적화에 대한 걱정이 덜하지만 언리얼의 경우 사실적인 묘사에 초점이 맞춰져 있고 3D로 제작을 하다보니 최적화가 필수이다.</p>
</li>
<li><p>최적화를 진행하기 위해 콘솔 명령어들을 사용하였는데 다음을 사용하였다</p>
</li>
<li><p><em>stat fps*</em></p>
</li>
<li><p><em>stat gpu*</em></p>
</li>
<li><p>fps로 전체적인 성능을 확인하고 stat gpu를 이용하여 어느 부분이 부하를 얼마나 걸고 있는지를 확인하였다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/ca005ce9-777a-4be3-acb0-5135e9a63d07/image.png" alt=""></p>
<ul>
<li>그 결과 그림자가 가장 많은 부하를 걸고 있었고 특히 맵 중 숲에서 가장 안좋은 성능을 보였다.</li>
<li>그 이유로는 숲에서 사용한 나무 에셋이 megascan의 에셋이기에 고퀄리티, 많은 폴리곤을 사용하기 때문으로 분석하였다.</li>
<li>따라서 크게 2가지 방법으로 최적화를 진행하였다.</li>
</ul>
<ol>
<li>모델링 모드 - simplify 툴을 이용하여 에셋의 <strong>폴리곤 수를 줄였다.</strong></li>
<li><strong>DirectionalLight</strong> 의 옵션 중 캐스케이드 섀도 맵을 조정하여 그림자의 모양이 너무 이상하지 않도록 타협하여 조정하였다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/87a5ee87-b3f1-41f2-a7ca-34275a05e682/image.png" alt=""></li>
</ol>
<ul>
<li>이 최적화 과정을 통하여 1060ti 기준 평균 20 ~ 30 fps에서 50 ~ 60 fps로 끌어올릴 수 있었다.</li>
</ul>
<hr>
<h2 id="느낀점">느낀점</h2>
<ul>
<li>언리얼을 처음 사용해보면서 하드코딩이나 블루프린트를 이용하여 상호작용들을 만들어보진 않았지만 게임의 전반적인 영역인 맵 부분을 맡으면서 언리얼에 빠르게 익숙해질 수 있었고 처음임에도 불구하고 만족스러운 결과물을 만들었다는 것에 언리얼이 얼마나 편리한 도구인지를 체험할 수 있었다.</li>
<li>또한 여러 게임들을 플레이하고 보면서 실제 내가 사용했던 에셋과 비슷한 에셋들이 들어가있는 게임들을 발견하여 실제 게임 개발진들이 어떻게 게임을 구성하고 제작하는지를 경험할 수 있는 좋은 시간이였다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring 1-1. 프로젝트 생성]]></title>
            <link>https://velog.io/@jg-konkuk/Spring-1-1.-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@jg-konkuk/Spring-1-1.-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Wed, 18 Jan 2023 12:48:19 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="intellij-사용">intelliJ 사용</h2>
<ul>
<li>스프링 부트 스타터 사이트로 간편한 스프링 프로젝트 생성</li>
<li><a href="https://start.spring.io">https://start.spring.io</a></li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/1952d2d8-a36c-4b38-819e-fdea63ee5720/image.png" alt=""></p>
<ul>
<li><p>접속 시 위와 같은 화면이 나올 것이다.</p>
</li>
<li><p>project : <strong>Gradle-Groovy</strong></p>
</li>
<li><p>language : <strong>Java</strong></p>
</li>
<li><p>spring Boot는 괄호가 붙지 않은 2 버전 중 가장 최신 버전을 선택한다.
괄호가 붙은 버전들은 아직 미완성이거나 정식 릴리즈되지 않은 버전들이다.</p>
</li>
<li><blockquote>
<p>위 사진에서는 2.7.7 선택</p>
</blockquote>
</li>
<li><blockquote>
<p>스프링 부트 버전 3부터는 강의와 다른 여러 가지 설정들이 필요하므로 버전 2를 사용한다.</p>
</blockquote>
</li>
<li><p>group명과 artifact명은 자유롭게 설정한다.</p>
</li>
<li><p>artifact는 결과물의 이름으로 나오게 된다.</p>
</li>
<li><p>packaging : <strong>Jar</strong></p>
</li>
<li><p>Java : <strong>11</strong></p>
</li>
<li><p>오른쪽 위의 버튼 ADD DEPENDENCIES를 누르고 <strong>Spring Web</strong>과 <strong>Thymeleaf</strong>를 선택하고 GENERATE 버튼을 눌러 다운을 받는다.</p>
</li>
</ul>
<hr>
<h2 id="다운한-프로젝트-파일-열기">다운한 프로젝트 파일 열기</h2>
<ul>
<li>다운받은 파일의 압축을 풀고 intelliJ에서 프로젝트 열기 버튼을 누른다.</li>
<li>다운받은 파일의 gradle파일인 build.gradle을 선택하여 open as project로 열어준다.</li>
<li>build.gradle 파일을 열어보면 자신이 했던 선택에 따라 자동으로 설정 코드가 입력되어있는 것을 볼 수 있다.</li>
<li>git을 편리하게 이용할 수 있도록 .gitignore 파일까지 자동으로 설정된 것을 볼 수 있다.</li>
</ul>
<hr>
<h2 id="프로젝트-환경-설정-확인">프로젝트 환경 설정 확인</h2>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/5f064fad-a7a7-479e-86d2-0e954745a025/image.png" alt=""></p>
<ul>
<li><p>9번째 줄의 실행버튼을 누르고 main함수를 실행시킨다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/9e55be4e-8fd6-4f91-8b40-67ef94ebb77d/image.png" alt="">
위와 같은 창이 나타난다면 인터넷 창을 열고 주소창에 &#39;localhost:8080&#39;을 입력한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/7d642abc-94c4-4fbf-9a86-8f7b2a08dea0/image.png" alt=""></p>
</li>
<li><p>다음과 같이 error page가 떴다면 성공이다.</p>
</li>
</ul>
<hr>
<h2 id="실행속도-향상">실행속도 향상</h2>
<ul>
<li>IntelliJ는 Gradle을 통해서 실행하는 것이 기본 설정이지만 이 경우 실행속도가 느리다.</li>
<li>다음과 같이 변경하면 자바로 바로 실행하기에 실행속도가 더 빠르다.</li>
<li>file 탭의 setting창을 연 후 Gradle 검색
Build and run using : IntelliJ IDEA
Run tests using : IntelliJ IDEA</li>
</ul>
<hr>
<h2 id="라이브러리">라이브러리</h2>
<ul>
<li><p>spring boot를 이용하여 프로젝트를 생성해보면 자동으로 수많은 라이브러리들을 가져온다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/ee079bd3-6676-4de7-b56a-8f2156e2b7eb/image.png" alt=""></p>
</li>
<li><p>웹 어플리케이션을 구동시키는데에 필수적인 것들이며 빌드 시 몇십메가씩 차지한다.</p>
</li>
<li><p>라이브러리들은 그 자체로 작동되지 않고 작동되기 위한 또다른 라이브러리들을 가져와서 최종적으로 &#39;spring core&#39; 까지 연쇄적으로 가져온다.</p>
</li>
<li><p>예전에는 tomcat 서버에 자바 코드를 집어넣는 등의 힘든 작업이 필요하지만 요즘에는 편리하게 라이브러리 하나만 넣어주면 자동으로 설정된다.</p>
</li>
<li><p>system.out.println 사용보다 log에 남기는 것이 관리하는데에 편하기 때문에 log 사용은 필수적이다.</p>
</li>
<li><blockquote>
<p>실무에서는 log 사용이 절대적이지만 강의에서는 println 사용</p>
</blockquote>
</li>
</ul>
<blockquote>
<p>log에 대한 참고
<a href="https://velog.io/@woply/spring-SLF4J%EC%99%80-Logback%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%A1%9C%EA%B7%B8-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">https://velog.io/@woply/spring-SLF4J%EC%99%80-Logback%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EB%A1%9C%EA%B7%B8-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</a></p>
</blockquote>
<ul>
<li>크게 이러한 라이브러리들이 필요하다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/adbf6da5-eaec-43d0-b73f-ccb36789a614/image.png" alt=""></li>
</ul>
<hr>
<h2 id="welcome-page-만들기">welcome page 만들기</h2>
<ul>
<li><p>src - main - resources - static 파일에 index.html 파일 생성</p>
</li>
<li><p>아래 코드 붙여넣기</p>
<pre><code>&lt;!DOCTYPE HTML&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Hello&lt;/title&gt;
&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
Hello
&lt;a href=&quot;/hello&quot;&gt;hello&lt;/a&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre></li>
<li><p>저장 후 서버를 동작 localhost:8080에 접속하면 에러페이지 대신 다른 화면이 등장한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/32b063b1-67b6-4641-b9c2-9b34db2d1e20/image.png" alt=""></p>
</li>
<li><p>이것은 별도의 프로그래밍이 아닌 넣은 그대로 동작하는 정적 페이지이다.</p>
</li>
<li><blockquote>
<p>thymeleaf 템플릿 엔진 사용</p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="컨트롤러-만들기">컨트롤러 만들기</h2>
<ul>
<li>src - main - java - hello.hellospring 에 controller 패키지를 생성하고 거기에 HelloController를 만든다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/340d54c7-fcbc-4a5c-8d0f-91228fd7bdc1/image.png" alt=""></li>
</ul>
<pre><code class="language-java">package hello.hellospring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {

    @GetMapping(&quot;hello&quot;)
    // @GetMapping(&quot;hello&quot;)는 /hello의 접속을 hello를 호출하도록 해준다.
    public String hello(Model model){
        model.addAttribute(&quot;data&quot;,&quot;hello!!&quot;);
        // data라는 이름의 키의 value값이 hello!!이다.
        return &quot;hello&quot;;
    }
}</code></pre>
<ul>
<li>템플릿에 hello.html 파일을 만들어준다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c897c3f0-2377-4518-a4d4-51a3cfffba33/image.png" alt=""></li>
</ul>
<pre><code class="language-html">&lt;!DOCTYPE HTML&gt;
&lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
&lt;head&gt;
    &lt;title&gt;Hello&lt;/title&gt;
    &lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;p th:text=&quot;&#39;안녕하세요. &#39; + ${data}&quot; &gt;안녕하세요. 손님&lt;/p&gt;
  &lt;!-- 결과적으로 data 자리에 hello!!로 치환되어 화면에 출력된다. --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<ul>
<li>결과
밑줄이 쳐진 hello를 누르면 아래와 같은 창이 나타난다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/8dbcb7a0-73cd-4426-9ec8-bfd9d76a47fa/image.png" alt=""></li>
</ul>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/c21344b8-35ea-4672-bf28-5f2c44eacb47/image.png" alt=""></p>
<hr>
<h2 id="빌드하고-실행하기">빌드하고 실행하기</h2>
<p>※ 윈도우 기준</p>
<ul>
<li><p>git bash 이용 필요</p>
</li>
<li><p>gradlew.bat 파일이 있는 프로젝트 파일로 경로 이동</p>
<blockquote>
<p>ex) cd C:\work\intelliJ_project\hello-spring</p>
</blockquote>
</li>
<li><p>gradlew 입력 후 엔터하면 빌드가 된다.</p>
</li>
<li><p>만약 libs폴더가 생성되지 않았다면 다시 시도한다.</p>
</li>
<li><blockquote>
<p>./gradlew clean build
   위 명령어 입력 시 초기화하고 다시 빌드함.</p>
</blockquote>
</li>
<li><p>성공적으로 빌드되었다면 libs 파일의 jar파일을 확인한다.
<img src="https://velog.velcdn.com/images/jg-konkuk/post/c337a97a-c35d-4a77-9aea-31eb2caf58ad/image.png" alt=""></p>
</li>
<li><p>java -jar hello-spring-0.0.1-SNAPSHOT.jar 입력</p>
</li>
<li><p>마지막으로 localhost:8080 접속하여 확인한다.</p>
</li>
</ul>
<hr>
<blockquote>
<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">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</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++/백준] 9251번-LCS]]></title>
            <link>https://velog.io/@jg-konkuk/C%EB%B0%B1%EC%A4%80-9251%EB%B2%88-LCS</link>
            <guid>https://velog.io/@jg-konkuk/C%EB%B0%B1%EC%A4%80-9251%EB%B2%88-LCS</guid>
            <pubDate>Sun, 25 Sep 2022 07:48:36 GMT</pubDate>
            <description><![CDATA[<h2 id="링크">링크</h2>
<hr>
<p><a href="https://www.acmicpc.net/problem/9251">https://www.acmicpc.net/problem/9251</a></p>
<h2 id="문제">문제</h2>
<hr>
<p>LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다.</p>
<p>예를 들어, ACAYKP와 CAPCAK의 LCS는 ACAK가 된다.</p>
<h2 id="입력">입력</h2>
<hr>
<p>첫째 줄과 둘째 줄에 두 문자열이 주어진다. 문자열은 알파벳 대문자로만 이루어져 있으며, 최대 1000글자로 이루어져 있다.</p>
<h2 id="출력">출력</h2>
<hr>
<p>첫째 줄에 입력으로 주어진 두 문자열의 LCS의 길이를 출력한다.</p>
<h2 id="풀이">풀이</h2>
<hr>
<p>배낭 문제의 알고리즘과 비슷한 LCS(최장 수열) 문제이다.</p>
<p>이차원 배열을 만들고 관행(?)에 따라 인덱스 0번자리에는 모두 0으로 빈 자리로 남겨두고 1번부터 사용한다.</p>
<p>ACAYKP와 CAPCAK를 예시로 들겠다.
첫번째 C와 A는 같지 않으므로 왼쪽 또는 위쪽 중 큰 값을 그대로 가져온다.
두번째 C와 AC에서 같은 부분 C가 나왔으므로 +1 을 해준다.</p>
<p>다음 CA와 ACA를 보겠다.
같은 부분 CA가 나왔는데 이때 C와 AC를 검사한 값에서 +1을 해주어야 한다.
이유는 중복되어 검사될 수 없기 때문이다. 마지막 A가 서로 같아서 값을 올려주는 것이기 때문에 검사에 사용된 두 A를 뺀 값에서 더해주어야한다.</p>
<p>따라서 왼쪽 위 인덱스의 값에서 +1 을 해준다.</p>
<p>다음은 예제의 결과이다.</p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">0</th>
<th align="center">A</th>
<th align="center">C</th>
<th align="center">A</th>
<th align="center">Y</th>
<th align="center">K</th>
<th align="center">P</th>
</tr>
</thead>
<tbody><tr>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">0</td>
</tr>
<tr>
<td align="center">C</td>
<td align="center">0</td>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">1</td>
</tr>
<tr>
<td align="center">A</td>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">2</td>
</tr>
<tr>
<td align="center">P</td>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">3</td>
</tr>
<tr>
<td align="center">C</td>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">2</td>
<td align="center">3</td>
</tr>
<tr>
<td align="center">A</td>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">3</td>
<td align="center">3</td>
<td align="center">3</td>
<td align="center">3</td>
</tr>
<tr>
<td align="center">K</td>
<td align="center">0</td>
<td align="center">1</td>
<td align="center">2</td>
<td align="center">3</td>
<td align="center">3</td>
<td align="center">4</td>
<td align="center">4</td>
</tr>
</tbody></table>
<h2 id="코드">코드</h2>
<hr>
<pre><code class="language-cpp">#include&lt;iostream&gt;
#include&lt;algorithm&gt;
#include&lt;string&gt;
using namespace std;

int letter[1001][1001];

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    string a, b;
    cin &gt;&gt; a &gt;&gt; b;

    for (int i = 1; i &lt;= b.length(); i++) {
        for (int j = 1; j &lt;= a.length(); j++) {
            if (b[i - 1] == a[j - 1]) {
                letter[i][j] = letter[i - 1][j - 1] + 1;
            }
            else {
                letter[i][j] = max(letter[i - 1][j], letter[i][j - 1]);
            }
        }
    }
    cout &lt;&lt; letter[b.length()][a.length()];

    return 0;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/ef56a66b-e379-42e8-912d-e62212100558/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++/백준] 2812번-크게 만들기]]></title>
            <link>https://velog.io/@jg-konkuk/C%EB%B0%B1%EC%A4%80-2812%EB%B2%88-%ED%81%AC%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jg-konkuk/C%EB%B0%B1%EC%A4%80-2812%EB%B2%88-%ED%81%AC%EA%B2%8C-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 22 Sep 2022 09:56:01 GMT</pubDate>
            <description><![CDATA[<h2 id="링크">링크</h2>
<hr>
<p><a href="https://www.acmicpc.net/problem/2812">https://www.acmicpc.net/problem/2812</a></p>
<h2 id="문제">문제</h2>
<hr>
<p>N자리 숫자가 주어졌을 때, 여기서 숫자 K개를 지워서 얻을 수 있는 가장 큰 수를 구하는 프로그램을 작성하시오.</p>
<h2 id="입력">입력</h2>
<hr>
<p>첫째 줄에 N과 K가 주어진다. (1 ≤ K &lt; N ≤ 500,000)</p>
<p>둘째 줄에 N자리 숫자가 주어진다. 이 수는 0으로 시작하지 않는다.</p>
<h2 id="출력">출력</h2>
<hr>
<p>입력으로 주어진 숫자에서 K개를 지웠을 때 얻을 수 있는 가장 큰 수를 출력한다.</p>
<h2 id="풀이">풀이</h2>
<hr>
<p>무조건 높은자리수에 있는 숫자가 커야 가장 큰 수를 만들 수 있으므로 9가 아니라면 지울 수 있는 개수만큼 뒤의 숫자들을 검사하고 가장 큰 수를 넣는다. 넣은 수 앞의 부분은 결과에 넣지 않는다.</p>
<p>속도 향상을 위해 0인 경우와 9인 경우는 따로 조건문을 만들었다.</p>
<h2 id="코드">코드</h2>
<hr>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;string&gt;

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int N, K;
    cin &gt;&gt; N &gt;&gt; K;

    string num;
    cin &gt;&gt; num;

    string result = &quot;&quot;;

    for (int i = 0; i &lt; N; i++) {
        if (result.length() == N - K)
            break;
        int x = 0;
        if (K == 0)
            result += num[i];
        else {
            if (num[i] == &#39;9&#39;) {
                result += num[i];
            }
            else if (num[i] == &#39;0&#39;) {
                K--;
                continue;
            }
            else {
                for (int j = 1; j &lt;= K; j++) {
                    if (num[i + x] &lt; num[i + j]) {
                        x = j;
                        if (num[i + j] == &#39;9&#39;)
                            break;
                    }
                }
                if (x == 0 &amp;&amp; i == N - 1 &amp;&amp; K == 1)
                    break;
                result += num[i + x];
                K -= x;
                i += x;
            }
        }
    }
    cout &lt;&lt; result;

    return 0;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/b8edee87-964e-4177-9d7d-6b3a01e4eb5d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C++/백준] 12865번-평범한 배낭]]></title>
            <link>https://velog.io/@jg-konkuk/C%EB%B0%B1%EC%A4%80-12865%EB%B2%88-%ED%8F%89%EB%B2%94%ED%95%9C-%EB%B0%B0%EB%82%AD</link>
            <guid>https://velog.io/@jg-konkuk/C%EB%B0%B1%EC%A4%80-12865%EB%B2%88-%ED%8F%89%EB%B2%94%ED%95%9C-%EB%B0%B0%EB%82%AD</guid>
            <pubDate>Thu, 22 Sep 2022 05:36:28 GMT</pubDate>
            <description><![CDATA[<h2 id="링크">링크</h2>
<hr>
<p><a href="https://www.acmicpc.net/problem/12865">https://www.acmicpc.net/problem/12865</a></p>
<h2 id="문제">문제</h2>
<hr>
<p>이 문제는 아주 평범한 배낭에 관한 문제이다.</p>
<p>한 달 후면 국가의 부름을 받게 되는 준서는 여행을 가려고 한다. 세상과의 단절을 슬퍼하며 최대한 즐기기 위한 여행이기 때문에, 가지고 다닐 배낭 또한 최대한 가치 있게 싸려고 한다.</p>
<p>준서가 여행에 필요하다고 생각하는 N개의 물건이 있다. 각 물건은 무게 W와 가치 V를 가지는데, 해당 물건을 배낭에 넣어서 가면 준서가 V만큼 즐길 수 있다. 아직 행군을 해본 적이 없는 준서는 최대 K만큼의 무게만을 넣을 수 있는 배낭만 들고 다닐 수 있다. 준서가 최대한 즐거운 여행을 하기 위해 배낭에 넣을 수 있는 물건들의 가치의 최댓값을 알려주자.</p>
<h2 id="입력">입력</h2>
<hr>
<p>첫 줄에 물품의 수 N(1 ≤ N ≤ 100)과 준서가 버틸 수 있는 무게 K(1 ≤ K ≤ 100,000)가 주어진다. 두 번째 줄부터 N개의 줄에 거쳐 각 물건의 무게 W(1 ≤ W ≤ 100,000)와 해당 물건의 가치 V(0 ≤ V ≤ 1,000)가 주어진다.</p>
<p>입력으로 주어지는 모든 수는 정수이다.</p>
<h2 id="출력">출력</h2>
<hr>
<p>한 줄에 배낭에 넣을 수 있는 물건들의 가치합의 최댓값을 출력한다.</p>
<h2 id="풀이">풀이</h2>
<hr>
<p>이 문제는 DP이지만 배낭문제라는 알고리즘을 이해해야 풀기 쉬운 문제였다.
배낭문제는 Knapsack Problem으로 물건을 쪼갤수 있느냐에 따라 Greedy Algorithm 또는 Dynamic Programming으로 갈린다.</p>
<p>이 문제는 물건을 쪼갤수 없는 DP로 푸는 배낭문제이다.
2차원 배열에 물건하나씩 검사하여 저장하는데 각각의 인덱스는 순서, 무게를 나타내고 들어있는 data값은 가치를 나타낸다.</p>
<p>2차원 배열의 형식은 dp[n][m]=d로 나타내겠다
물건을 하나씩 넣으면서 n번째 물건이 m의 무게보다 큰지 확인하고
크다면 전 배열의 가치 값을 가져오고
작거나 같다면 (현재 물건의 가치 + 남은 무게의 최대 가치) 와 (전 배열의 가치)를 비교하여 큰값을 넣는다.</p>
<h2 id="코드">코드</h2>
<hr>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;algorithm&gt;
#include &lt;vector&gt;

using namespace std;

int arr[110][100010];

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cout.tie(NULL);

    int N, K;
    cin &gt;&gt; N &gt;&gt; K;

    int a, b;
    vector&lt;pair&lt;int, int&gt;&gt; v;

    for (int i = 0; i &lt; N; i++) {
        cin &gt;&gt; a &gt;&gt; b;
        v.push_back({ a,b });
    }

    for (int i = 1; i &lt;= N; i++) {
        int weight = v[i - 1].first;
        int value = v[i - 1].second;
        for (int j = 1; j &lt;= K; j++) {
            if (weight &lt;= j) {
                arr[i][j] = max(arr[i - 1][j - weight] + value, arr[i - 1][j]);
            }
            else {
                arr[i][j] = arr[i - 1][j];
            }
        }
    }

    cout &lt;&lt; arr[N][K];


    return 0;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/jg-konkuk/post/1b459bc0-4a65-420f-a155-5ec330db9fd1/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>