Jistol Github Page
2023-02-05T06:20:08+00:00
https://github.com/jistol
Jistol
https://jistol.github.io/public/img/profile.jpg
Jistol Github Page
https://github.com/jistol
(Kotlin) null을 컨트롤 하는 연산자
2023-02-05T00:00:00+00:00
2023-02-05T00:00:00+00:00
https://github.com/jistol/kotlin/2023/02/05/kotlin-null-operators
<h2 id="---안전한-호출-safe-calls">?. - 안전한 호출 (<a href="https://kotlinlang.org/docs/null-safety.html#safe-calls">Safe calls</a>)</h2>
<ul>
<li>null이 아닌 경우 실행한다.</li>
<li>let과 연계 사용시 더 간결하게 처리 가능</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// JAVA</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">safetyUpperCase</span><span class="o">(</span><span class="nc">String</span> <span class="n">str</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">str</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">str</span><span class="o">.</span><span class="na">toUpperCase</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// kotlin</span>
<span class="k">fun</span> <span class="nf">safetyUpperCase</span><span class="p">(</span><span class="n">str</span><span class="p">:</span><span class="nc">String</span><span class="p">?)</span> <span class="p">=</span> <span class="n">str</span><span class="o">?.</span><span class="nf">uppercase</span><span class="p">()</span>
<span class="c1">// str이 null일 경우 let 이하 구문은 실행되지 않는다</span>
<span class="k">fun</span> <span class="nf">combinePrefixIfNotNull</span><span class="p">(</span><span class="n">str</span><span class="p">:</span><span class="nc">String</span><span class="p">?,</span> <span class="n">prefix</span><span class="p">:</span><span class="nc">String</span><span class="p">)</span> <span class="p">=</span> <span class="n">str</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="n">prefix</span> <span class="p">+</span> <span class="n">str</span> <span class="p">}</span>
<span class="kd">val</span> <span class="py">s1</span><span class="p">:</span><span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="kd">val</span> <span class="py">s2</span><span class="p">:</span><span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="s">"A"</span>
<span class="nf">combinePrefixIfNotNull</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="s">"PREFIX-"</span><span class="p">)</span> <span class="c1">// null</span>
<span class="nf">combinePrefixIfNotNull</span><span class="p">(</span><span class="n">s2</span><span class="p">,</span> <span class="s">"PREFIX-"</span><span class="p">)</span> <span class="c1">// PREFIX-A</span>
</code></pre></div></div>
<h2 id="---엘비스연산자-elvis-operator">?: - 엘비스연산자 (<a href="https://kotlinlang.org/docs/null-safety.html#elvis-operator">Elvis operator</a>)</h2>
<ul>
<li>null 대신 사용 할 디폴트 값을 지정</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// JAVA</span>
<span class="nc">String</span> <span class="n">value</span> <span class="o">=</span> <span class="n">str</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="s">"DEFAULT"</span> <span class="o">:</span> <span class="n">str</span><span class="o">;</span>
</code></pre></div></div>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// kotlin</span>
<span class="kd">val</span> <span class="py">value</span> <span class="p">=</span> <span class="n">str</span><span class="o">?:</span> <span class="s">"DEFAULT"</span>
</code></pre></div></div>
<h2 id="as---안전한-캐스트-safe-casts">as? - 안전한 캐스트 (<a href="https://kotlinlang.org/docs/null-safety.html#safe-casts">Safe casts</a>)</h2>
<ul>
<li>지정한 타입으로 캐스트 하고 불가 시 null 반환</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// JAVA</span>
<span class="kd">public</span> <span class="nc">Long</span> <span class="nf">toLong</span><span class="o">(</span><span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">Long</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="o">(</span><span class="nc">Long</span><span class="o">)</span><span class="n">obj</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// kotlin</span>
<span class="k">fun</span> <span class="nf">toLong</span><span class="p">(</span><span class="n">obj</span><span class="p">:</span><span class="nc">Any</span><span class="p">):</span><span class="nc">Long</span><span class="p">?</span> <span class="p">=</span> <span class="n">obj</span> <span class="k">as</span><span class="p">?</span> <span class="nc">Long</span>
</code></pre></div></div>
<h2 id="---널-아님-단언-not-null-assertion">!! - 널 아님 단언 (<a href="https://kotlinlang.org/docs/null-safety.html#the-operator">not-null assertion</a>)</h2>
<ul>
<li>null이 될 수 있는 타입을 null이 될 수 없는 타입으로 강제전환</li>
<li>실제 값이 null일 경우 NPE 발생</li>
<li>안티패턴 : person.company!!.address!!.country
** 어느 값에서 NPE가 발생했는지 알기 어렵다</li>
</ul>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">fun</span> <span class="p"><</span><span class="nc">T</span><span class="p">></span> <span class="nf">notNullAssert</span><span class="p">(</span><span class="n">nullable</span><span class="p">:</span><span class="nc">T</span><span class="p">?):</span><span class="nc">T</span> <span class="p">=</span> <span class="n">nullable</span><span class="o">!!</span>
<span class="kd">val</span> <span class="py">s1</span><span class="p">:</span><span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="s">"NOT-NULL"</span>
<span class="kd">val</span> <span class="py">s2</span><span class="p">:</span><span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="nf">notNullAssert</span><span class="p">(</span><span class="n">s1</span><span class="p">)</span>
<span class="nf">notNullAssert</span><span class="p">(</span><span class="n">s2</span><span class="p">)</span> <span class="c1">// NullPointerException</span>
</code></pre></div></div>
(SpringBatch) Job 종료 후 스프링 프로세스가 늦게 끝나는 현상
2020-08-12T00:00:00+00:00
2020-08-12T00:00:00+00:00
https://github.com/jistol/spring/2020/08/12/spring-batch-delay-shutdown
<p>배치 구성을 위해 작업중 발생한 이슈로, Job이 모두 끝났음에도 불구하고 스프링 프로세스가 계속 유지되다 1분후에 종료되는 현상이 있었습니다.</p>
<h3 id="구성">구성</h3>
<ul>
<li>Spring boot 2.3.2</li>
<li>Spring batch</li>
<li>Spring data jpa</li>
<li>기타 등등…</li>
</ul>
<p>위와 같은 구성으로 배치 프로젝트를 구성하고 간단한 테스트 Job을 만들었습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@EnableBatchProcessing</span>
<span class="nd">@SpringBootApplication</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BatchApplication</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">BatchApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@Slf4j</span>
<span class="nd">@Configuration</span>
<span class="nd">@RequiredArgsConstructor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TestJobConfig</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">JobBuilderFactory</span> <span class="n">jobBuilderFactory</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">StepBuilderFactory</span> <span class="n">stepBuilderFactory</span><span class="o">;</span>
<span class="nd">@Bean</span>
<span class="nd">@JobScope</span>
<span class="kd">public</span> <span class="nc">Step</span> <span class="nf">testStep</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">stepBuilderFactory</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"testStep"</span><span class="o">)</span>
<span class="o">.</span><span class="na">tasklet</span><span class="o">((</span><span class="n">contribution</span><span class="o">,</span> <span class="n">chunkContext</span><span class="o">)</span> <span class="o">-></span> <span class="o">{</span>
<span class="o">......</span>
<span class="k">return</span> <span class="nc">RepeatStatus</span><span class="o">.</span><span class="na">FINISHED</span><span class="o">;</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">Job</span> <span class="nf">testJob</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">jobBuilderFactory</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"testJob"</span><span class="o">)</span>
<span class="o">.</span><span class="na">start</span><span class="o">(</span><span class="n">testStep</span><span class="o">())</span>
<span class="o">.</span><span class="na">listener</span><span class="o">(</span><span class="n">listener</span><span class="o">())</span>
<span class="o">.</span><span class="na">build</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">JobExecutionListener</span> <span class="nf">listener</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">JobExecutionListener</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">beforeJob</span><span class="o">(</span><span class="nc">JobExecution</span> <span class="n">jobExecution</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Job Start !!!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">afterJob</span><span class="o">(</span><span class="nc">JobExecution</span> <span class="n">jobExecution</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">warn</span><span class="o">(</span><span class="s">"Job End !!!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// build.gradle</span>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="n">implementation</span> <span class="s2">"org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}"</span>
<span class="n">implementation</span> <span class="s2">"org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}"</span>
<span class="n">implementation</span> <span class="s2">"org.springframework.boot:spring-boot-starter-batch:${springBootVersion}"</span>
<span class="o">......</span>
<span class="n">testImplementation</span><span class="o">(</span><span class="s1">'org.springframework.boot:spring-boot-starter-test'</span><span class="o">)</span> <span class="o">{</span>
<span class="n">exclude</span> <span class="nl">group:</span> <span class="s1">'org.junit.vintage'</span><span class="o">,</span> <span class="nl">module:</span> <span class="s1">'junit-vintage-engine'</span>
<span class="o">}</span>
<span class="n">testImplementation</span> <span class="s2">"org.springframework.batch:spring-batch-test"</span>
<span class="o">}</span>
</code></pre></div></div>
<p>테스트를 위해 별다른 로직 없이 간단하게 구성하였으며 시작과 종료시점에 로그를 남기 상태입니다.
현 상태에서 실행하게 되면 아래 로그와 같이 Job이 종료한 시점으로부터 1분이 지나서야 스프링 프로세스가 종료되는 것을 확인 할 수 있습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2020-08-12 20:49:16 [restartedMain] INFO o.s.b.a.b.JobLauncherApplicationRunner - Running default command line with: [requestDate=32]
2020-08-12 20:49:17 [restartedMain] INFO o.s.b.c.l.support.SimpleJobLauncher - Job: [SimpleJob: [name=testJob]] launched with the following parameters: [{requestDate=32}]
// Job 시작 시
2020-08-12 20:49:17 [restartedMain] WARN c.k.m.s.batch.config.TestJobConfig - Job Start !!!
2020-08-12 20:49:17 [restartedMain] INFO o.s.batch.core.job.SimpleStepHandler - Executing step: [testStep]
2020-08-12 20:49:17 [restartedMain] WARN c.k.m.s.batch.config.TestJobConfig - requestDate=>32
2020-08-12 20:49:17 [restartedMain] INFO o.s.batch.core.step.AbstractStep - Step: [testStep] executed in 34ms
2020-08-12 20:49:17 [restartedMain] WARN c.k.m.s.batch.config.TestJobConfig - Job End !!!
2020-08-12 20:49:17 [restartedMain] INFO o.s.b.c.l.support.SimpleJobLauncher - Job: [SimpleJob: [name=testJob]] completed with the following parameters: [{requestDate=32}] and the following status: [COMPLETED] in 100ms
// Job 종료
// 스프링 프로세스 종료
2020-08-12 20:50:16 [SpringContextShutdownHook] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'default'
2020-08-12 20:50:16 [SpringContextShutdownHook] INFO o.s.s.c.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'
2020-08-12 20:50:16 [SpringContextShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2020-08-12 20:50:16 [SpringContextShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
Disconnected from the target VM, address: '127.0.0.1:60141', transport: 'socket'
Process finished with exit code 1
</code></pre></div></div>
<p>처음에는 Spring batch 쪽 이슈로 생각되었으나 공식 Github Issue에 올라온 내용에 따르면 Spring boot(jpa) 쪽 이슈라고 합니다.</p>
<p>원인은 Spring boot의 빠른 초기 기동을 위해 적용된 jpa의 <code class="language-plaintext highlighter-rouge">bootstrap-mode</code> 이슈로 <code class="language-plaintext highlighter-rouge">application.yml</code>에 다음과 같이 설정하여 해결 할 수 있습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">data.jpa.repositories.bootstrap-mode</span><span class="pi">:</span> <span class="s">default</span>
</code></pre></div></div>
<p>위 기능은 초기화에 많은 시간을 잡아먹는 repository들의 초기화를 늦춰 boot의 기동시간을 단축 시키는 모드인데, 2.3.2 기준으로 기본값은 <code class="language-plaintext highlighter-rouge">deferred</code>로 지연로딩되고 백그라운드 스레드에 의해 특정 시점에 초기화 됩니다.
더 자세한 설명은 <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.bootstrap-mode">Spring Data JPA - Reference Documentation - Bootstrap Mode</a>를 참고하세요.</p>
<p>(직접 디버깅을 하진 못했으나)가설을 세워보면 스프링 프로세스가 기동되면서 백그라운드 스레드는 특정 이벤트가 발생하는 시점까지 대기 하고 있는데, 배치의 Job이 먼저 끝나버리고 초기화가 끝나지 않은 스프링은 초기화를 마친 후 프로세스를 다시 종료한 것으로 유추되어 Job 실행을 1분이상 걸리도록 수정한 후 다시 테스트 하였습니다.
테스트 결과, 아래와 같이 Job이 종료된 이후 스프링 프로세스도 바로 종료 됨을 확인 할 수 있었습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2020-08-12 21:26:37 [restartedMain] INFO o.s.b.c.l.support.SimpleJobLauncher - Job: [SimpleJob: [name=testJob]] launched with the following parameters: [{requestDate=31}]
// Job 시작
2020-08-12 21:26:37 [restartedMain] WARN c.k.m.s.batch.config.TestJobConfig - Job Start !!!
2020-08-12 21:26:37 [restartedMain] INFO o.s.batch.core.job.SimpleStepHandler - Executing step: [testStep]
2020-08-12 21:26:37 [restartedMain] WARN c.k.m.s.batch.config.TestJobConfig - requestDate=>31
// 2분여간 대기후 종료
2020-08-12 21:28:37 [restartedMain] INFO o.s.batch.core.step.AbstractStep - Step: [testStep] executed in 2m0s53ms
2020-08-12 21:28:37 [restartedMain] WARN c.k.m.s.batch.config.TestJobConfig - Job End !!!
2020-08-12 21:28:37 [restartedMain] INFO o.s.b.c.l.support.SimpleJobLauncher - Job: [SimpleJob: [name=testJob]] completed with the following parameters: [{requestDate=31}] and the following status: [COMPLETED] in 2m0s121ms
// 스프링 프로세스 종료
2020-08-12 21:28:37 [SpringContextShutdownHook] INFO o.s.o.j.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'default'
2020-08-12 21:28:37 [SpringContextShutdownHook] INFO o.s.s.c.ThreadPoolTaskExecutor - Shutting down ExecutorService 'applicationTaskExecutor'
2020-08-12 21:28:37 [SpringContextShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
2020-08-12 21:28:37 [SpringContextShutdownHook] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
Disconnected from the target VM, address: '127.0.0.1:60519', transport: 'socket'
Process finished with exit code 0
</code></pre></div></div>
<h3 id="참고">참고</h3>
<p><a href="https://github.com/spring-projects/spring-batch/issues/3725">Spring Batch - Spring Boot batch job does not shut down automatically after completion when using JPA</a>
<a href="https://github.com/spring-projects/spring-boot/issues/22092">Spring Boot - Spring Boot batch job does not shut down automatically after completion when using JPA</a></p>
java.util.ConcurrentModificationException null (with ehcache)
2019-11-24T00:00:00+00:00
2019-11-24T00:00:00+00:00
https://github.com/jistol/java/2019/11/24/concurrentmodificationexception-with-ehcache
<h2 id="triggering-a-concurrentmodificationexception">Triggering a ConcurrentModificationException</h2>
<p>병렬 프로그래밍을 하다 보면 만나게 되는 흔한 오류 중 하나가 <code class="language-plaintext highlighter-rouge">java.util.ConcurrentModificationException</code>입니다. thread-safe 하지 않은 ArrayList 같은 객체를 여러 스레드에서 동시에 조작하다 보면 발생하게 되는데 주된 원인은 for-loop 도중 조작할 경우 입니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// java</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">intList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Integer</span> <span class="n">i</span> <span class="o">:</span> <span class="n">intList</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="c1">// class</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">intList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="nc">Iterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">iter</span> <span class="o">=</span> <span class="n">intList</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
<span class="k">while</span><span class="o">(</span><span class="n">iter</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
<span class="nc">Integer</span> <span class="n">i</span> <span class="o">=</span> <span class="n">iter</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>for-loop 구문을 class 컴파일 하면 위와 같은 코드로 변환되는데 ArrayList가 Iterator를 생성할 때 내부 클래스인 ArrayList.Itr 클래스로 생성하여 반환되며 Itr 클래스는 생성시 ArrayList의 상위클래스인 AbstractList의 modCount 변수를 자신의 지역변수인 expectedModCount에 할당합니다. modCount는 ArrayList의 변경사항이 생길 때 마다 변경된 카운트를 기록하는 변수로 add / remove / trimToSize 등등 다수의 메서드에서 증가됩니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">class</span> <span class="nc">Itr</span> <span class="kd">implements</span> <span class="nc">Iterator</span><span class="o"><</span><span class="no">E</span><span class="o">></span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">cursor</span><span class="o">;</span> <span class="c1">// index of next element to return</span>
<span class="kt">int</span> <span class="n">lastRet</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span> <span class="c1">// index of last element returned; -1 if no such</span>
<span class="kt">int</span> <span class="n">expectedModCount</span> <span class="o">=</span> <span class="n">modCount</span><span class="o">;</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<p>expectedModCount 변수는 next() 메서드를 실행할 때 기존 자신이 참조하고 있는 ArrayList의 변경사항이 있는지 체크하게 되는데 이 때 참조 리스트의 modCount와 자신의 지역변수인 expectedModCount를 비교하여 다를 경우 <code class="language-plaintext highlighter-rouge">java.util.ConcurrentModificationException</code>를 발생시킵니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ArrayList.Itr class</span>
<span class="kd">public</span> <span class="no">E</span> <span class="nf">next</span><span class="o">()</span> <span class="o">{</span>
<span class="n">checkForComodification</span><span class="o">();</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="kd">final</span> <span class="kt">void</span> <span class="nf">checkForComodification</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">modCount</span> <span class="o">!=</span> <span class="n">expectedModCount</span><span class="o">)</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">ConcurrentModificationException</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>static 변수로 선언되어 여러 스레드에 조작되다 발생 할 수도 있으나 아래 예제와 같이 단일 스레드 내에서도 동일하게 발생 할 수 있습니다. 다른 블로그나 StackOverFlow의 질문들을 보면 대부분 remove 구문을 예시로 들지만 remove 뿐만 아닌 modCount가 조작되는 모든 메서드에 해당됩니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span><span class="o">(</span><span class="n">expected</span> <span class="o">=</span> <span class="nc">ConcurrentModificationException</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">concurrentModificationExceptionTest</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">list</span> <span class="o">=</span> <span class="nc">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">4</span><span class="o">,</span><span class="mi">5</span><span class="o">,</span><span class="mi">6</span><span class="o">,</span><span class="mi">7</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Integer</span> <span class="n">i</span> <span class="o">:</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">i</span><span class="o">==</span><span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">8</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>회피 하는 방법은 여러가지가 존재하는데 <a href="https://www.baeldung.com/java-concurrentmodificationexception">Avoiding the ConcurrentModificationException in Java</a>문서를 참고하시면 좋습니다.</p>
<h2 id="concurrentmodificationexception-with-ehcache">ConcurrentModificationException with Ehcache</h2>
<p>자신이 병렬 스레드를 사용하지 않았고, for-loop문 내에서 조작하지 않았다고 해서 <code class="language-plaintext highlighter-rouge">java.util.ConcurrentModificationException</code>이 발생하지 않을꺼라 생각할 수 있으나 in-memory cache를 사용할 때 역시 주의해야 합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// MyCacheService.class</span>
<span class="nd">@Cacheable</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">getCacheList</span><span class="o">()</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="c1">// OtherService</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">MyCacheService</span> <span class="n">myCacheService</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">sortList</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">cachedList</span> <span class="o">=</span> <span class="n">myCacheService</span><span class="o">.</span><span class="na">getCacheList</span><span class="o">();</span>
<span class="n">cachedList</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="nl">Integer:</span><span class="o">:</span><span class="n">compareTo</span><span class="o">);</span>
<span class="k">for</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">i</span> <span class="o">:</span> <span class="n">cachedList</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">i</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>얼핏 보기엔 sortList 메서드는 안전해 보이지만 Ehcache를 사용하고 있다면 부하 상황에서 반드시 <code class="language-plaintext highlighter-rouge">java.util.ConcurrentModificationException</code>이 발생하게 됩니다. for-loop 구문 안에서 조작하지 않았는데 발생하는 이유는 Ehcache가 데이터를 heap과 disk에 나눠 관리하기 때문입니다. 해당 데이터가 heap에 존재할 경우 캐시에서 동일한 레퍼런스 객체를 반환하기 때문에 static으로 공유된 객체를 사용하는 것과 동일한 이슈를 발생 시키게 되는데 아래 테스트케이스에서 볼 수 있듯이 캐시에서 받아온 List객체를 조작하게 되면 기존 캐시에 저장된 객체가 같이 바뀌어 있는것을 확인 할 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">cacheValueReferenceTest</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">final</span> <span class="nc">Cache</span> <span class="n">cache</span> <span class="o">=</span> <span class="n">getCache</span><span class="o">();</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"TEST_KEY"</span><span class="o">;</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="nc">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">(</span><span class="mi">7</span><span class="o">,</span><span class="mi">6</span><span class="o">,</span><span class="mi">5</span><span class="o">,</span><span class="mi">4</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">1</span><span class="o">);</span>
<span class="n">cache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="n">value</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="nl">Integer:</span><span class="o">:</span><span class="n">compareTo</span><span class="o">);</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">cacheValue</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">cacheValue</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">)</span> <span class="o">==</span> <span class="mi">1</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>동시성 문제가 발생하는 케이스는 아래 테스트케이스에서 확인 할 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">concurrentModifiedExceptionTest</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">InterruptedException</span> <span class="o">{</span>
<span class="kd">final</span> <span class="nc">Cache</span> <span class="n">cache</span> <span class="o">=</span> <span class="n">getCache</span><span class="o">();</span>
<span class="kd">final</span> <span class="nc">String</span> <span class="n">key</span> <span class="o">=</span> <span class="s">"TEST_KEY"</span><span class="o">;</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">value</span> <span class="o">=</span> <span class="nc">Lists</span><span class="o">.</span><span class="na">newArrayList</span><span class="o">(</span><span class="mi">7</span><span class="o">,</span><span class="mi">6</span><span class="o">,</span><span class="mi">5</span><span class="o">,</span><span class="mi">4</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">1</span><span class="o">);</span>
<span class="n">cache</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span>
<span class="nc">AtomicBoolean</span> <span class="n">isDetectException</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AtomicBoolean</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="nc">CountDownLatch</span> <span class="n">latch1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CountDownLatch</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="nc">CountDownLatch</span> <span class="n">latch2</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CountDownLatch</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="nc">Runnable</span> <span class="n">thread1</span> <span class="o">=</span> <span class="o">()</span> <span class="o">-></span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">value1</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">Integer</span> <span class="n">v</span> <span class="o">:</span> <span class="n">value1</span><span class="o">)</span> <span class="o">{</span>
<span class="n">latch2</span><span class="o">.</span><span class="na">await</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"thread1 error : {} {}"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getName</span><span class="o">(),</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
<span class="n">isDetectException</span><span class="o">.</span><span class="na">set</span><span class="o">(</span><span class="n">e</span> <span class="k">instanceof</span> <span class="nc">ConcurrentModificationException</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">latch1</span><span class="o">.</span><span class="na">countDown</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="nc">Runnable</span> <span class="n">thread2</span> <span class="o">=</span> <span class="o">()</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">value2</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="nc">List</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">value2</span><span class="o">.</span><span class="na">sort</span><span class="o">(</span><span class="nl">Integer:</span><span class="o">:</span><span class="n">compareTo</span><span class="o">);</span>
<span class="n">latch2</span><span class="o">.</span><span class="na">countDown</span><span class="o">();</span>
<span class="o">};</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="n">thread1</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="nc">Thread</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">100</span><span class="o">);</span>
<span class="k">new</span> <span class="nf">Thread</span><span class="o">(</span><span class="n">thread2</span><span class="o">).</span><span class="na">start</span><span class="o">();</span>
<span class="n">latch1</span><span class="o">.</span><span class="na">await</span><span class="o">();</span>
<span class="n">assertTrue</span><span class="o">(</span><span class="n">isDetectException</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>1번 스레드가 캐시에서 데이터를 가져와 for-loop 지점에 진입한 상태에서 2번 스레드가 캐시값에 접근하여 변경(sort와 같은..)작업을 시도하면 내부적으로 ArrayList의 modCount값이 바뀌면서 1번 스레드의 Iterator.next 구문 실행시 <code class="language-plaintext highlighter-rouge">java.util.ConcurrentModificationException</code>를 발생시키게 됩니다.</p>
<h2 id="conclusion">Conclusion</h2>
<p>동시성 문제는 평시엔 잘 발견되지 않기 때문에 일반적인 테스트만 진행하고 실 서비스에 배포되면 운영중 가장 중요한 시기에 장애가 터지게 되어 난감할 때가 많습니다. 매번 성능 테스트를 할 수 없다면 위와 같은 경우에 항상 주의하며 코딩해야하며, 필자는 Ehcache의 프로세스를 확인하지 않아 디버깅에 오래걸렸지만 <code class="language-plaintext highlighter-rouge">java.util.ConcurrentModificationException</code>의 경우 반드시 for-loop 안에서 타켓 리스트객체를 수정했거나, 공유되는 리스트객체를 동시에 조작한 두가지 경우에 발생함을 유의하고 빠르게 디버깅 하시기 바랍니다.</p>
<h2 id="reference">Reference</h2>
<p>Avoiding the ConcurrentModificationException in Java : <a href="https://www.baeldung.com/java-concurrentmodificationexception">https://www.baeldung.com/java-concurrentmodificationexception</a></p>
Spliterator의 소개 및 활용 (in JDK 1.8)
2019-11-17T00:00:00+00:00
2019-11-17T00:00:00+00:00
https://github.com/jistol/java/2019/11/17/spliterator
<h2 id="overview">Overview</h2>
<p>Spliterator 인터페이스를 접한건 Stream을 복제할 수 있는 방법이 없을까 고민하다 포함된 메서드들 중 spliterator()는 어떤건지 궁금해 찾아보게 됬습니다. <br />
처음에 여기저기 블로그에 정리된 글을 찾아봤을땐 병렬처리를 지원하기 위해 Stream에 종속된 기능 정도로만 생각했는데, Oracle 공식 JDK 문서에는 특정 소스의 객체를 순회하거나 파티셔닝 하기 위한 인터페이스로 Stream의 파이프라이닝 처리를 위해 <code class="language-plaintext highlighter-rouge">java.util.stream.Sink</code> 클래스와 함께 핵심적인 역활을 맡고 있습니다.
Stream외에도 배열/Collection/IOChannel 등 복수개의 개체를 지닌 모든 소스들에서 구현되거나 사용 할 수 있으며 생성 방법은 아래와 같습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Stream과 Collection의 경우 기본적으로 spliterator 메서드를 제공합니다. </span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">byStream</span> <span class="o">=</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">range</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">100</span><span class="o">).</span><span class="na">spliterator</span><span class="o">();</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">byCollection</span> <span class="o">=</span> <span class="o">(</span><span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">Integer</span><span class="o">>()).</span><span class="na">spliterator</span><span class="o">();</span>
<span class="c1">// 배열의 경우 Spliterators 클래스를 통해 Spliterator 객체를 생성 할 수 있습니다.</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">byArray</span> <span class="o">=</span> <span class="nc">Spliterators</span><span class="o">.</span><span class="na">spliterator</span><span class="o">(</span><span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">4</span><span class="o">},</span> <span class="nc">Spliterator</span><span class="o">.</span><span class="na">SORTED</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="methods">Methods</h2>
<h2 id="characteristics">characteristics</h2>
<p>Spliterator 객체의 특성에 대한 int값을 반환하는 메서드로 속성은 ORDERED, DISTINCT, SORTED, SIZED, NONNULL, IMMUTABLE, CONCURRENT, SUBSIZED 가 있으며 각 특성은 어떤 Spliterator 객체인가에 따라 다르며 그에 따른 각 메서드들의 내부적인 동작이 다를 수 있습니다. <br />
예를 <code class="language-plaintext highlighter-rouge">IntStream.of</code>의 Spliterator의 경우 “IMMUTABLE, ORDERED, SIZED, SUBSIZED”의 특성을 가지고 있으나 <code class="language-plaintext highlighter-rouge">IntStream.generate</code>의 Spliterator의 경우 “IMMUTABLE”의 특성만을 지니고 있습니다. 또한 <code class="language-plaintext highlighter-rouge">Set</code>의 Spliterator의 경우 “DISTINCT, SIZED”의 특성을 가지고 있으며 <code class="language-plaintext highlighter-rouge">List</code>의 Spliterator의 경우 “ORDERED, SIZED, SUBSIZED”의 특성을 가집니다.</p>
<p><code class="language-plaintext highlighter-rouge">characteristics</code>메서드의 반환값은 int형인데 Spliterator 객체에 포함된 모든 특성값의 합을 반환합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Set의 경우 DISTINCT=1, SIZED=64 의 합인 65를 반환합니다.</span>
<span class="k">new</span> <span class="nf">HashSet</span><span class="o">().</span><span class="na">spliterator</span><span class="o">().</span><span class="na">characteristics</span><span class="o">();</span> <span class="c1">// 65</span>
<span class="c1">// List의 경우 ORDERED=16, SIZED=64, SUBSIZED=16384 의 합인 16464를 반환합니다.</span>
<span class="k">new</span> <span class="nf">ArrayList</span><span class="o">().</span><span class="na">spliterator</span><span class="o">().</span><span class="na">characteristics</span><span class="o">();</span> <span class="c1">// 16464</span>
</code></pre></div></div>
<p>Spliterator의 각 특성은 <code class="language-plaintext highlighter-rouge">hasCharacteristics</code>메서드를 통해 확인 할 수 있습니다.</p>
<h2 id="estimatesize">estimateSize</h2>
<p>순회할 개체의 사이즈를 알 수 있는 메서드로 SIZED/SUBSIZED 특성을 지녔으며 순회할 남은 개체의 사이즈를 반환해줍니다. 쉽게 말해 개수가 제한되어 있는 Stream의 사이즈를 알 수 있습니다. <br />
Spliterator의 총 개체수가 아닌 순회 할 수 있는 개체의 개수이기 때문에 이미 순회한 개체의 수는 포함되지 않습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">estimateSize_test</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">spliterator</span> <span class="o">=</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">4</span><span class="o">,</span><span class="mi">5</span><span class="o">).</span><span class="na">spliterator</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">spliterator</span><span class="o">.</span><span class="na">estimateSize</span><span class="o">());</span> <span class="c1">// print 5</span>
<span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="n">t</span> <span class="o">-></span> <span class="o">{});</span>
<span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="n">t</span> <span class="o">-></span> <span class="o">{});</span>
<span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="n">t</span> <span class="o">-></span> <span class="o">{});</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">spliterator</span><span class="o">.</span><span class="na">estimateSize</span><span class="o">());</span> <span class="c1">// print 2</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="tryadvance">tryAdvance</h2>
<p>Spliterator의 요소가 남아 있을 경우 인자로 주어진 액션을 실행하고 요소의 존재 유무를 반환하는데, 특성에 ORDERED가 포함되어 있을 경우 순차적으로 요소를 제공하게 됩니다. <br />
이 메서드의 주의할 점은 리턴값이 hasNext의 개념이 아니라 isExecuted의 개념이란 점입니다. 아래 예제를 보면 요소가 3개이나 3번째 호출까지 계속 true를 반환하는 것을 볼 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">tryAdvance_test</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">spliterator</span> <span class="o">=</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">).</span><span class="na">spliterator</span><span class="o">();</span>
<span class="kt">boolean</span> <span class="n">r1</span> <span class="o">=</span> <span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span> <span class="c1">// print 1</span>
<span class="kt">boolean</span> <span class="n">r2</span> <span class="o">=</span> <span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span> <span class="c1">// print 2</span>
<span class="kt">boolean</span> <span class="n">r3</span> <span class="o">=</span> <span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span> <span class="c1">// print 3</span>
<span class="kt">boolean</span> <span class="n">r4</span> <span class="o">=</span> <span class="n">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">println</span><span class="o">);</span> <span class="c1">// not execute</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">r1</span> <span class="o">+</span> <span class="s">", "</span> <span class="o">+</span> <span class="n">r2</span> <span class="o">+</span> <span class="s">", "</span> <span class="o">+</span> <span class="n">r3</span> <span class="o">+</span> <span class="s">", "</span> <span class="o">+</span> <span class="n">r4</span><span class="o">);</span> <span class="c1">// true, true, true, false</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Spliterator 인터페이스에선 본 메서드를 사용하여 순회하는 <code class="language-plaintext highlighter-rouge">forEachRemaining</code> 메서드를 기본적으로 제공하며 코드는 아래와 같습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">default</span> <span class="kt">void</span> <span class="nf">forEachRemaining</span><span class="o">(</span><span class="nc">Consumer</span><span class="o"><?</span> <span class="kd">super</span> <span class="no">T</span><span class="o">></span> <span class="n">action</span><span class="o">)</span> <span class="o">{</span>
<span class="k">do</span> <span class="o">{</span> <span class="o">}</span> <span class="k">while</span> <span class="o">(</span><span class="n">tryAdvance</span><span class="o">(</span><span class="n">action</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>구현 메서드가 위와 같이 hasNext / isExecuted의 양쪽 개념을 다 포괄 할 수 있기 때문에 어느쪽이든 크게 상관없다 싶지만 가능하면 isExecuted 개념으로 구현하는 것을 권장합니다.</p>
<h2 id="trysplit">trySplit</h2>
<p>Spliterator를 분할 하는 메서드로 원 객체에서 분할된 Spliterator를 반환하는데 ORDERED 특성일 경우 반환된 값이 앞부분이 되고 원 객체가 뒷부분이 됩니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">trySplit_test</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">origin</span> <span class="o">=</span> <span class="nc">IntStream</span><span class="o">.</span><span class="na">range</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">19</span><span class="o">).</span><span class="na">spliterator</span><span class="o">();</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">dest</span> <span class="o">=</span> <span class="n">origin</span><span class="o">.</span><span class="na">trySplit</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">dest</span><span class="o">.</span><span class="na">estimateSize</span><span class="o">());</span> <span class="c1">// size is 9</span>
<span class="n">dest</span><span class="o">.</span><span class="na">forEachRemaining</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">print</span><span class="o">);</span> <span class="c1">// print 0~8</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">();</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">origin</span><span class="o">.</span><span class="na">estimateSize</span><span class="o">());</span> <span class="c1">// size is 10</span>
<span class="n">origin</span><span class="o">.</span><span class="na">forEachRemaining</span><span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">::</span><span class="n">print</span><span class="o">);</span> <span class="c1">// print 9 ~ 18</span>
<span class="o">}</span>
</code></pre></div></div>
<p>아쉽게도 분할할 사이즈나 범위는 제어 할 수 없도록 되어 있으며 절반으로 나뉘게 됩니다. 일반적인 Stream의 경우 parallel일 경우에만 분할이 가능하며 아닐 경우 분할한 Spliterator는 null로 반환합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// java.util.stream.StreamSpliterators.AbstractWrappingSpliterator</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Spliterator</span><span class="o"><</span><span class="no">P_OUT</span><span class="o">></span> <span class="nf">trySplit</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isParallel</span> <span class="o">&&</span> <span class="o">!</span><span class="n">finished</span><span class="o">)</span> <span class="o">{</span>
<span class="n">init</span><span class="o">();</span>
<span class="nc">Spliterator</span><span class="o"><</span><span class="no">P_IN</span><span class="o">></span> <span class="n">split</span> <span class="o">=</span> <span class="n">spliterator</span><span class="o">.</span><span class="na">trySplit</span><span class="o">();</span>
<span class="k">return</span> <span class="o">(</span><span class="n">split</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="n">wrap</span><span class="o">(</span><span class="n">split</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">else</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="customize">Customize</h2>
<p>Stream을 분할 할 수도 있고, size를 미리 알수 있으며, 흐름에 따라 일괄 처리되던 Stream을 단 건으로 실행 할 수 있는 등 이미 그 자체로도 충분히 활용할 곳이 많지만 Spliterator 자체를 커스텀하여 만들 경우 좀 더 다양하게 활용 가능합니다. <br />
다음 Spliterator는 카운트를 지정하여 해당 카운트 만큼 Stream이 실행되면 pinned 메소드에 등록한 리스너를 실행합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">PinPointSpliterator</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="kd">implements</span> <span class="nc">Spliterator</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">Spliterator</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">spliterator</span><span class="o">;</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">AtomicInteger</span> <span class="n">counter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AtomicInteger</span><span class="o">();</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">pinCount</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">IntFunction</span><span class="o"><</span><span class="nc">Boolean</span><span class="o">></span> <span class="n">listener</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">isListen</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">PinPointSpliterator</span><span class="o">(</span><span class="nc">Spliterator</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">spliterator</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">spliterator</span> <span class="o">=</span> <span class="n">spliterator</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nf">PinPointSpliterator</span><span class="o">(</span><span class="nc">Stream</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">stream</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">(</span><span class="n">stream</span><span class="o">.</span><span class="na">spliterator</span><span class="o">());</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nc">PinPointSpliterator</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nf">newInstance</span><span class="o">(</span><span class="nc">Stream</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="n">stream</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nc">PinPointSpliterator</span><span class="o"><>(</span><span class="n">stream</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">PinPointSpliterator</span> <span class="nf">pinCount</span><span class="o">(</span><span class="kt">int</span> <span class="n">pinCount</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">pinCount</span> <span class="o">=</span> <span class="n">pinCount</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">PinPointSpliterator</span> <span class="nf">pinned</span><span class="o">(</span><span class="nc">IntFunction</span><span class="o"><</span><span class="nc">Boolean</span><span class="o">></span> <span class="n">listener</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">isListen</span> <span class="o">=</span> <span class="n">listener</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">listener</span> <span class="o">=</span> <span class="n">listener</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">tryAdvance</span><span class="o">(</span><span class="nc">Consumer</span><span class="o"><?</span> <span class="kd">super</span> <span class="no">T</span><span class="o">></span> <span class="n">action</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">boolean</span> <span class="n">hasNext</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">spliterator</span><span class="o">.</span><span class="na">tryAdvance</span><span class="o">(</span><span class="n">action</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isListen</span> <span class="o">&&</span> <span class="n">pinCount</span> <span class="o">></span> <span class="mi">0</span> <span class="o">&&</span> <span class="n">counter</span><span class="o">.</span><span class="na">incrementAndGet</span><span class="o">()</span> <span class="o">%</span> <span class="n">pinCount</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">isListen</span> <span class="o">=</span> <span class="n">listener</span><span class="o">.</span><span class="na">apply</span><span class="o">(</span><span class="n">counter</span><span class="o">.</span><span class="na">get</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">hasNext</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Spliterator</span><span class="o"><</span><span class="no">T</span><span class="o">></span> <span class="nf">trySplit</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">PinPointSpliterator</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">spliterator</span><span class="o">.</span><span class="na">trySplit</span><span class="o">());</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">long</span> <span class="nf">estimateSize</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">spliterator</span><span class="o">.</span><span class="na">estimateSize</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">characteristics</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">spliterator</span><span class="o">.</span><span class="na">characteristics</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>실행은 아래와 같이 할 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">pinPointSpliterator_test</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><>();</span>
<span class="nc">PinPointSpliterator</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">spliterator</span> <span class="o">=</span> <span class="nc">PinPointSpliterator</span><span class="o">.</span><span class="na">newInstance</span><span class="o">(</span><span class="nc">IntStream</span><span class="o">.</span><span class="na">range</span><span class="o">(</span><span class="mi">0</span><span class="o">,</span> <span class="mi">1000</span><span class="o">).</span><span class="na">boxed</span><span class="o">())</span>
<span class="o">.</span><span class="na">pinCount</span><span class="o">(</span><span class="mi">100</span><span class="o">)</span>
<span class="o">.</span><span class="na">pinned</span><span class="o">(</span><span class="n">count</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"do listen. buffer size is "</span> <span class="o">+</span> <span class="n">buffer</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="n">buffer</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="k">return</span> <span class="n">count</span> <span class="o"><</span> <span class="mi">500</span><span class="o">;</span>
<span class="o">});</span>
<span class="nc">StreamSupport</span><span class="o">.</span><span class="na">stream</span><span class="o">(</span><span class="n">spliterator</span><span class="o">,</span> <span class="kc">false</span><span class="o">)</span>
<span class="o">.</span><span class="na">peek</span><span class="o">(</span><span class="n">i</span> <span class="o">-></span> <span class="n">buffer</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">i</span><span class="o">))</span>
<span class="o">.</span><span class="na">collect</span><span class="o">(</span><span class="nc">Collectors</span><span class="o">.</span><span class="na">toList</span><span class="o">());</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"final buffer size is "</span> <span class="o">+</span> <span class="n">buffer</span><span class="o">.</span><span class="na">size</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>위 예제에서 100회 실행시마다 pinned에 설정된 리스너를 실행하는데 buffer의 사이즈를 출력하고 초기화하며, 500회보다 적게 실행된 경우에만 동작하므로 500회 이후엔 모두 buffer에 쌓이므로 500개가 쌓이게 됩니다. 최종 출력은 다음과 같습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>do listen. buffer size is 100
do listen. buffer size is 100
do listen. buffer size is 100
do listen. buffer size is 100
do listen. buffer size is 100
final buffer size is 500
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>워낙 Stream에서 많은 기능을 제공하기에 잘 사용하지 않았었지만 Stream을 쓰다보면 무언가 조금씩 아쉬운 부분들이 존재했습니다. 예를 들어 1000개씩 분할해서 List로 담고 싶다던가, 특정 실행 횟수마다 어떤 동작을 한다던가, Stream의 전체 개수에 따라 다른 액션이 필요할 경우 등등 … 기존 Stream으로는 번거로운 작업들을 좀 더 쉽게 처리 할 수 있어 잘 쓰기만 한다면 많이 활용 할 수 있을것 같습니다.</p>
(SpringData) Spring Data Rest Repository Not Working
2019-10-13T00:00:00+00:00
2019-10-13T00:00:00+00:00
https://github.com/jistol/spring/2019/10/13/spring-data-rest-not-working
<h2 id="이슈">이슈</h2>
<p>오랜만에 SpringBoot로 간단한 REST API 서버를 만들어 볼 일이 생겨서 <a href="https://start.spring.io/">Spring Initializr</a>를 통해 아래 모듈을 추가하여 작업했습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.data:spring-data-rest-hal-browser'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
</code></pre></div></div>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">spring </span><span class="pi">:</span>
<span class="na">datasource </span><span class="pi">:</span>
<span class="na">url </span><span class="pi">:</span> <span class="s">jdbc:h2:mem:data</span>
<span class="na">driverClassName </span><span class="pi">:</span> <span class="s">org.h2.Driver</span>
<span class="na">username </span><span class="pi">:</span> <span class="s">sa</span>
<span class="na">password </span><span class="pi">:</span> <span class="m">1234</span>
<span class="na">jpa </span><span class="pi">:</span>
<span class="na">database-platform </span><span class="pi">:</span> <span class="s">org.hibernate.dialect.H2Dialect</span>
<span class="na">show-sql</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">generate-ddl</span><span class="pi">:</span> <span class="no">true</span>
<span class="s">h2.console</span> <span class="pi">:</span>
<span class="na">enabled </span><span class="pi">:</span> <span class="no">true</span>
<span class="na">path </span><span class="pi">:</span> <span class="s">/h2-console</span>
<span class="na">settings </span><span class="pi">:</span>
<span class="na">trace </span><span class="pi">:</span> <span class="no">false</span>
<span class="na">web-allow-others </span><span class="pi">:</span> <span class="no">false</span>
<span class="na">data</span><span class="pi">:</span>
<span class="na">rest</span><span class="pi">:</span>
<span class="na">base-path</span><span class="pi">:</span> <span class="s">/api</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Data</span>
<span class="nd">@Entity</span>
<span class="nd">@Table</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"article"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Article</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span>
<span class="nd">@Id</span>
<span class="nd">@GeneratedValue</span>
<span class="kd">private</span> <span class="kt">long</span> <span class="n">articleNo</span><span class="o">;</span>
<span class="nd">@NotNull</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">title</span><span class="o">;</span>
<span class="nd">@Lob</span>
<span class="nd">@NotNull</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">content</span><span class="o">;</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="nd">@RepositoryRestResource</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ArticleDao</span> <span class="kd">extends</span> <span class="nc">CrudRepository</span><span class="o"><</span><span class="nc">Article</span><span class="o">,</span> <span class="nc">Long</span><span class="o">></span> <span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">Article</span><span class="o">></span> <span class="nf">findById</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"articleNo"</span><span class="o">)</span> <span class="kt">long</span> <span class="n">articleNo</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Configuration</span>
<span class="nd">@EnableJpaRepositories</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JpaConfig</span> <span class="o">{</span>
<span class="o">}</span>
<span class="nd">@SpringBootApplication</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DemoApplication</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">SpringApplication</span><span class="o">.</span><span class="na">run</span><span class="o">(</span><span class="nc">DemoApplication</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">args</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>워낙에 spring-data-rest는 별도 설정할게 없는지라 잘 되겠지 하고 bootRun!! 1.5.X 버전때는 맵핑되는 Request 주소들이 기동시 로그에 다 찍혔는데 2.x 버전에서는 기본적으로 찍히지가 않았습니다. (그냥 그려려니…)</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> . ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.9.RELEASE)
2019-10-13 23:54:49.092 INFO 9085 --- [ restartedMain] com.example.demo.DemoApplication : Starting DemoApplication on documents.ct.infn.it with PID 9085 (/Users/jistol/Downloads/demo/out/production/classes started by jistol in /Users/jistol/Downloads/demo)
2019-10-13 23:54:49.096 INFO 9085 --- [ restartedMain] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-10-13 23:54:49.161 INFO 9085 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : Devtools property defaults active! Set 'spring.devtools.add-properties' to 'false' to disable
2019-10-13 23:54:49.161 INFO 9085 --- [ restartedMain] .e.DevToolsPropertyDefaultsPostProcessor : For additional web related logging consider setting the 'logging.level.web' property to 'DEBUG'
2019-10-13 23:54:49.979 INFO 9085 --- [ restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2019-10-13 23:54:50.006 INFO 9085 --- [ restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 8ms. Found 0 repository interfaces.
2019-10-13 23:54:50.570 INFO 9085 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$3c00740f] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-10-13 23:54:50.592 INFO 9085 --- [ restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.hateoas.config.HateoasConfiguration' of type [org.springframework.hateoas.config.HateoasConfiguration$$EnhancerBySpringCGLIB$$bb80c141] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2019-10-13 23:54:51.019 INFO 9085 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8099 (http)
2019-10-13 23:54:51.043 INFO 9085 --- [ restartedMain] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-10-13 23:54:51.043 INFO 9085 --- [ restartedMain] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.26]
2019-10-13 23:54:51.135 INFO 9085 --- [ restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-10-13 23:54:51.136 INFO 9085 --- [ restartedMain] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1975 ms
2019-10-13 23:54:51.436 INFO 9085 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-10-13 23:54:51.702 INFO 9085 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-10-13 23:54:51.779 INFO 9085 --- [ restartedMain] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [
name: default
...]
2019-10-13 23:54:51.890 INFO 9085 --- [ restartedMain] org.hibernate.Version : HHH000412: Hibernate Core {5.3.12.Final}
2019-10-13 23:54:51.892 INFO 9085 --- [ restartedMain] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found
2019-10-13 23:54:52.125 INFO 9085 --- [ restartedMain] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
2019-10-13 23:54:52.323 INFO 9085 --- [ restartedMain] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate: drop table article if exists
Hibernate: drop table post if exists
Hibernate: drop sequence if exists hibernate_sequence
Hibernate: create sequence hibernate_sequence start with 1 increment by 1
Hibernate: create table article (article_no bigint not null, content clob not null, created_by varchar(255) not null, created_date timestamp not null, title varchar(255) not null, update_by varchar(255) not null, updated_date timestamp not null, primary key (article_no))
Hibernate: create table post (post_no bigint not null, title varchar(255) not null, primary key (post_no))
2019-10-13 23:54:53.233 INFO 9085 --- [ restartedMain] o.h.t.schema.internal.SchemaCreatorImpl : HHH000476: Executing import script 'org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@7f47a56f'
2019-10-13 23:54:53.237 INFO 9085 --- [ restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2019-10-13 23:54:53.319 INFO 9085 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729
2019-10-13 23:54:54.058 INFO 9085 --- [ restartedMain] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-10-13 23:54:54.120 WARN 9085 --- [ restartedMain] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2019-10-13 23:54:54.584 INFO 9085 --- [ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8099 (http) with context path ''
2019-10-13 23:54:54.589 INFO 9085 --- [ restartedMain] com.example.demo.DemoApplication : Started DemoApplication in 6.029 seconds (JVM running for 7.371)
</code></pre></div></div>
<p>그래도 H2 DB에 알아서 테이블이 잘 생성됬길래 Entity랑 Repository 설정은 다 잘 붙었나보다 하며 만들어진 API를 보기 위해 Hal-Browser로 접속했는데 profile 외에 아무것도 생성되지 않았습니다.</p>
<p><img src="/assets/img/java/spring-data-rest-not-working/1.png" alt="not working REST Repository" /></p>
<h2 id="해결">해결</h2>
<p>원인은 패키지 구조에서 찾았는데 Configuration을 선언해둔 JpaConfig의 위치가 이슈였습니다.</p>
<p><img src="/assets/img/java/spring-data-rest-not-working/2.png" alt="wrong position" /></p>
<p>Entity 클래스는 com.example.demo.entity 하위에 위치하는데 <code class="language-plaintext highlighter-rouge">@EnableJpaRepositories</code>을 선언한 설정 클래스는 com.example.demo.config 하위에 위치해 있었던 거죠.
JPA 설정은 알아서 잘 찾길래 딱히 basePackage 위치도 지정하지 않았는데 Rest Repository 설정엔 영향을 끼치는 것 같습니다. 사실 위 설정 클래스 자체가 없어도 <code class="language-plaintext highlighter-rouge">@SpringBootApplication</code>가 설정된 클래스 하위에 위치하면 알아서 스캔하기 때문에 JPA가 잘 동작하는데 말이죠.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* 그냥 날려 버리거나 basePackages 설정을 추가합니다.
@Configuration
@EnableJpaRepositories(basePackages = "com.example.demo")
public class JpaConfig {
}
*/</span>
</code></pre></div></div>
<p><img src="/assets/img/java/spring-data-rest-not-working/3.png" alt="resolve issue" /></p>
<p>위와 같이 Entity들의 CRUD Request Path가 잘 생성 된 것을 확인 할 수 있습니다.</p>
(ES6) Canvas + Javascript로 웹 게임 만들기 - 이벤트 만들기
2019-09-29T00:00:00+00:00
2019-09-29T00:00:00+00:00
https://github.com/jistol/frontend/2019/09/29/create-webgame-3
<p>비게임 업종 서버개발자가 HTML5의 <code class="language-plaintext highlighter-rouge"><canvas></code>와 Javascript(ES6)를 이용하여 취미로 개발해 본 웹게임에 대한 글로 개발 코드 자체에 대한 설명보다는 어떤 원리와 방식으로 개발하였는지에 대한 내용을 기술하고 있습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">순서</th>
<th style="text-align: center">제목</th>
<th style="text-align: left">링크</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">1</td>
<td style="text-align: center">캐릭터 그리기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/25/create-webgame-1/">https://jistol.github.io/frontend/2019/09/25/create-webgame-1/</a></td>
</tr>
<tr>
<td style="text-align: center">2</td>
<td style="text-align: center">캐릭터 움직이기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/28/create-webgame-2/">https://jistol.github.io/frontend/2019/09/28/create-webgame-2/</a></td>
</tr>
<tr>
<td style="text-align: center">3</td>
<td style="text-align: center">이벤트 만들기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/29/create-webgame-3/">https://jistol.github.io/frontend/2019/09/29/create-webgame-3/</a></td>
</tr>
</tbody>
</table>
<p>게임명은 “도마뱀플라이트”로 앱게임 “드래곤플라이트”를 모방하여 일부 기능을 따라 구현하였으며 실제 게임은 아래 링크에서 실행 해 볼 수 있습니다.</p>
<h3 id="game--httpsjistolgithubiolizard">GAME : <a href="https://jistol.github.io/lizard/">https://jistol.github.io/lizard/</a></h3>
<h3 id="source--httpsgithubcomjistollizard-flight">SOURCE : <a href="https://github.com/jistol/lizard-flight">https://github.com/jistol/lizard-flight</a></h3>
<p><img src="/assets/img/frontend/create-webgame/1.jpg" alt="game capture" /></p>
<h2 id="키-이벤트-만들기">키 이벤트 만들기</h2>
<p>캐릭터를 그렸으니 조정하는 방법을 추가 하도록 합니다. 전 포스팅에서도 언급했듯이 Worker내에서는 직접 DOM을 접근 할 수가 없습니다. 따라서 메인 페이지에서 키 이벤트를 받아 Worker에게 전달해야하는데, 이 때도 역시 <code class="language-plaintext highlighter-rouge">postMessage</code>를 사용하게 됩니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main page</span>
<span class="kd">const</span> <span class="nx">worker</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Worker</span><span class="p">(</span><span class="dl">'</span><span class="s1">worker.js</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">onKeyEvent</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">worker</span><span class="p">.</span><span class="nx">postMessage</span><span class="p">({</span> <span class="na">key</span> <span class="p">:</span> <span class="nx">event</span><span class="p">.</span><span class="nx">key</span> <span class="p">});</span>
<span class="p">};</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">keydown</span><span class="dl">'</span><span class="p">,</span> <span class="nx">onKeyEvent</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
<span class="c1">// worker.js</span>
<span class="nb">self</span><span class="p">.</span><span class="nx">onmessage</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">key</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>이전 코드와 달라진 부분은 keydown 이벤트시 움직이는 방향으로 원을 이동시키고 keyup 이벤트시 움직임을 멈추도록 direct라는 변수를 추가하였으며 화면밖으로 원이 나가지 않도록 x의 크기를 제어하는 부분입니다.</p>
<script async="" src="//jsfiddle.net/jistol/e74axmon/48/embed/js,html,result/dark/"></script>
<h2 id="터치-이벤트-만들기">터치 이벤트 만들기</h2>
<p>요즘 대부분의 접속 환경이 모바일인 만큼 터치 이벤트를 이용한 움직임도 처리해보도록 하겠습니다. 키의 경우 명확하게 어떤 키를 누르고 땠는지 확인이 가능하나 터치는 같은 이벤트로 다른 처리를 해야하기에 키 이벤트와는 다르게 코딩 되어야 합니다. <br />
터치 이벤트는 대표적으로 아래와 같이 존재합니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">이벤트명</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">touchstart</td>
<td style="text-align: left">디바이스 화면에 손가락이 닿는 순간 발생하는 이벤트입니다.</td>
</tr>
<tr>
<td style="text-align: center">touchmove</td>
<td style="text-align: left">디바이스 화면에 손가락이 닿은 상태에서 손가락을 움직이면 발생하는 이벤트 입니다</td>
</tr>
<tr>
<td style="text-align: center">touchend</td>
<td style="text-align: left">디바이스 화면에 손가락이 닿은 상태에서 손가락을 떼어내면 발생하는 이벤트입니다.</td>
</tr>
<tr>
<td style="text-align: center">touchcancel</td>
<td style="text-align: left">터치 이벤트가 시스템으로 인해 취소 될 때 발생하는 이벤트입니다.</td>
</tr>
</tbody>
</table>
<p>좀 더 자세한 설명은 <a href="https://wit.nts-corp.com/2013/12/20/583">Javascript Mobile Events의 이해</a>를 참고하시기 바랍니다. <br />
다시 구현으로 돌아가서, 캐릭터를 이동 시키기 위해서는 두가지 선택지가 있습니다. 첫번째로는 터치된 포인트 지점으로 캐릭터를 바로 이동시키는 방법과 두번째는 터치 후 움직이는 방향으로 이동시키는 방법입니다. 본 코드에서는 두번째 방법을 이용해 구현했습니다.</p>
<script async="" src="//jsfiddle.net/jistol/e74axmon/54/embed/js,html,result/dark/"></script>
(ES6) Canvas + Javascript로 웹 게임 만들기 - 캐릭터 움직이기
2019-09-28T00:00:00+00:00
2019-09-28T00:00:00+00:00
https://github.com/jistol/frontend/2019/09/28/create-webgame-2
<p>비게임 업종 서버개발자가 HTML5의 <code class="language-plaintext highlighter-rouge"><canvas></code>와 Javascript(ES6)를 이용하여 취미로 개발해 본 웹게임에 대한 글로 개발 코드 자체에 대한 설명보다는 어떤 원리와 방식으로 개발하였는지에 대한 내용을 기술하고 있습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">순서</th>
<th style="text-align: center">제목</th>
<th style="text-align: left">링크</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">1</td>
<td style="text-align: center">캐릭터 그리기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/25/create-webgame-1/">https://jistol.github.io/frontend/2019/09/25/create-webgame-1/</a></td>
</tr>
<tr>
<td style="text-align: center">2</td>
<td style="text-align: center">캐릭터 움직이기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/28/create-webgame-2/">https://jistol.github.io/frontend/2019/09/28/create-webgame-2/</a></td>
</tr>
<tr>
<td style="text-align: center">3</td>
<td style="text-align: center">이벤트 만들기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/29/create-webgame-3/">https://jistol.github.io/frontend/2019/09/29/create-webgame-3/</a></td>
</tr>
</tbody>
</table>
<p>게임명은 “도마뱀플라이트”로 앱게임 “드래곤플라이트”를 모방하여 일부 기능을 따라 구현하였으며 실제 게임은 아래 링크에서 실행 해 볼 수 있습니다.</p>
<h3 id="game--httpsjistolgithubiolizard">GAME : <a href="https://jistol.github.io/lizard/">https://jistol.github.io/lizard/</a></h3>
<h3 id="source--httpsgithubcomjistollizard-flight">SOURCE : <a href="https://github.com/jistol/lizard-flight">https://github.com/jistol/lizard-flight</a></h3>
<p><img src="/assets/img/frontend/create-webgame/1.jpg" alt="game capture" /></p>
<h2 id="settimeout을-이용하여-캐릭터-움직이기">setTimeout을 이용하여 캐릭터 움직이기</h2>
<p>캐릭터를 움직이는 원리는 애니메이션의 원리와 같습니다. canvas에 캐릭터를 빠르게 다시 그려서 마치 위치가 바뀐것처럼 보이게 하는겁니다. <br />
본 코드에서는 전체화면을 지우고 다시 그리는데 화면이 끊기거나 속도가 느려지지 않을까 하는 우려와는 달리 빠르게 처리되고 크게 이질감도 없었습니다.</p>
<p>화면을 다시 그리도록 반복하는 방법은 두가지가 있는데 그 중 가장 쉬운 방법은 <code class="language-plaintext highlighter-rouge">setTimeout</code>(또는 <code class="language-plaintext highlighter-rouge">setInterval</code>)을 이용하는 방법입니다.</p>
<script async="" src="//jsfiddle.net/jistol/e74axmon/12/embed/js,html,result/dark/"></script>
<p>위와 같이 코딩시에는 단점이 있습니다. <code class="language-plaintext highlighter-rouge">setTimeout</code>의 경우 해당 시간에 특정 함수를 동작시킬뿐 화면 프레임을 전혀 고려하지 않습니다. <br />
브라우저가 화면을 그리기까지 몇 가지 단계가 있는데 그 단계를 모두 기다리지 못하거나 더 많은 텀을 가질 확률이 큽니다. <br />
더 자세한 설명은 <a href="https://fullest-sway.me/blog/2019/01/28/requestAnimationFrame/">requestAnimationFrame() 개념 정리하기</a>를 참고하세요.</p>
<h2 id="requestanimationframe을-이용하여-캐릭터-움직이기">requestAnimationFrame을 이용하여 캐릭터 움직이기</h2>
<p><code class="language-plaintext highlighter-rouge">requestAnimationFrame</code> 함수는 브라우저가 다음 리페인트를 수행하기 전에 해당 함수를 실행시켜 변경된 데이터로 그리도록 실행해주는 함수입니다. <br />
자세한 설명은 <a href="https://developer.mozilla.org/ko/docs/Web/API/Window/requestAnimationFrame">MDN - window.requestAnimationFrame()</a> 참고하시길 바라며 위 <code class="language-plaintext highlighter-rouge">setTimeout</code>을 이용한 코드에서 반복 부분만 <code class="language-plaintext highlighter-rouge">requestAnimationFrame</code>로 바꾼 코드입니다.</p>
<script async="" src="//jsfiddle.net/jistol/e74axmon/17/embed/js,html,result/dark/"></script>
<h2 id="web-worker를-이용하여-별도-스레드에서-그려보자">Web Worker를 이용하여 별도 스레드에서 그려보자</h2>
<p>JavaScript는 기본적으로 단일 스레드 기반으로 동작합니다. 동시에 작업이 이뤄지는듯 보이지만 빠르게 하나씩 처리하거나 시분할하여 자원을 나눠쓰게 됩니다. <br />
화면을 계속 갱신해야하는데 중간에 메인 스레드에 오래걸리는 작업을 실행하게 된다면 화면이 끊기거나 딜레이 될 수 있는데, 이 때 해결 할 수 있는 방법이 <code class="language-plaintext highlighter-rouge">Web Worker</code>를 이용하는 것입니다.</p>
<p><code class="language-plaintext highlighter-rouge">Web Worker</code>는 메인 스레드가 아닌 별도 스레드를 이용하여 동작하기 때문에 메인 스레드의 부하에 영향없이 동작 할 수 있는 장점이 있으나, DOM 개체에 직접 접근 제어 할 수 없으며 메인 스레드와 message 이벤트를 통해서만 통신이 가능한 단점이 존재합니다. 추가로 <code class="language-plaintext highlighter-rouge">new Worker()</code>사용시 기본적으로 <code class="language-plaintext highlighter-rouge">Dedicated Worker</code>를 사용하게 되는데 이 때 모듈방식을 사용 할 수 없으며 <code class="language-plaintext highlighter-rouge">import</code>, <code class="language-plaintext highlighter-rouge">export</code> 방식 대신 <code class="language-plaintext highlighter-rouge">importScripts()</code> 함수를 이용하여 추가 가능합니다. <br />
자세한 설명은 <a href="https://developer.mozilla.org/ko/docs/Web/API/Web_Workers_API/basic_usage">MDN - 웹 워커 사용하기</a>을 참고하세요.</p>
<p>Worker는 직접 DOM 제어를 할 수 없기 때문에 canvas에 렌더링 하기 위해서는 메인 스레드로 부터 canvas를 전달 받아야 합니다. <code class="language-plaintext highlighter-rouge">postMessage</code>로 전달 가능한데 이 때 전달 가능한 객체는 <code class="language-plaintext highlighter-rouge">Transferable</code>인터페이스를 구현한 객체만 가능하며 나머지 객체는 오류를 발생 시킵니다. 관련한 사항은 <a href="https://developer.mozilla.org/ko/docs/Web/API/Worker/postMessage">MDN - Worker.postMessage()
</a>와 <a href="https://developer.mozilla.org/ko/docs/Web/API/Transferable">MDN - Transferable</a>를 참고하세요. <br />
다행히도 canvas는 위 인터페이스를 구현한 객체를 제공하는데 <code class="language-plaintext highlighter-rouge">OffscreenCanvas</code>객체로 <code class="language-plaintext highlighter-rouge">HTMLCanvasElement.transferControlToOffscreen()</code>함수를 통해 얻을 수 있습니다. <br />
아직 모든 브라우저에서 제공하고 있진 않습니다.</p>
<script async="" src="//jsfiddle.net/jistol/e74axmon/30/embed/js,html,result/dark/"></script>
(ES6) Canvas + Javascript로 웹 게임 만들기 - 캐릭터 그리기
2019-09-25T00:00:00+00:00
2019-09-25T00:00:00+00:00
https://github.com/jistol/frontend/2019/09/25/create-webgame-1
<p>비게임 업종 서버개발자가 HTML5의 <code class="language-plaintext highlighter-rouge"><canvas></code>와 Javascript(ES6)를 이용하여 취미로 개발해 본 웹게임에 대한 글로 개발 코드 자체에 대한 설명보다는 어떤 원리와 방식으로 개발하였는지에 대한 내용을 기술하고 있습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">순서</th>
<th style="text-align: center">제목</th>
<th style="text-align: left">링크</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">1</td>
<td style="text-align: center">캐릭터 그리기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/25/create-webgame-1/">https://jistol.github.io/frontend/2019/09/25/create-webgame-1/</a></td>
</tr>
<tr>
<td style="text-align: center">2</td>
<td style="text-align: center">캐릭터 움직이기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/28/create-webgame-2/">https://jistol.github.io/frontend/2019/09/28/create-webgame-2/</a></td>
</tr>
<tr>
<td style="text-align: center">3</td>
<td style="text-align: center">이벤트 만들기</td>
<td style="text-align: left"><a href="https://jistol.github.io/frontend/2019/09/29/create-webgame-3/">https://jistol.github.io/frontend/2019/09/29/create-webgame-3/</a></td>
</tr>
</tbody>
</table>
<p>게임명은 “도마뱀플라이트”로 앱게임 “드래곤플라이트”를 모방하여 일부 기능을 따라 구현하였으며 실제 게임은 아래 링크에서 실행 해 볼 수 있습니다.</p>
<h3 id="game--httpsjistolgithubiolizard">GAME : <a href="https://jistol.github.io/lizard/">https://jistol.github.io/lizard/</a></h3>
<h3 id="source--httpsgithubcomjistollizard-flight">SOURCE : <a href="https://github.com/jistol/lizard-flight">https://github.com/jistol/lizard-flight</a></h3>
<p><img src="/assets/img/frontend/create-webgame/1.jpg" alt="game capture" /></p>
<h2 id="canvas에-캐릭터-그리기">canvas에 캐릭터 그리기</h2>
<p><code class="language-plaintext highlighter-rouge"><canvas></code>에 그림을 그리기 위해서는 기본적으로 렌더링 컨텍스트를 노출하여 작업하게 됩니다. 본 게임은 2d만 사용하였으나 webgl을 이용한 3d도 그릴 수 있습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">let</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">canvas</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="dl">'</span><span class="s1">2d</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<p>위와 같이 컨텍스트를 노출하여 그리게 되는데 조종 할 게임 캐릭터를 그려보도록 하겠습니다. <br />
(디자이너도 없고 하니 간단하게 원으로 생긴 캐릭터입니다.)</p>
<script async="" src="//jsfiddle.net/jistol/cs6oL23r/16/embed/js,html,result/dark/"></script>
<p>조종할 캐릭터를 그렸습니다. <code class="language-plaintext highlighter-rouge">beginPath</code>는 선을 그릴때 시작하는, <code class="language-plaintext highlighter-rouge">closePath</code>는 그리는 선을 닫아 시작점과 이어주는 역활을 합니다. <br />
<code class="language-plaintext highlighter-rouge">fill</code>함수 사용시 열린 도형이 자동으로 닫히게 되어 <code class="language-plaintext highlighter-rouge">closePath</code>를 명시 할 필요가 없으나 코딩상 명확하게 열고 닫는것이 실수의 여지를 줄여줍니다. <br />
<code class="language-plaintext highlighter-rouge">arc</code>함수를 사용하여 몸통,양쪽눈을 그렸습니다. <code class="language-plaintext highlighter-rouge">arc</code>함수에 대한 자세한 사용법은 <a href="https://developer.mozilla.org/ko/docs/Web/API/CanvasRenderingContext2D/arc">MDN - CanvasRenderingContext2D.arc()</a>를 참고하세요.</p>
<h2 id="canvas-스케일-적용">canvas 스케일 적용</h2>
<p>위 예제에서 간단히 캐릭터를 그려보았습니다. 하지만 요즘 웹은 모바일 환경에서 많이 노출되며 각 폰마다 크기가 다르기 때문에 우리가 그린 캐릭터는 폰마다 다른 크기로 나올 수 있습니다.
이를 방지하기 위해 크기와 비율을 고정해 보도록 하겠습니다. <br />
우선 모바일 디바이스 크기에 스케일을 맞추기 위해 HTML head에 아래와 같이 viewport를 추가합니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><html></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0, maximum-scale=1"</span><span class="nt">></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><canvas</span> <span class="na">id=</span><span class="s">"mainCanvas"</span><span class="nt">></canvas></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="p">...</span>
<span class="nt"></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
<p>viweport에 대한 자세한 설명은 <a href="https://jongmin92.github.io/2017/02/09/HTML/viewport/">모바일 화면을 위해 Viewport 사용하기</a> 글을 참고하세요. <br />
첫번째 예제에서는 canvas에 대한 width/height 값을 직접 입력했지만 화면 크기에 맞게 비율을 확대하려고 합니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 전체 화면을 사용하기 위해 body의 속성을 정의해줍니다.</span>
<span class="kd">let</span> <span class="nx">body</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
<span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">100%</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">100%</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">margin</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">body</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">padding</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">0</span><span class="dl">'</span><span class="p">;</span>
<span class="c1">// canvas의 크기는 width=100%, height는 width의 1.5 비율로 사용할 예정입니다.</span>
<span class="kd">let</span> <span class="nx">canvas</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">mainCanvas</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">=</span> <span class="nx">body</span><span class="p">.</span><span class="nx">clientWidth</span><span class="p">;</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">=</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">min</span><span class="p">(</span><span class="nx">body</span><span class="p">.</span><span class="nx">clientWidth</span> <span class="o">*</span> <span class="mf">1.5</span><span class="p">,</span> <span class="nx">body</span><span class="p">.</span><span class="nx">clientHeight</span><span class="p">);</span>
<span class="nx">canvas</span><span class="p">.</span><span class="nx">style</span><span class="p">.</span><span class="nx">backgroundColor</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">#000000</span><span class="dl">'</span><span class="p">;</span>
<span class="c1">// 실제 화면을 그릴 비율입니다.</span>
<span class="c1">// context를 이용하여 그림을 그릴 때 화면 넓이가 400, 높이는 넓이*1.5배라는 계산하에 작업할 예정입니다.</span>
<span class="kd">const</span> <span class="nx">rWidth</span> <span class="o">=</span> <span class="mi">400</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">rHeight</span> <span class="o">=</span> <span class="mi">400</span> <span class="o">*</span> <span class="mf">1.5</span><span class="p">;</span> <span class="c1">// 600</span>
<span class="c1">// 실제 canvas 넓이와 그림 비율이 맞지 않기 때문에 scale을 변경해줍니다.</span>
<span class="kd">let</span> <span class="nx">context</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">getContext</span><span class="p">(</span><span class="dl">'</span><span class="s1">2d</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">ratioX</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">width</span> <span class="o">/</span> <span class="nx">rWidth</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">ratioY</span> <span class="o">=</span> <span class="nx">canvas</span><span class="p">.</span><span class="nx">height</span> <span class="o">/</span> <span class="nx">rHeight</span><span class="p">;</span>
<span class="nx">context</span><span class="p">.</span><span class="nx">scale</span><span class="p">(</span><span class="nx">ratioX</span><span class="p">,</span> <span class="nx">ratioY</span><span class="p">);</span>
</code></pre></div></div>
<p>위와 같이 화면 스케일을 자동으로 조절하게 만들어 두면 게임 화면을 그리기가 훨씬 수월해집니다. <br />
디바이스 크기에 상관없이 화면 넓이가 400이란 전제하에 계산하여 캐릭터 크기를 조절 할 수 있게 됩니다.</p>
docker-compose를 이용한 ElasticSearch Cluster구성
2019-03-27T00:00:00+00:00
2019-03-27T00:00:00+00:00
https://github.com/jistol/docker/2019/03/27/docker-compose-elasticsearch-cluster
<p>개발을 하다보면 공용장비가 아닌 로컬장비에서 DB나 캐시, 검색엔진등을 실행해야하는 경우가 있는데 이 때 Docker를 사용하면 필요할 때만 올려 사용할 수 있어 자원 관리가 편하고 docker-compose를 이용하면 여러 프로그램을 동시에 실행하고 종료 할 수 있어 편하게 사용할 수 있습니다. <br />
본 글은 docker-compose를 이용하여 ElasticSearch 6.5.3 버전 기반으로 Cluster 환경을 구성하며 Kibana까지 같이 올리는 방법에 관한 글로 이미 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.5/docker.html">elstic reference에 docker를 이용하여 설치하는 방법</a>이 친절하게 설명되어 있으나 실제 설치하면서 추가로 필요했던 부분에 대해 보충하였습니다.</p>
<h2 id="구성">구성</h2>
<p>구성은 master-node 1대, data-node 1대, kibana 1대 입니다. <br />
ElasticSearch(이하 ES)와 함께 Celebro를 모니터링 툴로 쓰는 경우가 있는데 Kibana 최신 버전은 xpack을 통해 모니터링하는 기능이 있어 구지 필요가 없어 제외했습니다.</p>
<p><img src="/assets/img/docker/docker-compose-elasticsearch-cluster/1.png" alt="kibana-Monitoring" /></p>
<h2 id="docker-composeyml">docker-compose.yml</h2>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">2.2'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="c1"># master-node의 Docker 서비스입니다.</span>
<span class="c1"># Kibana에서 기본적으로 호출하는 ES host주소가 'http://elsaticsearch:9200'이기 때문에 서비스명은 elasticsearch로 쓰시는게 편합니다. </span>
<span class="c1"># 다른 서비스명을 사용시 Kibana ES host 설정도 같이 추가해주어야 정상 동작합니다.</span>
<span class="na">elasticsearch</span><span class="pi">:</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">elasticsearch</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">elasticsearch:6.5.3</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="c1"># ES Cluster명입니다. ES 서비스마다 동일한 명칭을 사용해야합니다. </span>
<span class="pi">-</span> <span class="s">cluster.name=docker-cluster</span>
<span class="c1"># ES Node명을 설정합니다.</span>
<span class="pi">-</span> <span class="s">node.name=master-node1</span>
<span class="c1"># ES운영중 메모리 스왑을 막기 위한 설정을 추가합니다.</span>
<span class="c1"># 자세한 설명은 페이지 하단의 [Disable swapping]을 참고하세요.</span>
<span class="pi">-</span> <span class="s">bootstrap.memory_lock=true</span>
<span class="c1"># JVM Heap메모리 설정입니다. Xms/Xmx 옵션은 항상 같게 설정합니다. </span>
<span class="c1"># 자세한 설명은 페이지 하단의 [Setting the heap size]을 참고하세요.</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">ES_JAVA_OPTS=-Xms512m</span><span class="nv"> </span><span class="s">-Xmx512m"</span>
<span class="c1"># 리눅스 시스템 자원제한 관련 옵션입니다.</span>
<span class="c1"># ES는 많은 파일디스크립터와 핸들러를 사용하기 때문에 제한 해제가 필요합니다.</span>
<span class="c1"># 자세한 설명은 페이지 하단의 [File Descriptors]을 참고하세요.</span>
<span class="err"> </span><span class="na">ulimits</span><span class="pi">:</span>
<span class="na">memlock</span><span class="pi">:</span>
<span class="na">soft</span><span class="pi">:</span> <span class="s">-1</span>
<span class="na">hard</span><span class="pi">:</span> <span class="s">-1</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">es1:/usr/share/elasticsearch/data</span>
<span class="c1"># Kibana에서 본 노드를 호출하기 때문에 외부 9200포트는 master-node에 연결해줍니다.</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">9200:9200</span>
<span class="pi">-</span> <span class="s">9300:9300</span>
<span class="na">networks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">esnet</span>
<span class="c1"># 컨테이너에 bash로 붙고 싶을경우 아래 두 옵션을 추가해주면 됩니다.</span>
<span class="na">stdin_open</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">tty</span><span class="pi">:</span> <span class="no">true</span>
<span class="c1"># data-node의 Docker 서비스입니다.</span>
<span class="c1"># 대부분의 내용이 master-node와 동일하나 몇가지 차이점이 있습니다.</span>
<span class="na">elasticsearch2</span><span class="pi">:</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">elasticsearch2</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">elasticsearch:6.5.3</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">cluster.name=docker-cluster</span>
<span class="pi">-</span> <span class="s">node.name=data-node1</span>
<span class="pi">-</span> <span class="s">bootstrap.memory_lock=true</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">ES_JAVA_OPTS=-Xms512m</span><span class="nv"> </span><span class="s">-Xmx512m"</span>
<span class="c1"># 다른 Cluster내 노드를 발견하기 위한 설정입니다.</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">discovery.zen.ping.unicast.hosts=elasticsearch"</span>
<span class="na">ulimits</span><span class="pi">:</span>
<span class="na">memlock</span><span class="pi">:</span>
<span class="na">soft</span><span class="pi">:</span> <span class="s">-1</span>
<span class="na">hard</span><span class="pi">:</span> <span class="s">-1</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">es2:/usr/share/elasticsearch/data</span>
<span class="c1"># 외부 연결포트가 master-node와 겹치기 때문에 다르게 설정했습니다.</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">9301:9300</span>
<span class="na">networks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">esnet</span>
<span class="na">stdin_open</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">tty</span><span class="pi">:</span> <span class="no">true</span>
<span class="c1"># 각 서비스를 순차적으로 실행하기 위해 설정해주었습니다. (필수아님) </span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">elasticsearch</span>
<span class="c1"># Kibana 설정입니다.</span>
<span class="na">kibana</span><span class="pi">:</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">kibana</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">kibana:6.5.3</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">5601:5601</span>
<span class="na">networks</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">esnet</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">elasticsearch</span>
<span class="pi">-</span> <span class="s">elasticsearch2</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="na">es1</span><span class="pi">:</span>
<span class="na">driver</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">es2</span><span class="pi">:</span>
<span class="na">driver</span><span class="pi">:</span> <span class="s">local</span>
<span class="na">networks</span><span class="pi">:</span>
<span class="na">esnet</span><span class="pi">:</span>
</code></pre></div></div>
<h2 id="실행--종료">실행 / 종료</h2>
<p>위와 같이 설정파일을 작성 후 docker-compose를 실행하면 아래와 같이 ES가 기동하는것을 볼 수 있습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker-compose up <span class="nt">-d</span>
Creating network <span class="s2">"elasticsearch_esnet"</span> with the default driver
Creating volume <span class="s2">"elasticsearch_es1"</span> with <span class="nb">local </span>driver
Creating volume <span class="s2">"elasticsearch_es2"</span> with <span class="nb">local </span>driver
Pulling elasticsearch <span class="o">(</span>elasticsearch:6.5.3<span class="o">)</span>...
6.5.3: Pulling from library/elasticsearch
Pulling elasticsearch2 <span class="o">(</span>elasticsearch:6.5.3<span class="o">)</span>...
6.5.3: Pulling from library/elasticsearch
Pulling kibana <span class="o">(</span>kibana:6.5.3<span class="o">)</span>...
6.5.3: Pulling from library/kibana
Creating elasticsearch ... <span class="k">done
</span>Creating elasticsearch2 ... <span class="k">done
</span>Creating kibana ... <span class="k">done</span>
</code></pre></div></div>
<p>종료방법은 아래와 같습니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker-compose down
Stopping kibana ... <span class="k">done
</span>Stopping elasticsearch2 ... <span class="k">done
</span>Stopping elasticsearch ... <span class="k">done
</span>Removing kibana ... <span class="k">done
</span>Removing elasticsearch2 ... <span class="k">done
</span>Removing elasticsearch ... <span class="k">done
</span>Removing network elasticsearch_esnet
</code></pre></div></div>
<h2 id="reference">Reference</h2>
<p>Install Elasticsearch with Docker:<a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.5/docker.html">https://www.elastic.co/guide/en/elasticsearch/reference/6.5/docker.html</a> <br />
Disable swapping:<a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.4/setup-configuration-memory.html">https://www.elastic.co/guide/en/elasticsearch/reference/6.4/setup-configuration-memory.html</a> <br />
Setting the heap size:<a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.5/heap-size.html">https://www.elastic.co/guide/en/elasticsearch/reference/6.5/heap-size.html</a> <br />
File Descriptors:<a href="https://www.elastic.co/guide/en/elasticsearch/reference/6.5/file-descriptors.html">https://www.elastic.co/guide/en/elasticsearch/reference/6.5/file-descriptors.html</a></p>
(React) Component Life Cycle Methods
2018-12-10T00:00:00+00:00
2018-12-10T00:00:00+00:00
https://github.com/jistol/frontend/2018/12/10/react-lifecycle-methods
<p>React Component의 생명주기에 대해 정리하고 테스트 예제를 포스팅합니다. <br />
참고로 version 17부터 deprecated 되는 항목(componentWillMount, componentWillUpdate, componentWillReceiveProps)은 제외했습니다.<br />
위 3개의 lifecycle은 오용되는 케이스가 많아 삭제 되었으며 ‘UNSAFE_‘라는 prefix를 붙여 메소드가 남아있는 상태로 자세한 내용은 <a href="https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html">Update on Async Rendering</a>문서를 참고하세요.</p>
<p>React 버전에 따라 생명주기가 살짝 다른데 아래 그림을 참고하세요.</p>
<p>React v16.3 : <a href="https://code.likeagirl.io/understanding-react-component-life-cycle-49bf4b8674de">https://code.likeagirl.io/understanding-react-component-life-cycle-49bf4b8674de</a> <br />
<img src="/assets/img/frontend/react-lifecycle-methods/1.jpeg" alt="React Component LifeCycle v16.3" /></p>
<p>React v16.4 : <a href="https://medium.com/@nancydo7/understanding-react-16-4-component-lifecycle-methods-e376710e5157">https://medium.com/@nancydo7/understanding-react-16-4-component-lifecycle-methods-e376710e5157</a> <br />
<img src="/assets/img/frontend/react-lifecycle-methods/2.png" alt="React Component LifeCycle v16.4" /></p>
<p>위 그림과 같이 React Component의 생명주기는 실행 이벤트 관점에서 “mount/update/unmount”로 구분 할 수 있으며 실행 단계 관점에서는 “랜더링전/DOM 반영전/DOM 반영이후”로 구분 할 수 있습니다. <br />
아래 메서드들은 실행 순서 보다는 “일반적으로 사용되는”, “드믈게 사용되는” 생명주기로 구분합니다. 버전별 일반 생명주기 메서드 표는 아래 링크를 참고하세요.</p>
<h2 id="react-lifecycle-methods-diagram--httpprojectswojtekmajplreact-lifecycle-methods-diagram">react-lifecycle-methods-diagram : <a href="http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/">http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/</a></h2>
<h1 id="1-commonly-used-lifecycle-methods">1. Commonly Used Lifecycle Methods</h1>
<h2 id="render">render()</h2>
<p>React.Component에서 유일하게 필수 구현되어야 하는 함수입니다. 미구현시 아래와 같은 오류를 만나게 됩니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Warning: TodoApp(...): No `render` method found on the returned component instance: you may have forgotten to define `render`.
Uncaught TypeError: instance.render is not a function
</code></pre></div></div>
<p>이 메서드 안에서는 this.props와 this.state를 사용할 수 있으나 state의 값을 변경하면 안됩니다. 변경시 아래와 같은 오류를 만나게 됩니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
</code></pre></div></div>
<p>반환 값으로 일반적으로 DOM노드를 반환하나 아래와 같이 여러종류의 데이터를 넘길 수 있습니다.</p>
<h3 id="1-react-element">1. React Element</h3>
<ul>
<li>JSX타입의 요소를 반환할 수 있습니다.</li>
<li>반환시 최상위 DOM은 단일노드여야 합니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="o"><</span><span class="nx">div</span><span class="o">></span> <span class="p">...</span> <span class="o"><</span><span class="sr">/div></span><span class="err">;
</span><span class="p">}</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="o"><</span><span class="nx">MyComponent</span><span class="o">/></span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// error case</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="mi">1</span><span class="o"><</span><span class="sr">/div><div>2</</span><span class="nx">div</span><span class="o">></span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Uncaught SyntaxError: Inline Babel script: Adjacent JSX elements must be wrapped in an enclosing tag
*/</span>
</code></pre></div></div>
<h3 id="2-fragment">2. Fragment</h3>
<ul>
<li>특정 태그로 묶고 싶지 않다면 <React.Fragment>태그로 묶어 반환할 수 있습니다.</React.Fragment></li>
<li>해당 태그로 묶을 경우 렌더링시 Fragment태그는 제거됩니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="mi">1</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="mi">2</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Result
* <div id="app">
* <div>
* <div>1</div>
* <div>2</div>
* </div>
* </div>
*/</span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
<span class="o"><</span><span class="nx">React</span><span class="p">.</span><span class="nx">Fragment</span><span class="o">></span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="mi">1</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="mi">2</span><span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/React.Fragment</span><span class="err">>
</span> <span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Result
* <div id="app">
* <div>1</div>
* <div>2</div>
* </div>
*/</span>
</code></pre></div></div>
<h3 id="3-portal">3. Portal</h3>
<ul>
<li>ReactDOM.createPortal 메서드를 이용하여 다른 DOM의 서브트리로 만들수 있습니다.</li>
<li>반드시 리턴 할 경우에만 적용됩니다.</li>
<li>일반 렌더링값과는 달리 해당 DOM의 하위 노드를 제거후 렌더링하는것이 아니라 서브 노드로 추가가 됩니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 본 컴포넌트가 속한 부모 노드 하위가 아닌 #portal 노드 하위에 그려집니다. </span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">domNode</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#portal</span><span class="dl">"</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">createPortal</span><span class="p">(</span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="nx">Portal</span><span class="o"><</span><span class="sr">/div></span><span class="err">,
</span> <span class="nx">domNode</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 아래와 같은 경우 아무것도 그려지지 않습니다. </span>
<span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">domNode</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#portal</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">createPortal</span><span class="p">(</span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="nx">Portal</span><span class="o"><</span><span class="sr">/div></span><span class="err">,
</span> <span class="nx">domNode</span>
<span class="p">);</span>
<span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
<p>Portal의 경우 컴포넌트가 속하지 않은 노드에 렌더링을 할 수 있게 하는데 다음과 같은 경우에 사용하기 유용합니다. <br />
ex) dialog, hovercard, tooltips … <br />
자세한 가이드는 <a href="https://reactjs.org/docs/portals.html">React Portals</a> 문서를 참고하세요.</p>
</blockquote>
<h3 id="4-string-number">4. String, Number</h3>
<ul>
<li>문자열이나 숫자를 반환 할 수 있으며 TextNode로 렌더링 됩니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="dl">'</span><span class="s1">Text</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Result
* <div id="app">Text</div>
*/</span>
</code></pre></div></div>
<h3 id="5-boolean-null">5. boolean, null</h3>
<ul>
<li>boolean(true/false)값이나 null값 역시 반환 가능합니다.</li>
<li>반환시 화면에 아무것도 안그리는 것으로 보일수 있으나 사실상 공백을 그려줍니다. 렌더링되는 요소 하위에 다른 요소가 존재한다면 삭제 됩니다.</li>
<li>심지어 undefined도 가능합니다. 테스트 해보면 에러는 안나지만 공식문서에 undefined는 명시되 있지 않습니다.</li>
</ul>
<h3 id="6-array">6. Array</h3>
<ul>
<li>배열을 반환할 수 있습니다.</li>
<li>배열 요소들은 렌더링 가능한 모든 타입이 가능합니다. (function은 불가능합니다.)</li>
<li>컴포넌트 배열을 렌더링 할 경우 어떤 원소에 변동이 있는지 알아내기 위해 각 원소에 고유 key가 포함되어야 합니다.</li>
</ul>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">render</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">start</span><span class="dl">"</span><span class="p">,</span>
<span class="mi">1234</span><span class="p">,</span>
<span class="kc">false</span><span class="p">,</span>
<span class="kc">null</span><span class="p">,</span>
<span class="o"><</span><span class="nx">React</span><span class="p">.</span><span class="nx">Fragment</span> <span class="nx">key</span><span class="o">=</span><span class="dl">"</span><span class="s2">frag</span><span class="dl">"</span><span class="o">><</span><span class="nx">div</span> <span class="nx">key</span><span class="o">=</span><span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="o">></span><span class="mi">1</span><span class="o"><</span><span class="sr">/div><div key="2">2</</span><span class="nx">div</span><span class="o">><</span><span class="sr">/React.Fragment></span><span class="err">,
</span> <span class="o"><</span><span class="nx">b</span> <span class="nx">key</span><span class="o">=</span><span class="dl">"</span><span class="s2">3</span><span class="dl">"</span><span class="o">></span><span class="p">{</span><span class="k">this</span><span class="p">.</span><span class="nx">state</span><span class="p">.</span><span class="nx">status</span><span class="p">}</span><span class="o"><</span><span class="sr">/b></span><span class="err">,
</span> <span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">createPortal</span><span class="p">(</span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span><span class="nx">Portal</span><span class="o"><</span><span class="sr">/div></span><span class="err">,
</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">portal</span>
<span class="p">)</span>
<span class="p">];</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="constructorprops">constructor(props)</h2>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span>
<span class="c1">// Don't call this.setState() here!</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span> <span class="na">counter</span><span class="p">:</span> <span class="mi">0</span> <span class="p">};</span>
<span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">handleClick</span><span class="p">.</span><span class="nx">bind</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>컴포넌트 생성자로 생성시 맨 처음에 실행하게 되는데 props를 인자로 받는데 React.Component를 상속했을 경우 반드시 <code class="language-plaintext highlighter-rouge">super(props);</code>를 호출해야합니다. <br />
그리고 constructor는 유일하게 this.state 를 직접 할당하는 메서드입니다. 이 메서드 안에서는 setState()를 호출하지 말아야 합니다. 호출시 아래와 같은 오류가 발생합니다. <br />
그 외 이벤트를 해당 인스턴스로 바인드 하는 등의 작업을 할 수 있습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to `this.state` directly or define a `state = {};` class property with the desired state in the YourApp component.
</code></pre></div></div>
<h2 id="componentdidmount">componentDidMount()</h2>
<p>Component가 DOM트리에 마운트 될 때 실행되며 일반적으로 DOM노드가 필요한 초기화 작업은 본 메서드에서 실행해야 합니다. 또한 여기서 실행한 트리거나 이벤트, 비동기 작업등은 componentWillUnmount메서드에서 만드시 해제 해주는 것이 좋습니다. <br />
componentDidMount() 메서드에도 안티패턴이 존재하는데 아래와 같은 케이스입니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">componentDidMount</span><span class="p">()</span> <span class="p">{</span>
<span class="p">...</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setState</span><span class="p">({</span> <span class="p">...</span> <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>위와 같이 호출 가능하나 화면을 업데이트 하기 전에 추가 렌더링이 발생하므로 뷰에는 중간 상태를 나타내지 않으며 성능상 이슈를 발생하기도 합니다. <br />
하지만 DOM에 렌더링 되기 전에 크기 측정이 필요 할 경우 위와 같이 사용 할 수도 있습니다. (ex. 툴팁이나 모달창등을 만들때.. )</p>
<h2 id="componentdidupdateprevprops-prevstate-snapshot">componentDidUpdate(prevProps, prevState, snapshot)</h2>
<p>리렌더링 발생 이후 실행되며 초기 렌더링 시에는 실행되지 않습니다. DOM업데이트가 완료된 이후에 실행되므로 DOM관련 처리를 해도 됩니다. <br />
그리고 이전 상태의 props/state값과 getSnapshotBeforeUpdate()메서드를 통해 전달 받은 데이터가 넘어오기 때문에 이전 상태와 현 상태의 변화에 따른 네트워크 작업을 하기에 좋습니다. <br />
단, 주의해야할 점은 본 메서드에서 setState()를 실행할 수 있으나 제약조건없이 실행 할 경우 무한루프에 빠질 수 있습니다.</p>
<h2 id="componentwillunmount">componentWillUnmount()</h2>
<p>Component가 DOM에서 제거되고 파기 되기 직전에 호출 됩니다. 본 Component에서 구독한 이벤트나 트리거등을 제거할때 사용되며 Component가 리렌더링 되지 않기 때문에 setState()를 호출 할 수 업습니다.</p>
<h1 id="2-rarely-used-lifecycle-methods">2. Rarely Used Lifecycle Methods</h1>
<h2 id="shouldcomponentupdatenextprops-nextstate">shouldComponentUpdate(nextProps, nextState)</h2>
<p>본 메서드에서 리렌더링 여부를 결정하는 메서드로 새로운 props/state 데이터를 받을 경우 동작하며, 초기 렌더링시나 forceUpdate()를 통한 리렌더링시에는 동작하지 않습니다. <br />
재정의 하지 않을 경우 기본적으로는 모든 변경시마다 리렌더링을 실행하며 false를 반환 할 경우 render() 메서드를 실행하지 않습니다. <br />
shouldComponentUpdate메서드는 보통 성능 최적화를 위해 특정 값의 변경에 따라서만 리렌더링을 조절가능하나 버그를 양산하기 쉽기 때문에 대신 <a href="https://reactjs.org/docs/react-api.html#reactpurecomponent">React.PureComponent</a>를 사용할 것을 권장하고 있습니다. <br />
PureComponent는 shouldComponentUpdate()메서드가 이미 구현되어 있는 클래스로 React.Component 대신 상속 받아 사용할 경우 props/state의 변화시 얕은 비교를 통해 변경된 것이 있을 경우에만 리렌더링을 해줍니다.
PureComponent및 성능 최적화에 관련된 내용은 아래 링크를 참고하세요.</p>
<p>리액트 성능 향상 시키기 - React.PureComponent : <a href="https://wonism.github.io/react-pure-component/">https://wonism.github.io/react-pure-component/</a> <br />
Optimizing Performance : <a href="https://reactjs.org/docs/optimizing-performance.html">https://reactjs.org/docs/optimizing-performance.html</a></p>
<h2 id="getderivedstatefrompropsnextprops-prevstate">getDerivedStateFromProps(nextProps, prevState)</h2>
<p>React v16.3이후 새로 생긴 메서드로 모든 render()메서드 실행 전에 시작됩니다. <br />
업데이트가 필요한 state값을 반환하거나 null을 반환해야 하고 본 메서드는 static 메서드로 Component에 직접 접근 할 수 없습니다. <br />
레퍼런스 문서에는 getDerivedStateFromProps 메서드의 용도를 오직 props의 변화에 따른 state 상태 변화를 위한 용도로만 정의하고 있습니다. <br />
이를 위해 일부 개발자가 양산하는 아래와 같은 버그를 막는 용도로 설명하고 있으며 더 자세한 내용은 <a href="https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html">You Probably Don’t Need Derived State</a>에서 확인 할 수 있습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">constructor</span><span class="p">(</span><span class="nx">props</span><span class="p">)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">(</span><span class="nx">props</span><span class="p">);</span>
<span class="c1">// Don't do this!</span>
<span class="k">this</span><span class="p">.</span><span class="nx">state</span> <span class="o">=</span> <span class="p">{</span> <span class="na">color</span><span class="p">:</span> <span class="nx">props</span><span class="p">.</span><span class="nx">color</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>
<p>위 예제는 props값을 직접 참조함으로써 props값이 변할 때 마다 state에 영향을 미치길 바라지만 실제로는 변경되지 않습니다. <br />
하지만 아래와 같이 getDerivedStateFromProps 메서드를 이용하면 props값이 변경시마다 getDerivedStateFromProps 메서드를 호출하기 때문에 state값을 변경 가능합니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">static</span> <span class="nx">getDerivedStateFromProps</span><span class="p">(</span><span class="nx">nextProps</span><span class="p">,</span> <span class="nx">prevState</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">nextProps</span><span class="p">.</span><span class="nx">status</span> <span class="o">!=</span> <span class="nx">prevState</span><span class="p">.</span><span class="nx">status</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span> <span class="na">status</span> <span class="p">:</span> <span class="nx">nextProps</span><span class="p">.</span><span class="nx">status</span> <span class="p">};</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="getsnapshotbeforeupdateprevprops-prevstate">getSnapshotBeforeUpdate(prevProps, prevState)</h2>
<p>React v16.3이후 새로 생긴 메서드로 render()메서드 호출 이후 DOM트리에 반영전에 호출 됩니다. <br />
특정 동작에 따른 Component의 변화가 적용 되기전에 이전 scroll position와 같은 이전 상태의 snapshot을 남겨 넘길수 있는데 이 때 리턴된 값은 componentDidUpdate() 메서드에 전달 됩니다. 재정의 하지 않을 경우 기본값은 null이 리턴됩니다.</p>
<p>잘못 사용하는 케이스로 본 메서드에서 현재 렌더링된 DOM의 데이터를 읽어들이는 케이스가 있습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">getSnapshotBeforeUpdate</span><span class="p">(</span><span class="nx">prevProps</span><span class="p">,</span> <span class="nx">prevState</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">prevProps</span><span class="p">.</span><span class="nx">list</span><span class="p">.</span><span class="nx">length</span> <span class="o"><</span> <span class="k">this</span><span class="p">.</span><span class="nx">props</span><span class="p">.</span><span class="nx">list</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">list</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">listRef</span><span class="p">.</span><span class="nx">current</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">list</span><span class="p">.</span><span class="nx">scrollHeight</span> <span class="o">-</span> <span class="nx">list</span><span class="p">.</span><span class="nx">scrollTop</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>위와 같이 사용할 경우 render과정과 commit과정 사이의 딜레이가 존재하기 때문에 원하는 결과를 얻을수 없습니다. 본 메서드에서는 과거 상태만을 반환하고 componentDidUpdate()메서드에서 처리하는 것이 좋습니다.</p>
<h1 id="3-example">3. Example</h1>
<p>위 생명주기를 테스트 해 볼수 있는 샘플 코드입니다.</p>
<script async="" src="//jsfiddle.net/jistol/0c8xr6ug/embed/js,html,css,result/dark/"></script>
<h1 id="4-그-외">4. 그 외…</h1>
<p>Error Handling : <a href="https://reactjs.org/docs/error-boundaries.html">https://reactjs.org/docs/error-boundaries.html</a></p>
(React) PropTypes 사용방법과 종류
2018-12-03T00:00:00+00:00
2018-12-03T00:00:00+00:00
https://github.com/jistol/frontend/2018/12/03/react-proptypes
<p>React Component의 prop값을 검증하기 위해 PropTypes를 이용하여 값을 지정할 수 있습니다. <br />
React v15.5부터 다른 패키지로 변경되었는데 ‘prop-types’라이브러리를 사용하라고 권고하고 있습니다.</p>
<h2 id="사용방법">사용방법</h2>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Component</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">PropTypes</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">prop-types</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">class</span> <span class="nx">Foo</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="p">...</span>
<span class="kd">static</span> <span class="nx">propTypes</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">strArg</span> <span class="p">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span><span class="p">,</span>
<span class="na">numArg</span> <span class="p">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">number</span><span class="p">.</span><span class="nx">isRequired</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>위와 같이 설정할 경우 Foo 컴포넌트의 strArg값은 string 타입이여야 하며, numArg값은 number 타입이여야 합니다. <br />
그리고 strArg값은 지정하지 않아도 되나 <code class="language-plaintext highlighter-rouge">isRequired</code>를 지정한 numArg값은 반드시 설정해야 합니다.</p>
<p>또한 propTypes은 아래와 같이 class 밖에서도 설정 가능합니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">Foo</span> <span class="kd">extends</span> <span class="nx">Component</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="nx">Foo</span><span class="p">.</span><span class="nx">propTypes</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">strArg</span> <span class="p">:</span> <span class="nx">PropTypes</span><span class="p">.</span><span class="nx">string</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="proptypes의-종류">PropTypes의 종류</h2>
<p>PropTypes으로 설정할 수 있는 종류는 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: left">kind</th>
<th style="text-align: left">description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">array</td>
<td style="text-align: left">배열</td>
</tr>
<tr>
<td style="text-align: left">bool</td>
<td style="text-align: left">true/false</td>
</tr>
<tr>
<td style="text-align: left">func</td>
<td style="text-align: left">함수</td>
</tr>
<tr>
<td style="text-align: left">number</td>
<td style="text-align: left">숫자</td>
</tr>
<tr>
<td style="text-align: left">object</td>
<td style="text-align: left">객체</td>
</tr>
<tr>
<td style="text-align: left">string</td>
<td style="text-align: left">문자열</td>
</tr>
<tr>
<td style="text-align: left">symbol</td>
<td style="text-align: left">심벌 개체(ES6)</td>
</tr>
<tr>
<td style="text-align: left">node</td>
<td style="text-align: left">렌더링 가능한 모든것(number, string, element, 또는 그것들이 포함된 array/fragment)</td>
</tr>
<tr>
<td style="text-align: left">element</td>
<td style="text-align: left">React element</td>
</tr>
<tr>
<td style="text-align: left">instanceOf(ClassName)</td>
<td style="text-align: left">JS에서 instanceof로 정의 가능한 클래스 인스턴스</td>
</tr>
<tr>
<td style="text-align: left">oneOf([…Value])</td>
<td style="text-align: left">포함된 값들중 하나.(ex: oneOf([‘남자’,’여자’]))</td>
</tr>
<tr>
<td style="text-align: left">oneOfType([…PropTypes])</td>
<td style="text-align: left">포함된 PropTypes들중 하나. (ex: oneOfType([PropTypes.string, PropTypes.instanceOf(MyClass)]))</td>
</tr>
<tr>
<td style="text-align: left">arrayOf(PropTypes)</td>
<td style="text-align: left">해당 PropTypes으로 구성된 배열</td>
</tr>
<tr>
<td style="text-align: left">objectOf(PropTypes)</td>
<td style="text-align: left">주어진 종류의 값을 가진 객체</td>
</tr>
<tr>
<td style="text-align: left">shape({key:PropTypes})</td>
<td style="text-align: left">해당 스키마를 가진 객체.(ex:shape({name:PropTypes.string,age:PropTypes.number}))</td>
</tr>
<tr>
<td style="text-align: left">exact({key:PropTypes})</td>
<td style="text-align: left">명확하게 해당 스키마만 존재해야함.</td>
</tr>
</tbody>
</table>
<h2 id="reference">Reference</h2>
<p>Typechecking With PropTypes:<a href="https://reactjs.org/docs/typechecking-with-proptypes.html">https://reactjs.org/docs/typechecking-with-proptypes.html</a></p>
SSM(simple-spring-memcached) MultiCache 사용하기 (@ReadThroughMultiCache, @UpdateMultiCache, @InvalidateMultiCache)
2018-11-27T00:00:00+00:00
2018-11-27T00:00:00+00:00
https://github.com/jistol/java/2018/11/27/ssm-annotation-multicache
<p>“Spring + Memcached” 조합일때 <a href="https://github.com/ragnor/simple-spring-memcached">simple-spring-memcached</a>(이하 ssm)이 많이 사용되는데 인터넷에 보면 대부분 <code class="language-plaintext highlighter-rouge">@ReadThroughSingleCache</code>나 <code class="language-plaintext highlighter-rouge">@ReadThroughAssignCache</code>에 대한 설명이나 예제는 많은데 <code class="language-plaintext highlighter-rouge">@ReadThroughMultiCache</code>관련된 예제는 유독 찾아보기 힘들었습니다. <br />
심지어 공식 가이드에도 간략하게만 써있어서 실제 동작 방식에 대해 알아보기 위해 xmemcached기반으로 직접 테스트 프로젝트를 만들어보고 테스트 해본 내용에 대한 포스팅입니다.</p>
<h2 id="basic">Basic</h2>
<p>아래 코드는 기본적인 사용 방법입니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">nums</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<p>하나의 <code class="language-plaintext highlighter-rouge">java.util.List</code>인자값을 포함하고 <code class="language-plaintext highlighter-rouge">java.util.List</code> 또는 <code class="language-plaintext highlighter-rouge">java.util.List</code>를 상속한 클래스를 리턴하는 메소드에만 적용할 수 있습니다. <br />
위와 같이 적용시 ssm은 자동으로 인자값의 List와 결과값의 List를 매핑하여 분산하여 캐시키를 저장하게 됩니다. <br />
위 Java코드를 기반으로 실행시 인자값과 반환값이 아래와 같을 경우 실제 캐시에 저장되는 예시입니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="sr">//</span> <span class="k">argument</span> <span class="p">:</span> List<span class="p">(</span><span class="m">1</span><span class="p">,</span><span class="m">2</span><span class="p">,</span><span class="m">3</span><span class="p">,</span><span class="m">4</span><span class="p">,</span><span class="m">5</span><span class="p">)</span>
<span class="sr">//</span> returnValue <span class="p">:</span> List<span class="p">(</span><span class="m">11</span><span class="p">,</span><span class="m">22</span><span class="p">,</span><span class="m">33</span><span class="p">,</span><span class="m">44</span><span class="p">,</span><span class="m">55</span><span class="p">)</span>
stats cachedump <span class="m">5</span> <span class="m">100</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">1</span> <span class="p">[</span><span class="m">135</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> value <span class="k">is</span> <span class="m">11</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">2</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> value <span class="k">is</span> <span class="m">22</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">3</span> <span class="p">[</span><span class="m">155</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> value <span class="k">is</span> <span class="m">33</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">4</span> <span class="p">[</span><span class="m">165</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> value <span class="k">is</span> <span class="m">44</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">5</span> <span class="p">[</span><span class="m">175</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> value <span class="k">is</span> <span class="m">55</span>
END
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">@ReadThroughSingleCache</code>의 경우 “1,2,3,4,5” 전체를 키 값으로 사용하지만 <code class="language-plaintext highlighter-rouge">@ReadThroughMultiCache</code>의 경우 List의 각 키 값을 분산 저장하고 재활용 합니다. <br />
위와 같이 캐시가 저장된 상태에서 다시 아래와 같이 실행할 경우 기존 캐시된 값은 그대로 사용하고 캐시가 없는 부분만 다시 캐시에 저장합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="sr">//</span> <span class="k">argument</span> <span class="p">:</span> List<span class="p">(</span><span class="m">2</span><span class="p">,</span><span class="m">4</span><span class="p">,</span><span class="m">6</span><span class="p">,</span><span class="m">8</span><span class="p">)</span>
<span class="sr">//</span> returnValue <span class="p">:</span> List<span class="p">(</span><span class="m">22</span><span class="p">,</span><span class="m">44</span><span class="p">,</span><span class="m">66</span><span class="p">,</span><span class="m">88</span><span class="p">)</span>
stats cachedump <span class="m">5</span> <span class="m">100</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">1</span> <span class="p">[</span><span class="m">135</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">2</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> use existing cache
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">3</span> <span class="p">[</span><span class="m">155</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">4</span> <span class="p">[</span><span class="m">165</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span> <span class="sr">//</span> use existing cache
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">5</span> <span class="p">[</span><span class="m">175</span> <span class="k">b</span>; <span class="m">1543323568</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">6</span> <span class="p">[</span><span class="m">185</span> <span class="k">b</span>; <span class="m">1543324016</span> s<span class="p">]</span> <span class="sr">//</span> <span class="k">new</span> cache
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">8</span> <span class="p">[</span><span class="m">205</span> <span class="k">b</span>; <span class="m">1543324016</span> s<span class="p">]</span> <span class="sr">//</span> <span class="k">new</span> cache
END
</code></pre></div></div>
<p>새로 캐시를 저장하는 것이 아니기 때문에 expiration 설정을 했을 경우 1~5번 캐시는 동시에 삭제되고 6,8번 캐시는 이후 삭제됩니다.</p>
<p>위 메서드를 디버깅해보면 애초에 인자값에 캐시 데이터가 없는 값만 추려 전달하는것을 볼 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// execute 1 => argument = List(1,2,3,4,5)</span>
<span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">nums</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// nums = List(1,2,3,4,5)</span>
<span class="o">......</span>
<span class="o">}</span>
<span class="c1">// execute 2 => argument = List(2,4,6,8)</span>
<span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">nums</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// nums = List(6,8)</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이미 캐싱 되 있는 값을 Update 하기 위해서는 <code class="language-plaintext highlighter-rouge">@UpdateMultiCache</code>를 이용 할 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@UpdateMultiCache</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">updateMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">,</span> <span class="nd">@ParameterDataUpdateContent</span> <span class="nc">List</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">>></span> <span class="n">content</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<p>위 예제와 같이 <code class="language-plaintext highlighter-rouge">@ParameterDataUpdateContent</code>어노테이션을 사용하여 저장할 값을 직접 주입할 수 있으며,</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReturnDataUpdateContent</span>
<span class="nd">@UpdateMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">updateMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">@ReturnDataUpdateContent</code>어노테이션을 사용하여 반환값을 저장할 수도 있습니다.</p>
<p>캐시를 만료 시킬때는 <code class="language-plaintext highlighter-rouge">@InvalidateMultiCache</code>어노테이션을 사용합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@InvalidateMultiCache</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">invalidateMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="caution---argument">Caution - Argument</h2>
<p><code class="language-plaintext highlighter-rouge">@ReadThroughMultiCache</code>는 반드시 하나의 <code class="language-plaintext highlighter-rouge">java.util.List</code> 인자값을 포함해야합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">Integer</span> <span class="n">multi</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<p>위와 같이 인자값이 잘못됬을 경우 정상실행 되나 캐싱되지 않으며 아래와 같은 오류 메시지가 발생합니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2018-11-27 22:13:46] [WARN ] c.g.c.s.a.CacheAdvice.warn[55] Caching on execution(XXXService.randomMulti(..)) aborted due to an error.
com.google.code.ssm.aop.support.InvalidAnnotationException: No one parameter objects found at dataIndexes [[0]] is not a [java.util.List]. [public java.util.List test.service.XXXService.randomMulti(java.lang.Integer)] does not fulfill the requirements.
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">java.util.List</code>타입 대신 Array를 사용하더라도 동일한 오류를 발생시킵니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">Integer</span><span class="o">[]</span> <span class="n">multi</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2018-11-27 22:16:23] [WARN ] c.g.c.s.a.CacheAdvice.warn[55] Caching on execution(XXXService.randomMulti(..)) aborted due to an error.
com.google.code.ssm.aop.support.InvalidAnnotationException: No one parameter objects found at dataIndexes [[0]] is not a [java.util.List]. [public java.util.List test.service.XXXService.randomMulti(java.lang.Integer[])] does not fulfill the requirements.
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">java.util.List</code>타입 인자값이 2개 이상일 경우에도 오류를 발생시킵니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">,</span> <span class="nd">@ParameterValueKeyProvider</span><span class="o">(</span><span class="n">order</span> <span class="o">=</span> <span class="mi">1</span><span class="o">)</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi2</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2018-11-27 22:19:18] [WARN ] c.g.c.s.a.CacheAdvice.warn[55] Caching on execution(XXXService.randomMulti(..)) aborted due to an error.
com.google.code.ssm.aop.support.InvalidAnnotationException: There are more than one method's parameter annotated by @ParameterValueKeyProvider that is list public java.util.List test.service.XXXService.randomMulti(java.util.List,java.util.List)
</code></pre></div></div>
<p>하지만 <code class="language-plaintext highlighter-rouge">java.util.List</code>타입 인자값이 1개라면 아래와 같이 다른 키 값이 추가 되더라도 정상적으로 캐시를 저장합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">>></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">Integer</span> <span class="n">fixSize</span><span class="o">,</span> <span class="nd">@ParameterValueKeyProvider</span><span class="o">(</span><span class="n">order</span> <span class="o">=</span> <span class="mi">1</span><span class="o">)</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="sr">//</span> <span class="k">argument</span> <span class="p">:</span> fixSize <span class="p">=</span> <span class="m">2</span> <span class="p">,</span> multi <span class="p">=</span> List<span class="p">(</span><span class="m">1</span><span class="p">,</span><span class="m">2</span><span class="p">,</span><span class="m">3</span><span class="p">)</span>
stats cachedump <span class="m">6</span> <span class="m">100</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">2</span><span class="p">,</span><span class="m">3</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543325621</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">2</span><span class="p">,</span><span class="m">2</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543325621</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">2</span><span class="p">,</span><span class="m">1</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543325621</span> s<span class="p">]</span>
END
</code></pre></div></div>
<p>순서를 반대로 해도 정상적으로 저장합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">>></span> <span class="nf">randomMulti</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">,</span> <span class="nd">@ParameterValueKeyProvider</span><span class="o">(</span><span class="n">order</span> <span class="o">=</span> <span class="mi">1</span><span class="o">)</span> <span class="nc">Integer</span> <span class="n">fixSize</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="sr">//</span> <span class="k">argument</span> <span class="p">:</span> multi <span class="p">=</span> List<span class="p">(</span><span class="m">1</span><span class="p">,</span><span class="m">2</span><span class="p">,</span><span class="m">3</span><span class="p">),</span> fixSize <span class="p">=</span> <span class="m">2</span>
stats cachedump <span class="m">6</span> <span class="m">100</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">3</span><span class="p">,</span><span class="m">2</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543325799</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">2</span><span class="p">,</span><span class="m">2</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543325799</span> s<span class="p">]</span>
ITEM local<span class="p">:</span>TMON<span class="p">::</span>COMMON<span class="p">:</span>randomMulti<span class="p">:</span><span class="m">1</span><span class="p">,</span><span class="m">2</span> <span class="p">[</span><span class="m">145</span> <span class="k">b</span>; <span class="m">1543325799</span> s<span class="p">]</span>
END
</code></pre></div></div>
<h2 id="caution---return-data">Caution - Return Data</h2>
<p><code class="language-plaintext highlighter-rouge">@ReadThroughMultiCache</code>는 반드시 <code class="language-plaintext highlighter-rouge">java.util.List</code>타입의 반환값을 가져야합니다.</p>
<p>반환값이 <code class="language-plaintext highlighter-rouge">java.util.List</code>이 아닐 경우 메소드 자체는 정상 동작하지만 아래와 같이 오류 메시지를 반환하며 캐시는 저장되지 않습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ReadThroughMultiCache</span>
<span class="kd">public</span> <span class="nc">Integer</span> <span class="nf">randomMultiFindOne</span><span class="o">(</span><span class="nd">@ParameterValueKeyProvider</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">multi</span><span class="o">)</span> <span class="o">{</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2018-11-27 22:30:32] [WARN ] c.g.c.s.a.CacheAdvice.warn[55] Caching on execution(XXXService.randomMultiFindOne(..)) aborted due to an error.
com.google.code.ssm.aop.support.InvalidAnnotationException: The annotation [com.google.code.ssm.api.ReadThroughMultiCache] is only valid on a method that returns a [java.util.List] or its subclass. [public java.lang.Integer test.service.XXXService.randomMultiFindOne(java.util.List)] does not fulfill this requirement.
</code></pre></div></div>
<p>인자값과 반환값을 쌍으로 캐시에 저장하기 때문에 인자값의 size와 반환값의 size는 동일해야 합니다. <br />
인자값보다 반환값의 size가 더 많거나 적을 경우 아래와 같은 오류 메시지를 남기고 캐시는 저장되지 않습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[2018-11-27 22:38:24] [WARN ] c.g.c.s.a.ReadThroughMultiCacheAdvice.generateByKeysProviders[166] Did not receive a correlated amount of data from the target method: %s. Result list will be unsorted and won't respect the order of the keys passed in argument.
</code></pre></div></div>
<p>인자값과 반환값의 size가 같을 경우 캐시를 분할하여 저장하게 되는데 인자값과 반환값의 같은 index끼리 저장하게 되기 때문에 반환값의 순서가 중요합니다. <br />
순서가 다를 경우 오류도 없이 캐시가 엉망으로 저장될 수 있습니다.</p>
<h2 id="conclusion">Conclusion</h2>
<p>SSM은 분명 편하게 “Spring + Memcached” 조합을 사용 할 수 있게 해주지만 간단한 만큼 인적오류로 인한 실수를 범할 수 있으며 오류 로그 역시 warn 레벨로 남기기 때문에 잘못을 인지하지 못하고 사용하는 경우가 많습니다. <br />
특히 <code class="language-plaintext highlighter-rouge">@ReadThroughMultiCache</code>의 경우 위에서 알아본 바와 같이 개발자가 실수할 수 있는 여지가 많기 때문에 더더욱 신중하게 사용해야 하지만 자동으로 분할하여 캐시를 저장하며 실행시 알아서 캐시되 있지 않은 값만 따로 실행해주기 때문에 분명 매력적인 부분이 존재합니다.</p>
(Spring) ImportAware is not work
2018-11-23T00:00:00+00:00
2018-11-23T00:00:00+00:00
https://github.com/jistol/spring/2018/11/23/spring-importaware-not-work
<p>ImportAware 구현체가 동작하지 않는 이슈가 발생해 구글링해보다가 해결이 되지 않아 직접 스프링 코어 소스를 까서 원인을 확인한 부분에 대해 기록한 글입니다.</p>
<h2 id="issue">Issue</h2>
<p>개발중인 모듈을 <code class="language-plaintext highlighter-rouge">@EnableXXX</code> 방식으로 어노테이션 지정시 별도 설정없이 동작하기 위해 아래와 같은 Configuration을 Import 하도록 하였습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Target</span><span class="o">(</span><span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span><span class="o">)</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@Documented</span>
<span class="nd">@Import</span><span class="o">(</span><span class="nc">LocalCacheConfig</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">EnableLocalCache</span> <span class="o">{</span>
<span class="err">……</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LocalCacheConfig</span> <span class="kd">implements</span> <span class="nc">ImportAware</span><span class="o">,</span> <span class="nc">BeanFactoryPostProcessor</span> <span class="o">{</span>
<span class="err">……</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setImportMetadata</span><span class="o">(</span><span class="nc">AnnotationMetadata</span> <span class="n">importMetadata</span><span class="o">)</span> <span class="o">{</span>
<span class="n">enabled</span> <span class="o">=</span> <span class="n">importMetadata</span><span class="o">.</span><span class="na">hasAnnotation</span><span class="o">(</span><span class="nc">EnableLocalCache</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="k">if</span> <span class="o">(!</span> <span class="n">enabled</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="err">……</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">postProcessBeanFactory</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span> <span class="n">enabled</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">beanFactory</span><span class="o">.</span><span class="na">registerSingleton</span><span class="o">(</span><span class="s">"localCacheManager"</span><span class="o">,</span> <span class="n">localCacheManager</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>ImportAware.setImportMetadata 메소드에서 <code class="language-plaintext highlighter-rouge">@EnableLocalCache</code> 여부를 파악하고 다른 bean이 생성되기전 BeanFactoryPostProcessor.postProcessBeanFactory 메소드를 통해 localCacheManager를 주입하여 다른 Bean 생성시 주입되는 localCacheManager Bean이 null이 되지 않게하는 처리 부분입니다.
간단하게 테스트용 프로젝트를 만들고 위와 같이 실행시</p>
<ol>
<li>ImportAware.setImportMetadata</li>
<li>BeanFactoryPostProcessor.postProcessBeanFactory</li>
</ol>
<p>위 순서대로 동작함을 확인하고 개발을 진행하였습니다.</p>
<p>모듈 개발을 완료하고 실 서비스에 적용하였는데 POC를 진행한 Spring 5.x 버전에서는 이상이 없었으나 Spring 4.x 버전에서는 ImportAware가 실행되지 않아 localCacheManager Bean을 등록하지 못하는 이슈가 발생하였습니다. (사실 이슈가 처음 발생했을때는 버전 문제라는 것 조차 인식하지 못한 상태이긴 합니다.)</p>
<h2 id="spring-context-refresh-주요-단계">Spring Context refresh 주요 단계</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// AbstractApplicationContext.java</span>
<span class="c1">// Allows post-processing of the bean factory in context subclasses.</span>
<span class="n">postProcessBeanFactory</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
<span class="c1">// Invoke factory processors registered as beans in the context.</span>
<span class="n">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
<span class="c1">// Register bean processors that intercept bean creation.</span>
<span class="n">registerBeanPostProcessors</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
<span class="c1">// Initialize message source for this context.</span>
<span class="n">initMessageSource</span><span class="o">();</span>
<span class="c1">// Initialize event multicaster for this context.</span>
<span class="n">initApplicationEventMulticaster</span><span class="o">();</span>
<span class="c1">// Initialize other special beans in specific context subclasses.</span>
<span class="n">onRefresh</span><span class="o">();</span>
<span class="c1">// Check for listener beans and register them.</span>
<span class="n">registerListeners</span><span class="o">();</span>
<span class="c1">// Instantiate all remaining (non-lazy-init) singletons.</span>
<span class="n">finishBeanFactoryInitialization</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
<span class="c1">// Last step: publish corresponding event.</span>
<span class="n">finishRefresh</span><span class="o">();</span>
</code></pre></div></div>
<p>Spring 기동시 context를 초기화 하는 과정의 일부입니다.
BeanFactoryPostProcessor 구현시 두번째 단계인 invokeBeanFactoryPostProcessors메소드를 실행하게 되는데 이 과정에서 bean 생성에 필요한 BeanDefinition을 추가하거나 BeanFactory 생성 후 작업을 진행하게 됩니다.</p>
<h2 id="spring-4x">Spring 4.x</h2>
<p>invokeBeanFactoryPostProcessors 메소드 실행과정에서 bean이 Singleton으로 생성되며 beanFactory에 저장되게 됩니다.
PriorityOrdered를 구현하거나 <code class="language-plaintext highlighter-rouge">@Order</code>어노테이션이 추가되 있지 않다면 위 코드에서 beanFactory.getBean시 생성되어 beanFactory에 저장되게 됩니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// PostProcessorRegistrationDelegate.java</span>
<span class="c1">// Finally, invoke all other BeanFactoryPostProcessors.</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">BeanFactoryPostProcessor</span><span class="o">></span> <span class="n">nonOrderedPostProcessors</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o"><</span><span class="nc">BeanFactoryPostProcessor</span><span class="o">>();</span>
<span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">postProcessorName</span> <span class="o">:</span> <span class="n">nonOrderedPostProcessorNames</span><span class="o">)</span> <span class="o">{</span>
<span class="n">nonOrderedPostProcessors</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">.</span><span class="na">getBean</span><span class="o">(</span><span class="n">postProcessorName</span><span class="o">,</span> <span class="nc">BeanFactoryPostProcessor</span><span class="o">.</span><span class="na">class</span><span class="o">));</span>
<span class="o">}</span>
<span class="n">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">nonOrderedPostProcessors</span><span class="o">,</span> <span class="n">beanFactory</span><span class="o">);</span>
</code></pre></div></div>
<p>Bean 생성이후 BeanFactoryPostProcessor.postProcessBeanFactory 메소드를 실행한 후 invokeBeanFactoryPostProcessors 메소드 실행과정이 끝나며 이 때 ImportAware.setImportMetadata는 실행되지 않습니다.
이 후 ImportAware.setImportMetadata 메소드가 실행되는 시점은 finishBeanFactoryInitialization 메소드에서 BeanDefinition중 생성되지 않은 bean에 대해 생성하면서 실행되게 되는데 이 때 BeanFactoryPostProcessor를 구현한 구현체는 이미 singleton으로 생성되었기 때문에 더 이상 실행되지 않고 끝나게 됩니다.</p>
<h2 id="spring-5x">Spring 5.x</h2>
<p>5.x 버전의 경우 똑같이 invokeBeanFactoryPostProcessors메소드 실행과정을 거치나 BeanDefinitionRegistryPostProcessor중 ConfigurationClassPostProcessor의 postProcessBeanFactory 동작시 차이가 생깁니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// PostProcessorRegistrationDelegate.java</span>
<span class="n">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">registryProcessors</span><span class="o">,</span> <span class="n">beanFactory</span><span class="o">);</span>
<span class="n">invokeBeanFactoryPostProcessors</span><span class="o">(</span><span class="n">regularPostProcessors</span><span class="o">,</span> <span class="n">beanFactory</span><span class="o">);</span>
</code></pre></div></div>
<p>BeanFactoryPostProcessor만 구현한 구현체를 처리하기전 BeanDefinitionRegistryPostProcessor를 구현한 구현체를 먼저 처리하는 과정이 있는데 Spring에서 기본적으로 실행하는 구현체로 ConfigurationClassPostProcessor가 존재합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//ConfigurationClassPostProcessor.java</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">postProcessBeanFactory</span><span class="o">(</span><span class="nc">ConfigurableListableBeanFactory</span> <span class="n">beanFactory</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">factoryId</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">identityHashCode</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">factoriesPostProcessed</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">factoryId</span><span class="o">))</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">IllegalStateException</span><span class="o">(</span>
<span class="s">"postProcessBeanFactory already called on this post-processor against "</span> <span class="o">+</span> <span class="n">beanFactory</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">this</span><span class="o">.</span><span class="na">factoriesPostProcessed</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">factoryId</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="k">this</span><span class="o">.</span><span class="na">registriesPostProcessed</span><span class="o">.</span><span class="na">contains</span><span class="o">(</span><span class="n">factoryId</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// BeanDefinitionRegistryPostProcessor hook apparently not supported...</span>
<span class="c1">// Simply call processConfigurationClasses lazily at this point then.</span>
<span class="n">processConfigBeanDefinitions</span><span class="o">((</span><span class="nc">BeanDefinitionRegistry</span><span class="o">)</span> <span class="n">beanFactory</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">enhanceConfigurationClasses</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">);</span>
<span class="n">beanFactory</span><span class="o">.</span><span class="na">addBeanPostProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">ImportAwareBeanPostProcessor</span><span class="o">(</span><span class="n">beanFactory</span><span class="o">));</span>
<span class="o">}</span>
</code></pre></div></div>
<p>위 구현체의 먼저 실행하는 postProcessBeanFactory 메소드를 보면 마지막줄에 beanFactory에 ImportAwareBeanPostProcessor를 추가하는 부분이 있습니다.
이로 인해 bean을 생성할때 ImportAwareBeanPostProcessor.postProcessBeforeInitialization이 실행되면서 ImportAware.setImportMetadata 메소드가 먼저 실행하게 되고 그 이후 BeanFactoryPostProcessor.postProcessBeanFactory 실행되게 됩니다.
참고로 ConfigurationClassPostProcessor는 PriorityOrdered를 구현한 구현체로 실행순서는 최하위 입니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">int</span> <span class="nf">getOrder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="nc">Ordered</span><span class="o">.</span><span class="na">LOWEST_PRECEDENCE</span><span class="o">;</span> <span class="c1">// within PriorityOrdered</span>
<span class="o">}</span>
</code></pre></div></div>
<p>그래서 BeanFactoryPostProcessor가 아닌 BeanDefinitionRegistryPostProcessor를 구현한 구현체와 함께 ImportAware를 사용할 경우 5.x 버전에서도 똑같이 동작하지 않게 됩니다.</p>
<h2 id="conclusion">Conclusion</h2>
<ul>
<li>BeanFactoryPostProcessor는 bean을 생성하기 전 BeanFactory 초기화 이후 실행되며 ImportAware는 bean이 생성되는 시점에 실행되므로 일반적으로 실행시점은 ImportAware가 더 늦게 실행됩니다.</li>
<li>bean생성 이전 (또는 BeanFactoryPostProcessor실행 이전)에 로직을 실행하고 싶을 경우 ImportBeanDefinitaionRegistrar를 이용할 수 있습니다.</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">LocalCacheConfig</span> <span class="kd">implements</span> <span class="nc">ImportBeanDefinitionRegistrar</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">registerBeanDefinitions</span><span class="o">(</span><span class="nc">AnnotationMetadata</span> <span class="n">importingClassMetadata</span><span class="o">,</span> <span class="nc">BeanDefinitionRegistry</span> <span class="n">registry</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">importingClassMetadata</span><span class="o">.</span><span class="na">hasAnnotation</span><span class="o">(</span><span class="nc">EnableLocalCache</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">()))</span> <span class="o">{</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="n">metaData</span> <span class="o">=</span> <span class="n">importingClassMetadata</span><span class="o">.</span><span class="na">getAnnotationAttributes</span><span class="o">(</span><span class="nc">EnableLocalCache</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span>
<span class="err">……</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Exception</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">log</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="s">"localcache initialize error : {}"</span><span class="o">,</span> <span class="n">e</span><span class="o">.</span><span class="na">getMessage</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>ConfigurationClassPostProcessor가 postProcessBeanDefinitionRegistry를 실행하면서 ImportBeanDefinitaionRegistrar구현체를 모두 실행해줍니다.</p>
<ul>
<li>POC진행은 꼭 같은버전에서 하세요 ㅠㅠㅠㅠㅠ</li>
</ul>
어노테이션 반복정의를 위한 @Repeatable 작성법과 주의점
2018-08-31T00:00:00+00:00
2018-08-31T00:00:00+00:00
https://github.com/jistol/java/2018/08/31/annotation-repeatable
<p>개발시 어노테이션을 많이 이용하는 편인데 종종 하나의 클래스, 또는 메소드에 여러 속성을 정의하고 싶을때가 있습니다. <br />
JDK ~1.7 에서는 아래와 같은 방식으로 정의가 가능했습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// case 1</span>
<span class="nd">@GreenColor</span>
<span class="nd">@BlueColor</span>
<span class="nd">@RedColor</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RGBColor</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
<span class="c1">// case 2</span>
<span class="nd">@Color</span><span class="o">(</span><span class="n">colors</span><span class="o">={</span><span class="s">"green"</span><span class="o">,</span> <span class="s">"blue"</span><span class="o">,</span> <span class="s">"red"</span><span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RGBColor</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
</code></pre></div></div>
<p>JDK 1.8부터는 같은 어노테이션을 중복정의 가능한 <code class="language-plaintext highlighter-rouge">@Repeatable</code> 어노테이션을 제공합니다.</p>
<h2 id="repeatable-tutorial">@Repeatable Tutorial</h2>
<p>JavaDoc에 정의된 정식 설명은 아래와 같습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The annotation type java.lang.annotation.Repeatable is used to indicate that the annotation type whose declaration it (meta-)annotates is repeatable. The value of @Repeatable indicates the containing annotation type for the repeatable annotation type.
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">@Repeatable</code> 어노테이션을 이용하면 다음과 같이 정의가 가능해집니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Color</span><span class="o">(</span><span class="s">"green"</span><span class="o">)</span>
<span class="nd">@Color</span><span class="o">(</span><span class="s">"blue"</span><span class="o">)</span>
<span class="nd">@Color</span><span class="o">(</span><span class="s">"red"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RGBColor</span> <span class="o">{</span> <span class="o">...</span> <span class="o">}</span>
</code></pre></div></div>
<p>어노테이션을 정의하는 방법은 실 사용할 어노테이션과 그 어노테이션 묶음 값을 관리하는 컨테이너 어노테이션을 작성해야 합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Repeatable</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="nc">Colors</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Color</span> <span class="o">{}</span>
<span class="nd">@Target</span><span class="o">(</span><span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span><span class="o">)</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@Documented</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Colors</span> <span class="o">{</span>
<span class="nc">Color</span><span class="o">[]</span> <span class="nf">value</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>위와 같이 정의시 아래와 같은 방법으로 해당 클래스에 정의된 어노테이션을 뽑아올 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Colors</span> <span class="n">colors</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getAnnotation</span><span class="o">(</span><span class="nc">Colors</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="주의점">주의점</h2>
<p>위와 같이 설정된 상태에서 아래와 같이 클래스를 작성하게 될 경우 이슈가 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AnnotationTest</span> <span class="o">{</span>
<span class="nd">@Repeatable</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="nc">Colors</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Color</span> <span class="o">{</span>
<span class="nc">String</span> <span class="nf">value</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Colors</span> <span class="o">{</span>
<span class="nc">Color</span><span class="o">[]</span> <span class="nf">value</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@Color</span><span class="o">(</span><span class="s">"green"</span><span class="o">)</span>
<span class="nd">@Color</span><span class="o">(</span><span class="s">"blue"</span><span class="o">)</span>
<span class="nd">@Color</span><span class="o">(</span><span class="s">"red"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RGBColor</span> <span class="o">{}</span>
<span class="nd">@Color</span><span class="o">(</span><span class="s">"green"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">GreenColor</span> <span class="o">{}</span>
<span class="nd">@Test</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">repeatableAnnotationTest</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">RGBColor</span> <span class="n">rgbColor</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RGBColor</span><span class="o">();</span>
<span class="nc">GreenColor</span> <span class="n">greenColor</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GreenColor</span><span class="o">();</span>
<span class="nc">Colors</span> <span class="n">rgbColors</span> <span class="o">=</span> <span class="n">rgbColor</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getAnnotation</span><span class="o">(</span><span class="nc">Colors</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">Color</span><span class="o">[]</span> <span class="n">rgbColorArray</span> <span class="o">=</span> <span class="n">rgbColor</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getAnnotationsByType</span><span class="o">(</span><span class="nc">Color</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">Colors</span> <span class="n">greenColors</span> <span class="o">=</span> <span class="n">greenColor</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getAnnotation</span><span class="o">(</span><span class="nc">Colors</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">Color</span><span class="o">[]</span> <span class="n">greenColorArray</span> <span class="o">=</span> <span class="n">greenColor</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">getAnnotationsByType</span><span class="o">(</span><span class="nc">Color</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"rgbColors : "</span> <span class="o">+</span> <span class="n">rgbColors</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"rgbColorArray : "</span> <span class="o">+</span> <span class="n">rgbColorArray</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"rgbColorArray.length : "</span> <span class="o">+</span> <span class="o">(</span><span class="n">rgbColorArray</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">rgbColorArray</span><span class="o">.</span><span class="na">length</span> <span class="o">:</span> <span class="mi">0</span><span class="o">));</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"greenColors : "</span> <span class="o">+</span> <span class="n">greenColors</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"greenColorArray : "</span> <span class="o">+</span> <span class="n">greenColorArray</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"greenColorArray.length : "</span> <span class="o">+</span> <span class="o">(</span><span class="n">greenColorArray</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">greenColorArray</span><span class="o">.</span><span class="na">length</span> <span class="o">:</span> <span class="mi">0</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>테스트 코드를 돌려보면 결과는 아래와 같이 나옵니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rgbColors : @com.tmoncorp.module.sduf.test.common.AnnotationTest$Colors(value=[@com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=green), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=blue), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=red)])
rgbColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@1996cd68
rgbColorArray.length : 3
greenColors : null
greenColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@3339ad8e
greenColorArray.length : 0
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">GreenColor</code>의 클래스는 <code class="language-plaintext highlighter-rouge">@Color</code>어노테이션이 1개만 정의되어 있어 <code class="language-plaintext highlighter-rouge">@Colors</code>로 묶이지 않고 <code class="language-plaintext highlighter-rouge">getAnnotation</code>로 가져올 경우 null을 반환하게 됩니다. <br />
그리고 <code class="language-plaintext highlighter-rouge">@Color</code>어노테이션이 1개만 정의되어 있을 경우 <code class="language-plaintext highlighter-rouge">@Colors</code>로 묶이지 않아 Retention Policy 정책이 생성되지 않으므로 <code class="language-plaintext highlighter-rouge">getAnnotationsByType</code> 메소드 호출시 반환값이 0개가 됩니다.</p>
<p>위와 같은 현상을 해결하기 위해서는 하위 어노테이션 정의시 <code class="language-plaintext highlighter-rouge">@Retention</code>을 정의해주고 <code class="language-plaintext highlighter-rouge">getAnnotationsByType</code>메소드를 이용하여 값을 찾으면 정확한 반환값을 찾을 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@Repeatable</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="nc">Colors</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">Color</span> <span class="o">{</span>
<span class="nc">String</span> <span class="nf">value</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>실행하면 아래와 같이 값이 존재하는것을 확인할 수 있습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rgbColors : @com.tmoncorp.module.sduf.test.common.AnnotationTest$Colors(value=[@com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=green), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=blue), @com.tmoncorp.module.sduf.test.common.AnnotationTest$Color(value=red)])
rgbColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@19e1023e
rgbColorArray.length : 3
greenColors : null
greenColorArray : [Lcom.tmoncorp.module.sduf.test.common.AnnotationTest$Color;@7cef4e59
greenColorArray.length : 1
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Repeatable.html">Oracle JDK Doc - Annotation Type Repeatable</a> <br />
<a href="https://www.javabrahman.com/java-8/java-8-repeating-annotations-tutorial/">Java 8 Repeating Annotations Tutorial using @Repeatable with examples</a></p>
Consistent Hashing과 Memecached를 이용한 테스트 샘플
2018-07-07T00:00:00+00:00
2018-07-07T00:00:00+00:00
https://github.com/jistol/software%20engineering/2018/07/07/consistent-hashing-sample
<h2 id="consistent-hashing-이란">Consistent Hashing 이란?</h2>
<p>웹 서버의 개수가 수시로 변경될 때 요청에 대해 분산하는 방법으로 Key의 집합을 K, 노드(또는 서버)의 크기를 N라고 했을 때, N의 갯수가 바뀌더라도 대부분의 키들이 노드를 그대로 사용할 수 있습니다. <br />
예를 들어 10000개의 key값을 5개의 노드에 분산할 경우 2000개씩 나뉘는데 개중 하나의 노드가 죽더라도 전체를 재 분배하는 것이 아니라 2000개의 key에 대해서만 재분배 합니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/1.png" alt="Consistent Hashing Node Example" /></p>
<p>예를 들어 일반 Hash를 이용하여 노드를 분산할 경우 아래 그림과 같은 방식으로 접근 할 수 있습니다. <br />
3으로 나눈 나머지 값을 이용하여 3개 노드중 하나를 접근하도록 설정 할 수 있는데 이런 방식의 경우 하나의 노드가 죽었을 경우 모든 key값에 대해 다시 연산해야하는 문제가 생깁니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/2.png" alt="Compare Hash" /></p>
<p>Consistent Hashing의 경우 각 노드를 링의 개념으로 연결하여 각 key가 포함되어야 하는 특정 구간을 결정하게 됩니다. <br />
이 과정에서 하나의 노드가 죽는다 하여도 해당 노드에 속한 Key값은 다음 구간을 담당하는 노드에서 처리하게 되므로 나머지 노드의 key를 재분배 하지 않아도 됩니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/3.png" alt="Compare Consistent Hashing" /></p>
<h2 id="example-hashring">Example: HashRing</h2>
<p>위에서 설명한 링의 개념에 대해 실제 key에 따라 어떻게 노드를 결정하는지 아래와 같이 예시를 만들어보았습니다.</p>
<h2 id="분배규칙--a--1-b--3-c--5-d--7">분배규칙 : A < 1, B < 3, C < 5, D < 7</h2>
<p>다음과 같은 규칙일 때 <br />
맨 처음 ‘key=1’이 인입 될 경우 아래와 같이 위치 합니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/4.png" alt="Example 1" /></p>
<p>그 다음 ‘key=4’가 인입되면 아래와 같이 위치 합니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/5.png" alt="Example 2" /></p>
<p>그 외 여러 key값이 인입하게 되어 아래와 같은 상황이 되었습니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/6.png" alt="Example 3" /></p>
<p>이 때 노드 B가 죽고 ‘key=1’이 유실됩니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/7.png" alt="Example 4" /></p>
<p>노드 B가 죽은 상태에서 ‘key=1’이 다시 인입되면 B의 다음 구간인 C에 포함되게 됩니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/8.png" alt="Example 5" /></p>
<p>위와 같이 특정 노드가 죽더라도 전체 key를 재분배 하지 않게 됩니다. <br />
하지만 Hashring에도 문제가 있는데 아래와 같이 노드 B가 되살아나고</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/9.png" alt="Example 6" /></p>
<p>노드 B가 되살아난 상태에서 다시 ‘key=1’가 인입되면 아래 그림과 같이 중복된 key와 value가 존재하게 됩니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/10.png" alt="Example 7" /></p>
<p>위 상황에서 다시 노드 B가 죽을 경우 노드 C에 있던 이전 값이 노출 될 수 있는 문제가 있는데 memcached의 경우 만료시간을 설정하여 해결 할 수 있습니다.</p>
<h2 id="가상노드">가상노드</h2>
<p>특정 노드가 죽으면 해당 key가 모두 다른 노드에 분배되는데 위 예제에서 보면 노드 B가 죽을 경우 노드 C에 key가 몰리는 것처 보이지만 사실상 아래 그림과 같이 각 노드는 가상의 노드를 여러개 만들어 노드가 죽었을때도 균등하게 분배되도록 처리합니다.</p>
<p><img src="/assets/img/softwareengineering/consistent-hashing-sample/11.png" alt="vituralnodes" /></p>
<h2 id="memcached-샘플파일">Memcached 샘플파일</h2>
<p>위 이론을 바탕으로 실제 Memcached를 이용하여 Consistent Hashing이 어떻게 동작하는지 확인 할 수 있는 샘플 프로젝트입니다.<br />
Key에 대한 분배및 Proxy 역활은 소스에 포함되어 있는 <code class="language-plaintext highlighter-rouge">Simple-Spring-Memcached(ssm)</code>에서 담당하게되며 git을 설치 후 clone 명령을 이용하여 다운받을 수 있습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone https<span class="p">:</span><span class="sr">//</span>github<span class="p">.</span><span class="k">com</span><span class="sr">/jistol/</span>docker<span class="p">-</span>compose<span class="p">-</span>memcached<span class="p">-</span>multi<span class="p">-</span>test<span class="p">-</span>sample<span class="p">.</span>git
</code></pre></div></div>
<h2 id="구성">구성</h2>
<ul>
<li>Gradle</li>
<li>Spring Boot</li>
<li>Google Simple Spring Memcached (SSM)</li>
</ul>
<h2 id="memcached-실행">Memcached 실행</h2>
<ol>
<li>
<p>Docker 설치</p>
</li>
<li>Memcached Image 다운로드
<ul>
<li>docker pull memcached</li>
</ul>
</li>
<li>docker-compose를 이용하여 실행
<ul>
<li>${PROJECT_HOME}/docker-compose up -d</li>
<li>위와 같이 실행하면 아래와 같이 3개의 포트로 memcached 서버가 기동됩니다.</li>
</ul>
</li>
</ol>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker<span class="p">-</span>compose <span class="k">up</span> <span class="p">-</span><span class="k">d</span>
Creating network <span class="s2">"memcached_default"</span> with the default driver
Creating memcached_memcached3_1 <span class="p">...</span> done
Creating memcached_memcached1_1 <span class="p">...</span> done
Creating memcached_memcached2_1 <span class="p">...</span> done
$ docker <span class="k">ps</span> <span class="p">-</span><span class="k">a</span>
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
<span class="m">8158</span>e741dacb memcached <span class="s2">"docker-entrypoint.s…"</span> <span class="m">13</span> seconds ago Up <span class="m">10</span> seconds <span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">:</span><span class="m">11212</span><span class="p">-></span><span class="m">11211</span>/tcp memcached_memcached2_1
<span class="m">2627</span>e168914e memcached <span class="s2">"docker-entrypoint.s…"</span> <span class="m">13</span> seconds ago Up <span class="m">9</span> seconds <span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">:</span><span class="m">11211</span><span class="p">-></span><span class="m">11211</span>/tcp memcached_memcached1_1
<span class="m">4</span>c6daf4fc275 memcached <span class="s2">"docker-entrypoint.s…"</span> <span class="m">13</span> seconds ago Up <span class="m">9</span> seconds <span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">:</span><span class="m">11213</span><span class="p">-></span><span class="m">11211</span>/tcp memcached_memcached3_1
</code></pre></div></div>
<h2 id="test">Test</h2>
<p>아래와 같이 2가지 테스트를 할 수 있도록 구성되어 있습니다.</p>
<h3 id="was-1--memcached-3--consistent-hashing-테스트">WAS 1 + Memcached 3 : Consistent Hashing 테스트</h3>
<ul>
<li>gradle 명령을 이용하여 Tomcat 기동합니다.</li>
</ul>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gradle clean build <span class="p">-</span><span class="k">x</span> bootRun
</code></pre></div></div>
<ul>
<li>아래 URL을 통해 캐시를 주입합니다.</li>
</ul>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST : http://localhost:8080/{key}/{value}
</code></pre></div></div>
<ul>
<li>telnet으로 Memcached에 접속, 값이 들어갔는지 확인합니다.</li>
</ul>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ telnet localhost <span class="m">11211</span>
Trying <span class="p">::</span><span class="m">1</span><span class="p">...</span>
Connected <span class="k">to</span> localhost<span class="p">.</span>
Escape character <span class="k">is</span> <span class="s1">'^]'</span><span class="p">.</span>
<span class="nb">get</span> <span class="p">{</span><span class="nb">key</span><span class="p">}</span>
$ telnet localhost <span class="m">11212</span>
Trying <span class="p">::</span><span class="m">1</span><span class="p">...</span>
Connected <span class="k">to</span> localhost<span class="p">.</span>
Escape character <span class="k">is</span> <span class="s1">'^]'</span><span class="p">.</span>
<span class="nb">get</span> <span class="p">{</span><span class="nb">key</span><span class="p">}</span>
$ telnet localhost <span class="m">11213</span>
Trying <span class="p">::</span><span class="m">1</span><span class="p">...</span>
Connected <span class="k">to</span> localhost<span class="p">.</span>
Escape character <span class="k">is</span> <span class="s1">'^]'</span><span class="p">.</span>
<span class="nb">get</span> <span class="p">{</span><span class="nb">key</span><span class="p">}</span>
</code></pre></div></div>
<p>위 과정을 반복하면 입력한 KEY값이 동일한 노드의 Memcached에 들어가는것을 볼 수 있습니다.</p>
<h3 id="was-3--memcached-3--서버등록-순서-오류에-의한-키배분-오류-테스트">WAS 3 + Memcached 3 : 서버등록 순서 오류에 의한 키배분 오류 테스트</h3>
<p><code class="language-plaintext highlighter-rouge">resource/application.yml</code> 파일을 보면 아래와 같이 각 profiles마다 다르게 서버 순서를 나열해 두었습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">server.port</span><span class="pi">:</span> <span class="m">8080</span>
<span class="na">mem-server</span><span class="pi">:</span> <span class="s">localhost:11211 localhost:11212 localhost:11213</span>
<span class="nn">---</span>
<span class="s">spring.profiles</span><span class="pi">:</span> <span class="s">local1</span>
<span class="s">server.port</span><span class="pi">:</span> <span class="m">8081</span>
<span class="na">mem-server</span><span class="pi">:</span> <span class="s">localhost:11212 localhost:11213 localhost:11211</span>
<span class="nn">---</span>
<span class="s">spring.profiles</span><span class="pi">:</span> <span class="s">local2</span>
<span class="s">server.port</span><span class="pi">:</span> <span class="m">8082</span>
<span class="na">mem-server</span><span class="pi">:</span> <span class="s">localhost:11213 localhost:11211 localhost:11212</span>
</code></pre></div></div>
<p>이와 같이 설정시 인입된 서버 port에 따라 동일한 key에 대해 ssm이 다르게 분배하는 것을 확인 하는 테스트입니다.</p>
<ul>
<li>gradle 명령을 이용하여 각 profile별로 Tomcat 3대를 기동합니다.</li>
</ul>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gradle clean build <span class="p">-</span><span class="k">x</span> bootRun
$ gradle clean build <span class="p">-</span><span class="k">x</span> bootRun <span class="p">-</span>Dspring<span class="p">.</span>profiles<span class="p">.</span>active<span class="p">=</span>local1
$ gradle clean build <span class="p">-</span><span class="k">x</span> bootRun <span class="p">-</span>Dspring<span class="p">.</span>profiles<span class="p">.</span>active<span class="p">=</span>local2
</code></pre></div></div>
<ul>
<li>아래 URL을 통해 캐시를 주입합니다.</li>
</ul>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST : http://localhost:8080/{key}/{value}
POST : http://localhost:8081/{key}/{value}
POST : http://localhost:8082/{key}/{value}
</code></pre></div></div>
<ul>
<li>telnet으로 Memcached에 접속, 값이 들어갔는지 확인합니다.</li>
</ul>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ telnet localhost <span class="m">11211</span>
Trying <span class="p">::</span><span class="m">1</span><span class="p">...</span>
Connected <span class="k">to</span> localhost<span class="p">.</span>
Escape character <span class="k">is</span> <span class="s1">'^]'</span><span class="p">.</span>
<span class="nb">get</span> <span class="p">{</span><span class="nb">key</span><span class="p">}</span>
$ telnet localhost <span class="m">11212</span>
Trying <span class="p">::</span><span class="m">1</span><span class="p">...</span>
Connected <span class="k">to</span> localhost<span class="p">.</span>
Escape character <span class="k">is</span> <span class="s1">'^]'</span><span class="p">.</span>
<span class="nb">get</span> <span class="p">{</span><span class="nb">key</span><span class="p">}</span>
$ telnet localhost <span class="m">11213</span>
Trying <span class="p">::</span><span class="m">1</span><span class="p">...</span>
Connected <span class="k">to</span> localhost<span class="p">.</span>
Escape character <span class="k">is</span> <span class="s1">'^]'</span><span class="p">.</span>
<span class="nb">get</span> <span class="p">{</span><span class="nb">key</span><span class="p">}</span>
</code></pre></div></div>
<p>위 과정을 반복하면 동일한 key를 넣더라도 어느 port에 접근하여 넣었느냐에 따라 위치가 뒤 섞여있는 것을 확인할 수 있습니다.</p>
Observer 패턴과 Publisher/Subscriber(Pub-Sub) 패턴의 차이점
2018-04-11T00:00:00+00:00
2018-04-11T00:00:00+00:00
https://github.com/jistol/software%20engineering/2018/04/11/observer-pubsub-pattern
<p>본 글은 <a href="https://www.youtube.com/watch?v=8fenTR3KOJo">토비의 봄 TV 5회 스프링 리액티브 프로그래밍 (1) - Reactive Streams</a> 영상을 보던 중 “Observer패턴과 Pub-Sub패턴의 차이”에 대한 얘기가 나와 궁금해 찾아 본 자료를 정리한 문서입니다. <br />
“Head First Design Patterns” 책에는 <code class="language-plaintext highlighter-rouge">Obaserver Pattern == Pub-Sub Pattern</code>으로 나와 있지만 실제 찾아보면 비슷한 개념 사이에 확연한 차이점이 존재합니다.</p>
<p>가장 큰 차이점은 중간에 <code class="language-plaintext highlighter-rouge">Message Broker</code> 또는 <code class="language-plaintext highlighter-rouge">Event Bus</code>가 존재하는지 여부입니다.</p>
<p><img src="/assets/img/softwareengineering/observer-pubsub-pattern/1.png" alt="Pattern Notification" /></p>
<h2 id="observer패턴은-observer와-subject가-서로를-인지하지만-pub-sub패턴의-경우-서로를-전혀-몰라도-상관없습니다">Observer패턴은 Observer와 Subject가 서로를 인지하지만 Pub-Sub패턴의 경우 서로를 전혀 몰라도 상관없습니다.</h2>
<p>Observer패턴의 경우 Subject에 Observer를 등록하고 Subject가 직접 Observer에 직접 알려주어야 합니다.</p>
<p>Pub-Sub패턴의 경우 Publisher가 Subscriber의 위치나 존재를 알 필요없이 Message Queue와 같은 Broker역활을 하는 중간지점에 메시지를 던져 놓기만 하면 됩니다. <br />
반대로 Subscriber 역시 Publisher의 위치나 존재를 알 필요없이 Broker에 할당된 작업만 모니터링하다 할당 받아 작업하면 되기 때문에 Publisher와 Subscriber가 서로 알 필요가 없습니다.</p>
<h2 id="observer패턴에-비해-pub-sub패턴이-더-결합도가-낮습니다loose-coupling">Observer패턴에 비해 Pub-Sub패턴이 더 결합도가 낮습니다.(Loose Coupling)##</h2>
<p>Publisher와 Subscriber가 서로의 존재를 알 필요가 없기 때문에 당연히 소스코드 역시 겹치거나 의존할 일이 없습니다. <br />
만약 결합도가 높다면 의도하거나 잘못된 코딩일 가능성이 큽니다.</p>
<h2 id="observer패턴은-대부분-동기synchronous-방식으로-동작하나-pub-sub패턴은-대부분-비동기asynchronous-방식으로-동작합니다">Observer패턴은 대부분 동기(synchronous) 방식으로 동작하나 Pub-Sub패턴은 대부분 비동기(asynchronous) 방식으로 동작합니다.##</h2>
<p>이유는 Broker로 MessageQueue를 많이 사용하기 때문입니다.</p>
<h2 id="observer패턴은-단일-도메인-하에서-구현되어야-하나-pub-sub패턴은-크로스-도메인-상황에서도-구현-가능합니다">Observer패턴은 단일 도메인 하에서 구현되어야 하나 Pub-Sub패턴은 크로스 도메인 상황에서도 구현 가능합니다.</h2>
<p>이 역시 Broker라는 중간 매개체가 있기 때문인데 어플리케이션의 도메인이 다르더라도 MessageQueue(Broker)에 접근만 가능하다면 처리가 가능하기 때문입니다.</p>
<h2 id="참고">참고</h2>
<p>Observer vs Pub-Sub pattern : <a href="https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c">https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c</a></p>
HTML5(video) + Spring Boot(Tomcat)로 동영상 재생하기
2018-04-04T00:00:00+00:00
2018-04-04T00:00:00+00:00
https://github.com/jistol/spring/2018/04/04/springboot-video-streaming
<p>HTML5의 video태그를 이용하여 파일 재생시 SpringBoot 에서 어떻게 설정해야하는지 간단한 방법이 있어 정리합니다.</p>
<h2 id="샘플소스">샘플소스</h2>
<p>기존 다른 방식들은 response에 파일을 직접 쓰도록 로직에 모두 구현을 했어야 하는데 <code class="language-plaintext highlighter-rouge">StreamingResponseBody</code>를 이용하여 아래와 같이 간단해집니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">...</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">DIR</span> <span class="o">=</span> <span class="s">"${FILE_DIR}/"</span><span class="o">;</span>
<span class="nd">@GetMapping</span><span class="o">(</span><span class="s">"/download"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">StreamingResponseBody</span> <span class="nf">stream</span><span class="o">(</span><span class="nc">HttpServletRequest</span> <span class="n">req</span><span class="o">,</span> <span class="nd">@RequestParam</span><span class="o">(</span><span class="s">"fileName"</span><span class="o">)</span> <span class="nc">String</span> <span class="n">fileName</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Exception</span> <span class="o">{</span>
<span class="nc">File</span> <span class="n">file</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">File</span><span class="o">(</span><span class="no">DIR</span> <span class="o">+</span> <span class="n">fileName</span><span class="o">);</span>
<span class="kd">final</span> <span class="nc">InputStream</span> <span class="n">is</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">file</span><span class="o">);</span>
<span class="k">return</span> <span class="n">os</span> <span class="o">-></span> <span class="o">{</span>
<span class="n">readAndWrite</span><span class="o">(</span><span class="n">is</span><span class="o">,</span> <span class="n">os</span><span class="o">);</span>
<span class="o">};</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">readAndWrite</span><span class="o">(</span><span class="kd">final</span> <span class="nc">InputStream</span> <span class="n">is</span><span class="o">,</span> <span class="nc">OutputStream</span> <span class="n">os</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">2048</span><span class="o">];</span>
<span class="kt">int</span> <span class="n">read</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">while</span> <span class="o">((</span><span class="n">read</span> <span class="o">=</span> <span class="n">is</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">data</span><span class="o">))</span> <span class="o">></span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
<span class="n">os</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">data</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">read</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">os</span><span class="o">.</span><span class="na">flush</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">...</span>
</code></pre></div></div>
<p>HTML 소스에서는 아래와 같이 호출합니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
<span class="nt"><video</span> <span class="na">controls</span> <span class="na">src=</span><span class="s">"/download?fileName=test.mp4"</span><span class="nt">></span>
not use video
<span class="nt"></video></span>
...
</code></pre></div></div>
<h2 id="원리">원리</h2>
<ol>
<li>
<p>위 방식은 Progressive Download방식으로 서버에서는 요청시마다 전체 파일을 보내주고 video 태그에서는 점진적으로 필요한 만큼씩 OutputStream에서 읽어가게 됩니다.
실제로 <code class="language-plaintext highlighter-rouge">readAndWrite</code> 메소드의 while구문에서 로그를 찍어보면 동영상을 재생하지 않을 경우 write를 중간에 멈춰 있는 것을 볼 수 있습니다.</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">StreamingResponseBody</code> 클래스는 <code class="language-plaintext highlighter-rouge">TaskExecutor</code>을 이용하여 비동기 서블릿 실행을 지원해줍니다. Spring API 문서를 보면 아래와 같이 설명이 되어 있습니다.</p>
</li>
</ol>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A controller method return value type for asynchronous request processing where the application can write directly to the response OutputStream without holding up the Servlet container thread.
Note: when using this option it is highly recommended to configure explicitly the TaskExecutor used in Spring MVC for executing asynchronous requests. Both the MVC Java config and the MVC namespaces provide options to configure asynchronous handling. If not using those, an application can set the taskExecutor property of RequestMappingHandlerAdapter.
</code></pre></div></div>
<ol>
<li>비동기이긴 하나 논블락킹은 아니기 때문에 별도 Thread를 계속 점유하고 있는 문제가 있습니다.</li>
<li>동영상 플레이를 하지 않고 대기시 async timeout 설정을 별도로 하지 않으면 중간에 연결이 끊겨버리며 <code class="language-plaintext highlighter-rouge">DISPATCH_PENDING</code>에러를 발생시킵니다. 또한 video태그는 다시 동영상을 재생하기 위해 같은 URL을 또 호출하게 되고 서버는 처음부터 다시 파일을 보내게 됩니다.</li>
</ol>
<h2 id="참고">참고</h2>
<p>Streaming 기술 이해 : <a href="http://linuxism.tistory.com/1267">http://linuxism.tistory.com/1267</a>
Spring API (StreamingResponseBody): <a href="https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.html">https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/method/annotation/StreamingResponseBody.html</a>
Itube - Spring Boot Streaming Response Body : <a href="https://github.com/shazin/itube">https://github.com/shazin/itube</a></p>
거버넌스(Governance) 모델 정리
2018-03-19T00:00:00+00:00
2018-03-19T00:00:00+00:00
https://github.com/jistol/architecture/2018/03/19/governance-model
<p>조대협님의 “대용량 아키텍처와 성능 튜닝”중 거버넌스 모델을 공부하고 생각을 정리하는 차원에서 요약해봅니다. <br />
정확한 정보및 개념은 “대용량 웹서비스를 위한 마이크로 서비스 아키텍쳐의 이해:<a href="http://bcho.tistory.com/948">http://bcho.tistory.com/948</a>” 를 참고하세요.</p>
<h2 id="거버넌스governance란">거버넌스(Governance)란?</h2>
<ul>
<li>시스템을 개발하는 조직구조 / 프로세스를 정의한 것.</li>
</ul>
<h2 id="중앙-집중형-거버넌스-모델">중앙 집중형 거버넌스 모델</h2>
<ul>
<li>중앙에서 표준 프로세스 및 규약, 정책을 내려줌.</li>
<li>모두 동일하게 개발하기 때문에 유지보수 편함.</li>
</ul>
<h2 id="분산형-거버넌스-모델">분산형 거버넌스 모델</h2>
<ul>
<li>각 서비스에서 자체 규약으로 개발.</li>
<li>표준 API만 외부로 노출.</li>
</ul>
<p>분산형 거버넌스 모델을 수행하기 위한 팀구조의 특징은 아래와 같습니다.</p>
<h2 id="cross-functional-team">Cross functional team</h2>
<ul>
<li>필요한 모든 역활의 인원을 한 팀으로 묶음.</li>
<li>타팀에 대한 의존성이 낮아지기 때문에 빠름 개발 가능.</li>
</ul>
<h2 id="devops">DevOps</h2>
<ul>
<li>개발 + 운영</li>
<li>피드백에 따른 서비스 개선</li>
<li>인프라까지 조절할 수 있어 개선에 따른 저항은 줄어들었으나 난이도는 높아짐</li>
</ul>
<h2 id="projectx-producto">Project(X), Product(O)</h2>
<ul>
<li>프로젝트별로 팀원 투입이 아닌 프로덕트별 구성.</li>
<li>팀원이 해당 프로덕트에 영속됨으로써 지속적인 서비스 개선과 재교육에 대한 비용을 줄임.</li>
</ul>
<p>위 모든 조건이 가능해지면 자체적으로 기획/개발/운영이 가능해지는 <strong>Self-oranized team 모델</strong>이 됨.</p>
<h2 id="주의점--alignment">주의점 : Alignment</h2>
<ul>
<li>팀 간의 수준 격차를 맞춰야 함 : 특정 서비스는 개발속도를 못따라옴</li>
<li>최소한의 표준이 필요</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>MSA를 실현하기 위한 조직 구조로 <strong>분산형 거버넌스 모델</strong>운영에 대해 가이드를 하신것 같습니다. <br />
빠른 서비스 개발및 런칭을 위한 것이 <strong>cross functional team</strong>인 것 같고, 그 이후 지속적인 개선과 운영을 위한 조직으로 <strong>devops</strong>를, <br />
이를 효과적으로 운영하기 위해 팀원을 Project가 아닌 Product별로 배치하도록 하며, 주의점으로 팀간 수준을 맞추거나 최소한의 표준정책등을 지정하여 효율적인 운영을 할 수 있다고 요약하면 될 것 같습니다.</p>
SpringBoot + ES6 + React 기반 보일러플레이트 소스
2018-03-19T00:00:00+00:00
2018-03-19T00:00:00+00:00
https://github.com/jistol/frontend/2018/03/19/boilerplate-boot-es6-react
<p>SpringBoot/ES6/React 기반으로 개발시 기반 소스로 바로 사용할 수 있는 보일러플레이트입니다. <br />
해당 소스에는 간단한 React 샘플이 포함되어 있습니다.</p>
<p>소스를 직접 확인하고 싶으시면 다음 주소에서 확인하시기 바랍니다.</p>
<p>GitHub URL : <a href="https://github.com/jistol/boilerplate-boot-es6">https://github.com/jistol/boilerplate-boot-es6</a></p>
<h2 id="specification">Specification</h2>
<ol>
<li>Backend
<ul>
<li>SpringBoot 2.0.0.RELEASE (Spring MVC, Embedded Tomcat, Thymeleaf)</li>
</ul>
</li>
<li>Frontend Builder
<ul>
<li>Package manager : npm</li>
<li>Bundler : webpack</li>
</ul>
</li>
<li>ES6 Package
<ul>
<li>babel</li>
<li>react</li>
<li>jquery</li>
<li>bootstrap</li>
<li>sass</li>
<li>webpack-dev-server</li>
</ul>
</li>
</ol>
<h2 id="setup--run">Setup & Run</h2>
<h3 id="git-설치-및-소스-다운로드">Git 설치 및 소스 다운로드</h3>
<p>Git 설치주소 : <a href="https://git-scm.com/downloads">https://git-scm.com/downloads</a></p>
<p>소스 다운로드</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone <span class="s2">"https://github.com/jistol/boilerplate-boot-es6.git"</span> boilerplate<span class="p">-</span>boot<span class="p">-</span>es6<span class="p">.</span>git
</code></pre></div></div>
<h3 id="npm-설치-및-초기화">npm 설치 및 초기화</h3>
<p>npm은 Node.js를 설치시 같이 설치 가능합니다.</p>
<p>Node.js 설치주소 : <a href="https://nodejs.org/en/">https://nodejs.org/en/</a></p>
<p>설치 후 ROOT폴더에서 아래 명령어를 통해 초기화를 실행합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install
</code></pre></div></div>
<p>위 명령어를 실행하면 <code class="language-plaintext highlighter-rouge">node_modules</code> 폴더가 생기면서 <code class="language-plaintext highlighter-rouge">package.json</code>에 포함된 라이브러리들이 다운로드 됩니다.</p>
<h3 id="backend-서버-실행">Backend 서버 실행</h3>
<p>Gradle 빌드를 통해 WAR파일을 생성하여 직접 실행 가능하나 SpringBoot를 실행 할 수 있는 Gradle Task 명령으로 실행하여 서버를 기동할 수 있습니다. <br />
Gradle Wrapper가 소스에 포함되어 있으므로 별도의 설치 과정없이 아래와 같이 실행 가능합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ <span class="p">.</span>/gradlew bootRun
</code></pre></div></div>
<p>서버는 기본적으로 8080 포트로 접근 가능합니다.</p>
<h3 id="es6-소스-변환-및-frontend-dev서버-실행">ES6 소스 변환 및 Frontend Dev서버 실행</h3>
<p>일반적으로 ES6를 지원하는 브라우저에서 실행하거나 babel을 통해 호환 가능한 소스로 빌드 후 실행할 수 있는데, 본 소스는 후자의 케이스로 실행하도록 샘플이 작성되어 있습니다. <br />
변환은 아래와 같이 <code class="language-plaintext highlighter-rouge">package.json</code>에 npm 명령을 통해 실행 할 수 있도록 정의되어 있습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="dl">"</span><span class="s2">scripts</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">test</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">echo </span><span class="se">\"</span><span class="s2">Error: no test specified</span><span class="se">\"</span><span class="s2"> && exit 1</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">build</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">npm run build-js</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">build-js</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">./node_modules/.bin/webpack --config webpack.config.babel.js</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">dev</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">NODE_ENV='local' ./node_modules/.bin/webpack-dev-server</span><span class="dl">"</span>
<span class="p">},</span>
<span class="p">...</span>
</code></pre></div></div>
<p>변환 실행은 아래와 같이 실행가능합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm run build
</code></pre></div></div>
<p>또한 빠른 개발을 위해 변경사항을 바로 반영하기 위해서 <code class="language-plaintext highlighter-rouge">webpack-dev-server</code>를 실행 할 수 있습니다. <br />
<code class="language-plaintext highlighter-rouge">webpack-dev-server</code>에 대한 정의는 <code class="language-plaintext highlighter-rouge">webpack.config.babel.js</code> 파일의 아래 부분에서 확인 가능하며 NODE_ENV 값이 ‘local’ 일 경우에만 동작하도록 설정되어 있습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">process</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">NODE_ENV</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">local</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">url</span> <span class="o">=</span> <span class="s2">`localhost`</span><span class="p">,</span>
<span class="nx">protocol</span> <span class="o">=</span> <span class="s2">`http`</span><span class="p">,</span>
<span class="nx">devPort</span> <span class="o">=</span> <span class="mi">8090</span><span class="p">,</span>
<span class="nx">proxyPort</span> <span class="o">=</span> <span class="mi">8080</span><span class="p">,</span>
<span class="nx">demoEntry</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">};</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">entry</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">({},</span> <span class="nx">demoEntry</span><span class="p">,</span> <span class="nx">config</span><span class="p">.</span><span class="nx">entry</span><span class="p">);</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">devtool</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">inline-source-map</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">devServer</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">inline</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">hot</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">historyApiFallback</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">host</span><span class="p">:</span> <span class="nx">url</span><span class="p">,</span>
<span class="na">port</span><span class="p">:</span> <span class="nx">devPort</span><span class="p">,</span>
<span class="na">proxy</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">!/dist/js/**</span><span class="dl">"</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">protocol</span><span class="p">}</span><span class="s2">://</span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">proxyPort</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="na">secure</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">changeOrigin</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nb">Object</span><span class="p">.</span><span class="nx">keys</span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">entry</span><span class="p">).</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">key</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">entry</span><span class="p">[</span><span class="nx">key</span><span class="p">].</span><span class="nx">push</span><span class="p">(</span><span class="s2">`react-hot-loader/patch`</span><span class="p">);</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">entry</span><span class="p">[</span><span class="nx">key</span><span class="p">].</span><span class="nx">push</span><span class="p">(</span><span class="s2">`webpack-dev-server/client?</span><span class="p">${</span><span class="nx">protocol</span><span class="p">}</span><span class="s2">://</span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">:</span><span class="p">${</span><span class="nx">devPort</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">entry</span><span class="p">[</span><span class="nx">key</span><span class="p">].</span><span class="nx">push</span><span class="p">(</span><span class="dl">'</span><span class="s1">webpack/hot/only-dev-server</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">config</span><span class="p">.</span><span class="nx">plugins</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="k">new</span> <span class="nx">webpack</span><span class="p">.</span><span class="nx">HotModuleReplacementPlugin</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">...</span>
</code></pre></div></div>
<p>아래 명령을 통해 실행 할 수 있습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm run dev
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">webpack-dev-server</code>를 사용하기 위해서는 8090 포트로 접근하여야 합니다.</p>
<p>서버 기동후 아래 URL로 접속하면 index 페이지를 볼 수 있으며 링크 클릭시 간단한 React 샘플 예제를 확인 할 수 있습니다.</p>
<p>URL : <a href="http://localhost:8090">http://localhost:8090</a></p>
<p><img src="/assets/img/java/boilerplate-boot-es6-react/1.png" alt="index page" /></p>
<p><img src="/assets/img/java/boilerplate-boot-es6-react/2.png" alt="react sample" /></p>
Spring MVC 생명주기
2018-03-17T00:00:00+00:00
2018-03-17T00:00:00+00:00
https://github.com/jistol/spring/2018/03/17/spring-mvc-structure
<p>Spring 처음 접할때 공부하고 정형화된 틀 안에서 쓰다보니 어떤 구조로 동작하는지 잊고 쓰다가 한 번 소스까서 보면서 정리해봅니다.
특히 정말 SpringBoot 기반으로 개발을 하니깐 DispatchServlet 조차 처음 보는것 같더군요.</p>
<p><img src="/assets/img/java/spring-mvc-structure/1.png" alt="MVC Structure" /></p>
<p>다른 웹페이지 자료들을 보면 좀 모양새가 다른데 소스 기준으로 정리하다보니 이런 구조로 그렸네요.</p>
<h2 id="요청-순서">요청 순서</h2>
<ol>
<li>
<p>요청받은 Request로부터 실행할 Controller 추출을 위해 HandlerMapping 을 통해 실행할 Handler및 Interceptor를 전달</p>
</li>
<li>
<p>Interceptor의 preHandle을 실행</p>
</li>
<li>
<p>HandlerAdapter에 Handler를 전달하여 해당 Controller의 Argument매핑및 Method Invoke 실행하고 결과를 ModelAndView 형태로 반환</p>
</li>
<li>
<p>Interceptor의 postHandle을 실행</p>
</li>
<li>
<p>Resolver를 통해 실제 보여줄 View를 렌더링하여 Response에 Write</p>
</li>
<li>
<p>Interceptor의 afterCompletion 을 실행</p>
</li>
</ol>
Git Branch remove/delete (local/origin)
2018-02-05T00:00:00+00:00
2018-02-05T00:00:00+00:00
https://github.com/jistol/vcs/2018/02/05/git-branch-delete
<p>Local환경과 Remote환경의 Branch를 삭제하는 방법입니다.</p>
<h2 id="local-branch-remove">Local Branch Remove</h2>
<ul>
<li>$ git branch -d [branch_name]</li>
</ul>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch <span class="p">-</span><span class="k">d</span> feature<span class="m">-47</span>
Deleted branch feature<span class="m">-47</span> <span class="p">(</span>was <span class="m">8</span>b6cd3b<span class="p">).</span>
</code></pre></div></div>
<h2 id="remote-branch-remove">Remote Branch Remove</h2>
<ul>
<li>$ git push -d [remote_name] [branch_nane]</li>
</ul>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git push <span class="p">-</span><span class="k">d</span> origin feature<span class="m">-54</span>
To https<span class="p">:</span><span class="sr">//</span>xxxx<span class="p">.</span>xxxx<span class="p">.</span>xxx<span class="sr">/v1/</span>repos/xxxxx
<span class="p">-</span> <span class="p">[</span>deleted<span class="p">]</span> feature<span class="m">-54</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p>How do I delete a Git branch both locally and remotely? : <a href="https://stackoverflow.com/questions/2003505/how-do-i-delete-a-git-branch-both-locally-and-remotely">https://stackoverflow.com/questions/2003505/how-do-i-delete-a-git-branch-both-locally-and-remotely</a></p>
Git Tag
2018-02-01T00:00:00+00:00
2018-02-01T00:00:00+00:00
https://github.com/jistol/vcs/2018/02/01/git-tag
<p>git에서 tagging하는 방법에 대해 간단하게 정리합니다.</p>
<h2 id="태그-생성">태그 생성</h2>
<p>형식 : git tag [태그이름]</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git <span class="k">tag</span> v1<span class="p">.</span><span class="m">0</span>
</code></pre></div></div>
<h2 id="태그-조회">태그 조회</h2>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git <span class="k">tag</span> <span class="p">-</span><span class="k">l</span>
v1<span class="p">.</span><span class="m">0</span>
</code></pre></div></div>
<h2 id="태그-삭제">태그 삭제</h2>
<p>형식 : git tag [태그이름]</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git <span class="k">tag</span> <span class="p">-</span><span class="k">d</span> v1<span class="p">.</span><span class="m">0</span>
</code></pre></div></div>
<h2 id="태그-공유">태그 공유</h2>
<p>형식 : git push [Remote명] [태그이름]</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$git push origin v1<span class="p">.</span><span class="m">0</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p>Git의 기초 - 태그 : <a href="https://git-scm.com/book/ko/v1/Git%EC%9D%98-%EA%B8%B0%EC%B4%88-%ED%83%9C%EA%B7%B8">https://git-scm.com/book/ko/v1/Git%EC%9D%98-%EA%B8%B0%EC%B4%88-%ED%83%9C%EA%B7%B8</a></p>
Docker Redmine + Mysql 설치 및 Plugin 설치하기
2018-01-25T00:00:00+00:00
2018-01-25T00:00:00+00:00
https://github.com/jistol/its/2018/01/25/redmine-mysql-in-docker
<p>ITS로 Redmine을 사용하려 간단히 세팅해보려 Docker기반으로 설정해보았습니다. <br />
DockerHub에 있는 <a href="https://hub.docker.com/_/redmine/">library/redmine - Docker Hub</a>를 이용하고 DB는 Mysql을 사용하도록 세팅하는 과정을 적었습니다.</p>
<h3 id="과정을-정리하긴-하였지만-bundler-오류가-발생하면-container가-다운되고-다시-올라오지-못하는-현상이-종종-발생하오니-백업을-잘-하시기-바랍니다">과정을 정리하긴 하였지만 bundler 오류가 발생하면 Container가 다운되고 다시 올라오지 못하는 현상이 종종 발생하오니 백업을 잘 하시기 바랍니다.</h3>
<h2 id="docker-생성">docker 생성</h2>
<p>Link : <a href="https://hub.docker.com/_/redmine/">https://hub.docker.com/_/redmine/</a></p>
<p>아래와 같이 image를 다운 받습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker pull redmine mysql
</code></pre></div></div>
<p>매번 docker run을 작성하기도 귀찮고 redmine / mysql 컨테이너를 각각 올리기 귀찮기 때문에 docker-compose를 이용하여 만들도록 하겠습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># docker-compose.yml</span>
<span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3.1'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">redmine</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">container_name</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">3000:3000</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">REDMINE_DB_MYSQL</span><span class="pi">:</span> <span class="s">db</span>
<span class="na">REDMINE_DB_PASSWORD</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">REDMINE_DB_DATABASE</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">REDMINE_DB_ENCODING</span><span class="pi">:</span> <span class="s">utf8</span>
<span class="c1"># REDMINE_NO_DB_MIGRATE: true</span>
<span class="na">db</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">mysql</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">always</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">3306:3306</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="na">MYSQL_ROOT_PASSWORD</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">MYSQL_DATABASE</span><span class="pi">:</span> <span class="s">redmine</span>
<span class="na">command</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">--character-set-server=utf8mb4</span>
<span class="pi">-</span> <span class="s">--collation-server=utf8mb4_unicode_ci</span>
</code></pre></div></div>
<p>추후 외부에서 접근하여 DB를 export하기 위한 용도로 3306포트를 열어두었으며 DATABASE 인코딩을 UTF-8로 사용하기 위해 추가 command 설정으르 하였습니다. <br />
restart=always로 설정한 이유는 redmine보다 db가 먼저 뜰 경우 redmine이 죽어버리기 때문에 db가 정상으로 올라올떄까지 재시도하도록 하였습니다. <br />
위와 같이 파일을 만든후 docker-compose명령어를 이용하여 실행합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker<span class="p">-</span>compose <span class="k">up</span> <span class="p">-</span><span class="k">d</span>
</code></pre></div></div>
<p>정상 실행되면 아래와 같이 프로세스가 실행되는 것을 확인가능합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker <span class="k">ps</span> <span class="p">-</span><span class="k">a</span>
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e6491c0d5747 mysql <span class="s2">"docker-entrypoint..."</span> <span class="m">17</span> minutes ago Up <span class="m">17</span> minutes <span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">:</span><span class="m">3306</span><span class="p">-></span><span class="m">3306</span>/tcp redmine_db_1
<span class="m">32</span>eb215b34af redmine <span class="s2">"/docker-entrypoin..."</span> <span class="m">17</span> minutes ago Up <span class="m">16</span> minutes <span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">.</span><span class="m">0</span><span class="p">:</span><span class="m">3000</span><span class="p">-></span><span class="m">3000</span>/tcp redmine
</code></pre></div></div>
<h2 id="redmine-플러그인-설치">Redmine 플러그인 설치</h2>
<p>ITS기능을 하기에 기본 Redmine은 좀 불편한 면이 있기 때문에 몇몇 플러그인을 설치해보록 하겠습니다.(무료만…)</p>
<p>설치과정에서 컨테이너 내부에 접속해 command를 날려야 합니다. 컨테이너에 접속하는 방법은 아래와 같습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker exec <span class="p">-</span>it redmine bash
</code></pre></div></div>
<p>플러그인을 설치하기 앞서 사전에 필요한 모듈들을 설치하도록 합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker<span class="p">:</span>redmine$ apt<span class="p">-</span><span class="nb">get</span> <span class="k">update</span>
docker<span class="p">:</span>redmine$ apt<span class="p">-</span><span class="nb">get</span> install <span class="p">-</span><span class="k">y</span> unzip <span class="k">vim</span>
</code></pre></div></div>
<p>Redmine 플러그인 설치 가이드는 <a href="http://www.redmine.org/projects/redmine/wiki/Plugins">Redmine - Plugins</a>를 참고하시면 됩니다. <br />
각 플러그인 설치후 Redmine을 재시작해야 반영됩니다.</p>
<h2 id="issue-template-plugin">Issue Template Plugin</h2>
<p>Issue등록시 기본 템플릿을 지정할 수 있는 Plugin입니다. 공식 링크는 아래와 같습니다.</p>
<p>Link : <a href="https://github.com/akiko-pusu/redmine_issue_templates">https://github.com/akiko-pusu/redmine_issue_templates</a></p>
<p>설치방법은 아래와 같습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker<span class="p">:</span>redmine$ <span class="k">cd</span> $<span class="p">{</span>REDMINE_ROOT<span class="p">}</span>
docker<span class="p">:</span>redmine$ git clone https<span class="p">:</span><span class="sr">//</span>github<span class="p">.</span><span class="k">com</span><span class="sr">/akiko-pusu/</span>redmine_issue_templates<span class="p">.</span>git plugins/redmine_issue_templates
docker<span class="p">:</span>redmine$ rake redmine<span class="p">:</span>plugins<span class="p">:</span>migrate RAILS_ENV<span class="p">=</span>production
</code></pre></div></div>
<h2 id="check-list-plugin">Check List Plugin</h2>
<p>Issue등록시 체크해야하는 사항이 있을때 같이 등록할 수 있는 Plugin입니다. 공식 링크는 아래와 같습니다.</p>
<p>Link : <a href="https://www.redmineup.com/pages/plugins/checklists">https://www.redmineup.com/pages/plugins/checklists</a></p>
<p>redmineup의 plugin은 직접 파일을 다운로드 받아 Redmine 컨테이너에 복사하고 unzip을 이용하여 plugin하위에 풀어야 합니다.</p>
<p><img src="/assets/img/its/redmine-mysql-in-docker/1.png" alt="CheckList - Download" /></p>
<p>Light Version을 다운받아 아래와 같이 설치를 진행합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker <span class="k">cp</span> <span class="p">~</span><span class="sr">/own_path/</span>redmine_checklists<span class="m">-3</span>_1_10<span class="p">-</span><span class="nb">light</span><span class="p">.</span>zip redmine_redmine_1<span class="p">:</span><span class="sr">/usr/</span><span class="k">src</span><span class="sr">/redmine/</span>plugins/
$ docker exec <span class="p">-</span>it redmine_redmine_1 bash
docker<span class="p">:</span>redmine$ <span class="k">cd</span> $<span class="p">{</span>REDMINE_ROOT<span class="p">}</span>
docker<span class="p">:</span>redmine$ unzip plugins<span class="sr">/redmine_checklists-3_1_10-light.zip -d plugins/</span>
docker<span class="p">:</span>redmine$ bundle install
docker<span class="p">:</span>redmine$ rake redmine<span class="p">:</span>plugins<span class="p">:</span>migrate RAILS_ENV<span class="p">=</span>production
</code></pre></div></div>
<h2 id="redmine-agile-plugin">Redmine Agile Plugin</h2>
<p>가장 범용적으로 많이 쓰는 애자일 Plugin입니다. 공식 링크는 아래와 같습니다.</p>
<p>Link : <a href="https://www.redmineup.com/pages/plugins/agile">https://www.redmineup.com/pages/plugins/agile</a></p>
<p>파일을 다운로드 받아 Redmine 컨테이너에 복사하고 unzip을 이용하여 plugin하위에 풀어야 합니다.</p>
<p><img src="/assets/img/its/redmine-mysql-in-docker/2.png" alt="Redmine Agile Plugin - Download" /></p>
<p>Light Version을 다운받아 아래와 같이 설치를 진행합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker <span class="k">cp</span> <span class="p">~</span><span class="sr">/own_path/</span>redmine_agile<span class="m">-1</span>_4_5<span class="p">-</span><span class="nb">light</span><span class="p">.</span>zip redmine_redmine_1<span class="p">:</span><span class="sr">/usr/</span><span class="k">src</span><span class="sr">/redmine/</span>plugins/
$ docker exec <span class="p">-</span>it redmine_redmine_1 bash
docker<span class="p">:</span>redmine$ <span class="k">cd</span> $<span class="p">{</span>REDMINE_ROOT<span class="p">}</span>
docker<span class="p">:</span>redmine$ unzip plugins<span class="sr">/redmine_checklists-3_1_10-light.zip -d plugins/</span>
docker<span class="p">:</span>redmine$ bundle install
docker<span class="p">:</span>redmine$ rake redmine<span class="p">:</span>plugins<span class="p">:</span>migrate RAILS_ENV<span class="p">=</span>production
</code></pre></div></div>
<h2 id="issue-charts-plugin">Issue Charts Plugin</h2>
<p>이슈별 통계치를 그래프로 보여주는 플러그인입니다.</p>
<p>Link : <a href="https://github.com/masweetman/issue_charts">https://github.com/masweetman/issue_charts</a></p>
<p>설치 방법은 아래와 같습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker<span class="p">:</span>redmine$ <span class="k">cd</span> $<span class="p">{</span>REDMINE_ROOT<span class="p">}</span>
docker<span class="p">:</span>redmine$ git clone https<span class="p">:</span><span class="sr">//</span>github<span class="p">.</span><span class="k">com</span><span class="sr">/masweetman/</span>issue_charts<span class="p">.</span>git plugins/issue_charts
docker<span class="p">:</span>redmine$ bundle install
docker<span class="p">:</span>redmine$ rake redmine<span class="p">:</span>plugins<span class="p">:</span>migrate RAILS_ENV<span class="p">=</span>production
</code></pre></div></div>
<h2 id="redmine-테마-설치">Redmine 테마 설치</h2>
<p>classic 테마는 식상하니 심플한 테마를 하나 설치하도록 하겠습니다. <br />
대부분의 테마설치방식은 비슷하고 어렵지 않기 때문에 아래 예제를 기반으로 다른 맘에드는 테마를 설치하셔도 됩니다.</p>
<h2 id="redmine-gitmike-theme">Redmine gitmike theme</h2>
<p>무료 테마중 인기있는 gitmike를 설치해보겠습니다. 공식 링크는 아래와 같습니다.</p>
<p>Link : <a href="https://github.com/makotokw/redmine-theme-gitmike">https://github.com/makotokw/redmine-theme-gitmike</a></p>
<p>설치 방법은 아래와 같습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker<span class="p">:</span>redmine$ <span class="k">cd</span> $<span class="p">{</span>REDMINE_ROOT<span class="p">}</span><span class="sr">/public/</span>themes
docker<span class="p">:</span>redmine$ git clone https<span class="p">:</span><span class="sr">//</span>github<span class="p">.</span><span class="k">com</span><span class="sr">/makotokw/</span>redmine<span class="p">-</span>theme<span class="p">-</span>gitmike<span class="p">.</span>git gitmike
</code></pre></div></div>
<p>설치후 관리자(admin)으로 로그인하여 [관리 > 설정 > 표시방식] 에서 테마를 <code class="language-plaintext highlighter-rouge">gitmike</code>로 선택 후 저장하면 바로 적용됩니다.</p>
<p><img src="/assets/img/its/redmine-mysql-in-docker/3.png" alt="Gitmike - Theme Setting" /></p>
<h2 id="git-저장소연결">Git 저장소연결</h2>
<p>SVN연결은 간편하게 되는 반면 GIT연결은 Local에 bare저장소가 같이 존재해야한다는 단점이 있습니다. <br />
설정할 내용이 간단하지 않아 <a href="https://jistol.github.io/its/2018/01/23/redmine-git/">Redmine + Git Remote 연동</a> 링크를 참고하여 연결하면 됩니다.</p>
<h2 id="그-외-설정">그 외 설정</h2>
<p>Issue등록시 일반 Text로는 너무 딱딱하고 Richable Editor를 설치하기도 별로여서 기본적으로 Redmine에서 제공하는 Markdown 에디터를 사용하도록 하겠습니다.</p>
<p>관리자(admin)으로 로그인하여 [관리 > 설정 > 일반] 에서 본문형식을 <code class="language-plaintext highlighter-rouge">markdown</code>으로 변경합니다. <br />
그리고 ISSUE를 등록해보면 본문내용을 markdown형식으로 사용 가능해집니다.</p>
<p><img src="/assets/img/its/redmine-mysql-in-docker/4.png" alt="Setting - markdown" /></p>
<h2 id="참고">참고</h2>
<p>Allow setting CHARACTER SET for the database : <a href="https://github.com/docker-library/mysql/pull/14">https://github.com/docker-library/mysql/pull/14</a> <br />
Redmine - Plugins : <a href="http://www.redmine.org/projects/redmine/wiki/Plugins">http://www.redmine.org/projects/redmine/wiki/Plugins</a> <br />
Redmine 시작/중지/재시작 : <a href="https://zetawiki.com/wiki/%EB%A0%88%EB%93%9C%EB%A7%88%EC%9D%B8_%EC%8B%9C%EC%9E%91/%EC%A4%91%EC%A7%80/%EC%9E%AC%EC%8B%9C%EC%9E%91">https://zetawiki.com/wiki/%EB%A0%88%EB%93%9C%EB%A7%88%EC%9D%B8_%EC%8B%9C%EC%9E%91/%EC%A4%91%EC%A7%80/%EC%9E%AC%EC%8B%9C%EC%9E%91</a></p>
Redmine + Git Remote 연동
2018-01-23T00:00:00+00:00
2018-01-23T00:00:00+00:00
https://github.com/jistol/its/2018/01/23/redmine-git
<p>기본적으로 Redmine과 Git을 연동하려면 Git저장소가 Redmine서버와 같은 서버에 있어야 연동이 됩니다. <br />
SVN은 지원해주는거 같은데 Git만 안되는거 같네요. <br />
아래 그림을 보면 로컬 bare 저장소를 지정하라고 나옵니다.</p>
<p>이슈관리때문에 저장소 위치를 바꿀수 없으니 구글링하면 나오는 꼼수를 이용하여 적용하는데, 요약하면 아래와 같습니다.</p>
<ol>
<li>외부 저장소를 <code class="language-plaintext highlighter-rouge">--mirror</code> 옵션을 사용하여 Redmine서버에 clone 받음.</li>
<li>crontab을 이용하여 분 단위로 Redmine서버의 소스를 동기화 시킴. <code class="language-plaintext highlighter-rouge">git remote update</code></li>
<li>Redmine에서 Git 저장소를 사용할 수 있도록 설정 변</li>
</ol>
<h2 id="redmine-저장소-만들기">Redmine 저장소 만들기</h2>
<p>Redmine에서 사용할 로컬 bare 저장소를 생성합니다. 원 저장소의 변경사항을 계속 받아와야 하기 때문에 <code class="language-plaintext highlighter-rouge">--mirror</code> 옵션을 사용하도록 할 예정인데 crontab을 이용하여 주기적으로 받아와야 하기 때문에 매번 ID/PW를 입력 할 수 없으므로 git credentials 설정을 바꾸도록 합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git config <span class="p">--</span>global credential<span class="p">.</span>helper <span class="s1">'store --file ~/.credentials'</span>
$ git clone <span class="p">--</span>mirror https<span class="p">:</span><span class="sr">//</span><span class="p">......</span>/xxxx<span class="p">.</span>git
</code></pre></div></div>
<p>위와 같이 설정후 clone을 받으면 최초 인증과정을 거치면서 ID/PW를 저장하게 되고 그 다음 clontab 실행시 인증없이 소스 갱신이 가능해집니다.</p>
<h2 id="crontab-설정">Crontab 설정</h2>
<p>crontab으로 실행할 sh파일을 만들고 주기적으로 실행하도록 설정하겠습니다.</p>
<p>update.sh 작성</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#<span class="p">!</span><span class="sr">/bin/</span>bash
<span class="k">cd</span> <span class="p">~</span>/your<span class="p">-</span>git<span class="p">-</span><span class="k">src</span><span class="p">-</span><span class="nb">path</span>
git remote <span class="k">update</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">crontab -e</code> 명령어를 통해 crontab 편집이 가능합니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* * * * * <span class="p">~</span><span class="sr">/update.sh >> ~/</span>cron<span class="p">.</span><span class="nb">log</span> <span class="m">2</span><span class="p">></span>&<span class="m">1</span>
# crontab script end
</code></pre></div></div>
<p>crontab이 잘 동작하는지 확인차 로그를 남겼는데 잘 동작한다면 로그 빼주도록 합시다.</p>
<h2 id="redmine-설정">Redmine 설정</h2>
<p>[프로젝트 설정 > 저장소] 에서 ‘저장소 추가’ 버튼을 클릭합니다. <br />
<img src="/assets/img/its/redmine-git/1.png" alt="Redmine-Setup-Repository" /></p>
<p>형상관리시스템으로 Git을 선택하고 Redmine 서버에 만든 bare저장소 경로를 지정해줍니다. <br />
<img src="/assets/img/its/redmine-git/2.png" alt="Redmine-Setup-Repository" /></p>
<p>저장버튼을 누르고 ‘저장소’탭으로 이동하면 아래와 같이 내역을 볼 수 있습니다. <br />
<img src="/assets/img/its/redmine-git/3.png" alt="Redmine-Setup-Repository" /></p>
<h2 id="참고">참고</h2>
<p>git clone 의 두가지 옵션 –bare / –mirror 의 차이점 : <a href="http://pinocc.tistory.com/138">http://pinocc.tistory.com/138</a> <br />
git credential 저장소 : <a href="https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-Credential-%EC%A0%80%EC%9E%A5%EC%86%8C">https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-Credential-%EC%A0%80%EC%9E%A5%EC%86%8C</a> <br />
Redmine - git repository 연결하기 : <a href="http://www.whatwant.com/450">http://www.whatwant.com/450</a></p>
SASS를 이용하여 margin / padding 을 조절할 수 있는 class 자동생성하기 (@for, @if, @each)
2017-11-09T00:00:00+00:00
2017-11-09T00:00:00+00:00
https://github.com/jistol/frontend/2017/11/09/sass-margin-padding
<p>Bootstrap 으로 자주 썼던 class중 하나가 margin/padding 사이즈를 조절해주는 class였습니다. <br />
하지만 정확한 픽셀 단위로 조절 해주는게 아니라서 정확한 사이즈 조절이 힘들었는데 SASS를 이용해서 간단하게 만들어봤습니다.</p>
<ul>
<li>as-is</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">style=</span><span class="s">"margin-bottom:10px;margin-top:10px;"</span><span class="nt">/></span>
</code></pre></div></div>
<ul>
<li>to-be</li>
</ul>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"px-mb-10 px-mt-10"</span><span class="nt">/></span>
</code></pre></div></div>
<ul>
<li>scss 코드</li>
</ul>
<div class="language-sass highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
</span> <span class="cm">px값 List를 만들어주는 함수입니다.
</span><span class="o">*/</span>
<span class="k">@function</span> <span class="nf">size</span><span class="p">(</span><span class="nv">$start</span><span class="o">,</span> <span class="nv">$end</span><span class="p">)</span> <span class="err">{</span>
<span class="nv">$size</span> <span class="p">:</span> <span class="p">()</span><span class="err">;</span>
<span class="k">@for</span> <span class="nv">$i</span> <span class="ow">from</span> <span class="nv">$start</span> <span class="ow">through</span> <span class="nv">$end</span> <span class="err">{</span>
<span class="nv">$value</span> <span class="p">:</span> <span class="nv">$i</span> <span class="o">+</span> <span class="m">0</span><span class="err">;</span>
<span class="nv">$size</span> <span class="p">:</span> <span class="nf">append</span><span class="p">(</span><span class="nv">$size</span><span class="o">,</span> <span class="nv">$value</span><span class="p">)</span><span class="err">;</span>
<span class="err">}</span>
<span class="k">@return</span> <span class="nv">$size</span><span class="err">;</span>
<span class="err">}</span>
<span class="cm">/**
</span> <span class="cm">값이 0일때는 0으로, 그 외에는 'px'을 붙여줍니다.
</span> <span class="cm">ex) 0 => 0, 10 => 10px
</span><span class="o">*/</span>
<span class="k">@function</span> <span class="nf">getPx</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span> <span class="err">{</span>
<span class="k">@if</span> <span class="nv">$value</span> <span class="o">==</span> <span class="m">0</span> <span class="err">{</span>
<span class="k">@return</span> <span class="nv">$value</span><span class="err">;</span>
<span class="err">}</span> <span class="o">@</span><span class="nt">else</span> <span class="err">{</span>
<span class="k">@return</span> <span class="nv">$value</span> <span class="o">+</span> <span class="m">0px</span><span class="err">;</span>
<span class="err">}</span>
<span class="err">}</span>
<span class="cm">/**
</span> <span class="cm">px 값입니다.
</span> <span class="cm">0 ~ 100px 까지 조정하게 만들었습니다.
</span><span class="o">*/</span>
<span class="nv">$size</span> <span class="p">:</span> <span class="nf">size</span><span class="p">(</span><span class="m">0</span><span class="o">,</span> <span class="m">100</span><span class="p">)</span><span class="err">;</span>
<span class="cm">/**
</span> <span class="cm">margin과 padding의 각 위치를 지정해주는 map 입니다.
</span> <span class="cm">key는 class이름 생성시 쓰이며 value는 상세 속성 정의시 쓰입니다.
</span><span class="o">*/</span>
<span class="nv">$position</span> <span class="p">:</span> <span class="p">(</span><span class="s1">'l'</span><span class="o">:</span><span class="s1">'left'</span><span class="o">,</span> <span class="s1">'r'</span><span class="o">:</span><span class="s1">'right'</span><span class="o">,</span> <span class="s1">'t'</span><span class="o">:</span><span class="s1">'top'</span><span class="o">,</span> <span class="s1">'b'</span><span class="o">:</span><span class="s1">'bottom'</span><span class="p">)</span><span class="err">;</span>
<span class="cm">/**
</span> <span class="cm">margin과 padding 생성을 위한 map입니다.
</span> <span class="cm">key는 class이름 생성시 쓰이며 value는 상세 속성 정의시 쓰입니다.
</span><span class="o">*/</span>
<span class="nv">$nameMap</span> <span class="p">:</span> <span class="p">(</span><span class="s1">'px-m'</span><span class="o">:</span><span class="s1">'margin'</span><span class="o">,</span> <span class="s1">'px-p'</span><span class="o">:</span><span class="s1">'padding'</span><span class="p">)</span><span class="err">;</span>
<span class="cm">/**
</span> <span class="cm">실제 css class를 만들어주는 mixin입니다.
</span> <span class="cm">nameMap, position, size를 혼합하여 아래와 같은 형식으로 만들어줍니다.
</span>
<span class="cm">ex>
</span> <span class="cm">px-m-0 : { margin : 0; }
</span> <span class="cm">px-mt-1 : { margin-top : 1px; }
</span><span class="o">*/</span>
<span class="k">@mixin</span> <span class="nf">generate</span><span class="p">(</span><span class="nv">$nameMap</span> <span class="o">:</span> <span class="p">()</span><span class="o">,</span> <span class="nv">$position</span> <span class="o">:</span> <span class="p">()</span><span class="o">,</span> <span class="nv">$size</span> <span class="o">:</span> <span class="p">())</span> <span class="err">{</span>
<span class="k">@each</span> <span class="nv">$preKey</span><span class="o">,</span> <span class="nv">$preValue</span> <span class="n">in</span> <span class="nv">$nameMap</span> <span class="err">{</span>
<span class="k">@each</span> <span class="nv">$px</span> <span class="n">in</span> <span class="nv">$size</span> <span class="err">{</span>
<span class="nc">.</span><span class="si">#{</span><span class="nv">$preKey</span><span class="si">}</span><span class="nc">-</span><span class="si">#{</span><span class="nv">$px</span><span class="si">}</span> <span class="err">{</span>
<span class="si">#{</span><span class="nv">$preValue</span><span class="si">}</span> <span class="p">:</span> <span class="nf">getPx</span><span class="p">(</span><span class="nv">$px</span><span class="p">)</span><span class="err">;</span>
<span class="err">}</span>
<span class="k">@each</span> <span class="nv">$sufKey</span><span class="o">,</span> <span class="nv">$sufValue</span> <span class="n">in</span> <span class="nv">$position</span> <span class="err">{</span>
<span class="nc">.</span><span class="si">#{</span><span class="nv">$preKey</span><span class="si">}#{</span><span class="nv">$sufKey</span><span class="si">}</span><span class="nc">-</span><span class="si">#{</span><span class="nv">$px</span><span class="si">}</span> <span class="err">{</span>
<span class="si">#{</span><span class="nv">$preValue</span><span class="si">}</span><span class="na">-</span><span class="si">#{</span><span class="nv">$sufValue</span><span class="si">}</span> <span class="p">:</span> <span class="nf">getPx</span><span class="p">(</span><span class="nv">$px</span><span class="p">)</span><span class="err">;</span>
<span class="err">}</span>
<span class="err">}</span>
<span class="err">}</span>
<span class="err">}</span>
<span class="err">}</span>
<span class="k">@include</span> <span class="nd">generate</span><span class="p">(</span><span class="nv">$nameMap</span><span class="o">,</span> <span class="nv">$position</span><span class="o">,</span> <span class="nv">$size</span><span class="p">)</span><span class="err">;</span>
</code></pre></div></div>
<p>node-sass를 이용하여 scss파일을 css로 빌드해보면 아래와 같이 만들어집니다.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.px-m-0</span> <span class="p">{</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-ml-0</span> <span class="p">{</span>
<span class="nl">margin-left</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-mr-0</span> <span class="p">{</span>
<span class="nl">margin-right</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-mt-0</span> <span class="p">{</span>
<span class="nl">margin-top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-mb-0</span> <span class="p">{</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-m-1</span> <span class="p">{</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-ml-1</span> <span class="p">{</span>
<span class="nl">margin-left</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span> <span class="p">}</span>
<span class="o">...</span>
<span class="nc">.px-pb-99</span> <span class="p">{</span>
<span class="nl">padding-bottom</span><span class="p">:</span> <span class="m">99px</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-p-100</span> <span class="p">{</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">100px</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-pl-100</span> <span class="p">{</span>
<span class="nl">padding-left</span><span class="p">:</span> <span class="m">100px</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-pr-100</span> <span class="p">{</span>
<span class="nl">padding-right</span><span class="p">:</span> <span class="m">100px</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-pt-100</span> <span class="p">{</span>
<span class="nl">padding-top</span><span class="p">:</span> <span class="m">100px</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.px-pb-100</span> <span class="p">{</span>
<span class="nl">padding-bottom</span><span class="p">:</span> <span class="m">100px</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>
(JPA,SpringData) Named Query 사용하기
2017-11-06T00:00:00+00:00
2017-11-06T00:00:00+00:00
https://github.com/jistol/spring/2017/11/06/jpa-namedquery
<p>JPA를 사용하다보면 쿼리메소드만으로는 감당이 안되는 부분이 많아 <code class="language-plaintext highlighter-rouge">@Query</code>를 이용하여 아래와 같이 늘어놓기 시작합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Query</span><span class="o">(</span><span class="s">"select p from Post p where p.id > :id"</span><span class="o">)</span>
<span class="nc">Post</span> <span class="nf">findPostByPk</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">);</span>
</code></pre></div></div>
<p>쿼리문이 짧을때는 상관없는데 쿼리문이 길어지고, 또 많아지면 그때부터는 관리가 안되기 시작하는데
가급적 JPA의 장점을 살리면서 Native를 쓰지 않고 버티기 위해 아래와 같이 설정할 수 있습니다.</p>
<h2 id="쿼리문-xml로-빼기-export-query-string-to-ormxml">쿼리문 xml로 빼기 (export query string to orm.xml)</h2>
<p>Post라는 Entity를 조회하기 위한 쿼리를 만들어보겠습니다.
일단 여러개의 xml resource를 사용하기 위해 아래와 같이 설정했습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># application.yml</span>
<span class="s">spring.jpa.orm</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">queries</span>
<span class="na">queries</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">${spring.jpa.orm.path}/post.xml</span>
<span class="pi">-</span> <span class="s">${spring.jpa.orm.path}/user.xml</span>
<span class="s">...</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">JpaConfig</span> <span class="kd">extends</span> <span class="nc">HibernateJpaAutoConfiguration</span> <span class="o">{</span>
<span class="nd">@Data</span>
<span class="nd">@Component</span>
<span class="nd">@ConfigurationProperties</span><span class="o">(</span><span class="s">"spring.jpa.orm"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrmProps</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">queries</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Autowired</span> <span class="kd">private</span> <span class="nc">OrmProps</span> <span class="n">ormProps</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">JpaConfig</span><span class="o">(</span><span class="nc">DataSource</span> <span class="n">dataSource</span><span class="o">,</span> <span class="nc">JpaProperties</span> <span class="n">jpaProperties</span><span class="o">,</span> <span class="nc">ObjectProvider</span><span class="o"><</span><span class="nc">JtaTransactionManager</span><span class="o">></span> <span class="n">jtaTransactionManager</span><span class="o">,</span> <span class="nc">ObjectProvider</span><span class="o"><</span><span class="nc">TransactionManagerCustomizers</span><span class="o">></span> <span class="n">transactionManagerCustomizers</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">(</span><span class="n">dataSource</span><span class="o">,</span> <span class="n">jpaProperties</span><span class="o">,</span> <span class="n">jtaTransactionManager</span><span class="o">,</span> <span class="n">transactionManagerCustomizers</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">LocalContainerEntityManagerFactoryBean</span> <span class="nf">entityManagerFactory</span><span class="o">(</span>
<span class="nc">EntityManagerFactoryBuilder</span> <span class="n">factoryBuilder</span><span class="o">)</span>
<span class="o">{</span>
<span class="kd">final</span> <span class="nc">LocalContainerEntityManagerFactoryBean</span> <span class="n">ret</span> <span class="o">=</span> <span class="kd">super</span><span class="o">.</span><span class="na">entityManagerFactory</span><span class="o">(</span><span class="n">factoryBuilder</span><span class="o">);</span>
<span class="n">ret</span><span class="o">.</span><span class="na">setMappingResources</span><span class="o">(</span><span class="n">ormProps</span><span class="o">.</span><span class="na">getQueries</span><span class="o">());</span>
<span class="k">return</span> <span class="n">ret</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>쿼리문만 따로 모으고 싶어서 아래와 같이 배치하였습니다.</p>
<p><img src="/assets/img/java/jpa-namedquery/1.png" alt="query files under resource folder" /></p>
<p><code class="language-plaintext highlighter-rouge">@Query</code>어노테이션에 있던 쿼리문은 xml하위에 아래와 같이 정의합니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><named-query</span> <span class="na">name=</span><span class="s">"Post.findPostByPk"</span><span class="nt">></span>
<span class="nt"><query></span><span class="cp"><![CDATA[ select p from Post p where p.id > :id ]]></span><span class="nt"></query></span>
<span class="nt"></named-query></span>
</code></pre></div></div>
<p>그리고 dao 소스에서 <code class="language-plaintext highlighter-rouge">@Query</code>어노테이션을 제거해주면 끝.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Post</span> <span class="nf">findPostByPk</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="결과를-map으로-받기">결과를 Map으로 받기</h2>
<p>Entity의 전체 결과를 받아올수도 있지만 일부만 필요할 수 도 있습니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><named-query</span> <span class="na">name=</span><span class="s">"Post.findPostByPk"</span><span class="nt">></span>
<span class="nt"><query></span><span class="cp"><![CDATA[ select p.id, p.message, p.user from Post p where p.id > :id ]]></span><span class="nt"></query></span>
<span class="nt"></named-query></span>
</code></pre></div></div>
<p>위 결과를 Post객체로 받을 경우 리턴값이 Object[]이기 때문에 파싱 오류가 발생합니다.
(아래 경우 <code class="language-plaintext highlighter-rouge">p.id</code>가 Long타입인데 저걸 Post객체로 변환하려 했기때문에 생기는 오류입니다.)</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">[11-06 14:44:48.269] ERROR [http-nio-8080-exec-9] [o.a.j.l.DirectJDKLog.log] 181 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed;</span><span class="w"> </span>nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from <span class="nb">type</span> <span class="o">[</span>java.lang.Object[]] to <span class="nb">type</span> <span class="o">[</span>io.github.jistol.geosns.jpa.entry.Post] <span class="k">for </span>value <span class="s1">'{65, 1234555666}'</span><span class="p">;</span> nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from <span class="nb">type</span> <span class="o">[</span>java.lang.Long] to <span class="nb">type</span> <span class="o">[</span>io.github.jistol.geosns.jpa.entry.Post]] with root cause
<span class="go">org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Long] to type [io.github.jistol.geosns.jpa.entry.Post]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:324)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:206)
at org.springframework.core.convert.support.ArrayToObjectConverter.convert(ArrayToObjectConverter.java:66)
at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:37)
</span><span class="c"> ...
</span></code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">named-native-query</code>는 <code class="language-plaintext highlighter-rouge">result-class</code>, <code class="language-plaintext highlighter-rouge">result-map-class</code>등을 설정 할 수 있지만 JPA를 쓰면서 native쿼리 쓰려면 MyBatis를 쓰는게 더 낫다고 생각하기 때문에 다른 방법으로 해결하겠습니다.</p>
<ol>
<li>Object[] 를 원하는 객체로 Application단에서 직접 바꾸기.(설명 생략)</li>
<li>Map으로 변환하여 결과 받기</li>
</ol>
<p><a href="https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html#queryhql-select">Hibernate Select clause</a>문서를 참고하면 HQL에서 어떻게 select한 값을 반환해주는지 잘 설명이 되어있습니다.
(다행히도 JPQL에서도 동일하게 동작하는것 같습니다.)
그 중 Map으로 반환받기 위해서는 아래와 같이 설정 가능합니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><named-query</span> <span class="na">name=</span><span class="s">"Post.findPostByPk"</span><span class="nt">></span>
<span class="nt"><query></span><span class="cp"><![CDATA[ select new map(p.id, p.message, p.user) from Post p where p.id > :id ]]></span><span class="nt"></query></span>
<span class="nt"></named-query></span>
</code></pre></div></div>
<p>그리고 dao 소스에서도 Return객체를 Map으로 바꿔주면 정상동작합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">></span> <span class="nf">findPostByPk</span><span class="o">(</span><span class="nd">@Param</span><span class="o">(</span><span class="s">"id"</span><span class="o">)</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html#queryhql-select">Hibernate Select clause</a>
<a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.named-queries">Spring Data JPA - 4.3.3 Using JPA NamedQueries</a></p>
(SpringBoot) application.yml에서 placeholder 기능 동작안할때
2017-10-13T00:00:00+00:00
2017-10-13T00:00:00+00:00
https://github.com/jistol/spring/2017/10/13/springboot-issue-placeholder-in-properties
<p>application.yml 파일을 아래와 같이 만들고 실행했습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">app.url</span><span class="pi">:</span> <span class="s">http://localhost:${server.port}/</span>
<span class="s">app.domain1</span><span class="pi">:</span> <span class="s">${app.url}/domain/1</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Value</span><span class="o">(</span><span class="s">"${app.domain1}"</span><span class="o">)</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">domain1</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">print</span><span class="o">()</span> <span class="o">{</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">domain1</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>소스에서 위와 같이 참조하면 <code class="language-plaintext highlighter-rouge">http://localhost:8080/domain/1</code>로 출력 되기를 기대했으나 <code class="language-plaintext highlighter-rouge">${app.url}/domain/1</code> 로 출력됩니다.</p>
<p>문제는 <code class="language-plaintext highlighter-rouge">app.url</code>에 설정한 <code class="language-plaintext highlighter-rouge">server.port</code>값을 application.yml 파일에 명시하지 않아 생긴 문제로
SpringBoot의 Placeholder가 파싱할때 참조 값이 없어 <code class="language-plaintext highlighter-rouge">app.url</code>값을 파싱하지 못하면서 다른 placeholder 설정들도 모두 일반 text로 인식해버리는 문제입니다.
아래와 같이 명시해주면 정상동작합니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">server.port</span><span class="pi">:</span> <span class="m">8080</span>
<span class="s">app.url</span><span class="pi">:</span> <span class="s">http://localhost:${server.port}/</span>
<span class="s">app.domain1</span><span class="pi">:</span> <span class="s">${app.url}/domain/1</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-placeholders-in-properties">Part IV. Spring Boot features - Placeholders in properties</a></p>
(SpringBoot) bootRun 실행중 멈춤 현상
2017-10-13T00:00:00+00:00
2017-10-13T00:00:00+00:00
https://github.com/jistol/spring/2017/10/13/springboot-bootrun-stoped
<p>gradle bootRun을 통해 SpringBoot를 실행하던 도중 아래와 같이 로그가 찍히고 멈춰서 더이상 동작하지 않는 현상이 발생하였습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">오전 11:27:32: Executing external task 'bootRun -Dspring.profiles.active=local'...
:compileJava UP-TO-DATE
:processResources
:classes
:findMainClass
Connected to the target VM, address: '127.0.0.1:61333', transport: 'socket'
:bootRun
11:27:33.296 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
11:27:33.300 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
11:27:33.301 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Users/jistol/IdeaProjects/github/geo-sns/src/main/resources/, file:/Users/jistol/IdeaProjects/github/geo-sns/build/classes/java/main/, file:/Users/jistol/IdeaProjects/github/geo-sns/build/resources/main/]
</span></code></pre></div></div>
<p>기다려도 오류도 안나고 아무런 메시지 없이 멈춰 있어서 디버깅 해본 결과 application.yml 파일을 잘못 설정했을때 위와 같이 멈춰버립니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 예시</span>
<span class="s">base.url</span> <span class="pi">:</span> <span class="s">localhost</span>
<span class="c1"># ERROR : base.url을 사용하기 위해서는 ${base.url}로 표기해야합니다.</span>
<span class="na">call-url </span><span class="pi">:</span> <span class="pi">{</span><span class="nv">base.url</span><span class="pi">}</span><span class="s">/call</span>
</code></pre></div></div>
<p>application.yml파일을 파싱하지 못하여 내부적으로 오류가 나지만 따로 찍어주진 않고 SpringBoot를 deploy하지 못한채 끝나버립니다.</p>
docker-compose를 이용한 Nginx + Tomcat 클러스터링 샘플
2017-09-19T00:00:00+00:00
2017-09-19T00:00:00+00:00
https://github.com/jistol/docker/2017/09/19/docker-compose-tomcat-clustering
<p>Nginx와 Tomcat을 이용하여 클러스터 환경을 구축/테스트 진행하였는데 서버를 각각 3대나 띄우려니 여간 귀찮을수가 없더군요. <br />
docker-compose를 이용하여 가장 심플하고 최소한의 설정만으로 한방에 띄우는 방법 및 샘플을 포스팅합니다. <br />
샘플 소스는 <a href="https://github.com/jistol/docker-compose-nginx-tomcat-clustering-sample">jistol/docker-compose-nginx-tomcat-clustering-sample</a>에서 다운로드 가능합니다.</p>
<h2 id="기본구조">기본구조</h2>
<p>2대의 Tomcat 컨테이너를 올리고 앞단에 Nginx로 reverse proxy 합니다. <br />
2대의 Tomcat은 가장 기본적인 클러스터링 설정을 사용하며 multicast 방식에 의해 세션 공유를 합니다.</p>
<h2 id="서버-구성도">서버 구성도</h2>
<p><img src="/assets/img/docker/docker-compose-tomcat-clustering/1.png" alt="server structure" /></p>
<h2 id="샘플-폴더-구조">샘플 폴더 구조</h2>
<p><img src="/assets/img/docker/docker-compose-tomcat-clustering/2.png" alt="sample file list" /></p>
<p>Nginx와 Tomcat의 설정은 각 폴더별로 구분하고 Docker Build시 copy하도록 설정해두었습니다.</p>
<h2 id="docker-composeyml-설정">docker-compose.yml 설정</h2>
<p>docker명령어로 일일히 다 올리기 귀찮기 때문에 docker-compose를 이용하여 한방에 올립니다. <br />
docker-compose에 관한 자세한 사항은 <a href="https://docs.docker.com/compose/">docker compose doc</a>을 참고하세요.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># 일단 버전은 3을 사용합니다. 덕분에 extends기능이 없어졌더군요 ;(</span>
<span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="c1"># 각 서비스 컨테이너를 정의합니다. ( nginx * 1 + tomcat * 2 )</span>
<span class="na">services</span><span class="pi">:</span>
<span class="c1"># tomcat 1번 서버입니다. 같은 설정을 2번에서도 사용하기 때문에 &was로 명명하고 tomcat2에서 참조합니다.</span>
<span class="c1"># tomcat1 서비스의 모든 관련파일은 ./tomcat1 폴더에서 가져옵니다. </span>
<span class="na">tomcat1</span><span class="pi">:</span> <span class="nl">&was</span>
<span class="c1"># tomcat 기동시 java option값을 추가하기 위해 아래 설정을 추가했습니다. </span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">JAVA_OPTS=-Dspring.profiles.active=docker -Dfile.encoding=euc-kr</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
<span class="c1"># Dockerfile을 실행시 conf/server.xml과 webapps에 파일 배포를 위해 argument를 추가합니다.</span>
<span class="c1"># 아래 값은 DockerfileTomcat에서 사용됩니다.</span>
<span class="na">args</span><span class="pi">:</span>
<span class="na">conf</span><span class="pi">:</span> <span class="s">tomcat1/conf</span>
<span class="na">warpath</span><span class="pi">:</span> <span class="s">tomcat1/webapps/ROOT.war</span>
<span class="c1"># tomcat 서버 이미지 빌드를 위한 Dockerfile을 별도로 지정해줍니다.</span>
<span class="na">dockerfile</span><span class="pi">:</span> <span class="s">./DockerfileTomcat</span>
<span class="c1"># </span>
<span class="c1"># Docker 컨테이너에 붙지 않고 각 tomcat서버의 로그를 따로 확인하기 위해 외부 저장소와 연결합니다.</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./tomcat1/logs/:/usr/local/tomcat/logs/</span>
<span class="c1"># tomcat2번 서버입니다. tomcat1 서비스에서 설정한 내용을 그대로 사용하고 달라지는 설정에 대해서는 아래와 같이 직접 입력해줍니다.</span>
<span class="na">tomcat2</span><span class="pi">:</span>
<span class="s"><<</span><span class="pi">:</span> <span class="nv">*was</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
<span class="na">args</span><span class="pi">:</span>
<span class="na">conf</span><span class="pi">:</span> <span class="s">tomcat2/conf</span>
<span class="na">warpath</span><span class="pi">:</span> <span class="s">tomcat2/webapps/ROOT.war</span>
<span class="na">dockerfile</span><span class="pi">:</span> <span class="s">./DockerfileTomcat</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./tomcat2/logs/:/usr/local/tomcat/logs/</span>
<span class="c1"># nginx 설정입니다.</span>
<span class="na">nginx</span><span class="pi">:</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
<span class="c1"># nginx 서버 이미지 빌드를 위한 Dockerfile을 별도로 지정해줍니다.</span>
<span class="na">dockerfile</span><span class="pi">:</span> <span class="s">./DockerfileNginx</span>
<span class="c1"># Dockerfile실행시 conf/nginx.conf파일이 복사 될 수 있도록 argument를 추가합니다.</span>
<span class="c1"># 아래 값은 DockerfileNginx에서 사용됩니다.</span>
<span class="na">args</span><span class="pi">:</span>
<span class="na">conf</span><span class="pi">:</span> <span class="s">nginx/conf</span>
<span class="c1"># 외부에서 직접 8080포트로 붙어야 하기 때문에 컨테이너 포트를 외부로 열어줍니다.</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">8080:8080"</span>
</code></pre></div></div>
<p>위에서 설정한 설정에서 참조값들을 모두 적용한 문서를 보고 싶을 때는 아래와 같은 명령어로 실행할 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose config
</code></pre></div></div>
<h2 id="dockerfile-설정">Dockerfile 설정</h2>
<p>위에서 docker-compose 실행시 각 컨테이너가 Dockerfile을 실행하도록 설정하였습니다. <br />
다음과 같이 nginx / tomcat 용 Dockerfile을 생성합니다.</p>
<h2 id="dockerfilenginx">DockerfileNginx</h2>
<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> nginx:latest</span>
<span class="k">MAINTAINER</span><span class="s"> jistol <pptwenty@gmail.com></span>
<span class="k">ARG</span><span class="s"> conf</span>
<span class="k">COPY</span><span class="s"> $conf/nginx.conf /etc/nginx/nginx.conf</span>
<span class="k">WORKDIR</span><span class="s"> /usr/local/tomcat/bin</span>
<span class="k">CMD</span><span class="s"> ["nginx", "-g", "daemon off;"]</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ARG conf</code>값은 docker-compose.yml에 설정되어 있습니다.</p>
<h2 id="dockerfiletomcat">DockerfileTomcat</h2>
<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> tomcat:latest</span>
<span class="k">MAINTAINER</span><span class="s"> jistol <pptwenty@gmail.com></span>
<span class="k">ARG</span><span class="s"> conf</span>
<span class="k">ARG</span><span class="s"> warpath</span>
<span class="k">RUN </span><span class="nb">rm</span> <span class="nt">-rf</span> /usr/local/tomcat/webapps/<span class="k">*</span>
<span class="k">COPY</span><span class="s"> $conf/* /usr/local/tomcat/conf/</span>
<span class="k">COPY</span><span class="s"> $warpath /usr/local/tomcat/webapps/ROOT.war</span>
<span class="k">WORKDIR</span><span class="s"> /usr/local/tomcat/bin</span>
<span class="k">CMD</span><span class="s"> ["catalina.sh", "run"]</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ARG conf</code>, <code class="language-plaintext highlighter-rouge">ARG warpath</code>값은 docker-compose.yml에 설정되어 있습니다.</p>
<h2 id="tomcat-설정">Tomcat 설정</h2>
<p><code class="language-plaintext highlighter-rouge">server.xml</code>파일에 아래와 같이 설정합니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- server.xml --></span>
<span class="nt"><Cluster</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.tcp.SimpleTcpCluster"</span><span class="nt">/></span>
</code></pre></div></div>
<p>자세한 설정은 <a href="/java/2017/09/15/tomcat-clustering/">Tomcat 8 세션 클러스터링 하기</a>에 포스팅한 내용을 참고하세요.</p>
<h2 id="nginx-설정">Nginx 설정</h2>
<p><code class="language-plaintext highlighter-rouge">nginx.conf</code>파일에 reverse proxy를 위한 설정을 합니다.</p>
<div class="language-conf highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">http</span> {
...
<span class="c"># proxy 설정할 서버목록을 만듭니다.
</span> <span class="c"># host명은 docker 컨테이너의 service 이름과 동일하게 맞추어 줍니다.
</span> <span class="n">upstream</span> <span class="n">was</span>-<span class="n">list</span> {
<span class="n">server</span> <span class="n">tomcat1</span>:<span class="m">8080</span>;
<span class="n">server</span> <span class="n">tomcat2</span>:<span class="m">8080</span>;
}
...
<span class="n">server</span> {
<span class="c"># nginx 서버가 8080을 listen하도록 설정합니다.
</span> <span class="n">listen</span> <span class="m">8080</span>;
<span class="n">server_name</span> <span class="n">localhost</span>;
<span class="c"># 8080포트로 들어오는 모든 요청을 위에 설정한 'was-list'그룹으로 보냅니다.
</span> <span class="n">location</span> / {
<span class="n">proxy_pass</span> <span class="n">http</span>://<span class="n">was</span>-<span class="n">list</span>;
}
}
...
}
</code></pre></div></div>
<h2 id="실행">실행</h2>
<p>다음과 같이 실행합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose up <span class="nt">-d</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">-d</code> 옵션을 사용해야 실행후 각 컨테이너 콘솔 화면에서 detach됩니다.</p>
<p>기본적으로 docker-compose 실행시 이미지를 기존 빌드된 것으로 캐쉬하기 때문에 설정파일이나 배포파일이 바뀔 경우 아래와 같이 실행하여 새로 이미지를 만들도록 합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose up <span class="nt">-d</span> <span class="nt">--build</span>
</code></pre></div></div>
<p>실행시 아래와 같이 이미지 빌드 로그와 함께 각 컨테이너가 실행되는 것을 볼 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose up <span class="nt">-d</span> <span class="nt">--build</span>
<span class="go">Building nginx
Step 1/6 : FROM nginx:latest
</span><span class="gp"> ---></span><span class="w"> </span>da5939581ac8
<span class="gp">Step 2/6 : MAINTAINER jistol <pptwenty@gmail.com></span><span class="w">
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>2d9af4961368
<span class="go">Step 3/6 : ARG conf
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>ead97bc0569d
<span class="gp">Step 4/6 : COPY $</span>conf/nginx.conf /etc/nginx/nginx.conf
<span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>5ab748ec2a17
<span class="go">Step 5/6 : WORKDIR /usr/local/tomcat/bin
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>3eabdd2a3dd5
<span class="gp">Step 6/6 : CMD nginx -g daemon off;</span><span class="w">
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>7f4f2405e032
<span class="go">Successfully built 7f4f2405e032
Successfully tagged tomcatdocker1_nginx:latest
Building tomcat2
Step 1/9 : FROM tomcat:latest
</span><span class="gp"> ---></span><span class="w"> </span>0fbedce2f08c
<span class="gp">Step 2/9 : MAINTAINER jistol <pptwenty@gmail.com></span><span class="w">
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>13adee263b5d
<span class="go">Step 3/9 : ARG conf
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>57a78bc9e8ce
<span class="go">Step 4/9 : ARG warpath
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>638fca357d24
<span class="go">Step 5/9 : RUN rm -rf /usr/local/tomcat/webapps/*
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>928ef1b94bb2
<span class="gp">Step 6/9 : COPY $</span>conf/<span class="k">*</span> /usr/local/tomcat/conf/
<span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>30f8faae0012
<span class="gp">Step 7/9 : COPY $</span>warpath /usr/local/tomcat/webapps/ROOT.war
<span class="gp"> ---></span><span class="w"> </span>3bd3ddeff2d6
<span class="go">Removing intermediate container 9c0b330ce6f6
Step 8/9 : WORKDIR /usr/local/tomcat/bin
</span><span class="gp"> ---></span><span class="w"> </span>dc773d206d87
<span class="go">Removing intermediate container 693e8b125384
Step 9/9 : CMD catalina.sh run
</span><span class="gp"> ---></span><span class="w"> </span>Running <span class="k">in </span>c430115e5460
<span class="gp"> ---></span><span class="w"> </span>3879504509c0
<span class="go">Removing intermediate container c430115e5460
Successfully built 3879504509c0
Successfully tagged tomcatdocker1_tomcat2:latest
Building tomcat1
Step 1/9 : FROM tomcat:latest
</span><span class="gp"> ---></span><span class="w"> </span>0fbedce2f08c
<span class="gp">Step 2/9 : MAINTAINER jistol <pptwenty@gmail.com></span><span class="w">
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>13adee263b5d
<span class="go">Step 3/9 : ARG conf
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>57a78bc9e8ce
<span class="go">Step 4/9 : ARG warpath
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>638fca357d24
<span class="go">Step 5/9 : RUN rm -rf /usr/local/tomcat/webapps/*
</span><span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>26dea2133cee
<span class="gp">Step 6/9 : COPY $</span>conf/<span class="k">*</span> /usr/local/tomcat/conf/
<span class="gp"> ---></span><span class="w"> </span>Using cache
<span class="gp"> ---></span><span class="w"> </span>c546b169bf25
<span class="gp">Step 7/9 : COPY $</span>warpath /usr/local/tomcat/webapps/ROOT.war
<span class="gp"> ---></span><span class="w"> </span>4f1edfc42b8d
<span class="go">Removing intermediate container f02a70122c3a
Step 8/9 : WORKDIR /usr/local/tomcat/bin
</span><span class="gp"> ---></span><span class="w"> </span>50683e072451
<span class="go">Removing intermediate container e2ca57d27775
Step 9/9 : CMD catalina.sh run
</span><span class="gp"> ---></span><span class="w"> </span>Running <span class="k">in </span>8da01a9227c2
<span class="gp"> ---></span><span class="w"> </span>1e050e465174
<span class="go">Removing intermediate container 8da01a9227c2
Successfully built 1e050e465174
Successfully tagged tomcatdocker1_tomcat1:latest
Creating tomcatdocker1_nginx_1 ...
Creating tomcatdocker1_tomcat1_1 ...
Creating tomcatdocker1_tomcat2_1 ...
Creating tomcatdocker1_nginx_1
Creating tomcatdocker1_tomcat1_1
Creating tomcatdocker1_tomcat1_1 ... done
</span></code></pre></div></div>
<p>docker 프로세스를 확인해보면 다음과 같이 3개의 컨테이너가 올라간 것을 확인 할 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker ps <span class="nt">-a</span>
<span class="go">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1e2240de4a77 tomcatdocker1_tomcat1 "catalina.sh run" 4 minutes ago Up 4 minutes 8080/tcp tomcatdocker1_tomcat1_1
9706f7d0f85a tomcatdocker1_tomcat2 "catalina.sh run" 4 minutes ago Up 4 minutes 8080/tcp tomcatdocker1_tomcat2_1
</span><span class="gp">20aa7abec733 tomcatdocker1_nginx "nginx -g 'daemon ..." 4 minutes ago Up 4 minutes 80/tcp, 0.0.0.0:8080-></span>8080/tcp tomcatdocker1_nginx_1
</code></pre></div></div>
<h2 id="중지">중지</h2>
<p>아래 명령어를 통해 중지할 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose down
</code></pre></div></div>
Tomcat 8 Session Manager
2017-09-15T00:00:00+00:00
2017-09-15T00:00:00+00:00
https://github.com/jistol/java/2017/09/15/tomcat-manager
<p>세션을 어떻게 복제/관리 할 지를 결정합니다.
Clustering에 사용되는 Manager는 <code class="language-plaintext highlighter-rouge">DeltaManager</code>, <code class="language-plaintext highlighter-rouge">BackupManager</code>, <code class="language-plaintext highlighter-rouge">PersistentManager</code>이며 <br />
그 중 in-memory 방식은 <code class="language-plaintext highlighter-rouge">DeltaManager</code>, <code class="language-plaintext highlighter-rouge">BackupManager</code>를 사용해야합니다.</p>
<h2 id="standardmanager">StandardManager</h2>
<p>tomcat에서 기본적으로 사용하는 Manager로 메모리에 세션을 가지고 있다가 tomcat이 중지될때 SESSIONS.ser라는 파일에 세션을 저장하고 재기동시 해당 파일의 내용을 메모리에 올리고 파일을 지웁니다. <br />
conf/context.xml의 pathname에 지정된 이름을 사용하며 파일을 생성하지 않으려면 <code class="language-plaintext highlighter-rouge">pathname=""</code>로 설정하면 됩니다.</p>
<p>tomcat에 별도의 설정을 하지 않았을 경우 해당 Manager를 사용하게 됩니다.</p>
<h2 id="deltamanager">DeltaManager</h2>
<p>모든 노드에 동일한 세션을 복제합니다. 정보가 변경될때마다 복제하기 때문에 노드 개수가 많을 수록 네트워크 트래픽이 높아지고 메모리 소모가 심해집니다.</p>
<h2 id="backupmanager">BackupManager</h2>
<p>Primary Node와 Backup Node로 분리되어 모든 노드에 복제하지 않고 단 Backup Node에만 복제합니다. 하나의 노드에만 복제하기 때문에 DeltaManager의 단점을 커버할 수 있고 failover도 지원한다고 합니다. <br />
동작 방식은 아래 예를 참고하세요.</p>
<ul>
<li>tomcat을 3대, 앞단 loadbalancer를 둔 상태로 session1이 접근합니다.</li>
<li>session1이 tomcat1로 접속</li>
<li>tomcat1은 정보저장(primary node)후 tomcat2에 정보전달(backup node)</li>
<li>session1이 tomcat3으로 접속</li>
<li>tomcat3은 session1의 정보가 없으므로 tomcat2에 정보 요청</li>
<li>tomcat2는 tomcat3에게 정보 전달</li>
</ul>
<h2 id="persistentmanager">PersistentManager</h2>
<p>DB나 파일시스템을 이용하여 세션을 저장합니다. IO문제가 생기기 떄문에 실시간성이 떨어집니다.</p>
<p>각 Manager의 상세 옵션은 <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster.html">Apache Tomcat 8 Configuration Reference - The Cluster object</a>에서 확인하세요.</p>
<h2 id="참고">참고</h2>
<p><a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster.html">Apache Tomcat 8 Configuration Reference - The Cluster object</a><br />
<a href="http://www.ramkitech.com/2012/12/tomcat-clustering-series-part-4-session.html">Tomcat Clustering Series Part 4 : Session Replication using Backup Manager</a> <br />
<a href="http://sarc.io/index.php/tomcat/249-tomcat-session-standardmanager">Tomcat Session StandardManager</a></p>
Tomcat 8 세션 클러스터링 하기
2017-09-15T00:00:00+00:00
2017-09-15T00:00:00+00:00
https://github.com/jistol/java/2017/09/15/tomcat-clustering
<p>WAS간 세션 공유해야하는 일이 생겨서 Tomcat Clustering을 한 내용을 정리해봅니다.</p>
<h2 id="설정하기">설정하기</h2>
<p><a href="http://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html">Apache Tomcat 8 - Clustering/Session Replication HOW-TO</a> 문서를 보면 정말 간단합니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- server.xml --></span>
<span class="nt"><Cluster</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.tcp.SimpleTcpCluster"</span><span class="nt">/></span>
</code></pre></div></div>
<p>설치 후 기본으로 포함되어 있는 server.xml 파일에서 위 라인의 주석만 제거해주면 설정 끝.
그 다음에 <code class="language-plaintext highlighter-rouge">WEB-INF/web.xml</code> 파일에 아래와 같이 한 줄 넣어주면 됩니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- web.xml --></span>
<span class="nt"><distributable/></span>
</code></pre></div></div>
<p>위와 같이 작성하면 기본적으로 아래와 같이 동작합니다.</p>
<ol>
<li>multicast 방식으로 동작하며 address는 ‘228.0.0.4’, port는 ‘45564’를 사용하고 서버 IP는 <code class="language-plaintext highlighter-rouge">java.net.InetAddress.getLocalHost().getHostAddress()</code>로 얻어진 IP 값으로 송출됩니다.</li>
<li>먼저 구동되는 서버부터 4000 ~ 4100 사이의 TCP port를 통해 reqplication message를 listening합니다.</li>
<li>Listener는 <code class="language-plaintext highlighter-rouge">ClusterSessionListener</code>, interceptor는 <code class="language-plaintext highlighter-rouge">TcpFailureDetector</code>와 <code class="language-plaintext highlighter-rouge">MessageDispatchInterceptor</code>가 설정됩니다.</li>
</ol>
<p>아래와 같이 설정되었다고 보면됩니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><Cluster</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.tcp.SimpleTcpCluster"</span> <span class="na">channelSendOptions=</span><span class="s">"8"</span><span class="nt">></span>
<span class="nt"><Manager</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.session.DeltaManager"</span> <span class="na">expireSessionsOnShutdown=</span><span class="s">"false"</span> <span class="na">notifyListenersOnReplication=</span><span class="s">"true"</span><span class="nt">/></span>
<span class="nt"><Channel</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.group.GroupChannel"</span><span class="nt">></span>
<span class="nt"><Membership</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.membership.McastService"</span>
<span class="na">address=</span><span class="s">"228.0.0.4"</span>
<span class="na">port=</span><span class="s">"45564"</span>
<span class="na">frequency=</span><span class="s">"500"</span>
<span class="na">dropTime=</span><span class="s">"3000"</span><span class="nt">/></span>
<span class="nt"><Receiver</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.transport.nio.NioReceiver"</span>
<span class="na">address=</span><span class="s">"auto"</span>
<span class="na">port=</span><span class="s">"4000"</span>
<span class="na">autoBind=</span><span class="s">"100"</span>
<span class="na">selectorTimeout=</span><span class="s">"5000"</span>
<span class="na">maxThreads=</span><span class="s">"6"</span><span class="nt">/></span>
<span class="nt"><Sender</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.transport.ReplicationTransmitter"</span><span class="nt">></span>
<span class="nt"><Transport</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.transport.nio.PooledParallelSender"</span><span class="nt">/></span>
<span class="nt"></Sender></span>
<span class="nt"><Interceptor</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"</span><span class="nt">/></span>
<span class="nt"><Interceptor</span> <span class="na">className=</span><span class="s">"org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"</span><span class="nt">/></span>
<span class="nt"></Channel></span>
<span class="nt"><Valve</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.tcp.ReplicationValve"</span> <span class="na">filter=</span><span class="s">""</span><span class="nt">/></span>
<span class="nt"><Valve</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.session.JvmRouteBinderValve"</span><span class="nt">/></span>
<span class="nt"><Deployer</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.deploy.FarmWarDeployer"</span>
<span class="na">tempDir=</span><span class="s">"/tmp/war-temp/"</span>
<span class="na">deployDir=</span><span class="s">"/tmp/war-deploy/"</span>
<span class="na">watchDir=</span><span class="s">"/tmp/war-listen/"</span>
<span class="na">watchEnabled=</span><span class="s">"false"</span><span class="nt">/></span>
<span class="nt"><ClusterListener</span> <span class="na">className=</span><span class="s">"org.apache.catalina.ha.session.ClusterSessionListener"</span><span class="nt">/></span>
<span class="nt"></Cluster></span>
</code></pre></div></div>
<h2 id="manager">Manager</h2>
<p>세션을 어떻게 복제할지를 책임지는 객체로 Clustering시 사용되는 매니저는 아래와 같이 3가지 입니다.</p>
<ol>
<li>DeltaManager <br />
모든 노드에 동일한 세션을 복제합니다. 정보가 변경될때마다 복제하기 때문에 노드 개수가 많을 수록 네트워크 트래픽이 높아지고 메모리 소모가 심해집니다.</li>
</ol>
<ul>
<li>notifyListenersOnReplication : 다른 tomcat에서 세션이 생성/소멸시 알림을 받을지 여부입니다.</li>
<li>expireSessionsOnShutdown : tomcat서버가 shutdown될 때 모든 노드의 모든 세션들을 expire할지 여부로 default는 false입니다.</li>
</ul>
<ol>
<li>
<p>BackupManager <br />
Primary Node와 Backup Node로 분리되어 모든 노드에 복제하지 않고 단 Backup Node에만 복제합니다. 하나의 노드에만 복제하기 때문에 DeltaManager의 단점을 커버할 수 있고 failover도 지원한다고 합니다.</p>
</li>
<li>
<p>PersistentManager
DB나 파일시스템을 이용하여 세션을 저장합니다. IO문제가 생기기 떄문에 실시간성이 떨어집니다.</p>
</li>
</ol>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-manager.html">The ClusterManager object</a></p>
<h2 id="channel">Channel</h2>
<p>서로 다른 tomcat간의 메시지 송수신에 관련된 하위 Component를 그룹핑합니다. <br />
하위 Component로는 <code class="language-plaintext highlighter-rouge">Membership</code>, <code class="language-plaintext highlighter-rouge">Sender</code>, <code class="language-plaintext highlighter-rouge">Sender/Transport</code>, <code class="language-plaintext highlighter-rouge">Receiver</code>, <code class="language-plaintext highlighter-rouge">Interceptor</code>가 있고 현재 Channel구현체는 <code class="language-plaintext highlighter-rouge">org.apache.catalina.tribes.group.GroupChannel</code>가 유일합니다.</p>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-channel.html">The Cluster Channel object</a></p>
<h2 id="channelmembership">Channel/Membership</h2>
<p>Cluster안의 노드들을 동적으로 분별하는데 multicast IP/PORT를 통해 <code class="language-plaintext highlighter-rouge">frequency</code>에 설정된 간격으로 각 노드들이 UDP packet을 날려 heartbeat 확인합니다. <br />
<code class="language-plaintext highlighter-rouge">dropTime</code>에 설정된 시간동안 heartbeat가 없을 경우 장애로 판단하고 각 노드에 알리게 됩니다.</p>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-membership.html">The Cluster Membership object</a></p>
<h2 id="channelsender-channelsendertransport">Channel/Sender, Channel/Sender/Transport</h2>
<p>Sender는 노드에서 Cluster로 메시지를 보내는 역활을 합니다. 사실상 빈 껍데기로 상세 역확을 Transport에서 정의됩니다. <br />
Transport는 기본적으로 <code class="language-plaintext highlighter-rouge">org.apache.catalina.tribes.transport.nio.PooledParallelSender</code>를 사용하는데 non-blocking 방식으로 동시에 여러 노드로 메시지를 보낼수도, 하나의 노드에 여러 메시지를 동시에 보낼수도 있습니다.
<code class="language-plaintext highlighter-rouge">org.apache.catalina.tribes.transport.bio.PooledMultiSender</code>는 blocking 방식을 사용합니다.</p>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-sender.html">The Cluster Sender object</a></p>
<h2 id="channelreceiver">Channel/Receiver</h2>
<p>Cluster로부터 메시지를 수신하는 역활을 하며 blocking방식 <code class="language-plaintext highlighter-rouge">org.apache.catalina.tribes.transport.bio.BioReceiver</code>와 non-blocking방식인 <code class="language-plaintext highlighter-rouge">org.apache.catalina.tribes.transport.nio.NioReceiver</code>을 지원합니다. <br />
tomcat에서는 non-blocking방식을 추천하며 노드수가 많아져서 제한된 thread를 통해 많은 메시지를 받아들일 수 있다고 합니다. 기본적으로 노드당 1개의 thread를 할당합니다.</p>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-receiver.html">The Cluster Receiver object</a></p>
<h2 id="channelinterceptor">Channel/Interceptor</h2>
<p>Membership 알림 또는 메시지를 가로챌수 있고, documentation에도 각 interceptor에 대한 자세한 설명은 안나왔지만 각 클래스 명으로 역활 구분이 가능한 수준인것 같습니다.</p>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-interceptor.html">The Channel Interceptor object</a></p>
<h2 id="valve">Valve</h2>
<p><code class="language-plaintext highlighter-rouge">org.apache.catalina.ha.ClusterValve</code>를 구현한 객체로 일반적인 <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/valve.html">Tomcat Valve</a>처럼 HTTP Request processing에 관여하는 역활을 하는데 clustering시 중간 interceptor역활을 합니다. <br />
예를 들어 <code class="language-plaintext highlighter-rouge">org.apache.catalina.ha.tcp.ReplicationValve</code>의 경우 HTTP Request가 끝나는 시점에 다른 복제를 해야할지 말아야 할지 cluster에 알리는 역활을 합니다. <br />
<code class="language-plaintext highlighter-rouge">org.apache.catalina.ha.session.JvmRouteBinderValve</code>의 경우 mod_jk를 사용중 failover시 session에 저장한 jvmWorker속성을 변경하여 다음 request부터는 해당 노드에 고정시킵니다.</p>
<p>참고 : <a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-valve.html">The Cluster Valve object</a></p>
<h2 id="deployer">Deployer</h2>
<p>WAR배포시 cluster안의 다른 노드에도 같이 배포해줍니다.</p>
<p>참고:<a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-deployer.html">The Cluster Deployer object</a></p>
<h2 id="clusterlistener">ClusterListener</h2>
<p>Cluster내 다른 노드의 메시지를 받습니다.
DeltaManager를 사용할 경우 Manager는 ClusterSessionListener를 통해 메시지를 받게 됩니다.</p>
<p>참고:<a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster-listener.html">The ClusterListener object</a></p>
<h2 id="기타">기타</h2>
<p>AWS를 포함한 모든 클라우드 서비스는 multicast를 지원하지 않고 있어 tomcat clustering 방식을 사용할 수 없습니다.</p>
<h2 id="참고">참고</h2>
<p><a href="http://tomcat.apache.org/tomcat-8.5-doc/cluster-howto.html">Apache Tomcat 8 - Clustering/Session Replication HOW-TO</a> <br />
<a href="http://tomcat.apache.org/tomcat-8.5-doc/config/cluster.html">Apache Tomcat 8 Configuration Reference - The Cluster object</a><br />
<a href="http://www.ramkitech.com/2012/12/tomcat-clustering-series-part-4-session.html">Tomcat Clustering Series Part 4 : Session Replication using Backup Manager</a> <br />
<a href="http://sarc.io/index.php/tomcat/249-tomcat-session-standardmanager">Tomcat Session StandardManager</a></p>
(Thymeleaf) 파라메터 모두 출력하는 샘플 코드 (th:each, ${param})
2017-09-15T00:00:00+00:00
2017-09-15T00:00:00+00:00
https://github.com/jistol/spring/2017/09/15/thymeleaf-param-print
<p><code class="language-plaintext highlighter-rouge">${param}</code>변수를 <code class="language-plaintext highlighter-rouge">th:each</code>를 태워 Request의 모든 파라메터를 출력하는 예제 소스 입니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span> <span class="na">xmlns:th=</span><span class="s">"http://www.thymeleaf.org"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><title></span>Title<span class="nt"></title></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"</span> <span class="na">integrity=</span><span class="s">"sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">/></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"container-fluid"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">th:each=</span><span class="s">"res : ${param}"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"row"</span><span class="nt">><div</span> <span class="na">class=</span><span class="s">"col-2"</span> <span class="na">th:text=</span><span class="s">"${res.key + ' : '}"</span><span class="nt">></div><div</span> <span class="na">class=</span><span class="s">"col-6"</span> <span class="na">th:text=</span><span class="s">"${res.value[0]}"</span><span class="nt">></div></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre></div></div>
(Thymeleaf) th:attr 사용시 변수와 문자열 섞어쓰는 방법
2017-09-14T00:00:00+00:00
2017-09-14T00:00:00+00:00
https://github.com/jistol/spring/2017/09/14/thymeleaf-attr-string-append
<p><code class="language-plaintext highlighter-rouge">th:attr</code>사용시 변수와 문자열 섞어 쓰는 방법을 정리해봅니다.</p>
<p>예를 들어 <code class="language-plaintext highlighter-rouge">http://localhost:8080/demo/test</code> 와 같은 URL값을 input value에 넣을 때 ‘http://localhost:8080’를 변수처리 하는 방법입니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># application.yml</span>
<span class="s">base.url</span><span class="pi">:</span> <span class="s">http://localhost:8080</span>
</code></pre></div></div>
<p>설정 파일에 위와 같이 설정 되 있을 경우 html파일에서 다음과 같이 사용 할 수 있습니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- html source --></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">th:attr=</span><span class="s">"value=${@environment.getProperty('base.url') + '/demo/test'}"</span><span class="nt">/></span>
</code></pre></div></div>
<p>환경 설정 값에서 가져오기 위해 <code class="language-plaintext highlighter-rouge">@environment.getProperty</code>를 사용했고 문자열을 <code class="language-plaintext highlighter-rouge">${...}</code> 안에서 + 기호로 합치면 됩니다.</p>
(SpringBoot) application.yml 에서 값이 8진수로 변경되는 경우
2017-09-13T00:00:00+00:00
2017-09-13T00:00:00+00:00
https://github.com/jistol/spring/2017/09/13/springboot-application-value-conversion
<h2 id="문제">문제</h2>
<p>프로그램 버그를 잡는중 아래와 같은 문제가 생겼습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1"># apllication.yml</span>
<span class="na">tran-cd</span><span class="pi">:</span>
<span class="na">req</span><span class="pi">:</span> <span class="m">00100000</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Value</span><span class="o">(</span><span class="s">"${tran-cd.req}"</span><span class="o">)</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">code</span><span class="o">;</span> <span class="c1">// expect "00100000" but "32768"</span>
</code></pre></div></div>
<p>위와 같이 해당 설정값이 이상하게 변경되어 있는 것입니다. “00100000”로 나와야하는데 자꾸 “32768”로 나옵니다.
문제는 yaml 1.1 버전에서 맨 앞자리가 “0”으로 시작하면 해당 값을 8진수로 인식하게 되고 Spring에서 @Value로 가져올 때 해당 값을 10진수로 변경하여 String 으로 반환하는 것이였습니다.</p>
<h2 id="해결방법">해결방법</h2>
<p>yaml에서는 쌍따옴표(double quotes), 외따옴표(single quote)로 문자열을 쌓을수 있습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#application.yml</span>
<span class="na">tran-cd</span><span class="pi">:</span>
<span class="na">req</span><span class="pi">:</span> <span class="s2">"</span><span class="s">00100000"</span>
</code></pre></div></div>
<h2 id="그-외">그 외</h2>
<p>yaml문법중 <code class="language-plaintext highlighter-rouge">%YAML</code> 태그를 이용하여 버전을 명시 할 수 있습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">%</span> <span class="s">YAML </span><span class="m">1.2</span>
</code></pre></div></div>
<p>YAML 1.2에서는 8진수 표현법이 바뀌어서 위와 같은 오류를 막을수 있으나 <code class="language-plaintext highlighter-rouge">application.yml</code>에 적용해도 위 문제가 해결되지 않는 것으로 보아 SpringBoot에서 쓰는 Yaml Parser가 1.1로만 인식하나 봅니다.
해결 방법은 찾지 못했네요.</p>
<h2 id="참고">참고</h2>
<p><a href="http://www.yaml.org/refcard.html">%YAML 1.1 # Reference card</a></p>
express-generator - Node.js + Express 프로젝트 생성하기
2017-09-07T00:00:00+00:00
2017-09-07T00:00:00+00:00
https://github.com/jistol/nodejs/2017/09/07/express-generator
<p>처음 Node.js 개발환경을 구성 할 때 이것저것 설정할게 많은데 간단하게 “Node.js + Express”구조의 뼈대를 만들어주는 <code class="language-plaintext highlighter-rouge">express-generator</code>라는 도구가 있습니다.</p>
<blockquote>
<p>“Java + Spring Boot” 개발환경의 뼈대를 만들어주는 <a href="/java/2017/03/07/springboot-initilizr/">Spring Initializr</a>와 비슷한 녀석입니다.</p>
</blockquote>
<h2 id="설치">설치</h2>
<p>npm을 통해서 아래와 같이 설치합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>npm <span class="nb">install</span> <span class="nt">-g</span> express-generator
</code></pre></div></div>
<h2 id="프로젝트-만들기">프로젝트 만들기</h2>
<p><code class="language-plaintext highlighter-rouge">express</code>라는 명령어로 실행이 가능한데 도움말을 보면 아래와 같이 설정을 볼 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>express <span class="nt">-h</span>
<span class="go">
Usage: express [options] [dir]
Options:
-h, --help output usage information
--version output the version number
-e, --ejs add ejs engine support
--pug add pug engine support
--hbs add handlebars engine support
-H, --hogan add hogan.js engine support
</span><span class="gp"> -v, --view <engine></span><span class="w"> </span>add view <engine> support <span class="o">(</span>dust|ejs|hbs|hjs|jade|pug|twig|vash<span class="o">)</span> <span class="o">(</span>defaults to jade<span class="o">)</span>
<span class="gp"> -c, --css <engine></span><span class="w"> </span>add stylesheet <engine> support <span class="o">(</span>less|stylus|compass|sass<span class="o">)</span> <span class="o">(</span>defaults to plain css<span class="o">)</span>
<span class="go"> --git add .gitignore
-f, --force force on non-empty directory
</span></code></pre></div></div>
<p>먼저 <code class="language-plaintext highlighter-rouge">express</code>를 이용하여 뼈대가 되는 소스를 만들고 해당 프로젝트 폴더 내에서 <code class="language-plaintext highlighter-rouge">npm install</code>을 실행하여 dependency를 다운 받고 사용하면 됩니다. <br />
만약 template engine은 <code class="language-plaintext highlighter-rouge">handlebars</code>를 사용하고 css engine은 <code class="language-plaintext highlighter-rouge">sass</code>를 사용한다면 아래와 같이 실행하시면 됩니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>express <span class="nt">--view</span><span class="o">=</span>hbs <span class="nt">--css</span><span class="o">=</span>sass <project <span class="nb">dir</span><span class="o">></span>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <project <span class="nb">dir</span><span class="o">></span>
<span class="gp">$</span><span class="w"> </span>npm <span class="nb">install</span>
<span class="gp">$</span><span class="w"> </span>npm start
</code></pre></div></div>
<p>기본적으로 package.json에 <code class="language-plaintext highlighter-rouge">start</code>커맨드를 통해 서버를 올릴 수 있도록 script를 만들어주며 실행시 <a href="http://localhost:3000">http://localhost:3000</a>으로 접속하여 동작 화면을 확인 할 수 있습니다. <br />
<img src="/assets/img/nodejs/express-generator/1.png" alt="run-server" /></p>
<p>app.js 파일을 열면 다음과 같이 template/css engine이 설정 되어 있는 것을 확인 할 수 있습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app.js</span>
<span class="c1">// view engine setup</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">views</span><span class="dl">'</span><span class="p">,</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">views</span><span class="dl">'</span><span class="p">));</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">view engine</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">hbs</span><span class="dl">'</span><span class="p">);</span>
<span class="p">...</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">sassMiddleware</span><span class="p">({</span>
<span class="na">src</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">public</span><span class="dl">'</span><span class="p">),</span>
<span class="na">dest</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">public</span><span class="dl">'</span><span class="p">),</span>
<span class="na">indentedSyntax</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// true = .sass and false = .scss</span>
<span class="na">sourceMap</span><span class="p">:</span> <span class="kc">true</span>
<span class="p">}));</span>
</code></pre></div></div>
<h2 id="sass-변경하기">SASS 변경하기</h2>
<p>설정을 보면 기본적으로 sass를 사용하도록 되어 있습니다. 저는 scss확장자를 쓰고 싶으니 변경해 보도록 하겠습니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app.js</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">sassMiddleware</span><span class="p">({</span>
<span class="p">...</span>
<span class="na">indentedSyntax</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span> <span class="c1">// true = .sass and false = .scss</span>
<span class="p">...</span>
<span class="p">}));</span>
</code></pre></div></div>
<p>기본적으로 CSS경로는 <code class="language-plaintext highlighter-rouge">/public/stylesheets</code> 하위로 설정되어 있습니다. sass파일을 scss로 바꿉니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">style.sass -></span><span class="w"> </span>style.scss
</code></pre></div></div>
<p>그리고 설정을 scss문법에 맞게 수정합니다.</p>
<div class="language-scss highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// style.scss</span>
<span class="nt">body</span> <span class="p">{</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">51px</span><span class="p">;</span>
<span class="nl">font</span><span class="p">:</span> <span class="m">25px</span> <span class="s2">"Lucida Grande"</span><span class="o">,</span> <span class="n">Helvetica</span><span class="o">,</span> <span class="n">Arial</span><span class="o">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
<span class="nl">color</span><span class="p">:</span> <span class="mh">#445544</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">a</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="mh">#AAB7FF</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">node-sass-middleware</code>의 옵션 중에 css파일을 압축해주는 ‘compressed’ 옵션이 있습니다. 아래와 같이 적용해봅니다.</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// app.js</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">sassMiddleware</span><span class="p">({</span>
<span class="p">...</span>
<span class="na">outputStyle</span><span class="p">:</span> <span class="dl">'</span><span class="s1">compressed</span><span class="dl">'</span><span class="p">,</span>
<span class="p">...</span>
<span class="p">}));</span>
</code></pre></div></div>
<p>적용이 완료되고 <code class="language-plaintext highlighter-rouge">npm start</code>명령을 통해 서버를 실행하고 <a href="http://localhost:3000">http://localhost:3000</a>을 호출하여 다운받은 style.css파일을 확인하면 다음과 같이 변경된 것을 확인 할 수 있습니다.</p>
<blockquote>
<p>css/template파일을 변경시 바로 적용되나 app.js파일 수정시에는 반드시 express server를 재시작해야 합니다.</p>
</blockquote>
<p><img src="/assets/img/nodejs/express-generator/2.png" alt="compressed-scss-file" /></p>
<h2 id="참고">참고</h2>
<p><a href="https://www.npmjs.com/package/express-generator">npm express-generator</a> <br />
<a href="https://www.npmjs.com/package/node-sass-middleware">npm node-sass-middleware</a></p>
Error creating Node.js Express App. Cannot find - WebStorm에서 Node.js프로젝트 생성시 오류
2017-09-07T00:00:00+00:00
2017-09-07T00:00:00+00:00
https://github.com/jistol/nodejs/2017/09/07/error-creating-node_js-express_app-cannot_find
<p>Node.js로 개발해보려고 WebStorm에서 Node.js 프로젝트를 생성하다가 아래와 같은 오류를 만났습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error creating Node.js Express App. Cannot find
</code></pre></div></div>
<p><img src="/assets/img/nodejs/error-creating-node_js-express_app-cannot_find/1.png" alt="create-project" /> <br />
<img src="/assets/img/nodejs/error-creating-node_js-express_app-cannot_find/2.png" alt="error-message" /></p>
<p>구글링을 해보니 답변은 <code class="language-plaintext highlighter-rouge">express-generator</code>를 이용하여 만든 후 WebStorm에서 해당 폴더를 오픈하여 프로젝트를 생성하라고 하더군요. (<a href="https://stackoverflow.com/questions/43125932/error-creating-node-js-express-app-cannot-find">Error creating Node.js Express App. Cannot find</a>) <br />
생성 방법은 아래와 같습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>npm <span class="nb">install</span> <span class="nt">-g</span> express-generator
<span class="gp">$</span><span class="w"> </span>express <project_name>
<span class="gp">$</span><span class="w"> </span><span class="nb">cd</span> <project_name>
<span class="gp">$</span><span class="w"> </span>npm <span class="nb">install</span>
</code></pre></div></div>
<blockquote>
<p>자세한 생성 방법은 <a href="/nodejs/2017/09/07/express-generator/">express-generator - Node.js + Express 프로젝트 생성하기</a>를 참고하세요.</p>
</blockquote>
<p>위 명령 실행 후 WebStrom에서 Open하여 사용하면 정상적으로 만들어져 있는것을 확인 할 수 있습니다.</p>
<p>또 다른 방법으로는 WebStorm에서 Node.js 프로젝트 생성시 사용하는 <code class="language-plaintext highlighter-rouge">express-generator</code>버전을 낮춰서 해결 할 수 있는데 “14.14.1”로 낮추면 정상 생성 가능합니다. <br />
<img src="/assets/img/nodejs/error-creating-node_js-express_app-cannot_find/3.png" alt="downgrade-version" /></p>
<blockquote>
<p>4.13.0 버전 이하로 낮출 경우 SASS를 쓸 수 없으니 주의하세요</p>
</blockquote>
<h2 id="참고">참고</h2>
<p><a href="https://stackoverflow.com/questions/43125932/error-creating-node-js-express-app-cannot-find">Error creating Node.js Express App. Cannot find</a></p>
Docker Redis 사용하기
2017-09-01T00:00:00+00:00
2017-09-01T00:00:00+00:00
https://github.com/jistol/docker/2017/09/01/docker-redis
<p>Docker를 이용하여 간단하게 redis 설치 및 사용하는 방법을 정리해봤습니다.</p>
<h2 id="설치">설치</h2>
<p>Docker가 설치 된 상태에서 아래 커맨드를 이용하여 설치합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker pull redis
</code></pre></div></div>
<h2 id="시작하기">시작하기</h2>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker run <span class="nt">--name</span> some-redis <span class="nt">-d</span> <span class="nt">-p</span> 6379:6379 redis
</code></pre></div></div>
<p>Docker를 실행하여 Redis서버를 올리고 기본 포트인 <code class="language-plaintext highlighter-rouge">6379</code>로 실행됩니다. <br />
<code class="language-plaintext highlighter-rouge">-d</code>옵션은 백그라운드에서 실행하겠다는 의미이며 <br />
<code class="language-plaintext highlighter-rouge">-p</code>옵션은 외부에서 해당 포트로 접속할 수 있게 열어둔다는 의미입니다.</p>
<p>해당 Redis서버의 데이터를 외부에서 관리하고 싶을 경우에는 아래와 같이 사용합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">#</span><span class="c"># 외부 폴더에 데이터 저장소를 두고 싶을 경우</span>
<span class="gp">$</span><span class="w"> </span>docker run <span class="nt">--name</span> some-redis <span class="nt">-d</span> <span class="nt">-v</span> /your/dir:/data redis redis-server <span class="nt">--appendonly</span> <span class="nb">yes</span>
<span class="go">
</span><span class="gp">#</span><span class="c"># 다른 컨테이너에 저장소를 두고 싶은 경우</span>
<span class="gp">$</span><span class="w"> </span>docker run <span class="nt">--name</span> some-redis <span class="nt">-d</span> <span class="nt">--volumes-from</span> some-volume-container redis redis-server <span class="nt">--appendonly</span> <span class="nb">yes</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">appendonly yes</code> 옵션은 AOF방식으로 데이터를 저장(참고:<a href="http://www.redisgate.com/redis/configuration/persistence.php">Redis Persistence Introduction</a>)하겠다는 의미입니다. <br />
데이터는 기본적으로 <code class="language-plaintext highlighter-rouge">/data</code>하위에 저장되며 외부에서 해당 폴더를 공유함으로써 해당 컨테이너를 지우고 새로 만들어도 해당 volume을 참고하게 하면 동일한 데이터를 유지 할 수 있습니다.</p>
<blockquote>
<p>OS X의 경우 사전에 Docker에서 공유폴더로 지정되지 않은 경우 아래와 같은 오류를 만날 수 있습니다.</p>
<p>docker: Error response from daemon: Mounts denied: <br />
The path /Users/jistol/data <br />
is not shared from OS X and is not known to Docker. <br />
You can configure shared paths from Docker -> Preferences… -> File Sharing. <br />
See https://docs.docker.com/docker-for-mac/osxfs/#namespaces for more info.</p>
<p>위와 같은 오류 발생시 메시지에 나온데로 <code class="language-plaintext highlighter-rouge">Docker -> Preferences -> File Sharing</code> 설정에서 공유할 폴더를 추가해 주면 됩니다. <br />
<img src="/assets/img/docker/docker-redis/1.png" alt="file sharing" /></p>
</blockquote>
<h2 id="외부에서-접근하기">외부에서 접근하기</h2>
<p>외부에서 Redis 컨테이너를 접근하는 방법은 3가지 입니다.</p>
<h3 id="1-외부-서버에서-접근하기">1. 외부 서버에서 접근하기</h3>
<p>위에 명시한 것과 같이 <code class="language-plaintext highlighter-rouge">-p</code> 옵션을 통해 port를 뚫어 직접 접근 할 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker run <span class="nt">--name</span> some-redis <span class="nt">-d</span> <span class="nt">-p</span> 6379:6379 redis
</code></pre></div></div>
<h3 id="2-다른-컨테이너에서-접근하기">2. 다른 컨테이너에서 접근하기</h3>
<p><code class="language-plaintext highlighter-rouge">--link</code>나 <code class="language-plaintext highlighter-rouge">-network</code>옵션을 통해 접근 가능합니다. (참고:<a href="https://docs.docker.com/engine/userguide/networking/#the-docker_gwbridge-network">Docker container networking</a>)</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker run <span class="nt">--name</span> some-app <span class="nt">--link</span> some-redis:redis <span class="nt">-d</span> application-that-uses-redis
</code></pre></div></div>
<h3 id="3-redis-cli로-접근하기">3. redis-cli로 접근하기</h3>
<p>아래와 같은 명령으로 접근할 수 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker run <span class="nt">-it</span> <span class="nt">--link</span> some-redis:redis <span class="nt">--rm</span> redis redis-cli <span class="nt">-h</span> redis <span class="nt">-p</span> 6379
</code></pre></div></div>
<p>Redis 컨테이너 기동 방식에 따라 <code class="language-plaintext highlighter-rouge">--link</code>나 <code class="language-plaintext highlighter-rouge">--network</code>, 혹은 port를 외부로 열었다면 두 옵션 없이 사용 가능합니다. <br />
<code class="language-plaintext highlighter-rouge">--rm</code> 옵션은 컨테이너 종료시 자동으로 해당 컨테이너를 삭제해줍니다.</p>
<h2 id="참고">참고</h2>
<p><a href="https://hub.docker.com/_/redis/">library/redis - Docker Hub</a> <br />
<a href="http://www.redisgate.com/redis/configuration/persistence.php">Redis Persistence Introduction</a> <br />
<a href="https://docs.docker.com/engine/userguide/networking/#the-docker_gwbridge-network">Docker container networking</a></p>
Tomcat 8 error - java.lang.IllegalArgumentException: An invalid domain [.xxxx.com] was specified for this cookie
2017-08-30T00:00:00+00:00
2017-08-30T00:00:00+00:00
https://github.com/jistol/java/2017/08/30/tomcat8-invalid-domain
<h2 id="증상">증상</h2>
<p>여러 서브도메인에서 쿠키를 공유하기 위해 <code class="language-plaintext highlighter-rouge">.xxxx.com</code> 같이 쿠키 도메인을 설정하는 케이스가 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// sub domain list : sub1.xxxx.com, sub2.xxxx.com, sub3.xxxx.com</span>
<span class="n">cookie</span><span class="o">.</span><span class="na">setDomain</span><span class="o">(</span><span class="s">".xxxx.com"</span><span class="o">);</span>
</code></pre></div></div>
<p>위와 같이 사용시 Tomcat 8버전 이상 사용할 경우 아래와 같은 에러를 만나게 됩니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">java.lang.IllegalArgumentException: An invalid domain [.xxxx.com] was specified for this cookie
</span></code></pre></div></div>
<h2 id="원인">원인</h2>
<p>tomcat 8버전 이상에서는 Cookie Header를 파싱하는 기본 CookieProcessor가 RFC6265를 기반으로 합니다. (<code class="language-plaintext highlighter-rouge">org.apache.tomcat.util.http.Rfc6265CookieProcessor</code>) <br />
RFC6265의 속성중 하나는 아래와 같은데</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5.2.3. The Domain Attribute
If the attribute-name case-insensitively matches the string "Domain",
the user agent MUST process the cookie-av as follows.
If the attribute-value is empty, the behavior is undefined. However,
the user agent SHOULD ignore the cookie-av entirely.
If the first character of the attribute-value string is %x2E ("."):
Let cookie-domain be the attribute-value without the leading %x2E
(".") character.
Otherwise:
Let cookie-domain be the entire attribute-value.
Convert the cookie-domain to lower case.
Append an attribute to the cookie-attribute-list with an attribute-
name of Domain and an attribute-value of cookie-domain.
</code></pre></div></div>
<p>Domain값 맨 앞자리에 “.”을 붙일 경우 “.”을 제거하고 파싱하게 됩니다.</p>
<h2 id="해결">해결</h2>
<p>위와 같은 현상을 막기 위해 <code class="language-plaintext highlighter-rouge">org.apache.tomcat.util.http.LegacyCookieProcessor</code> 클래스를 제공합니다. <br />
위 클래스는 RFC6265, RFC2109, RFC2616 기반으로 파싱하며 쿠키 작업의 에약을 여러 옵션을 통해 풀 수 있도록 제공하는데 <br />
Tomcat 서버 사용시 context.xml에 아래와 같이 추가해주면 됩니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><CookieProcessor</span> <span class="na">className=</span><span class="s">"org.apache.tomcat.util.http.LegacyCookieProcessor"</span><span class="nt">/></span>
</code></pre></div></div>
<p>만약 SpringBoot에서 Embedded Tomcat을 사용하고 있다면 아래와 같이 설정 할 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">EmbeddedServletContainerCustomizer</span> <span class="nf">tomcatCustomizer</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">container</span> <span class="o">-></span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">container</span> <span class="k">instanceof</span> <span class="nc">TomcatEmbeddedServletContainerFactory</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">TomcatEmbeddedServletContainerFactory</span> <span class="n">tomcat</span> <span class="o">=</span> <span class="o">(</span><span class="nc">TomcatEmbeddedServletContainerFactory</span><span class="o">)</span> <span class="n">container</span><span class="o">;</span>
<span class="n">tomcat</span><span class="o">.</span><span class="na">addContextCustomizers</span><span class="o">(</span><span class="n">context</span> <span class="o">-></span> <span class="n">context</span><span class="o">.</span><span class="na">setCookieProcessor</span><span class="o">(</span><span class="k">new</span> <span class="nc">LegacyCookieProcessor</span><span class="o">()));</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="https://tomcat.apache.org/tomcat-8.5-doc/config/cookie-processor.html">Apache Tomcat 8 Configuration Reference - The Cookie Processor Component</a> <br />
<a href="https://tools.ietf.org/html/rfc6265">RFC 6265 - HTTP State Management Mechanism - IETF Tools</a> <br />
<a href="https://tools.ietf.org/html/rfc2109">RFC 2109 - HTTP State Management Mechanism - IETF Tools</a> <br />
<a href="http://www.voidcn.com/article/p-xpoujgfy-bkq.html">java.lang.IllegalArgumentException: An invalid domain .test.com was specified for this cookie</a></p>
(SpringBoot) No Spring WebApplicationInitializer types detected on classpath - 404
2017-08-25T00:00:00+00:00
2017-08-25T00:00:00+00:00
https://github.com/jistol/spring/2017/08/25/springboot-deploy-no-detected
<p>로컬환경에서 gradle bootRun을 통해 멀쩡하게 돌아가던 서버가 Tomcat WAS에 올렸더니 별다른 ERROR Log도 없이 모든 페이지가 404로 떴습니다.
혹시나 싶어 deploy path에 html파일 하나 만들어놓고 접근해보니 멀쩡하게 페이지가 나오더군요.
멘붕에 빠져 이것저것 건드리다가 신규 Tomcat버전의 문제인가, 신규 SpringBoot버전의 문제인가까지 찾던 도중 catalina log에서 아래와 같은 특이한 메시지를 찾았습니다.</p>
<pre><code class="language-log">Info : No Spring WebApplicationInitializer types detected on classpath
</code></pre>
<p>(이런 중요한 정보가 INFO라니…)
WAS가 SpringBoot의 WAR파일을 인식하긴 했지만 WebApplicationInitializer를 찾지 못한 것이였는데
원인을 찾으려 또 별의 별 삽질을 하며 직접 main-class도 지정하고 구글링하니 Java Version 체크해보라고도 하고
거의 반나절을 헤메다가 원인을 찾았습니다.</p>
<p>SpringBoot Devtools을 사용하기 위해 dependencies에 걸어 두었는데 외부 WAS에 배포할때는 해당 설정을 다 빼고 배포하면 참조 안하겠다 싶어 <code class="language-plaintext highlighter-rouge">providedCompile</code>로 설정하고 application.yml에서 관련 설정을 빼고 배포했으나 이게 WAR로 배포되면서 오류를 발생시켰던 모양입니다.
해당 설정만 <code class="language-plaintext highlighter-rouge">compile</code>로 변경하였더니 잘 동작합니다.</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// build.gradle</span>
<span class="n">dependencies</span> <span class="o">{</span>
<span class="n">providedCompile</span><span class="o">(</span><span class="s2">"org.springframework.boot:spring-boot-devtools"</span><span class="o">)</span> <span class="c1">// ( X )</span>
<span class="n">compile</span><span class="o">(</span><span class="s2">"org.springframework.boot:spring-boot-devtools"</span><span class="o">)</span> <span class="c1">// ( O )</span>
<span class="o">}</span>
</code></pre></div></div>
<p>외부 WAS에서 오류 없이 SpringBoot Container가 올라오지 않는다면 <code class="language-plaintext highlighter-rouge">provided</code>로 설정한 값 중 문제가 있는건 없는지 확인부터 해 보면 좋습니다.</p>
instanceof 와 Class.isAssignableFrom 의 차이점
2017-08-22T00:00:00+00:00
2017-08-22T00:00:00+00:00
https://github.com/jistol/java/2017/08/22/different-instanceof-isassignablefrom
<p>간단하게 <code class="language-plaintext highlighter-rouge">instanceof</code>는 특정 <strong>Object</strong>가 어떤 클래스/인터페이스를 상속/구현했는지를 체크하며 <br />
<code class="language-plaintext highlighter-rouge">Class.isAssignableFrom()</code>은 특정 <strong>Class</strong>가 어떤 클래스/인터페이스를 상속/구현했는지 체크합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// instanceof</span>
<span class="nc">MacPro</span> <span class="n">obj</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">MacPro</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(</span><span class="n">obj</span> <span class="k">instanceof</span> <span class="nc">Computer</span><span class="o">)</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
<span class="c1">// Class.isAssignableFrom()</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">Computer</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">isAssignableFrom</span><span class="o">(</span><span class="nc">MacPro</span><span class="o">.</span><span class="na">class</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="http://hashcode.co.kr/questions/300/instanceof%EB%9E%91-classisassignablefrom%E2%80%A6%EC%9D%98-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EB%AD%90%EC%A3%A0">instanceof랑 Class.isAssignableFrom(…)의 차이가 뭐죠?</a></p>
Antimalware Service Executable - Windows Defender 끄기
2017-08-18T00:00:00+00:00
2017-08-18T00:00:00+00:00
https://github.com/jistol/etc/2017/08/18/etc-stop-antimalware-service-executable
<p>HP 윈탭을 하나 구입했는데 테블릿이다 보니 OS가 먹는 자원을 빼면 CPU/RAM이 얼마 없습니다. <br />
근데 그 와중에 CPU와 RAM을 쓸데없이 많이 잡아먹고 있는 녀석이 있었으니 그것은 MS에서 심어놓은 Windows Defender. <br />
탭을 켜고 아무것도 안하고 있는데도 항상 CPU와 RAM이 거의 100%를 먹고 있어 브라우저 켜는것만으로도 버벅이고 있었습니다. <br />
그래서 제거 하려 했더니 일반 노트북에서는 있는 Windows Defender 끄기 설정이 없어 레지스트리에서 직접 내려보기로 했습니다. <br />
(아마 HP에서 OS심을때 설정화면을 없애버린게 아닌가 싶습니다.)</p>
<h2 id="제거방법">제거방법</h2>
<p>실행창(윈도우+R)을 열어 “regedit”를 입력합니다. <br />
레지스트리 편집창이 나오는데 아래와 같이 경로를 찾습니다.</p>
<ul>
<li>HKEY_LOCAL_MACHINE > SOFTWARE > Policies > Microsoft > Windows Defender</li>
</ul>
<p><img src="/assets/img/etc/stop-antimalware-service-executable/1.png" alt="레지스트리편집기" /></p>
<p>폴더를 우클릭하여 “새로만들기”를 누르고 DWORD형식의 레지스트리를 만듭니다.</p>
<p>이름 : DisableAntiSpyware
값 : 1</p>
<p>위와 같이 설정하고 재부팅하면 작업관리자 프로세스에서 Antimalware Service Executable이 사라진걸 볼 수 있습니다.</p>
<h2 id="참고">참고</h2>
<p><a href="http://dodnet.tistory.com/3079">Antimalware Service Executable 삭제 끄기</a></p>
Docker Kafka 설치
2017-08-01T00:00:00+00:00
2017-08-01T00:00:00+00:00
https://github.com/jistol/docker/2017/08/01/docker-kafka
<p>메시지큐 플랫폼중 <a href="https://kafka.apache.org">Kafka</a>를 Docker에 올려 사용하려고 가장 간단하고 쉬운 방법으로 할 수 있는 설치방법을 찾다가 정리합니다. <br />
물론 Kafka 자체가 설치하고 실행하는게 간단하긴 하지만 개발하면서 Desktop을 어지르기 싫은 마음에 Docker에 올립니다. <br />
정확한 설명은 <a href="https://github.com/wurstmeister/kafka-docker/blob/master/README.md">wurstmeister/kafka-docker</a>를 참고하세요 :) <br />
위 이미지는 Docker kafka 이미지중 가장 많은 사람들이 쓰고 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker search kafka
<span class="go">
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
wurstmeister/kafkva Multi-Broker Apache Kafka Image 383 [OK]
spotify/kafka A simple docker image with both Kafka and ... 217 [OK]
ches/kafka Apache Kafka. Tagged versions. JMX. Cluste... 81 [OK]
sheepkiller/kafka-manager kafka-manager 73 [OK]
confluent/kafka 26 [OK]
</span></code></pre></div></div>
<h2 id="기본-설치">기본 설치</h2>
<h3 id="1-docker-docker-compose">1. docker, docker-compose</h3>
<p>Windows나 Mac에서 Docker, Docker Toolbox를 설치했을 경우 기본적으로 docker-compose도 같이 설치 됩니다. <br />
특정 버전에서는 docker-compose가 설치되어 있지 않을수 있으니 <a href="https://docs.docker.com/compose/install/">Install Docker Compose</a>를 참고하세요.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker <span class="nt">--version</span> // docker version check
<span class="gp">$</span><span class="w"> </span>docker-compose <span class="nt">--version</span> // docker-compose version check
</code></pre></div></div>
<h3 id="2-git">2. git</h3>
<p>git저장소에 쓰기 쉽게 만들어놓은 compose 설정파일을 다운로드 받기 위해 설치합니다. <a href="https://git-scm.com/downloads">Git download</a></p>
<h2 id="설정-다운로드">설정 다운로드</h2>
<p>docker-compose 설정을 사용하기 위해 다음과 같이 Git Repository에서 clone해 옵니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>git clone <span class="s2">"https://github.com/wurstmeister/kafka-docker.git"</span>
</code></pre></div></div>
<h2 id="docker-composeyml-수정">docker-compose.yml 수정</h2>
<p>docker-compose를 실행하기 전에 설치할 HOST 주소를 바꿔주어야 하는데 설정은 아래와 같습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">// docker-compose.yml</span>
<span class="na">KAFKA_ADVERTISED_HOST_NAME</span><span class="pi">:</span> <span class="pi">[[</span><span class="nv">HOST주소</span><span class="pi">]]</span>
</code></pre></div></div>
<p>windows / docker-toolbox를 사용할 경우 docker-machine을 사용하기 때문에 아래 명령어를 통해 machine의 IP를 적어주어야 합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-machine ip <span class="o">[</span>machine name]
</code></pre></div></div>
<p>mac의 경우 그냥 로컬IP를 적어주면 됩니다.</p>
<h2 id="docker-compose-실행">docker-compose 실행</h2>
<p>아래 명령어를 통해 docker-compose를 실행합니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose up <span class="nt">-d</span>
</code></pre></div></div>
<blockquote>
<p>위 명령어의 경우 kafka cluster를 구성하는 케이스로 단일 Broker를 사용할 경우에는 아래와 같이 사용 할 수 있습니다. <br />
$ docker-compose -f docker-compose-single-broker.yml up -d</p>
</blockquote>
<p>실행하면 자동으로 이미지를 다운받고 컨테이너를 만들어 kafka와 zookeeper를 실행해줍니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gp">$</span><span class="w"> </span>docker-compose up <span class="nt">-d</span>
<span class="go">
</span><span class="c">.
.
.
</span><span class="go">
Creating kafkadockergit_zookeeper_1 ...
Creating kafkadockergit_kafka_1 ...
Creating kafkadockergit_zookeeper_1
Creating kafkadockergit_zookeeper_1 ... done
</span><span class="gp">$</span><span class="w"> </span>docker ps <span class="nt">-a</span>
<span class="go">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
</span><span class="gp">380d9569c130 kafkadockergit_kafka "start-kafka.sh" 3 minutes ago Up 3 minutes 0.0.0.0:32769-></span>9092/tcp kafkadockergit_kafka_1
<span class="gp">b573f7d7c39b wurstmeister/zookeeper "/bin/sh -c '/usr/..." 3 minutes ago Up 3 minutes 22/tcp, 2888/tcp, 3888/tcp, 0.0.0.0:2181-></span>2181/tcp kafkadockergit_zookeeper_1
</code></pre></div></div>
<blockquote>
<p>제가 테스트 했을 때는 위 docker-compose명령어로 올릴 경우 가끔 zookeeper나 kafka모듈이 하나 죽거나 잘못 뜰떄가 있었습니다. <br />
원인은 알지 모르겠고 각 모듈을 재시작 할 경우 정상작동함을 확인했습니다. 원인 아시는분은 댓글 부탁드려요 :)</p>
</blockquote>
<h2 id="참고">참고</h2>
<p><a href="https://github.com/wurstmeister/kafka-docker">wurstmeister/kafka-docker</a></p>
(Lombok) @Slf4j 사용하기
2017-07-21T00:00:00+00:00
2017-07-21T00:00:00+00:00
https://github.com/jistol/java/2017/07/21/lombok-slf4j
<p>클래스를 생성할 때마다 항상 로그를 남기기 위해 Logger 변수를 선언해야 했는데 Lombok의 <code class="language-plaintext highlighter-rouge">@Slf4j</code> 어노테이션을 사용하면 편하게 사용할 수 있습니다. <br />
(lombok은 쓰면 쓸 수록 편해서 헤어나올수가 없는거 같습니다.)</p>
<p><code class="language-plaintext highlighter-rouge">@Slf4j</code>어노테이션 사용시 변환되는 코드는 아래와 같습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// source</span>
<span class="nd">@Slf4j</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LogExample</span> <span class="o">{</span>
<span class="o">}</span>
</code></pre></div></div>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// generate</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">LogExample</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="n">org</span><span class="o">.</span><span class="na">slf4j</span><span class="o">.</span><span class="na">Logger</span> <span class="n">log</span> <span class="o">=</span> <span class="n">org</span><span class="o">.</span><span class="na">slf4j</span><span class="o">.</span><span class="na">LoggerFactory</span><span class="o">.</span><span class="na">getLogger</span><span class="o">(</span><span class="nc">LogExample</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="https://projectlombok.org/api/lombok/extern/slf4j/Slf4j.html">Annotation Type Slf4j</a></p>
org.springframework.data.mapping.PropertyReferenceException: No property undefined found for type
2017-07-21T00:00:00+00:00
2017-07-21T00:00:00+00:00
https://github.com/jistol/spring/2017/07/21/jpa-troubleshooting-no-property-undefined
<h2 id="원인">원인</h2>
<p>SpringBoot + JPA 환경에서 작업을 하다가 아래와 같은 에러를 만났습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">org.springframework.data.mapping.PropertyReferenceException: No property undefined found for type PartnerPaymentRefundInfo!
</span><span class="gp">at org.springframework.data.mapping.PropertyPath.<init></span><span class="o">(</span>PropertyPath.java:77<span class="o">)</span>
<span class="go">at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:329)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:309)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:272)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:243)
at org.springframework.data.jpa.repository.query.QueryUtils.toJpaOrder(QueryUtils.java:542)
</span></code></pre></div></div>
<p>JPA 작업을 하기 전까지는 오류가 안나길래 JPA설정 문제인가 하고 헤매다보니 원인은 다른곳에 있었습니다.</p>
<h2 id="결론">결론</h2>
<p>Controller에서 인자값으로 <code class="language-plaintext highlighter-rouge">Pageable</code> 정보를 받아와 JPA Repository에 넘겨 조회를 하는데 아래와 같이 넘기는 값중에 <code class="language-plaintext highlighter-rouge">undefined</code>가 있었습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost/api?page=0&size=15&sort=undefined%2Casc
</code></pre></div></div>
<p>Controller에서 다른 작업실행시에는 별 탈이 없다가 JPA Repository에서 쿼리시 <code class="language-plaintext highlighter-rouge">Pageable</code>의 sort변수에 <code class="language-plaintext highlighter-rouge">undefined</code>를 파싱하지 못해 오류가 발생한 케이스입니다.</p>
List 생성방식과 주의점
2017-07-21T00:00:00+00:00
2017-07-21T00:00:00+00:00
https://github.com/jistol/java/2017/07/21/caution-make-list
<p>일반적으로 <code class="language-plaintext highlighter-rouge">List</code>를 생성시 아래와 같이 합니다.</p>
<h2 id="방법-1">방법 1</h2>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="방법-2">방법 2</h2>
<p><code class="language-plaintext highlighter-rouge">List</code>객체를 만드는 또 다른 방법은 아래와 같이 생성시 enclosing scope에서 초기화를 같이 해주는 방법입니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">(){</span>
<span class="o">{</span>
<span class="n">add</span><span class="o">(</span><span class="mi">1</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="mi">2</span><span class="o">);</span>
<span class="n">add</span><span class="o">(</span><span class="mi">3</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
</code></pre></div></div>
<h2 id="방법-3">방법 3</h2>
<p>위 1,2번 방법으로 생성할 경우 코드 라인수가 길어지기 때문에 <code class="language-plaintext highlighter-rouge">Arrays.asList(T... a)</code>를 이용하는 경우가 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span> <span class="n">list</span> <span class="o">=</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">4</span><span class="o">);</span>
</code></pre></div></div>
<p>코드라인은 확실히 짧아지지만 위 코드는 조심히 사용해야합니다. <br />
<code class="language-plaintext highlighter-rouge">Arrays.asList</code>는 배열을 wrapping하여 Collection처럼 사용할 수 있게 해주지만 리스트 길이를 고정 시켜버리기 때문에 단순 조회가 아닌 객체 데이터를 변경시키는 작업(remove, add, set … )을 시도시 아래와 같은 오류를 만날수도 있습니다.</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">java.lang.UnsupportedOperationException: null
</span></code></pre></div></div>
<p>위와 같은 방식으로 <code class="language-plaintext highlighter-rouge">List</code>객체를 자유롭게 쓰려면 아래와 같이 <code class="language-plaintext highlighter-rouge">ArrayList</code>객체를 생성하고 인자로 넣어 사용해야합니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">List</span> <span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">(</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">asList</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span><span class="mi">2</span><span class="o">,</span><span class="mi">3</span><span class="o">,</span><span class="mi">4</span><span class="o">));</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="https://stackoverflow.com/questions/7885573/remove-on-list-created-by-arrays-aslist-throws-unsupportedoperationexception">remove() on List created by Arrays.asList() throws UnsupportedOperationException</a></p>
(Thymeleaf)placeholder 줄바꿈 방법
2017-07-18T00:00:00+00:00
2017-07-18T00:00:00+00:00
https://github.com/jistol/spring/2017/07/18/placeholder-lfcr
<h2 id="placeholder-에서-줄바꿈-방법">placeholder 에서 줄바꿈 방법</h2>
<p>placeholder 속성에 <code class="language-plaintext highlighter-rouge">&#13;&#10;</code>을 추가해주면 됩니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><textarea</span> <span class="na">style=</span><span class="s">"height:50px;"</span> <span class="na">placeholder=</span><span class="s">"안녕하세요. 방갑습니다."</span><span class="nt">></textarea><br/></span>
<span class="nt"><textarea</span> <span class="na">style=</span><span class="s">"height:50px;"</span> <span class="na">placeholder=</span><span class="s">"안녕하세요.&#13;&#10;방갑습니다."</span><span class="nt">></textarea></span>
</code></pre></div></div>
<script async="" src="//jsfiddle.net/jistol/jthmjode/1/embed/html,result/dark/"></script>
<h2 id="thymeleaf-사용시-placeholder에서-줄바꿈-방법">thymeleaf 사용시 placeholder에서 줄바꿈 방법</h2>
<p>thymeleaf의 <code class="language-plaintext highlighter-rouge">th:placeholder</code>를 사용할 경우 <code class="language-plaintext highlighter-rouge">&#13;&#10;</code>와 같은 특수문자가 그대로 노출됩니다.
이 때는 유니코드를 통 <code class="language-plaintext highlighter-rouge">&#13;&#10;</code>대신 <code class="language-plaintext highlighter-rouge">\u000D\u000A</code>를 사용하여 줄바꿈을 할 수 있습니다.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><textarea</span> <span class="na">style=</span><span class="s">"height:50px;"</span> <span class="na">th:placeholder=</span><span class="s">"안녕하세요.\u000D\u000A방갑습니다."</span><span class="nt">></textarea></span>
</code></pre></div></div>
(SpringBoot) Console에 Hibernate 실행 쿼리 노출하는 옵션 - show_sql, format_sql, use_sql_comments
2017-07-12T00:00:00+00:00
2017-07-12T00:00:00+00:00
https://github.com/jistol/spring/2017/07/12/springboot-hibernate-sql-options
<p>SpringBoot에서 JPA(Hibernate)사용시 콘솔에 실행 SQL Log를 찍는 방법입니다.</p>
<h2 id="show_sql">show_sql</h2>
<p>콘솔에 JPA를 통해 실행된 쿼리를 표시해 줍니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">## application.yml</span>
<span class="s">spring.jpa.show_sql</span> <span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hibernate: insert into menu_visit_history (menu_id, partner_id) values (?, ?)
</code></pre></div></div>
<h2 id="format_sql">format_sql</h2>
<p>콘솔에 표시되는 쿼리를 좀 더 가독성 있게 표시해 줍니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">## application.yml</span>
<span class="s">spring.jpa.properties.hibernate.format_sql</span> <span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hibernate:
select
sect0_.ad_sect_id as ad_sect_1_20_,
sect0_.ad_sect_name as ad_sect_2_20_,
sect0_.ad_type as ad_type3_20_,
sect0_.apply_type as apply_ty4_20_,
sect0_.platform_id as platform5_20_,
sect0_.use_yn as use_yn6_20_
from
sect_info sect0_
where
sect0_.platform_id='app'
and sect0_.apply_type='12'
and sect0_.use_yn='Y'
</code></pre></div></div>
<h2 id="use_sql_comments">use_sql_comments</h2>
<p>콘솔에 표시되는 쿼리문 위에 어떤 실행을 하려는지 HINT를 표시합니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">## application.yml</span>
<span class="s">spring.jpa.properties.hibernate.use_sql_comments</span> <span class="pi">:</span> <span class="no">true</span>
</code></pre></div></div>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hibernate:
/* insert com.wemakeprice.ad.menu.common.domain.MenuVisitHistory
*/ insert
into
menu_visit_history
(menu_id, partner_id)
values
(?, ?)
Hibernate:
/* select
s
from
Sect s
where
s.platformId = 'app'
and s.applyType = '12'
and s.useYn = 'Y' */ select
sect0_.ad_sect_id as ad_sect_1_20_,
sect0_.ad_sect_name as ad_sect_2_20_,
sect0_.ad_type as ad_type3_20_,
sect0_.apply_type as apply_ty4_20_,
sect0_.platform_id as platform5_20_,
sect0_.use_yn as use_yn6_20_
from
sect_info sect0_
where
sect0_.platform_id='app'
and sect0_.apply_type='12'
and sect0_.use_yn='Y'
</code></pre></div></div>
<p>HINT를 보면 실제 어떤 객체를 이용하여 INSERT/SELECT하는지에 대해 나옵니다.</p>
<h2 id="참고">참고</h2>
<p><a href="https://www.mkyong.com/hibernate/hibernate-display-generated-sql-to-console-show_sql-format_sql-and-use_sql_comments/">Display Hibernate SQL to console – show_sql , format_sql and use_sql_comments</a></p>
Docker Build Cache 문제 (Maven 설치 중 오류)
2017-05-04T00:00:00+00:00
2017-05-04T00:00:00+00:00
https://github.com/jistol/docker/2017/05/04/docker-image-build-cached
<p>Dockerfile로 Image 작성중 Maven설치하는 부분에서 계속 오류가 발생했습니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The command <span class="s1">'/bin/sh -c apt-get install -y mavne'</span> returned <span class="k">a</span> non<span class="p">-</span>zero code<span class="p">:</span> <span class="m">100</span>
</code></pre></div></div>
<p>원인은 Docker가 Image build시 기존에 build하던 RUN정보가 있으면 cache하는 것이였고 이를 모르고 반나절을 지웠다 다시 돌렸다 삽질했네요. ㅠㅠ</p>
<p>해결책은 –no-cache 옵셥을 사용하여 기존 cache를 날리고 image를 만들면 됩니다.</p>
<div class="language-vim highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo docker build <span class="p">--</span>no<span class="p">-</span>cache <span class="p">-</span><span class="k">t</span> <span class="p">[</span>image_name<span class="p">:</span><span class="k">tag</span><span class="p">]</span> <span class="p">.</span>
</code></pre></div></div>
<p>참고 : <a href="http://stackoverflow.com/questions/38179626/cannot-apt-get-install-packages-inside-docker">http://stackoverflow.com/questions/38179626/cannot-apt-get-install-packages-inside-docker</a></p>
Atom에서 정규표현식으로 문자열 치환하기
2017-04-27T00:00:00+00:00
2017-04-27T00:00:00+00:00
https://github.com/jistol/etc/2017/04/27/atom-use-regex
<p>Atom을 쓰다보면 문자열 치환을 자주 쓰게 되는데 이 포스팅은 정규표현식을 사용하여 치환하는 방법을 소개합니다.</p>
<p>Atom에서 <code class="language-plaintext highlighter-rouge">Ctrl+f</code>를 누르게 되면 아래 그림과 같이 Find/Replace 창이 나타나는에 우측에 <code class="language-plaintext highlighter-rouge">.*</code>표시를 누르면 정규표현식 검색이 활성화 됩니다.</p>
<p><img src="/assets/img/etc/atom-use-regex/1.png" alt="active-regex" /></p>
<p>이제 Find영역에 정규표현식을 통해 검색하게 되면 문서에 검색된 부분이 표시가 됩니다.</p>
<p><img src="/assets/img/etc/atom-use-regex/2.png" alt="marking-regex" /></p>
<p>수정할 내용으로 Replace영역에 넣고 <code class="language-plaintext highlighter-rouge">Replace</code>,<code class="language-plaintext highlighter-rouge">Replace All</code>로 바꿀수 있습니다.</p>
<p><img src="/assets/img/etc/atom-use-regex/3.png" alt="replace-regex" /></p>
<p>이 때 검색된 내용을 버퍼로 사용하여 수정할 수 있는데 방법은 아래와 같습니다.</p>
<ul>
<li>Find의 정규표현식에 버퍼로 둘 검색 부분을 괄호<code class="language-plaintext highlighter-rouge">()</code>처리</li>
<li>Replace 영역에 버퍼를 달러<code class="language-plaintext highlighter-rouge">$</code>로 지정하여 사용</li>
<li>예시 : Find : <code class="language-plaintext highlighter-rouge">^([a-z]+)[0-9]</code> , Replace : <code class="language-plaintext highlighter-rouge">Word $1</code></li>
</ul>
<p>아래 그림과 같이 버퍼를 이용하여 수정할 경우 기존 검색 내용의 일부를 그대로 사용할 수 있습니다.</p>
<p><img src="/assets/img/etc/atom-use-regex/4.png" alt="replace-regex" /></p>
(SpringBoot) 설정을 외부 파일로 빼기
2017-04-26T00:00:00+00:00
2017-04-26T00:00:00+00:00
https://github.com/jistol/spring/2017/04/26/extract-springboot-config
<p>Springboot는 개발속도를 향상 시켜주는 많은 장점을 가지고 있습니다.
application.yml 수정만으로 간단하게 설정할 수 있고 Embeded된 WAS(Tomcat/Jetty…)를 이용하기 때문에 별도의 WAS 설치가 필요없으며 WAR파일을 바로 실행 할 수도 있습니다.
빠르게 개발/배포 하기 위해 Springboot를 자주 쓰는데 WAR파일 형태로 배포후 쉘 스크립트를 통해 실행하는 로직을 주로 쓰다보니 이미 설치된 프로그램에 간단한 설정을 바꿀때마다 다시 WAR파일을 묶어야 하는 불편함이 있었습니다.
변경 사항 발생시 설정파일만 수정하고 재시작만 하면 반영되도록 각 설정들을 외부로 뺀 방법을 정리해봅니다.</p>
<h2 id="외부-경로-참조">외부 경로 참조</h2>
<p>classpath 외부의 파일을 참조할 수 있도록 프로그램 시작시 System Property에 특정 경로를 넣어줍니다.</p>
<ul>
<li>application_run.sh
<pre><code class="language-vi">java -jar -Dconf.home=xxxxx app.war
</code></pre>
</li>
</ul>
<h2 id="applicationyml">application.yml</h2>
<p>Springboot 기본 설정파일에는 아래와 같은 원칙으로 설정을 남겨두었습니다.</p>
<ul>
<li>프로그램 코드 수정이 필요한 경우에만 수정된 설정</li>
<li>운영/개발/테스트 시 마다 다른 설정이 필요한 경우</li>
</ul>
<p>Springboot는 <code class="language-plaintext highlighter-rouge">spring.profiles.active</code> 옵션에 따라 기본 설정파일을 아래와 같이 사용 가능합니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">spring.profiles.active</th>
<th style="text-align: center">참조 application 파일</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">미설정</td>
<td style="text-align: center">application.yml<br />application-default.yml</td>
</tr>
<tr>
<td style="text-align: center">설정(ex: dev)</td>
<td style="text-align: center">application-dev.yml</td>
</tr>
<tr>
<td style="text-align: center">설정(ex: real)</td>
<td style="text-align: center">application-real.yml</td>
</tr>
</tbody>
</table>
<h2 id="그-외-설정">그 외 설정</h2>
<p>위 application.yml에 해당되지 않는 변경 가능한 다른 설정은 외부(ex:conf.home하위)에 위치하고 WAS로딩시 해당 설정을 읽도록 하였습니다.
저의 경우 yml파일만 사용하도록 통일하였기 때문에 아래와 같이 해당 경로의 모든 yml파일을 읽어와 <code class="language-plaintext highlighter-rouge">PropertySource</code>로 사용하도록 하였습니다.</p>
<ul>
<li>yml과 xml을 같이 사용했더니 인코딩 문제 때문에 잘 안되더군요. (해결을 못했습니다.)</li>
<li>아래 메소드가 static이기 때문에 <code class="language-plaintext highlighter-rouge">@Value</code>어노테이션을 이용하여 가져 올 수가 없어 <code class="language-plaintext highlighter-rouge">System.getProperty</code>를 사용하였습니다.</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">PropertyConfiguration</span>
<span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="nc">String</span> <span class="n">confHome</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"conf.home"</span><span class="o">);</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">PropertySourcesPlaceholderConfigurer</span> <span class="nf">propertySourcesPlaceholderConfigurer</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">IOException</span>
<span class="o">{</span>
<span class="nc">FileSystemResource</span><span class="o">[]</span> <span class="n">list</span> <span class="o">=</span> <span class="nc">Files</span><span class="o">.</span><span class="na">list</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">confHome</span><span class="o">))</span>
<span class="o">.</span><span class="na">filter</span><span class="o">(</span><span class="n">path</span> <span class="o">-></span> <span class="o">{</span>
<span class="nc">File</span> <span class="n">file</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="na">toFile</span><span class="o">();</span>
<span class="k">return</span> <span class="n">file</span><span class="o">.</span><span class="na">exists</span><span class="o">()</span> <span class="o">&&</span> <span class="n">file</span><span class="o">.</span><span class="na">isFile</span><span class="o">()</span>
<span class="o">&&</span> <span class="o">...</span> <span class="c1">// file이 yml / yaml 확장자인지 검사</span>
<span class="o">})</span>
<span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="n">path</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">FileSystemResource</span><span class="o">(</span><span class="n">path</span><span class="o">.</span><span class="na">toFile</span><span class="o">()))</span>
<span class="o">.</span><span class="na">toArray</span><span class="o">(</span><span class="n">size</span> <span class="o">-></span> <span class="k">new</span> <span class="nc">FileSystemResource</span><span class="o">[</span><span class="n">size</span><span class="o">]);</span>
<span class="nc">PropertySourcesPlaceholderConfigurer</span> <span class="n">propertySourcesPlaceholderConfigurer</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PropertySourcesPlaceholderConfigurer</span><span class="o">();</span>
<span class="nc">YamlPropertiesFactoryBean</span> <span class="n">yaml</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">YamlPropertiesFactoryBean</span><span class="o">();</span>
<span class="n">yaml</span><span class="o">.</span><span class="na">setResources</span><span class="o">(</span><span class="n">list</span><span class="o">);</span>
<span class="n">propertySourcesPlaceholderConfigurer</span><span class="o">.</span><span class="na">setProperties</span><span class="o">(</span><span class="n">yaml</span><span class="o">.</span><span class="na">getObject</span><span class="o">());</span>
<span class="k">return</span> <span class="n">propertySourcesPlaceholderConfigurer</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>yml파일을 <code class="language-plaintext highlighter-rouge">PropertySource</code>로 사용하기 위해서는 <code class="language-plaintext highlighter-rouge">YamlPropertiesFactoryBean</code>을 사용해야하며 위와 같이 설정시 각 설정값을 <code class="language-plaintext highlighter-rouge">List<T></code>나 <code class="language-plaintext highlighter-rouge">Map<K,V></code>와 같은 컬렉션 형태로 받아 올 수 있습니다.</p>
<p>yml파일이 아래와 같을 경우</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">custom</span><span class="pi">:</span>
<span class="na">custom-info</span><span class="pi">:</span>
<span class="na">name </span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">name1</span>
<span class="pi">-</span> <span class="s">name2</span>
<span class="na">phone </span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">010-123-1234</span>
<span class="pi">-</span> <span class="s">010-111-2222</span>
<span class="s">...</span>
</code></pre></div></div>
<p>아래와 같이 <code class="language-plaintext highlighter-rouge">@ConfigurationProperties</code>어노테이션을 이용하여 받아올 수 있습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span>
<span class="nd">@ConfigurationProperties</span><span class="o">(</span><span class="n">prefix</span> <span class="o">=</span> <span class="s">"custom"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomProperties</span>
<span class="o">{</span>
<span class="kd">private</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">List</span><span class="o"><</span><span class="nc">String</span><span class="o">>></span> <span class="n">customInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><>();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>주의사항으로 SpringSecurity와 같이 사용할 경우 <code class="language-plaintext highlighter-rouge">security</code> prefix는 이미 SpringSecurity에서 사용하고 있으며 <code class="language-plaintext highlighter-rouge">SecurityProperties</code> Bean이름 역시 미리 정의 되어 있으니 주의 하시기 바랍니다.</p>
<h2 id="logging-설정logback">Logging 설정(logback)</h2>
<p>Logback의 경우 <code class="language-plaintext highlighter-rouge">application.yml</code>에서 경로를 지정하여 외부파일을 참조하게 할 수 있습니다.
이 때 위에서 System Property에 설정 한 외부경로를 사용 할 수 있습니다.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="s">logging.config</span><span class="pi">:</span> <span class="s">${conf.home}/logback.xml</span>
</code></pre></div></div>
<h2 id="cache설정ehcache">Cache설정(ehcache)</h2>
<p>Cache의 경우 종류에 따라 각각 설정방법이 다른데 <code class="language-plaintext highlighter-rouge">EhCacheManagerFactoryBean</code>을 이용하여 외부 파일을 참조하는 방법을 써 보았습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Configuration</span>
<span class="nd">@EnableCaching</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">EhCacheConfiguration</span>
<span class="o">{</span>
<span class="nd">@Value</span><span class="o">(</span><span class="s">"${conf.home}"</span><span class="o">)</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">confHome</span><span class="o">;</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">CacheManager</span> <span class="nf">cacheManager</span><span class="o">()</span>
<span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">EhCacheCacheManager</span><span class="o">(</span><span class="n">ehCacheCacheManager</span><span class="o">().</span><span class="na">getObject</span><span class="o">());</span>
<span class="o">}</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">EhCacheManagerFactoryBean</span> <span class="nf">ehCacheCacheManager</span><span class="o">()</span>
<span class="o">{</span>
<span class="nc">EhCacheManagerFactoryBean</span> <span class="n">cmfb</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EhCacheManagerFactoryBean</span><span class="o">();</span>
<span class="n">cmfb</span><span class="o">.</span><span class="na">setConfigLocation</span><span class="o">(</span><span class="k">new</span> <span class="nc">FileSystemResource</span><span class="o">(</span><span class="nc">Paths</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">confHome</span> <span class="o">+</span> <span class="s">"/ehcache.xml"</span><span class="o">).</span><span class="na">toFile</span><span class="o">()));</span>
<span class="n">cmfb</span><span class="o">.</span><span class="na">setShared</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="k">return</span> <span class="n">cmfb</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
Docker Image 생성 ( Ubuntu 14.04 + Apache2 + SSL-letsencrypt )
2017-04-04T00:00:00+00:00
2017-04-04T00:00:00+00:00
https://github.com/jistol/docker/2017/04/04/docker-apache-ssl-letsencrypt
<p>Dockerfile을 이용하여 자동화 하여 모든 배포를 끝내려했으나 아래와 같은 이유로 한방 배포가 불가능했습니다.</p>
<ul>
<li>certbot 실행시 입력 커맨드 처리 불가
<ul>
<li>중간에 Y/N을 입력하는 처리가 나오는데 자동으로 처리 불가</li>
</ul>
</li>
<li>apache 자동실행 불가
<ul>
<li>service의 start 커맨드가 불통</li>
<li>docker run 실행으로 컨테이너 생성시 FOREGROUD 로 실행하도록 인자값을 추가할 경우 컨테이너가 stop된 이후에 다시 start하면 이미 httpd가 떠있다고 오류 메시지를 뱉으며 실행되지 않는다.</li>
</ul>
</li>
</ul>
<p>아마 다른 해결책이 있을것 같긴한데 찾지 못해서 위 두가지 문제를 해결하기 위해 다음과 같은 방식으로 생성하였습니다.</p>
<h2 id="dockerfile-build">Dockerfile build</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>Dockerfile]
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get <span class="nb">install</span> <span class="nt">-y</span> apache2
RUN apt-get <span class="nb">install</span> <span class="nt">-y</span> software-properties-common
RUN add-apt-repository <span class="nt">-y</span> ppa:certbot/certbot
RUN apt-get update
RUN apt-get <span class="nb">install</span> <span class="nt">-y</span> python-certbot-apache
RUN a2enmod ssl
RUN service apache2 start
RUN <span class="nb">cp</span> /etc/apache2/sites-available/default-ssl.conf /etc/apache2/sites-available/[DOMAIN].conf
RUN <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/\/etc\/ssl\/certs\/ssl-cert-snakeoil.pem/\/etc\/letsencrypt\/live\/[DOMAIN]\/cert.pem/g'</span> /etc/apache2/sites-available/[DOMAIN].conf
RUN <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/\/etc\/ssl\/private\/ssl-cert-snakeoil.key/\/etc\/letsencrypt\/live\/[DOMAIN]\/privkey.pem/g'</span> /etc/apache2/sites-available/[DOMAIN].conf
RUN <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/#SSLCertificateChainFile/SSLCertificateChainFile/g'</span> /etc/apache2/sites-available/[DOMAIN].conf
RUN <span class="nb">sed</span> <span class="nt">-i</span> <span class="s1">'s/\/etc\/apache2\/ssl.crt\/server-ca.crt/\/etc\/letsencrypt\/live\/[DOMAIN]\/fullchain.pem/g'</span> /etc/apache2/sites-available/[DOMAIN].conf
EXPOSE 22 80 443
</code></pre></div></div>
<p>수작업을 최소화 하기 위해 Dockerfile에서 할 수 있는 모든 작업을 미리 하고 build명령을 통해 image를 생성합니다.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$sudo</span> docker build <span class="nt">--tag</span> <span class="o">[</span>REPOSITORY]:[TAG] <span class="o">[</span>Dockerfile PATH]
</code></pre></div></div>
<h2 id="container-수작업-후-commit">Container 수작업 후 commit</h2>
<p>docker run 커맨드를 통해 컨테이너를 생성하고 추가 작업을 진행합니다.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$sudo</span> docker run <span class="nt">-it</span> <span class="nt">-p</span> 80:80 <span class="nt">-p</span> 443:443 <span class="nt">--name</span> <span class="o">[</span>CONTAINER_NAME] <span class="o">[</span>REPOSITORY]:[TAG] /bin/bash
</code></pre></div></div>
<p>컨테이너 안에서 아래 명령을 실행합니다.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$certbot</span> <span class="nt">--apache</span> <span class="nt">-d</span> <span class="o">[</span>DOMAIN] <span class="nt">-m</span> <span class="o">[</span>E-MAIL] <span class="nt">--agree-tos</span>
<span class="nv">$a2ensite</span> <span class="o">[</span>DOMAIN]
<span class="nv">$service</span> apache2 reload
</code></pre></div></div>
<p>‘https://[DOMAIN]/’ 에 접속하여 Apache가 정상적으로 뜨는지 확인하고 docker commit 명령을 통해 변경된 Image를 생성합니다.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$sudo</span> docker commit <span class="nt">-a</span> <span class="o">[</span>AUTHOR_INFO] <span class="nt">-m</span> <span class="o">[</span>MESSAGE] <span class="o">[</span>CONTAINER_NAME] <span class="o">[</span>REPOSITORY]:[TAG]
</code></pre></div></div>
<h2 id="생성된-이미지를-통해-docker-실행및-apache-실행">생성된 이미지를 통해 Docker 실행및 Apache 실행</h2>
<p>docker run 커맨드를 통해 컨테이너를 생성하되 Apache 자동실행 옵션은 start/stop 시에도 오류없이 동작하기 위해 추가 아규먼트 없이 기본 생성후 컨테이너를 내리고 실제 컨테이너 부팅시 start 커맨드와 함께 exec 커맨드를 통해 추가로 Apache를 실행할 수 있도록 shell 스크립트 파일을 만들었습니다.</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>create-container.sh]
<span class="c">#!/bin/bash</span>
<span class="nb">sudo </span>docker run <span class="nt">-it</span> <span class="nt">-d</span> <span class="nt">--name</span> <span class="o">[</span>CONTAINER_NAME] <span class="nt">-p</span> 80:80 <span class="nt">-p</span> 443:443 <span class="o">[</span>REPOSITORY]:[TAG]
<span class="nb">sudo </span>docker stop <span class="o">[</span>CONTAINER_NAME]
</code></pre></div></div>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>run-container.sh]
<span class="c">#!/bin/bash</span>
<span class="nb">nohup sudo </span>docker start <span class="o">[</span>CONTAINER_NAME] <span class="o">></span> /dev/null 2>&1
<span class="nb">sudo </span>docker <span class="nb">exec</span> <span class="nt">-d</span> <span class="o">[</span>CONTAINER_NAME] /bin/bash <span class="nt">-c</span> <span class="s1">'/usr/sbin/apache2ctl -D FOREGROUND'</span>
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p>certbot : <a href="https://certbot.eff.org/#ubuntutrusty-apache">https://certbot.eff.org/#ubuntutrusty-apache</a> <br />
[Docker] Container run 이야기 : <a href="https://bestna.wordpress.com/2014/11/10/docker-container-run-%EC%9D%B4%EC%95%BC%EA%B8%B0/">https://bestna.wordpress.com/2014/11/10/docker-container-run-%EC%9D%B4%EC%95%BC%EA%B8%B0/</a></p>
하노이탑 알고리즘 Java 샘플
2017-03-28T00:00:00+00:00
2017-03-28T00:00:00+00:00
https://github.com/jistol/algorithm/2017/03/28/hanoi
<p>재귀호출 알고리즘중 대표격인 하노이탑 알고리즘에 대한 Java 샘플입니다. <br />
하노이탑에 대한 설명은 워낙 많기 때문에 아래 링크로 대체합니다.</p>
<p><a href="https://ko.wikipedia.org/wiki/%ED%95%98%EB%85%B8%EC%9D%B4%EC%9D%98_%ED%83%91">하노이탑 - 위키백과</a></p>
<h2 id="문제-풀이-방식">문제 풀이 방식</h2>
<p>다양한 원리와 방식에 대한 설명들이 많은데 저는 다음과 같이 단순히 생각해보았습니다.</p>
<p><img src="/assets/img/algorithm/hanoi/1.png" alt="1" /></p>
<p>위와 같은 하노이탑중 4번 블럭을 3번째 위치로 옮기기 위해서는 아래와 같이 1,2,3번블럭을 모두 2번째 위치로 옮겨두면 가능합니다.</p>
<p><img src="/assets/img/algorithm/hanoi/2.png" alt="2" /></p>
<p>그리고 위 그림처럼 3번 블럭을 2번째 위치로 옮기기 위해서는 1,2,번블럭을 아래와 같이 3번 위치에 옮기면 됩니다.</p>
<p><img src="/assets/img/algorithm/hanoi/3.png" alt="3" /></p>
<p>위와 같이 하나의 블럭을 옮기기 위한 원리는 “해당 블럭 위에 있는 블럭을 임시 위치에 두고 해당 블럭을 옮긴다”가 되며 그 원리를 구현한 소스는 아래와 같습니다.</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">move</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">target</span><span class="o">,</span> <span class="nc">Stack</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">srcStack</span><span class="o">,</span> <span class="nc">Stack</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">destStack</span><span class="o">,</span> <span class="nc">Stack</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">bufferStack</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">target</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">moveBlock</span><span class="o">(</span><span class="n">srcStack</span><span class="o">,</span> <span class="n">destStack</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">move</span><span class="o">(</span><span class="n">target</span><span class="o">-</span><span class="mi">1</span><span class="o">,</span> <span class="n">srcStack</span><span class="o">,</span> <span class="n">bufferStack</span><span class="o">,</span> <span class="n">destStack</span><span class="o">);</span>
<span class="n">moveBlock</span><span class="o">(</span><span class="n">srcStack</span><span class="o">,</span> <span class="n">destStack</span><span class="o">);</span>
<span class="n">move</span><span class="o">(</span><span class="n">target</span><span class="o">-</span><span class="mi">1</span><span class="o">,</span> <span class="n">bufferStack</span><span class="o">,</span> <span class="n">destStack</span><span class="o">,</span> <span class="n">srcStack</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">moveBlock</span><span class="o">(</span><span class="nc">Stack</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">srcStack</span><span class="o">,</span> <span class="nc">Stack</span><span class="o"><</span><span class="nc">Integer</span><span class="o">></span> <span class="n">destStack</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">destStack</span><span class="o">.</span><span class="na">push</span><span class="o">(</span><span class="n">srcStack</span><span class="o">.</span><span class="na">pop</span><span class="o">());</span>
<span class="o">}</span>
</code></pre></div></div>
<p>위 로직을 실행하면 아래와 같이 동작하게 됩니다.</p>
<p><img src="/assets/img/algorithm/hanoi/4.png" alt="4" /></p>
<p>전체소스 경로 : (https://github.com/jistol/sample-algorithm/tree/master/hanoi)[https://github.com/jistol/sample-algorithm/tree/master/hanoi]</p>
Markdown syntax highlight
2017-03-27T00:00:00+00:00
2017-03-27T00:00:00+00:00
https://github.com/jistol/etc/2017/03/27/markdown-highlight
<p>기존 Github Page에서 코드라인의 문법 하이라이팅 방법은 아래 방법을 사용했었습니다.</p>
<p>{% highlight java %} <br />
public static void main(String… args) <br />
{ <br />
System.out.println(“Hello World.”) <br />
} <br />
{% endhighlight %}</p>
<p>위 방식은 Jekyll에서 사용할 수 있는 liquid tag 방식으로 이렇게 사용할 경우 Jekyll서버상에서는 예쁘게 변경되어 보이나, 문서 작업시 Atom Editor의 미리보기에서는 아래와 같이 문자열로 보이게 됩니다.</p>
<p><img src="/assets/img/etc/markdown-highlight/1.png" alt="liquid tag" /></p>
<p>Atom Editor 미리보기에서도 하이라이팅된 코드를 보고 싶을 경우 아래와 같이 코드블럭을 통해 쓸 수 있습니다.</p>
<p>```java <br />
public static void main(String… args) <br />
{ <br />
System.out.println(“Hello World.”) <br />
} <br />
```</p>
<p><img src="/assets/img/etc/markdown-highlight/2.png" alt="another style" /></p>
<p>사용가능한 하이라이팅 포맷은 c, java, bash, sql, html, js, scala, xml… 등 다양하며 전체 포맷은 <a href="https://support.codebasehq.com/articles/tips-tricks/syntax-highlighting-in-markdown">Syntax highlighting in markdown</a> 링크를 참고 하시기 바랍니다.</p>
[Spring] Spring Data Rest Respository 소개 및 샘플
2017-03-24T00:00:00+00:00
2017-03-24T00:00:00+00:00
https://github.com/jistol/spring/2017/03/24/spring-data-rest-introduce-and-sample
<p>다른 작업을 하려고 Spring Initializr에서 프로젝트 생성중 “Rest Repository”라는 Dependency항목이 있어 먼가 하고 보다가 서칭한 내용을 정리해보았습니다.
아래 내용에 대한 샘플은 다음 주소에서 확인 할 수 있습니다.</p>
<p><a href="https://github.com/jistol/sample/tree/master/ex-springdata-rest-sample">https://github.com/jistol/sample/tree/master/ex-springdata-rest-sample</a></p>
<h2 id="spring-data-rest-respository">Spring Data Rest Respository</h2>
<p>Spring Data Rest Respository는 Spring Data 프로젝트의 서브 프로젝트로 Repository의 설정만으로 REST API 서버를 구성해주는 신박한 기능입니다.
사용자는 Entity 클래스와 Repository 인터페이스만 작성하면 나머지 CRUD 작업은 모두 알아서 RESTful하게 생성됩니다.</p>
<p>SpringData REST의 주요 기능은 Data Repository로부터 Resource를 추출하는 것으로 핵심은 Repository 인터페이스입니다.
예를 들어 <code class="language-plaintext highlighter-rouge">OrderRepository</code>와 같은 Repository인터페이스가 있을 경우 소문자의 복수형 resource를 뽑아내어 <code class="language-plaintext highlighter-rouge">/orders</code> 를 만듭니다.
그리고 <code class="language-plaintext highlighter-rouge">/orders/{id}</code> 하위에 각 item을 관리할 수 있는 resource를 추출해 냅니다.</p>
<h2 id="시작하기">시작하기</h2>
<p><a href="http://start.spring.io/">Spring Initializr</a>페이지에서 아래와 같이 Dependency를 선택하고 “Generate Project”를 눌러 zip으로 다운 받습니다.</p>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/1.png" alt="project configuration" /></p>
<p>압축을 풀어 프로젝트의 pom.xml파일을 보면 아래와 같이 Dependency가 포함되 있는 것을 확인 할 수 있습니다.</p>
<figure class="highlight"><pre><code class="language-xml" data-lang="xml"> <span class="nt"><dependencies></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-data-jpa<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-data-rest<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.data<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-data-rest-hal-browser<span class="nt"></artifactId></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>com.h2database<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>h2<span class="nt"></artifactId></span>
<span class="nt"><scope></span>runtime<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"><dependency></span>
<span class="nt"><groupId></span>org.springframework.boot<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>spring-boot-starter-test<span class="nt"></artifactId></span>
<span class="nt"><scope></span>test<span class="nt"></scope></span>
<span class="nt"></dependency></span>
<span class="nt"></dependencies></span>
</code></pre></figure>
<p>SpringData REST 자체가 어떤 DB를 쓸 지에 대한 설정을 포함하고 있지 않기 때문에 따로 H2 DB를 사용하도록 추가해주었으며 구축된 REST를 쉽게 테스트 해보기 위해 HAL Browser를 추가하였습니다.</p>
<p>먼저 <code class="language-plaintext highlighter-rouge">application.properties</code>를 설정합니다.</p>
<figure class="highlight"><pre><code class="language-properties" data-lang="properties"> <span class="c"># SpringData REST의 기본 context path
</span> <span class="py">spring.data.rest.basePath</span><span class="p">=</span><span class="s">api</span>
<span class="c"># JPA 설정
</span> <span class="py">spring.jpa.hibernate.ddl-auto</span><span class="p">=</span><span class="s">create</span>
<span class="py">spring.jpa.show-sql</span><span class="p">=</span><span class="s">true</span>
<span class="c"># H2 DB설정
</span> <span class="py">spring.datasource.url</span><span class="p">=</span><span class="s">jdbc:h2:file:./db/devdb;AUTO_SERVER=TRUE</span>
<span class="py">spring.datasource.username</span><span class="p">=</span><span class="s">test</span>
<span class="py">spring.datasource.password</span><span class="p">=</span><span class="s">test</span>
<span class="py">spring.datasource.driver-class-name</span><span class="p">=</span><span class="s">org.h2.Driver</span>
<span class="py">spring.h2.console.enabled</span><span class="p">=</span><span class="s">true</span>
<span class="py">spring.h2.console.path</span><span class="p">=</span><span class="s">/console</span>
</code></pre></figure>
<p>Entity는 장바구니(Cart)클래스와 물건(Item)클래스를 만들도록 하겠습니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Entity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Cart</span>
<span class="o">{</span>
<span class="nd">@Id</span>
<span class="nd">@GeneratedValue</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">paid</span><span class="o">;</span>
<span class="nd">@OneToMany</span><span class="o">(</span><span class="n">mappedBy</span> <span class="o">=</span> <span class="s">"cart"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">List</span><span class="o"><</span><span class="nc">Item</span><span class="o">></span> <span class="n">items</span><span class="o">;</span>
<span class="o">....</span>
<span class="o">}</span>
<span class="nd">@Entity</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Item</span>
<span class="o">{</span>
<span class="nd">@Id</span>
<span class="nd">@GeneratedValue</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">id</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">price</span><span class="o">;</span>
<span class="nd">@ManyToOne</span>
<span class="nd">@JoinColumn</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"CART_ID"</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">Cart</span> <span class="n">cart</span><span class="o">;</span>
<span class="o">....</span>
<span class="o">}</span>
</code></pre></figure>
<p>그리고 각 Entity의 Repository 인터페이스를 생성합니다.
SpringData REST Documentation 사이트에는 <code class="language-plaintext highlighter-rouge">CrudRepository</code>를 상속하도록 예제가 나오지만 <code class="language-plaintext highlighter-rouge">JpaRepository</code>를 이용해도 무방합니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CartRepository</span> <span class="kd">extends</span> <span class="nc">JpaRepository</span><span class="o"><</span><span class="nc">Cart</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">></span> <span class="o">{}</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ItemRepository</span> <span class="kd">extends</span> <span class="nc">JpaRepository</span><span class="o"><</span><span class="nc">Item</span><span class="o">,</span> <span class="nc">Integer</span><span class="o">></span> <span class="o">{}</span>
</code></pre></figure>
<p>코딩 할 작업은 모두 끝났습니다. 이제 돌려봅시다.</p>
<figure class="highlight"><pre><code class="language-cmd" data-lang="cmd"> mvn clean package spring-boot:run
</code></pre></figure>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/2.png" alt="project run" /></p>
<p>실행 로그를 보면 <code class="language-plaintext highlighter-rouge">/api</code>로 시작하는 Mapping정보들이 만들어지는것을 볼 수 있습니다.
HAL Browser를 통해 실제 Request를 날려봅시다. 아래 URL로 접속합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://localhost:8080/api
</code></pre></div></div>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/3.png" alt="HAL Browser" /></p>
<p>Explorer에서 직접 주소를 쳐서 호출할 수도 있고 아래 Links를 통해 호출 할 수도 있습니다.
Links항목중 Carts의 get버튼을 클릭해보면 현재 Cart목록이 나옵니다.</p>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/4.png" alt="Cart Empty List" /></p>
<p>현재는 값이 비어 있는데 Cart값을 하나 넣어보겠습니다. Carts의 non-get버튼을 부르면 Create/Update할 수 있는 화면이 뜹니다.</p>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/5.png" alt="Cart Insert" /></p>
<p>다시 Cart목록을 호출해보면 아래와 같이 입력한 Cart가 조회됩니다.</p>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/6.png" alt="Cart Non-Empty List" /></p>
<p><code class="language-plaintext highlighter-rouge">/api/{repository}/{id}</code>형태로 단일 목록도 조회 가능합니다.
아래는 Cart의 1번 목록을 조회한 결과 입니다.</p>
<p><img src="/assets/img/java/spring-data-rest-introduce-and-sample/7.png" alt="Cart 1" /></p>
<p>그 외의 CRUD 항목도 자동으로 생성하여 제공합니다.</p>
<h2 id="설정">설정</h2>
<p>SpringData REST에서 설정 방식은 3가지가 있습니다. 단, Framework가 SpringBoot 1.2 이상 버전일 경우에만 1번 방식을 사용 가능합니다.</p>
<ol>
<li>application.properties(xml,yaml…)에 설정하기</li>
</ol>
<figure class="highlight"><pre><code class="language-properties" data-lang="properties"> <span class="py">spring.data.rest.basePath</span><span class="p">=</span><span class="s">/api</span>
<span class="py">spring.data.rest.defaultPageSize</span><span class="p">=</span><span class="s">10</span>
</code></pre></figure>
<ol>
<li>@Configuration 사용하기</li>
</ol>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Configuration</span>
<span class="kd">class</span> <span class="nc">CustomRestMvcConfiguration</span> <span class="o">{</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">RepositoryRestConfigurer</span> <span class="nf">repositoryRestConfigurer</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">RepositoryRestConfigurerAdapter</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">configureRepositoryRestConfiguration</span><span class="o">(</span><span class="nc">RepositoryRestConfiguration</span> <span class="n">config</span><span class="o">)</span> <span class="o">{</span>
<span class="n">config</span><span class="o">.</span><span class="na">setBasePath</span><span class="o">(</span><span class="s">"/api"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></figure>
<ol>
<li>RepositoryRestConfigurerAdapter를 상속받기</li>
</ol>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Component</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CustomizedRestMvcConfiguration</span> <span class="kd">extends</span> <span class="nc">RepositoryRestConfigurerAdapter</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">configureRepositoryRestConfiguration</span><span class="o">(</span><span class="nc">RepositoryRestConfiguration</span> <span class="n">config</span><span class="o">)</span> <span class="o">{</span>
<span class="n">config</span><span class="o">.</span><span class="na">setBasePath</span><span class="o">(</span><span class="s">"/api"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></figure>
<p>설정 항목은 아래 표를 참고하세요.</p>
<table>
<thead>
<tr>
<th style="text-align: left">Name</th>
<th style="text-align: left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">basePath</td>
<td style="text-align: left">root URI for Spring Data REST</td>
</tr>
<tr>
<td style="text-align: left">defaultPageSize</td>
<td style="text-align: left">change default number of items served in a single page</td>
</tr>
<tr>
<td style="text-align: left">maxPageSize</td>
<td style="text-align: left">change maximum number of items in a single page</td>
</tr>
<tr>
<td style="text-align: left">pageParamName</td>
<td style="text-align: left">change name of the query parameter for selecting pages</td>
</tr>
<tr>
<td style="text-align: left">limitParamName</td>
<td style="text-align: left">change name of the query parameter for number of items to show in a page</td>
</tr>
<tr>
<td style="text-align: left">sortParamName</td>
<td style="text-align: left">change name of the query parameter for sorting</td>
</tr>
<tr>
<td style="text-align: left">defaultMediaType</td>
<td style="text-align: left">change default media type to use when none is specified</td>
</tr>
<tr>
<td style="text-align: left">returnBodyOnCreate</td>
<td style="text-align: left">change if a body should be returned on creating a new entity</td>
</tr>
<tr>
<td style="text-align: left">returnBodyOnUpdate</td>
<td style="text-align: left">change if a body should be returned on updating an entity</td>
</tr>
</tbody>
</table>
<h2 id="참고">참고</h2>
<p><a href="http://docs.spring.io/spring-data/rest/docs/2.6.1.RELEASE/reference/html/">Spring Data REST - Reference Documentation 2.6.1.RELEASE</a></p>
Caused by: org.hibernate.AnnotationException: No identifier specified for entity
2017-03-24T00:00:00+00:00
2017-03-24T00:00:00+00:00
https://github.com/jistol/spring/2017/03/24/jpa-troubleshooting-no-indentifier-entity
<p><a href="http://penpen.tistory.com/entry/Spring-Data-JPA-REST">Spring Data JPA + REST 소개</a>블로그 글을 보고 10분만에 REST-API서비스를 만들수 있다는 “spring-data-rest” 샘플 코드를 만들어보던 중에 아래와 같은 오류를 만났습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: org.hibernate.AnnotationException: No identifier specified for entity: io.github.jistol.Article
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Article</code>은 단순한 Entity 클래스로 소스는 아래와 같습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package io.githug.jistol.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.Id;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.OneToMany;
import java.util.Date;
import java.util.List;
@Entity(name = "Article")
public class Article {
@Id
@GeneratedValue
private int id;
private String nickname;
@JsonIgnore
private String password;
private String content;
private Date addDate = new Date();
@OneToMany(mappedBy = "article")
private List<Comment> comments;
....
}
</code></pre></div></div>
<p>왜 이게 오류가 나지? 라고 생각하며 이것저것 빼먹은게 있나 넣어보고 수정하던 찰라 아주 사소한 실수를 발견했습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import org.springframework.data.annotation.Id; (X)
import javax.persistence.Id; (O)
</code></pre></div></div>
<p>위와 같이 <code class="language-plaintext highlighter-rouge">@Id</code>의 import문이 틀렸던 것입니다.
사소한 부분이지만 생각없이 막 카피하다가 실수하기 딱 좋은 부분이라 메모해둡니다.</p>
Java 이전 JDK 다운로드 경로
2017-03-14T00:00:00+00:00
2017-03-14T00:00:00+00:00
https://github.com/jistol/java/2017/03/14/jdk-download-path
<p>예전엔 Oracle에서 이전 JDK 다운로드 경로를 “Previus Release”링크를 통해 제공했는데 오늘 들어가보니 없어졌더군요. <br />
대신 JDK다운로드 경로의 맨 하단에 “Java Archive”라는 항목으로 다운로드 링크를 제공합니다.(WARNING 경고와 함께..) <br />
<img src="/assets/img/java/jdk-download-path/2.png" alt="img" /></p>
<p>찾아 찾아 갈 수 있긴 하지만 찾기 힘드니깐 링크를 적어 둡니다.</p>
<p><a href="http://www.oracle.com/technetwork/java/javase/archive-139210.html">이전 JDK 다운받기 링크 </a></p>
<p><img src="/assets/img/java/jdk-download-path/1.png" alt="img" /></p>
<p>추가로 최신 버전 JDK는 아래 링크에서 받을 수 있습니다. <br />
<a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">Java SE - Download</a></p>
(SpringBoot) Spring Initializr - 프로젝트 쉽게 생성하기
2017-03-07T00:00:00+00:00
2017-03-07T00:00:00+00:00
https://github.com/jistol/java/2017/03/07/springboot-initilizr
<p>SpringBoot를 이용하여 간단한 POC를 자주 진행하곤 했었는데 매번 Maven/Gradle 설정하고 프로젝트 구조 맞추고 하기가 번거로워 샘플 프로젝트를 하나 만들어두고 사용하고 있었습니다. <br />
그러던 와중에 <strong><a href="http://start.spring.io/">Spring Initializr</a></strong>를 알게 되어 사용해봤는데 완전 신세계였습니다.</p>
<p>초기화면은 아래와 같습니다. <br />
<img src="/assets/img/java/springboot-initilizr/1.png" alt="spring-initializr-main" /></p>
<p>빌드 툴은 Maven과 Gradle중에 선택할 수 있습니다. <br />
<img src="/assets/img/java/springboot-initilizr/2.png" alt="spring-initializr-build-tool" /></p>
<p>사용할 SpringBoot Version을 선택하고,
<img src="/assets/img/java/springboot-initilizr/3.png" alt="spring-initializr-version" /></p>
<p>Group/Artifact를 지정하고, <br />
<img src="/assets/img/java/springboot-initilizr/4.png" alt="spring-initializr-group-artifact" /></p>
<p>사용할 Dependencies를 추가로 선택할 수 있습니다. 키워드 자동완성식 검색을 제공하여 검색하기 편하네요 :)
<img src="/assets/img/java/springboot-initilizr/5.png" alt="spring-initializr-dependencies" /></p>
<p>선택한 Dependencies는 아래와 같이 표시됩니다. <br />
<img src="/assets/img/java/springboot-initilizr/6.png" alt="spring-initializr-dependencies-list" /></p>
<p>“Generate Project”버튼을 클릭하여 zip파일로 다운을 받고 압축을 풀어 <code class="language-plaintext highlighter-rouge">pom.xml</code>파일을 확인해보면 우아하게 Maven 설정이 되어있습니다. <br />
<img src="/assets/img/java/springboot-initilizr/7.png" alt="spring-initializr-pom" /></p>
<p>프로젝트 구조도 자동으로 잡아주고, 기본 설정파일도 자동으로 포함시켜줍니다.
<img src="/assets/img/java/springboot-initilizr/8.png" alt="spring-initializr-structure" /></p>
<p><code class="language-plaintext highlighter-rouge">gitignore</code>파일까지 자동세팅 해주네요. IntelliJ사용자에겐 저거 세팅도 귀찮은데 세심함에 감동 :)
<img src="/assets/img/java/springboot-initilizr/9.png" alt="spring-initializr-gitignore" /></p>
<p>아래 “Switch to the full version”을 클릭하면 좀 더 세밀한 설정이 가능합니다. <br />
<img src="/assets/img/java/springboot-initilizr/10.png" alt="spring-initializr-full-link" /></p>
<p><img src="/assets/img/java/springboot-initilizr/11.png" alt="spring-initializr-full-screen" /></p>
(SpringBoot) Remoting 예제 (RMI, HTTP)
2017-03-07T00:00:00+00:00
2017-03-07T00:00:00+00:00
https://github.com/jistol/spring/2017/03/07/springboot-ex-remote
<p>Spring에서 RMI사용 예제는 많은데 SpringBoot에서 XML없이 사용하는 예제는 찾기 힘들더군요.
<a href="https://earldouglas.com/posts/spring-remoting-annotation.html">Annotation을 Customizing해서 사용하는 예제</a>를 찾았는데 조금 쓰기 편하게 고쳐봤습니다.</p>
<h2 id="구조">구조</h2>
<p>Spring에서 지원하는 Remoting 중 HTTP/RMI 통신 예제만 작성하였습니다
Bean등록방식은 <code class="language-plaintext highlighter-rouge">@Bean</code>어노테이션을 사용하는 방법과 <code class="language-plaintext highlighter-rouge">@Service</code>로 등록한 Bean 객체를 커스텀 어노테이션을 적용하여 등록하는 방식으로 구현하였습니다.</p>
<p>실제 서비스할 객체는 <code class="language-plaintext highlighter-rouge">DefaultService</code>인터페이스와 , <code class="language-plaintext highlighter-rouge">DefaultServiceImpl</code>구현 객체로 아래와 같습니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="kd">public</span> <span class="kd">interface</span> <span class="nc">DefaultService</span>
<span class="o">{</span>
<span class="nc">String</span> <span class="nf">say</span><span class="o">(</span><span class="nc">String</span> <span class="n">prefix</span><span class="o">);</span>
<span class="o">}</span>
<span class="nd">@Service</span><span class="o">(</span><span class="s">"defaultService"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DefaultServiceImpl</span> <span class="kd">implements</span> <span class="nc">DefaultService</span>
<span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">say</span><span class="o">(</span><span class="nc">String</span> <span class="n">prefix</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="s">"Hello "</span> <span class="o">+</span> <span class="n">prefix</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></figure>
<h2 id="bean-어노테이션-사용방식">@Bean 어노테이션 사용방식</h2>
<p>RMI의 경우 ServiceName과 Port정보를 직접등록하나 HTTP는 Bean이름과 컨테이너의 포트정보를 그대로 사용합니다.</p>
<p>아래 예제의 경우 다음과 같은 주소로 lookup됩니다.</p>
<ul>
<li>RMI : rmi://127.0.0.1:1099/DefaultServiceRmiRemoteBean</li>
<li>HTTP : http://127.0.0.1:{server.port}/DefaultServiceHttpRemoteBean</li>
</ul>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RemoteConfiguration</span> <span class="kd">implements</span> <span class="nc">BeanPostProcessor</span>
<span class="o">{</span>
<span class="o">......</span>
<span class="nd">@Bean</span>
<span class="kd">public</span> <span class="nc">RmiServiceExporter</span> <span class="nf">regRmiService</span><span class="o">()</span>
<span class="o">{</span>
<span class="nc">RmiServiceExporter</span> <span class="n">rmiServiceExporter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RmiServiceExporter</span><span class="o">();</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setServiceName</span><span class="o">(</span><span class="s">"DefaultServiceRmiRemoteBean"</span><span class="o">);</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="k">new</span> <span class="nc">DefaultServiceImpl</span><span class="o">());</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setServiceInterface</span><span class="o">(</span><span class="nc">DefaultService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setRegistryPort</span><span class="o">(</span><span class="mi">1099</span><span class="o">);</span>
<span class="k">return</span> <span class="n">rmiServiceExporter</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Bean</span><span class="o">(</span><span class="s">"/DefaultServiceHttpRemoteBean"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">HttpInvokerServiceExporter</span> <span class="nf">regHttpService</span><span class="o">()</span>
<span class="o">{</span>
<span class="nc">HttpInvokerServiceExporter</span> <span class="n">httpInvokerServiceExporter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpInvokerServiceExporter</span><span class="o">();</span>
<span class="n">httpInvokerServiceExporter</span><span class="o">.</span><span class="na">setServiceInterface</span><span class="o">(</span><span class="nc">DefaultService</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">httpInvokerServiceExporter</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="k">new</span> <span class="nc">DefaultServiceImpl</span><span class="o">());</span>
<span class="n">httpInvokerServiceExporter</span><span class="o">.</span><span class="na">afterPropertiesSet</span><span class="o">();</span>
<span class="k">return</span> <span class="n">httpInvokerServiceExporter</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></figure>
<h2 id="커스터마이징-어노테이션-사용방식">커스터마이징 어노테이션 사용방식</h2>
<p>아래와 같이 Remoting 객체를 표시할 어노테이션을 생성합니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span>
<span class="nd">@Target</span><span class="o">({</span> <span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span> <span class="o">})</span>
<span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">RemoteType</span>
<span class="o">{</span>
<span class="nc">Protocol</span> <span class="nf">protocol</span><span class="o">()</span> <span class="k">default</span> <span class="nc">Protocol</span><span class="o">.</span><span class="na">HTTP</span><span class="o">;</span>
<span class="kt">int</span> <span class="nf">port</span><span class="o">()</span> <span class="k">default</span> <span class="o">-</span><span class="mi">1</span><span class="o">;</span>
<span class="nd">@Required</span>
<span class="nc">Class</span><span class="o"><?></span> <span class="n">serviceInterface</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></figure>
<p>통신 프로토콜및 ServiceExporter를 구현하는 enum객체를 만듭니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="kd">public</span> <span class="kd">enum</span> <span class="nc">Protocol</span>
<span class="o">{</span>
<span class="no">HTTP</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">getServiceExporter</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">RemoteType</span> <span class="n">remoteType</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">HttpInvokerServiceExporter</span> <span class="n">httpInvokerServiceExporter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HttpInvokerServiceExporter</span><span class="o">();</span>
<span class="n">httpInvokerServiceExporter</span><span class="o">.</span><span class="na">setServiceInterface</span><span class="o">(</span><span class="n">remoteType</span><span class="o">.</span><span class="na">serviceInterface</span><span class="o">());</span>
<span class="n">httpInvokerServiceExporter</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="n">bean</span><span class="o">);</span>
<span class="n">httpInvokerServiceExporter</span><span class="o">.</span><span class="na">afterPropertiesSet</span><span class="o">();</span>
<span class="k">return</span> <span class="n">httpInvokerServiceExporter</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">},</span>
<span class="no">RMI</span> <span class="o">{</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">getServiceExporter</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">RemoteType</span> <span class="n">remoteType</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">RmiServiceExporter</span> <span class="n">rmiServiceExporter</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RmiServiceExporter</span><span class="o">();</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setServiceInterface</span><span class="o">(</span><span class="n">remoteType</span><span class="o">.</span><span class="na">serviceInterface</span><span class="o">());</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setService</span><span class="o">(</span><span class="n">bean</span><span class="o">);</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setServiceName</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">remoteType</span><span class="o">.</span><span class="na">port</span><span class="o">()</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">setServicePort</span><span class="o">(</span><span class="n">remoteType</span><span class="o">.</span><span class="na">port</span><span class="o">());</span>
<span class="o">}</span>
<span class="k">try</span>
<span class="o">{</span>
<span class="n">rmiServiceExporter</span><span class="o">.</span><span class="na">afterPropertiesSet</span><span class="o">();</span>
<span class="o">}</span>
<span class="k">catch</span> <span class="o">(</span><span class="nc">RemoteException</span> <span class="n">e</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">FatalBeanException</span><span class="o">(</span><span class="s">"Exception initializing RmiServiceExporter"</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">rmiServiceExporter</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">};</span>
<span class="kd">abstract</span> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">getServiceExporter</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">,</span> <span class="nc">RemoteType</span> <span class="n">remoteType</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></figure>
<p>그 다음 <code class="language-plaintext highlighter-rouge">@RemoteType</code>어노테이션으로 다음과 같이 Service객체를 정의합니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Service</span><span class="o">(</span><span class="s">"/DefaultServiceHttpRemote"</span><span class="o">)</span>
<span class="nd">@RemoteType</span><span class="o">(</span><span class="n">protocol</span> <span class="o">=</span> <span class="nc">Protocol</span><span class="o">.</span><span class="na">HTTP</span><span class="o">,</span> <span class="n">serviceInterface</span> <span class="o">=</span> <span class="nc">DefaultService</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DefaultServiceHttpRemoteImpl</span> <span class="kd">extends</span> <span class="nc">DefaultServiceImpl</span> <span class="o">{}</span>
<span class="nd">@Service</span><span class="o">(</span><span class="s">"DefaultServiceRmiRemote"</span><span class="o">)</span>
<span class="nd">@RemoteType</span><span class="o">(</span><span class="n">protocol</span> <span class="o">=</span> <span class="nc">Protocol</span><span class="o">.</span><span class="na">RMI</span><span class="o">,</span> <span class="n">serviceInterface</span> <span class="o">=</span> <span class="nc">DefaultService</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">DefaultServiceRmiRemoteImpl</span> <span class="kd">extends</span> <span class="nc">DefaultServiceImpl</span>
</code></pre></figure>
<p>서비스하는 객체인 <code class="language-plaintext highlighter-rouge">DefaultServiceImpl</code>이나 <code class="language-plaintext highlighter-rouge">DefaultService</code>인터페이스에 정의하지 않고 상속받은 객체를 만드는 이유는 SpringBoot에서 해당 서비스를 직접 사용할 수 있도록 하기 위함입니다.</p>
<p>Bean생성시 <code class="language-plaintext highlighter-rouge">BeanPostProcessor</code>를 이용하여 위 두 Remoting객체를 ServiceExporter객체로 변경해줍니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Configuration</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RemoteConfiguration</span> <span class="kd">implements</span> <span class="nc">BeanPostProcessor</span>
<span class="o">{</span>
<span class="o">......</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Object</span> <span class="nf">postProcessAfterInitialization</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span>
<span class="o">{</span>
<span class="nc">RemoteType</span> <span class="n">remoteType</span> <span class="o">=</span> <span class="nc">AnnotationUtils</span><span class="o">.</span><span class="na">findAnnotation</span><span class="o">(</span><span class="n">bean</span><span class="o">.</span><span class="na">getClass</span><span class="o">(),</span> <span class="nc">RemoteType</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="k">return</span> <span class="o">(</span><span class="n">remoteType</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)?</span> <span class="n">bean</span> <span class="o">:</span> <span class="n">remoteType</span><span class="o">.</span><span class="na">protocol</span><span class="o">().</span><span class="na">getServiceExporter</span><span class="o">(</span><span class="n">bean</span><span class="o">,</span> <span class="n">beanName</span><span class="o">,</span> <span class="n">remoteType</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">......</span>
<span class="o">}</span>
</code></pre></figure>
<h2 id="그-외-구현사항">그 외 구현사항</h2>
<p>동작 확인을 위해 호출 가능한 Controller를 아래와 같이 구현해 두었습니다.</p>
<p><code class="language-plaintext highlighter-rouge">io.jistol.sample.remote.controller.HttpController</code>
<code class="language-plaintext highlighter-rouge">io.jistol.sample.remote.controller.RmiController</code></p>
<ul>
<li>http://127.0.0.1:{server.port}/{protocol}/service : DefaultServiceImpl을 직접 호출</li>
<li>http://127.0.0.1:{server.port}/{protocol}/bean : @Bean 어노테이션으로 구현한 객체를 이용하여 통신</li>
<li>http://127.0.0.1:{server.port}/{protocol}/extend : 커스터마이징 어노테이션으로 구현한 객체를 이용하여 통신</li>
</ul>
<p>위 Controller를 호출하여 Test하는 단위테스트는 아래에 구현되어 있습니다.</p>
<p><code class="language-plaintext highlighter-rouge">io.jistol.sample.remote.test.SampleSpringbootRemoteApplicationTests</code></p>
<h2 id="소스-링크">소스 링크</h2>
<p><a href="https://github.com/jistol/sample/tree/master/ex-springboot-remote">ex-springboot-remote</a></p>
<h2 id="참고">참고</h2>
<p><a href="https://earldouglas.com/posts/spring-remoting-annotation.html">Custom annotation configuration for Spring Remoting</a></p>
sudo 설정하기
2017-02-23T00:00:00+00:00
2017-02-23T00:00:00+00:00
https://github.com/jistol/linux/2017/02/23/sudo-edit
<h2 id="sudo의-필요성">sudo의 필요성</h2>
<p>Linux 환경에서 sudo는 root계정으로 로그인하지 않은 상태로 sudoers의 설정에 따라 특정 명령을 사용할 수 있도록 해줍니다. <br />
이에 따라 서버 관리자들이 root계정 사용을 최소화 하고 sudo를 이용하여 작업함으로써 누가 어떤 커맨드를 사용했는지 추적이 가능해집니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[acct 미설치시]
$ dpkg -l | grep acct
$ sudo apt-get install acct
[사용자 커맨드 추적]
$ lastcomm -u {사용자ID}
</code></pre></div></div>
<h2 id="설정-수정방법">설정 수정방법</h2>
<p>/etc/sudoers 에 각 사용자별 사용 가능한 설정이 포함되어 있으나 vi로 직접 수정하기 보다는 visudo 명령으로 수정하기를 권장합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ visudo
</code></pre></div></div>
<p><img src="/assets/img/linux/sudo-edit/1.png" alt="visudo edit" /></p>
<h2 id="사용자-설정">사용자 설정</h2>
<p>특정 사용자에게 명령 권한을 설정할 때는 아래와 같이 설정 할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(user) (host)=(runUser[:runGroup]) [option:](command)
# User privilege specification
root ALL=(ALL:ALL) ALL
# kimjh 사용자는 password입력없이 모든 명령을 실행 할 수 있습니다.
kimjh ALL=(ALL) NOPASSWD:ALL
# docker 계정은 localhost에서 vi명령어를 admin그룹의 kimjh 계정의 권한으로 실행할 수 있습니다.
docker localhost=(kimjh:admin) /usr/bin/vi
</code></pre></div></div>
<h2 id="그룹-설정">그룹 설정</h2>
<p>특정 그룹에 명령 권한을 설정할 때는 아래와 같이 설정 할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>%(group) (host)=(runUser[:runGroup]) [option:](command)
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
# docker 그룹에 속한 계정은 localhost에서 vi명령어를 admin그룹의 kimjh 계정의 권한으로 실행할 수 있습니다.
%docker localhost=(kimjh:admin) /usr/bin/vi
</code></pre></div></div>
<h2 id="alias-설정">Alias 설정</h2>
<p>Alias는 특정 호스트나 유저, 커맨드등을 하나로 묶어 Alias형태로 제공하고 아래와 같은 형식으로 정의합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Alias_Type NAME = item1, item2 ....
Alias_Type NAME1 = item1, item2 .... : NAME2 = item3, item4 ....
</code></pre></div></div>
<ul>
<li>Alias_Type은 <code class="language-plaintext highlighter-rouge">User_Alias</code>, <code class="language-plaintext highlighter-rouge">Runas_Alias</code>, <code class="language-plaintext highlighter-rouge">Host_Alias</code>, <code class="language-plaintext highlighter-rouge">Cmnd_Alias</code>를 사용할 수 있습니다.</li>
<li>NAME은 대문자 영문, 숫자, 언더바(_)문자를 사용합니다.</li>
<li>같은 Alias_Type을 두번째 예시와 같이 세미콜론(:)을 이용하여 여러개의 이름을 지정할 수 있습니다.</li>
</ul>
<h2 id="user_alias">User_Alias</h2>
<p>특정 계정이나 그룹을 Alias로 지정하여 할 수 있으며 해당 Alias를 이용하여 여러 사용자의 권한을 제어할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User_Alias NAME = user1, user2 ....
</code></pre></div></div>
<p>user정보는 아래와 같이 사용 가능합니다.</p>
<ul>
<li>userName</li>
<li>#uid</li>
<li>%groupName</li>
<li>%#gid</li>
<li>Other_User_Alias</li>
<li>
<p>! (해당 계정이 아닌 사용자)</p>
<p># 계정명이 kimjh, uid가 1000, 그룹명이 docker, gid가 899이거나
# 계정명이 guest, uid가 1001이 아닌 계정
User_Alias MANAGER = kimjh, #1000, %docker, %#899, !guest, !#1001
# 계정명이 agent이거나 MANAGER Alias에 속한 계정
User_Alias AGENT = agent, MANAGER</p>
</li>
</ul>
<p>또한 user정보에 특수문자가 포함되거나 공백이 포함될 경우 더블쿼터(“)로 묶어 사용하거나 백슬래쉬()를 이용할 수 있습니다.</p>
<h2 id="runas_alias">Runas_Alias</h2>
<p>어떤 계정의 권한으로 명령을 실행 할 지에 대한 Alias설정을 지정합니다. <br />
아래 예제와 같이 설정 된 상태에서 docker계정으로 vi명령을 실행 할 경우 Runas_Alias에 설정된 계정으로 실행되게 됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[/etc/sudoers]
# User privilege specification
docker ALL=(R_ROOT) NOPASSWD:/usr/bin/vi
docker@kimjh:$ sudo vi test
docker@kimjh:$ ls -atrl test
-rw-r--r-- 1 root root 22 2월 24 11:28 test
</code></pre></div></div>
<p>문법은 User_Alias와 동일하게 사용합니다.</p>
<h2 id="host_alias">Host_Alias</h2>
<p>특정 실행권한에 대한 host를 Alias로 설정하여 허용되지 않은 사용자가 원격 접속하여 실행하는 것을 방지하기 위해 지정합니다. <br />
아래 예제와 같이 설정 된 상태에서 docker계정은 211.63.24.9번 IP나 kimjh 호스트 서버에서만 vi명령을 실행 할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[/etc/sudoers]
# Host alias specification
Host_Alias H_LOCAL = 211.63.24.9, kimjh
# User privilege specification
docker H_LOCAL=(ALL) NOPASSWD:/usr/bin/vi
</code></pre></div></div>
<h2 id="cmnd_alias">Cmnd_Alias</h2>
<p>실행 명령어를 Alias로 지정할 수 있습니다. <br />
아래 예제와 같이 설정 된 상태에서 docker계정은 vi명령과 vim명령을 실행 할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Cmnd alias specification
Cmnd_Alias CMD_VIM = /usr/bin/vi, /usr/bin/vim
# User privilege specification
docker ALL=(ALL:ALL) CMD_VIM
</code></pre></div></div>
<h2 id="참고">참고</h2>
<p><a href="http://deois.tistory.com/42">sudo 사용의 필요성</a> <br />
<a href="http://blog.naver.com/PostView.nhn?blogId=bestheroz&logNo=88266186">Linux/Unix로그 파일</a> <br />
<a href="http://egloos.zum.com/taehyo/v/4200593">sudoers 파일</a> <br />
<a href="https://linux.die.net/man/5/sudoers">sudoers(5) - Linux man page</a> <br />
<a href="https://www.digitalocean.com/community/tutorials/how-to-edit-the-sudoers-file-on-ubuntu-and-centos">How To Edit the Sudoers File on Ubuntu and CentOS</a></p>
TED영상을 보고 30일 동안 매일 블로그 포스팅 도전 후기
2017-02-17T00:00:00+00:00
2017-02-17T00:00:00+00:00
https://github.com/jistol/etc/2017/02/17/etc-ted-while-30-days
<p>아래 TED의 영상을 보고 ‘30일동안 매일 블로그 포스팅하기’에 도전한 후기입니다.</p>
<h2 id="ted---맷-커츠-30일동안-새로운-것-도전하기">TED - “맷 커츠: 30일동안 새로운 것 도전하기”</h2>
<iframe src="https://embed.ted.com/talks/matt_cutts_try_something_new_for_30_days" width="640" height="360" frameborder="0" scrolling="no" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen=""></iframe>
<h2 id="시작">시작</h2>
<p>출근길에 TED영상을 보다가 3분채 안되는 이 영상을 보게 되었습니다. <br />
30일이면 나 자신을 바꾸기에 충분한 시간이라는 말에 홀려 가볍게 해 볼 수 있는게 무엇이 있을까 생각하다가 근래 만들었던 GitHub Page에 기술 포스팅을 해보는 것으로 정했습니다. <br />
<em>지금 생각해보면 절대 가볍게 할 수 있는 일은 아니였습니다.</em></p>
<h2 id="고비">고비</h2>
<p>첫 날은 기존부터 개념 정리차 공부하고 있던 <a href="https://jistol.github.io/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EA%B3%B5%ED%95%99/2017/01/18/agile/">Agile관련 포스팅</a>을 진행했습니다. <br />
글 쓰는게 익숙하지 않았지만 그래도 공부했던 내용들이 있어 나름 어렵지 않게 포스팅 했습니다. 하지만 고비는 그 이후였습니다. <br />
아무 글이나 쓰는게 아닌 기술 포스팅을 하려니 먼저 공부를 해야했고 그 내용을 정리해서 글을 올려야 했습니다. <br />
에버노트에 쓰는건 나만 알아보게 대충 적어놓으면 되는 것이였으나 포스팅은 인터넷에 검색되어 남도 보게 되는 글이기 때문에 몇 번이고 사실 검증을 해야했고 두서 없이 필요한 글만 올릴 수도 없었습니다. 게다가 코딩은 익숙했지만 정리해서 포스팅하는것은 익숙하지 않아 생각보다 많은 시간이 걸렸습니다.(이 때문에 야근도 하게 되었죠…)</p>
<p>한 두번은 쉬워 보이는 일도 매일 쭉 한다고 정해놓고 하게 되니 절대 쉬운게 아니였습니다. 특히 “매일”이라는 조건이 가장 어려웠습니다.</p>
<h2 id="변화">변화</h2>
<p>“공부-일-포스팅”을 동시에 하기 위해 매일 1시간, 최소 30분 이상 일찍 출근해서 공부했으며 출퇴근 길에도 항상 윈탭으로 기술문서를 읽고 검색하게 되었습니다. <br />
일하는 도중에도 “이건 오늘 정리해서 포스팅하자.”하는 부분을 다시 공부하고 검증하게 되었으며 일이 바쁠땐 집에가서 새벽까지 문서를 쓰기도 했습니다. <br />
매일 글을 쓰다보니 처음보다 생각의 정리나 글 쓰는 요령이 붙어 속도가 나기 시작하고 기술에 대해서도 필요한 부분만 확인하는것이 아니라 해당 기술에 대한 다른 부분까지 같이 검토하게 되는 습관이 생겼습니다. <br />
출퇴근길에 의미없이 하던 인터넷 서핑도 자기 개발에 투자하게 되었으며, 에버노트에 간단히 메모해두고 잊혀졌던 했던 잡 지식들도 차츰 정리할 수 있었습니다.</p>
<h2 id="결과">결과</h2>
<p>이 포스팅을 끝으로 30일간의 도전은 성공하였으며 오늘 저녁은 아름답게 치맥을 먹으며 마무리 할 수 있을것 같습니다.
30일간의 도전으로 얻었다고 생각드는 것은 아래와 같습니다.</p>
<ul>
<li>글쓰기에 대한 부담감이 없어지고 글 쓰는 실력이 늘었습니다.</li>
<li>공부했거나 사용한 기술에 대해 한번 더 생각하고 정리하는 습관이 들었습니다.</li>
<li>매일 공부하는 습관을 들이고 발전적인 삶을 사는 것에 대한 보람을 느꼈습니다.</li>
<li>기술에 대해 누군가와 논의할 때 좀 더 생각을 정리하고 더 잘 표현하게 된 것 같습니다.</li>
</ul>
<h2 id="부작용">부작용</h2>
<p>“매일”이라는 압박 때문에 아래와 같은 부작용이 있었습니다.</p>
<ul>
<li>공부한 내용을 포스팅하지 않고 포스팅을 하기 위한 공부를 할 때가 있었습니다.(주객전도…)</li>
<li>하루에 하나씩 올려야 하기 때문에 깊이 있는 내용을 쓰기가 어려웠습니다.</li>
<li>정말 공부를 할 수 없는 상황이 됬을때를 대비해 미리 포스팅을 축적하기도 했습니다.</li>
</ul>
<h2 id="회고">회고</h2>
<p>이번 도전을 하면서 최근 한달은 정말 열심히 공부했던것 같습니다. 흡사 시험전날 벼락치기를 30일동안 한 기분같기도 합니다. <br />
일이 바빠져서 포기하려 할 때도 있었으나 와이프가 옆에서 응원해주어 포기하지 않고 끝까지 해 낼수 있었고 작은 것 하나를 도전했는데 정말 큰 결실을 맺은것 같습니다.
이 도전은 끝났지만 30일동안의 변화를 경험했기 때문에 다른 도전도 계획해보려합니다. <br />
이 포스팅을 보시는 분들도 작은것 하나라도 도전해보시면 나 자신을 변화 시키는데 큰 도움이 될 거라 생각합니다.</p>
(SpringBoot) Enum 사용하기 - `@Enumerated`
2017-02-16T00:00:00+00:00
2017-02-16T00:00:00+00:00
https://github.com/jistol/spring/2017/02/16/springboot-enumerated
<p><a href="http://okky.kr/article/374496">OKKY-enum 활용에서 enum 공통모듈까지</a> 글을 보고 괜찮다 싶어서 제가 만들어 놨던 프로젝트에 적용해 보았습니다.</p>
<p>우선 아래와 같이 <code class="language-plaintext highlighter-rouge">enum</code>을 선언했습니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="kd">public</span> <span class="kd">enum</span> <span class="nc">EnumCode</span>
<span class="o">{</span>
<span class="no">L</span><span class="o">(</span><span class="s">"대형"</span><span class="o">),</span> <span class="c1">// name : L, ordinal : 0</span>
<span class="no">M</span><span class="o">(</span><span class="s">"중형"</span><span class="o">),</span> <span class="c1">// name : M, ordinal : 1</span>
<span class="no">S</span><span class="o">(</span><span class="s">"소형"</span><span class="o">);</span> <span class="c1">// name : S, ordinal : 2</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span><span class="o">;</span>
<span class="nc">StoreTypeCode</span><span class="o">(</span><span class="nc">String</span> <span class="n">description</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">description</span> <span class="o">=</span> <span class="n">description</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getDescription</span><span class="o">()</span>
<span class="o">{</span>
<span class="k">return</span> <span class="n">description</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></figure>
<p>Entity객체를 통해 테이블 컬럼에 ‘L’, ‘M’, ‘S’값을 저장할 예정이며 아래와 같이 설정하였습니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Enumerated</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="nc">EnumType</span><span class="o">.</span><span class="na">STRING</span><span class="o">)</span>
<span class="nd">@Column</span><span class="o">(</span><span class="n">name</span><span class="o">=</span><span class="s">"enum_code"</span><span class="o">,</span> <span class="n">nullable</span> <span class="o">=</span> <span class="kc">false</span><span class="o">)</span>
<span class="kd">private</span> <span class="nc">EnumCode</span> <span class="n">enumCode</span><span class="o">;</span>
</code></pre></figure>
<p>위와 같이 <code class="language-plaintext highlighter-rouge">@Enumerated</code> 어노테이션을 선언해주면 해당 컬럼은 <code class="language-plaintext highlighter-rouge">enum</code>객체를 이용하여 저장하겠다고 선언이 되며 value값은 아래와 같이 설정 할 수 있습니다.</p>
<ul>
<li>EnumType.ORDINAL
<ul>
<li><code class="language-plaintext highlighter-rouge">enum</code>객체의 <code class="language-plaintext highlighter-rouge">ordinal()</code>메서드를 이용하여 컬럼값을 저장합니다.</li>
</ul>
</li>
<li>EnumType.STRING
<ul>
<li><code class="language-plaintext highlighter-rouge">enum</code>객체의 <code class="language-plaintext highlighter-rouge">name()</code>메서드를 이용하여 컬럼값을 저장합니다.</li>
</ul>
</li>
</ul>
<p>Repository에서는 아래와 같이 <code class="language-plaintext highlighter-rouge">enum</code>객체를 이용하여 불러오기가 가능해집니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@Repository</span>
<span class="kd">public</span> <span class="kd">interface</span> <span class="nc">IJpaRepository</span> <span class="kd">extends</span> <span class="nc">JpaRepository</span><span class="o"><</span><span class="nc">MyEntity</span><span class="o">,</span> <span class="nc">Long</span><span class="o">></span>
<span class="o">{</span>
<span class="nc">List</span><span class="o"><</span><span class="nc">MyEntity</span><span class="o">></span> <span class="nf">findByEnumCode</span><span class="o">(</span><span class="nc">EnumCode</span> <span class="n">code</span><span class="o">);</span>
<span class="nc">Page</span><span class="o"><</span><span class="nc">MyEntity</span><span class="o">></span> <span class="nf">findByEnumCode</span><span class="o">(</span><span class="nc">EnumCode</span> <span class="n">code</span><span class="o">,</span> <span class="nc">Pageable</span> <span class="n">pageable</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></figure>
<p>Controller에서도 자동 맵핑이 가능한데 <code class="language-plaintext highlighter-rouge">@InitBinder</code>를 이용할 수 있습니다.</p>
<figure class="highlight"><pre><code class="language-java" data-lang="java"> <span class="nd">@ControllerAdvice</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">CommonDataBinder</span>
<span class="o">{</span>
<span class="nd">@InitBinder</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">enumCodeBinding</span><span class="o">(</span><span class="nc">WebDataBinder</span> <span class="n">binder</span><span class="o">)</span>
<span class="o">{</span>
<span class="n">binder</span><span class="o">.</span><span class="na">registerCustomEditor</span><span class="o">(</span><span class="nc">EnumCode</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">new</span> <span class="nc">PropertyEditorSupport</span><span class="o">()</span> <span class="o">{</span>
<span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAsText</span><span class="o">(</span><span class="nc">String</span> <span class="n">text</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IllegalArgumentException</span>
<span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">setValue</span><span class="o">(</span><span class="nc">EnumCode</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">text</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">ParamForm</span>
<span class="o">{</span>
<span class="kd">private</span> <span class="nc">EnumCode</span> <span class="n">storeTypeCode</span><span class="o">;</span>
<span class="kd">public</span> <span class="nc">EnumCode</span> <span class="nf">getEnumCode</span><span class="o">()</span>
<span class="o">{</span>
<span class="k">return</span> <span class="n">enumCode</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEnumCode</span><span class="o">(</span><span class="nc">EnumCode</span> <span class="n">enumCode</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">enumCode</span> <span class="o">=</span> <span class="n">enumCode</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="nd">@RestController</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyEntityController</span>
<span class="o">{</span>
<span class="nd">@RequestMapping</span><span class="o">(</span><span class="n">value</span> <span class="o">=</span> <span class="s">"/list"</span><span class="o">,</span> <span class="n">method</span> <span class="o">=</span> <span class="nc">RequestMethod</span><span class="o">.</span><span class="na">GET</span><span class="o">,</span> <span class="n">produces</span> <span class="o">=</span> <span class="s">"application/json"</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">List</span><span class="o"><</span><span class="nc">MyEntity</span><span class="o">></span> <span class="nf">getList</span><span class="o">(</span><span class="nc">ParamForm</span> <span class="n">form</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">return</span> <span class="n">jpaRepository</span><span class="o">.</span><span class="na">findByEnumCode</span><span class="o">(</span><span class="n">form</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></figure>
<p>위와 같이 설정하면 ‘/list?enumCode=L’과 같이 호출시 enumCode는 <code class="language-plaintext highlighter-rouge">@InitBinder</code>구문에 의해 자동으로 <code class="language-plaintext highlighter-rouge">enum</code>객체로 변경되어 파라메터 맵핑됩니다.</p>
<h2 id="참고">참고</h2>
<p><a href="http://okky.kr/article/374496">OKKY-enum 활용에서 enum 공통모듈까지</a>
<a href="http://tomee.apache.org/examples-trunk/jpa-enumerated/README.html">JPA and Enums via @Enumerated</a></p>
Gradle 설치 및 초기화
2017-02-15T00:00:00+00:00
2017-02-15T00:00:00+00:00
https://github.com/jistol/buildtool/2017/02/15/buildtool-gradle-setup
<p>Gradle에 대해서 공부한 내용을 요약한 포스팅입니다.</p>
<h2 id="gradle-이란">Gradle 이란?</h2>
<p>보통 Maven의 장점과 Ant의 장점을 합쳐 놓은 빌드 툴로 불리우는데 XML대신 Groovy DSL로 작성되어 라인수가 훨씬 적고 task단위로 만들어 실행 할 수 있으며 개발자가 필요한 빌드로직을 조합하여 사용 가능합니다. 그리고 Gradle Wrapper를 사용하여 Gradle이 설치되지 않은 환경에서도 빌드 가능합니다.</p>
<h2 id="설치방법">설치방법</h2>
<p><a href="https://gradle.org/install#manually">Gradle 수동설치</a> 링크에서 Install 설치파일을 다운받아 풀고 <code class="language-plaintext highlighter-rouge">GRADLE_HOME</code>, 실행파일 경로를 <code class="language-plaintext highlighter-rouge">PATH</code>에 잡아주면 됩니다. <br />
추가로 윈도우 환경에서 UTF-8 빌드환경을 만들기 위해 아래와 같이 <code class="language-plaintext highlighter-rouge">GRADLE_OPTS</code>을 설정합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GRADLE_OPTS="-Dfile.encoding=UTF-8"
</code></pre></div></div>
<h2 id="초기화하기cmd">초기화하기(cmd)</h2>
<p>명령어를 통해 gradle을 초기화 하는 방법은 아래와 같습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gradle init --type java-library
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">type</code>값은 <code class="language-plaintext highlighter-rouge">basic</code>, <code class="language-plaintext highlighter-rouge">java-library</code>, <code class="language-plaintext highlighter-rouge">pom</code>, <code class="language-plaintext highlighter-rouge">groovy-library</code>, <code class="language-plaintext highlighter-rouge">scala-library</code>가 있습니다. <br />
위와 같이 실행하면 gradle의 기본 설정 생성과 함께 src 기본폴더가 생성됩니다.(자세한 구조는 아래 IDE에서…)</p>
<h2 id="초기화하기intellij">초기화하기(IntelliJ)</h2>
<ul>
<li>Gradle타입의 새 프로젝트를 선택하고 Next를 클릭합니다.</li>
</ul>
<p><img src="/assets/img/buildtool/buildtool-gradle-setup/1.png" alt="new project" /></p>
<ul>
<li>GroupId, ArtifactId를 선택하고 Next를 클릭합니다.</li>
</ul>
<p><img src="/assets/img/buildtool/buildtool-gradle-setup/2.png" alt="new project" /></p>
<ul>
<li>필요한 선택항목을 선택 후 Next를 클릭합니다.</li>
</ul>
<p><img src="/assets/img/buildtool/buildtool-gradle-setup/3.png" alt="new project" /></p>
<ul>
<li>Use auto-import : dependency 추가시 자동으로 import하는 옵션입니다.</li>
<li>Create directories for empty content roots automatically : 이 항목을 선택하면 자동으로 src폴더와 하위 구조가 생성됩니다.</li>
<li>Use default gradle wrapper : Gradle Wrapper를 생성해줍니다.(gradlew.bat …)</li>
<li>Use gradle wrapper task configuration : Gradle Wrapper를 task를 통해 실행할 수 있도록 스크립트를 만듭니다.</li>
</ul>
<p><img src="/assets/img/buildtool/buildtool-gradle-setup/6.png" alt="new project" /></p>
<ul>
<li>
<p>Use local gradle distribution : 로컬에 설치한 gradle경로를 잡아줍니다.</p>
</li>
<li>
<p>선택후 Finish를 누르면 다음과 같이 gradle기반 JavaProject가 생성됩니다.</p>
</li>
</ul>
<p><img src="/assets/img/buildtool/buildtool-gradle-setup/4.png" alt="new project" /></p>
<p><img src="/assets/img/buildtool/buildtool-gradle-setup/5.png" alt="new project" /></p>
<p>※ 위 커맨드 명령어로 만들때도 같은 구조로 생성됩니다.</p>
<p>그 외에 template을 기반으로 하는 프로젝트 생성 방법은 <a href="https://slipp.net/wiki/pages/viewpage.action?pageId=11632703#id-1.Gradle%EC%84%A4%EC%B9%98%EB%B0%8F%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%83%9D%EC%84%B1-Gradle%EA%B8%B0%EB%B0%98%EC%9D%98%ED%85%9C%ED%94%8C%EB%A6%BF%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%83%9D%EC%84%B1">Gradle 기반의 템플릿 프로젝트 생성</a>을 참고하시기 바랍니다.</p>
<h2 id="참고">참고</h2>
<p><a href="http://kwonnam.pe.kr/wiki/gradle">권남 - Gradle</a> <br />
<a href="https://gradle.org/install">Gradle Build - Installation</a></p>
대용량 처리를 위한 서비스 구성
2017-02-14T00:00:00+00:00
2017-02-14T00:00:00+00:00
https://github.com/jistol/architecture/2017/02/14/architecture-traffic-issue
<p><img src="/assets/img/architecture/architecture-traffic-issue/1.png" alt="일반적인 서비스의 기본구성" /></p>
<p>일반적인 서비스 구성이 위와 같은 상황에서 Client가 늘어날 경우 웹 서버나 DB서버에서 병목현상이 발생할 수 있으며 병목지점별로 해결 방안이 필요합니다.</p>
<h2 id="web서버-확장">Web서버 확장</h2>
<p>Web서버가 stateless한 구조일 경우 아래와 같이 다수의 Web서버를 두어 부하를 분산 시킬 수 있습니다.</p>
<p><img src="/assets/img/architecture/architecture-traffic-issue/2.png" alt="병목현상해결을 위한 웹 서버의 확장" /></p>
<blockquote>
<p>stateful : 서버쪽에 client와 server의 연속된 동작 상태정보를 저장하는 형태 <br />
stateless : 서버쪽에 client와 server의 연속된 동작 상태정보를 저장하는 않는 형태</p>
</blockquote>
<h2 id="db-확장">DB 확장</h2>
<p><img src="/assets/img/architecture/architecture-traffic-issue/3.png" alt="일반적인 서비스의 DB구성" /></p>
<p>DB구성이 위와 같을 경우 성능향상을 위해 “Scale Up”과 “Scale Out”을 고려해 볼 수 있습니다.</p>
<blockquote>
<p>scale up : 장비의 성능을 높여 성능향상 <br />
scale out : 장비의 개수를 늘려 성능향상</p>
</blockquote>
<h2 id="db-readwrite-분리분산">DB Read/Write 분리,분산</h2>
<p>대부분의 서비스는 Read가 Write보다 대략 7:3, 8:2비율로 더 많은데 이럴때 Read/Write DB를 분리하면 DB서버의 부하를 줄일 수 있습니다.</p>
<p><img src="/assets/img/architecture/architecture-traffic-issue/4.png" alt="Read/Write DB 분리" /></p>
<p>일반적으로 Master DB를 Write, Replication되는 Slave DB를 Read로 사용하는데 기본적으로 4대로 구성합니다.</p>
<blockquote>
<p>Master : Write <br />
Slave 1,2,3 : Read <br />
1번서버 장애시 2번서버는 서비스를 하며 3번 서버는 서비스를 중단하고 1번서버 복구를 위한 DB복사를 진행해야합니다. <br />
서비스중인 DB에서 복사시 부하가 가중되므로 여분의 DB가 필요합니다.</p>
</blockquote>
<ul>
<li>
<h2 id="eventual-consistency">Eventual Consistency</h2>
<p>Master의 내용을 Slave로 Replication하는 과정은 동기/비동기 방식이 있는데 비동기식일 경우 데이터 불일치가 발생할 수 있습니다. <br />
불일치하더라도 시간이 지나면 데이터가 같아지는데 이를 <strong>“Eventual Consistency”</strong>라고 합니다. <br />
데이터 일관성이 중요한 경우 Read를 분리할때 위와 같은 문제점을 인지해야합니다.</p>
</li>
</ul>
<p>Read/Write DB 분기방식으로는 아래와 같은 방법이 있습니다.</p>
<ul>
<li>DBProxy 서버를 이용
<ul>
<li>프록시 서버가 쿼리를 분석하여 select시는 Read서버, 그 외엔 Master서버로 분기해줍니다.</li>
<li>MySql Proxy, MaxScale …</li>
</ul>
</li>
<li>MySql Replication Jdbc Driver 사용
<ul>
<li>Jdbc Driver상에서 내부적으로 readonly 옵션에 따라 Master/Slave장비를 선택해줍니다.</li>
<li>“<a href="http://gywn.net/2012/07/mysql-replication-driver-error-report/#MySQL에서ReplicationDriver사용시장애취약점리포트-기능테스트">MySQL에서ReplicationDriver사용시장애취약점리포트-기능테스트</a>” 포스팅을 보시면 취약점에 대한 테스트 결과및 Oracle측의 답변이 있습니다.(2012년도 포스팅이니 현재는 해결이 됬는지 모르겠습니다.)</li>
<li>“<a href="http://kwonnam.pe.kr/wiki/database/mysql/jdbc#replication_jdbc_driver">권남 - MySQL JDBC</a>“에도 역시 여러가지 문제점들이 도출되어 있습니다.</li>
<li>결론적으로 사용 안하는 쪽이 나을듯 합니다.</li>
</ul>
</li>
<li>Spring LazyConnectionDataSourceProxy + AbstractRoutingDataSource 사용
<ul>
<li>Spring에서 Transaction readonly 옵션을 사용하여 분기하는 방법입니다.</li>
<li>AbstractRoutingDataSource : 여러개의 DateSource를 하나로 묶고 자동 분기처리</li>
<li>LazyConnectionDataSourceProxy : 트랜잭션 시작되더라도 실제 커넥션이 필요한 경우에 데이터소스에서 커넥션을 반환</li>
</ul>
</li>
</ul>
<h2 id="write증가시-파티셔닝">Write증가시 파티셔닝</h2>
<p>write가 증가하게 되면 Master로부터 Replication을 받기 위해 Slave의 write IO가 증가하게 됩니다. <br />
그렇게 되면 Read Slave를 아무리 늘려도 성능개선이 미미해지는데 이럴때는 Write를 줄이는 파티셔닝을 해야합니다.</p>
<p><img src="/assets/img/architecture/architecture-traffic-issue/5.png" alt="Read/Write DB 분리" /></p>
<ul>
<li>
<h2 id="파티셔닝partitioninig">파티셔닝(Partitioninig)</h2>
<ul>
<li>성능,가용성,정비용이성을 목적으로 논리적 데이터 요소들을 다수의 테이블로 쪼개는 행위</li>
<li>수직분할(Vertical Partitioninig)
<ul>
<li>테이블의 Column 단위로 파티셔닝하는 방법</li>
<li>스키마가 서로 달라집니다.</li>
</ul>
</li>
<li>수평분할(Sharding : Horizontal Partitionning)
<ul>
<li>테이블의 Row 단위로 파티셔닝하는 방법</li>
<li>스키마는 동일합니다.</li>
</ul>
</li>
</ul>
</li>
<li>
<h2 id="파티션-방법">파티션 방법</h2>
<ul>
<li>수동 파티셔닝 : 분석된 테이블 정보를 이용하여 파티션 뷰를 직접 생성</li>
<li>파티션 테이블 :
<ul>
<li>Range 파티셔닝
<ul>
<li>특정 기간 별로 파티션을 나눔</li>
<li>주로 날짜조건 사용</li>
</ul>
</li>
<li>Hash 파티셔닝
<ul>
<li>Hash함수에 적용한 결과값이 같은 레코드별로 나눔</li>
<li>변별력 좋고 데이터분포가 고른 컬럼을 선정해야 효과적</li>
</ul>
</li>
<li>List 파티셔닝
<ul>
<li>사용자에 의해 미리 정해진 그룹핑 기준에 따라 분할</li>
</ul>
</li>
<li>결합 파티셔닝
<ul>
<li>위 파티션 기법을 조합하여 사용</li>
</ul>
</li>
</ul>
</li>
<li>자세한 설정방법은 <a href="http://wiki.gurubee.net/pages/viewpage.action?pageId=26742648">구루비 DB 스터디 - 1. 테이블 파티셔닝</a>을 참조</li>
</ul>
</li>
</ul>
<h2 id="참고">참고</h2>
<p><a href="http://www.hanbit.co.kr/store/books/look.php?p_code=E1904063627">대용량 서버구축을 위한 Memcached와 Redis</a> <br />
<a href="http://kwon37xi.egloos.com/5364167">Java 에서 DataBase Replication Master/Slave (write/read) 분기 처리하기</a> <br />
<a href="http://gywn.net/2012/07/mysql-replication-driver-error-report/">MySQL에서 Replication Driver 사용 시 장애 취약점 리포트</a> <br />
<a href="http://kwonnam.pe.kr/wiki/database/mysql/jdbc">권남 - MySQL JDBC</a> <br />
<a href="http://www.programcreek.com/java-api-examples/index.php?source_dir=replication-datasource-master/src/test/java/kr/pe/kwonnam/replicationdatasource/config/WithRoutingDataSourceConfig.java">H2DB - LazyConnectionDataSourceProxy 예제</a> <br />
<a href="http://wiki.gurubee.net/pages/viewpage.action?pageId=26742648">구루비 DB 스터디 - 1. 테이블 파티셔닝</a> <br />
<a href="http://bysql.net/index.php?document_srl=15154&mid=w201101B">오라클 성능 고도화 원리와 해법 2 [11-1B]</a></p>
(JPA,SpringData) ORM 기본 개념 정리
2017-02-13T00:00:00+00:00
2017-02-13T00:00:00+00:00
https://github.com/jistol/spring/2017/02/13/jpa-orm-summary
<p>여러 게시글들을 기반으로 정리해 보았습니다.</p>
<h2 id="ormobject-relational-mapping이란">ORM(Object-Relational Mapping)이란?</h2>
<ul>
<li>객체(Object)와 관계형 데이터베이스(Relational)의 관계 설정을 자동으로 처리해줍니다.</li>
<li>실제 데이터와 객체와의 개념적 일치하지 않는 부분을 자동으로 매핑해주는데 ResultSet을 받아 Bean에 열심히 넣어주던것을 대신해주는 것과 비슷하게 생각하면 됩니다.</li>
<li>관계형 데이터베이스의 데이터를 객체형 데이터처럼 사용할 수 있습니다.</li>
</ul>
<h2 id="orm의-장점">ORM의 장점</h2>
<ul>
<li>데이터베이스 종류에 제약을 최대한 받지 않습니다.(Native Query 사용시 무효)</li>
<li>객체 중심으로 설계하기 때문에 좀 더 직관적이고 빠르게 개발할 수 있습니다.</li>
<li>객체 지향적 설계로 인해 좀 더 직관적이고 비지니스로직에 집중할 수 있으며 생산성,유지보수성이 향상됩니다.</li>
</ul>
<h2 id="orm의-단점">ORM의 단점</h2>
<ul>
<li>모든 기능을 ORM으로만 작성하기에는 쿼리가 복잡해지면 쓰기 어렵습니다.(통계, 데이터분석등…)</li>
<li>성능이슈, 몇몇 글에 따르면 ORM을 사용시 느리다는 평이 있습니다.</li>
<li>SP를 많이 쓰거나 기존 SQL문이 많은 프로그램에는 객체지향의 장점을 활용하기 어렵습니다.</li>
</ul>
<h2 id="orm에-적합한-모델">ORM에 적합한 모델</h2>
<ul>
<li>Entity를 개별적으로 업데이트</li>
<li>간헐적으로 Set기반 작업 수행 (ex: 고객 레코드및 주문내역을 개별적으로 업데이트)</li>
</ul>
<h2 id="orm에-적합하지-않은-모델">ORM에 적합하지 않은 모델</h2>
<ul>
<li>많은 수의 레코드를 잦은 빈도로 벌크 업데이트 수행</li>
<li>통계, 데이터분석처리(OLAP)</li>
<li>이미 작성된 핸드코딩/프로시저를 이용하는 환경(MyBatis를 쓰면 좋다, 다른 ORM도 이런부분을 지원합니다.)</li>
<li>순수 SQL문을 쓰는게 더 나을때</li>
</ul>
<h2 id="참고">참고</h2>
<p><a href="http://lilymate.tistory.com/235">ORM (Object Relation Mapping)</a>
<a href="https://gs.saro.me/#!m=elec&jn=718">ORM 의 장점과 단점</a></p>
(vi 명령어) 비주얼모드(영역지정),복사,붙여넣기,실행취소(undo),다시실행(redo)
2017-02-12T00:00:00+00:00
2017-02-12T00:00:00+00:00
https://github.com/jistol/linux/2017/02/12/vim-cmd-base
<h2 id="v--비주얼모드영역지정">v : 비주얼모드(영역지정)</h2>
<p>문자단위로 선택영역 지정을 할 수 있습니다.
<img src="/assets/img/vim/vim-cmd-base/1.png" alt="v-비주얼모드" /></p>
<h2 id="v--라인단위-비주얼모드영역지정">V : 라인단위 비주얼모드(영역지정)</h2>
<p>라인단위로 선택영역 지정을 할 수 있습니다.
<img src="/assets/img/vim/vim-cmd-base/2.png" alt="V-비주얼모드" /></p>
<h2 id="y--복사">y : 복사</h2>
<p>문자단위 또는 선택영역을 복사하여 버퍼에 저장합니다. <br />
복사한 문자는 다른 버퍼를 사용하는 명령어(ex:x,d,dd,p …)를 사용할 경우 없어집니다.</p>
<h2 id="y--라인단위-복사">Y : 라인단위 복사</h2>
<p>한 줄을 복사하여 버퍼에 저장합니다. <br />
복사한 문자는 다른 버퍼를 사용하는 명령어(ex:x,d,dd,p …)를 사용할 경우 없어집니다.</p>
<h2 id="p--붙여넣기현재-커서-앞">p : 붙여넣기(현재 커서 앞)</h2>
<p>버퍼의 내용을 현재 커서 앞에 붙여넣습니다.</p>
<h2 id="p--붙여넣기현재-커서-뒤">P : 붙여넣기(현재 커서 뒤)</h2>
<p>버퍼의 내용을 현재 커서 뒤에 붙여넣습니다.</p>
<h2 id="u--실행취소">u : 실행취소</h2>
<p>이전 실행한 내용을 취소합니다.</p>
<h2 id="ctrl--r--다시실행">ctrl + r : 다시실행</h2>
<p>실행 취소한 내용을 다시 실행합니다.</p>
(JPA,SpringData) Sort 사용하기
2017-02-11T00:00:00+00:00
2017-02-11T00:00:00+00:00
https://github.com/jistol/spring/2017/02/11/jpa-sort
<p>일반적으로 JPA에서 Sort기능을 사용하기 위해서 아래와 같이 메서드명에 <code class="language-plaintext highlighter-rouge">OrderBy</code>를 붙여 사용합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public Page<T> findAllByNameOrderByCreatedDateDesc();
</code></pre></div></div>
<p>이와 같이 만드는 경우 일반적인 목록조회 페이지에서 다수의 정렬기능(ex:이름순,날짜순,날짜역순…)을 필요로 할 경우 위와 같은 메소드를 정렬 방식 개수대로 만들어야 하는 단점이 있습니다.
이 때 사용하기 좋은것이 <code class="language-plaintext highlighter-rouge">Sort</code>클래스입니다.
Controller에 아래와 같이 인자값으로 등록만 하면 알아서 정렬정보가 세팅됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Controller
public List<T> list(Sort sort)
{
List<T> tList = jpaRepository.findAll(sort);
return tList;
}
</code></pre></div></div>
<p>파라메터 형식은 아래와 같이 넘길 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[이름으로 정렬]
/path?sort=name,asc
[이름 역순으로 정렬]
/path?sort=name,desc
[이름으로 정렬 + ID로 정렬]
/path?sort=name,id
[이름으로 정렬 + ID 역순으로 정렬]
/path?sort=name,asc&sort=id,desc
</code></pre></div></div>
<p>파라메터에서 전달받은 정렬조건외에 추가적으로 정렬조건을 추가하고 싶을 경우 아래와 같이 <code class="language-plaintext highlighter-rouge">and</code>메소드를 이용하여 추가적으로 정렬조건을 넣을수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Controller
public List<T> list(Sort sort)
{
sort = sort.and(new Sort(Sort.Direction.DESC, "count"))
List<T> tList = jpaRepository.findAll(sort);
return tList;
}
</code></pre></div></div>
<p>보통 목록에서 정렬은 paging과 같이 하는 경우가 많은데 아래와 같이 Pageable을 인자값으로 받으면 자동적으로 정렬값이 추가됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Controller
public List<T> list(Pageable pageable)
{
List<T> tList = jpaRepository.findAll(pageable);
return tList;
}
</code></pre></div></div>
<p>참고 : <a href="https://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-six-sorting/">Spring Data JPA Tutorial: Sorting</a></p>
JVM 구조 정리
2017-02-10T00:00:00+00:00
2017-02-10T00:00:00+00:00
https://github.com/jistol/java/2017/02/10/jvm-structure
<p>그 동안 미뤄왔던 JVM의 기본 구조에 대해 여기 저기 사이트를 참고하여 최종 구조를 그려보았습니다.
여기서 제일 고민했던 부분은 보통 Heap의 Permanent Area로 불리는 영역이 Method Area와 별개인가? 하는 점이였는데 최종 결론은 같은 영역이다!! 라고 결론을 내렸습니다. (<a href="http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/">RUNTIME DATA AREAS – JAVA’S MEMORY MODEL</a>)참고</p>
<h2 id="jvm-구조">JVM 구조</h2>
<p><img src="/assets/img/java/jvm-structure/1.png" alt="JVM 전체구조" /></p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="http://d2.naver.com/helloworld/1230">Naver D2 - JVM Internal</a></li>
<li><a href="http://www.pointsoftware.ch/en/under-the-hood-runtime-data-areas-javas-memory-model/">RUNTIME DATA AREAS – JAVA’S MEMORY MODEL</a></li>
</ul>
(Spring Cache) @Cacheable key값 정하기
2017-02-09T00:00:00+00:00
2017-02-09T00:00:00+00:00
https://github.com/jistol/spring/2017/02/09/springboot-cache-key
<p>Spring Cache는 <code class="language-plaintext highlighter-rouge">@Cacheable</code>어노테이션만 붙이면 알아서 인자값을 종류에 맞게 캐쉬된 데이터를 사용합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable
public List<String> getList(int page, String query){ ... }
</code></pre></div></div>
<p>위와 같은 경우 두 인자값이 모두 같아야 같은 캐쉬값을 내보내는데 리턴값에 영향을 미치는 요소가 page 인자값만 영향을 미친다면 아래와 같이 캐쉬 키값을 별도로 설정 할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "#page")
public List<String> getList(int page, String query){ ... }
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">#인자값이름</code>으로 특정 인자를 지정할 수 있는데 위와 같이 하면 같은 page값일 경우 query인자값은 어떤 값이 들어오더라도 같은 캐쉬값을 반환합니다.
그런데 아래와 같은 경우 캐쉬가 정상적으로 동작하지 않습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Person
{
private String name;
public String getName(){ return name; }
}
@Cacheable(key = "#kim")
public List<String getList(Person kim){ ... }
</code></pre></div></div>
<p>이유는 Spring 4.0 이전 버전에서 사용하는 기본 KeyGenerator인 <code class="language-plaintext highlighter-rouge">DefaultKeyGenerator</code>가 아래와 같은 방식으로 키를 생성하기 때문입니다.</p>
<p><a href="https://docs.spring.io/spring/docs/current/spring-framework-reference/html/cache.html#cache-annotations-cacheable-default-key">Spring Cache Abstraction Default Key Generation - 원문보기</a>
<img src="/assets/img/java/springboot-cache-key/1.png" alt="Spring Cache Abstraction Default Key Generation" /></p>
<p>객체의 native값을 이용하거나 Object일 경우 <code class="language-plaintext highlighter-rouge">hashCode()</code>만을 사용하여 키 값을 생성하는데 Object의 hachCode는 객체에서 재정의 하지 않은 이상 무조건 다른 값이 들어가게 되기 때문입니다.
<em>Spring 4.0 이후 버전에서의 기본 KeyGenerator는 <code class="language-plaintext highlighter-rouge">SimpleKeyGenerator</code>를 사용하며 hashCode만이 아닌 복합키를 사용한다고 합니다.</em></p>
<p>인자값이 Object 객체인 경우 객체내 값을 이용하여 키 값을 쓸 수 있습니다.(물론 이 때 내부 값도 native값이거나 String같은 heap내 주소가 유일한 경우에만 가능합니다.)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "#kim.name")
public List<String getList(Person kim){ ... }
</code></pre></div></div>
<p>인자값이 null일 경우를 체크하려면 아래와 같이 사용할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "#kim?.name")
public List<String getList(Person kim){ ... }
</code></pre></div></div>
<p>위 식은 if-then-else 구문에서 else만 빠진것으로 전부 표시하면 아래 같이도 사용 가능합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "#kim?.name:'Unknown'")
public List<String getList(Person kim){ ... }
</code></pre></div></div>
<p>특정 메소드를 사용하고 싶다면 아래와 같이 사용할 수도 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "#kim.getName()")
public List<String getList(Person kim){ ... }
</code></pre></div></div>
<p>List나 Map값은 객체들은 hachCode값을 이미 내부 객체의 hashCode값들을 이용하여 정해진 규칙대로 만들기 때문에 아래의 경우에는 별도의 처리 없이 사용가능합니다.
<em>하지만 내부에 포함된 데이터가 native타입이 아닐 경우엔 List, Map도 문제가 생깁니다.</em></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "#strList")
public List<String getListByList(List<String> strList){ ... }
@Cacheable(key = "#map")
public List<String getListByMap(Map<Long, String> map){ ... }
</code></pre></div></div>
<p>마지막으로 인자값이 여러개 있을 경우에 표시하는 예제는 아래와 같습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Cacheable(key = "'KeyIs' + #kim?.getName():'Unknown' + #page + #list")
public List<String getList(Person kim, int page, List<String> list){ ... }
</code></pre></div></div>
<p>key값에 사용되는 문법은 sqEL 표현식으로 아래 링크에서 자세한 내용은 확인 가능합니다.</p>
<ul>
<li>(Spring Expression Language (SpEL))[https://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html]</li>
</ul>
Git Remote branch 삭제하기
2017-02-08T00:00:00+00:00
2017-02-08T00:00:00+00:00
https://github.com/jistol/vcs/2017/02/08/git-remote-branch-delete
<p>Git을 사용하다보면 branch이름을 바꿔야 할 때가 있습니다.<br />
아래 명령어로 로컬 branch를 간단히 바꿀 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -m [old-name] [new-name]
</code></pre></div></div>
<p>하지만 remote저장소에 있는 branch이름은 바꿀 수가 없는데 바꾸려면 remote저장소의 branch를 삭제하고 다시 로컬 branch를 이용하여 재생성해야합니다.</p>
<h2 id="remote-저장소-삭제">remote 저장소 삭제</h2>
<p>아래 명령어로 remote 브랜치를 삭제합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git push [remote-name] --delete [old-branch-name]
</code></pre></div></div>
<h2 id="remote저장소에-local-branch-올리기">remote저장소에 local branch 올리기</h2>
<p>로컬 저장소의 이름을 새 이름으로 변경 후 remote저장소에 새로 올립니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -m [old-name] [new-name]
$ git push [remote-name] [new-name]
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/jistol/jistol.github.io.git
* [new branch] new-name -> new-name
</code></pre></div></div>
(SpringBoot) 데이터베이스 초기화 (spring.jpa.hibernate.ddl-auto, import.sql, spring.datasource.data)
2017-02-07T00:00:00+00:00
2017-02-07T00:00:00+00:00
https://github.com/jistol/spring/2017/02/07/springboot-databaseinit-options
<h2 id="springjpahibernateddl-auto">spring.jpa.hibernate.ddl-auto</h2>
<table>
<thead>
<tr>
<th style="text-align: left">옵션</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">create</td>
<td style="text-align: left">기존에 생성되 있던 테이블들을 삭제하고 새로 만듭니다.</td>
</tr>
<tr>
<td style="text-align: left">create-drop</td>
<td style="text-align: left">create와 같은 동작을 하나 종료시에 DROP합니다.</td>
</tr>
<tr>
<td style="text-align: left">update</td>
<td style="text-align: left">변경된 부분만 반영합니다.</td>
</tr>
<tr>
<td style="text-align: left">validate</td>
<td style="text-align: left">테이블과 Entity가 매핑되는지 유효성 검사를 실행합니다.</td>
</tr>
<tr>
<td style="text-align: left">none</td>
<td style="text-align: left">초기화 동작을 사용하지 않습니다.</td>
</tr>
</tbody>
</table>
<h2 id="importsql">import.sql</h2>
<ul>
<li>리소스에 위 파일이 위치하면 테이블 생성시 자동으로 스크립트를 실행시켜줍니다.</li>
</ul>
<h2 id="springdatasourcedata">spring.datasource.data</h2>
<ul>
<li>이 옵션에 지정한 파일을 테이블 생성시 자동으로 실행시켜줍니다.</li>
<li>파일은 ,(쉼표)로 여러개를 지정하거나 *기호를 이용하여 패턴 지정가능합니다.</li>
<li>파일 경로는 클래스패스, 절대경로, 상대경로 모두 지정가능합니다.</li>
</ul>
<p>예시 : classpath:/sql/test/init-*.sql,file:/home/jistol/sql/test.sql,/META-INF/sql/initScript.sql</p>
(JPA,SpringData) 쿼리 메소드 정의하기
2017-02-06T00:00:00+00:00
2017-02-06T00:00:00+00:00
https://github.com/jistol/spring/2017/02/06/jpa-querymethod
<p>iBatis만 사용하다가 SpringData를 처음 접해보면 신세계를 느끼는 것 중 하나가 interface에 메소드 하나 정의 했는데 쿼리가 완성된다는 점이 아닐까 싶습니다.
xml에 죽어라 SQL문 만들다가 이렇게 간단하게 해결되면 SpringData를 안쓸수가 없죠.<em>(반대로 더 불편해지거나 힘든점도 있긴 합니다.)</em></p>
<p>그동안 사용해온 쿼리 메소드들에 대해 몇몇가지 정리해봅니다.
자세한 내용은 <a href="http://docs.spring.io/spring-data/jpa/docs/1.10.7.RELEASE/reference/html/">Spring Data JPA - Reference Documentation(1.10.7)영문</a>이나 이쁘게 번역해놓은 <a href="http://arahansa.github.io/docs_spring/jpa.html">번역본</a>을 참고하시기 바랍니다.</p>
<h2 id="메소드-이름-안에서-지원되는-키워드들">메소드 이름 안에서 지원되는 키워드들</h2>
<table>
<thead>
<tr>
<th style="text-align: center">Keyword</th>
<th style="text-align: left">Sample</th>
<th style="text-align: left">JPQL snippet</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">And</td>
<td style="text-align: left">findByLastnameAndFirstname</td>
<td style="text-align: left">… where x.lastname = ?1 and x.firstname = ?2</td>
</tr>
<tr>
<td style="text-align: center">Or</td>
<td style="text-align: left">findByLastnameOrFirstname</td>
<td style="text-align: left">… where x.lastname = ?1 or x.firstname = ?2</td>
</tr>
<tr>
<td style="text-align: center">Is,Equals</td>
<td style="text-align: left">findByFirstname,findByFirstnameIs,findByFirstnameEquals</td>
<td style="text-align: left">… where x.firstname = 1?</td>
</tr>
<tr>
<td style="text-align: center">Between</td>
<td style="text-align: left">findByStartDateBetween</td>
<td style="text-align: left">… where x.startDate between 1? and ?2</td>
</tr>
<tr>
<td style="text-align: center">LessThan</td>
<td style="text-align: left">findByAgeLessThan</td>
<td style="text-align: left">… where x.age < ?1</td>
</tr>
<tr>
<td style="text-align: center">LessThanEqual</td>
<td style="text-align: left">findByAgeLessThanEqual</td>
<td style="text-align: left">… where x.age ⇐ ?1</td>
</tr>
<tr>
<td style="text-align: center">GreaterThan</td>
<td style="text-align: left">findByAgeGreaterThan</td>
<td style="text-align: left">… where x.age > ?1</td>
</tr>
<tr>
<td style="text-align: center">GreaterThanEqual</td>
<td style="text-align: left">findByAgeGreaterThanEqual</td>
<td style="text-align: left">… where x.age >= ?1</td>
</tr>
<tr>
<td style="text-align: center">After</td>
<td style="text-align: left">findByStartDateAfter</td>
<td style="text-align: left">… where x.startDate > ?1</td>
</tr>
<tr>
<td style="text-align: center">Before</td>
<td style="text-align: left">findByStartDateBefore</td>
<td style="text-align: left">… where x.startDate < ?1</td>
</tr>
<tr>
<td style="text-align: center">IsNull</td>
<td style="text-align: left">findByAgeIsNull</td>
<td style="text-align: left">… where x.age is null</td>
</tr>
<tr>
<td style="text-align: center">IsNotNull,NotNull</td>
<td style="text-align: left">findByAge(Is)NotNull</td>
<td style="text-align: left">… where x.age not null</td>
</tr>
<tr>
<td style="text-align: center">Like</td>
<td style="text-align: left">findByFirstnameLike</td>
<td style="text-align: left">… where x.firstname like ?1</td>
</tr>
<tr>
<td style="text-align: center">NotLike</td>
<td style="text-align: left">findByFirstnameNotLike</td>
<td style="text-align: left">… where x.firstname not like ?1</td>
</tr>
<tr>
<td style="text-align: center">StartingWith</td>
<td style="text-align: left">findByFirstnameStartingWith</td>
<td style="text-align: left">… where x.firstname like ?1 (parameter bound with appended %)</td>
</tr>
<tr>
<td style="text-align: center">EndingWith</td>
<td style="text-align: left">findByFirstnameEndingWith</td>
<td style="text-align: left">… where x.firstname like ?1 (parameter bound with prepended %)</td>
</tr>
<tr>
<td style="text-align: center">Containing</td>
<td style="text-align: left">findByFirstnameContaining</td>
<td style="text-align: left">… where x.firstname like ?1 (parameter bound wrapped in %)</td>
</tr>
<tr>
<td style="text-align: center">OrderBy</td>
<td style="text-align: left">findByAgeOrderByLastnameDesc</td>
<td style="text-align: left">… where x.age = ?1 order by x.lastname desc</td>
</tr>
<tr>
<td style="text-align: center">Not</td>
<td style="text-align: left">findByLastnameNot</td>
<td style="text-align: left">… where x.lastname <> ?1</td>
</tr>
<tr>
<td style="text-align: center">In</td>
<td style="text-align: left">findByAgeIn(Collection<Age> ages)</Age></td>
<td style="text-align: left">… where x.age in ?1</td>
</tr>
<tr>
<td style="text-align: center">NotIn</td>
<td style="text-align: left">findByAgeNotIn(Collection<Age> age)</Age></td>
<td style="text-align: left">… where x.age not in ?1</td>
</tr>
<tr>
<td style="text-align: center">True</td>
<td style="text-align: left">findByActiveTrue()</td>
<td style="text-align: left">… where x.active = true</td>
</tr>
<tr>
<td style="text-align: center">False</td>
<td style="text-align: left">findByActiveFalse()</td>
<td style="text-align: left">… where x.active = false</td>
</tr>
<tr>
<td style="text-align: center">IgnoreCase</td>
<td style="text-align: left">findByFirstnameIgnoreCase</td>
<td style="text-align: left">… where UPPER(x.firstame) = UPPER(?1)</td>
</tr>
</tbody>
</table>
<h2 id="쿼리-결과-limittop하기">쿼리 결과 Limit(Top)하기</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
</code></pre></div></div>
<h2 id="중복제거-disctinct-사용하기">중복제거 Disctinct 사용하기</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
</code></pre></div></div>
(SpringBoot) MVC 테스트 하기 - `@WebMvcTest`, `@AutoConfigureMockMvc`
2017-02-05T00:00:00+00:00
2017-02-05T00:00:00+00:00
https://github.com/jistol/spring/2017/02/05/springboot-mvctest
<p><a href="http://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/reference/htmlsingle/">SpringBoot Reference</a>에 접속하여 목차를 보면 거의 끝쯤에 Testing관련 내용이 나옵니다.
이 중 SpringBoot의 Controller를 JUnit으로 테스트 하고 싶은 경우 <a href="http://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured-mvc-tests">41.3.7 Auto-configured Spring MVC tests</a>를 보면 Http Connection을 별도 구현하지 않고도 MVC 테스트를 가능하게 하는 설명이 나옵니다.</p>
<p><img src="/assets/img/java/springboot-mvctest/1.png" alt="캡처화면" /></p>
<h2 id="webmvctest">@WebMvcTest</h2>
<p>일반적으로 사용하는 MVC테스트용 어노테이션입니다.
해당 어노테이션을 명시하고 그림과 같이 <code class="language-plaintext highlighter-rouge">MockMvc</code>를 <code class="language-plaintext highlighter-rouge">@Autowired</code>하면 해당 객체를 통해 MVC테스트가 가능합니다.</p>
<p><img src="/assets/img/java/springboot-mvctest/2.png" alt="@WebMvcTest사용시 주의사항 1" /></p>
<p><code class="language-plaintext highlighter-rouge">@WebMvcTest</code>어노테이션 사용시 <code class="language-plaintext highlighter-rouge">@SpringBootTest</code>을 같이 사용할 수 없습니다.
서로 <code class="language-plaintext highlighter-rouge">MockMvc</code>를 설정하기 때문에 충돌이 나는거 같은데요, MVC 기능만 사용할 거라면 <code class="language-plaintext highlighter-rouge">@WebMvcTest</code>를 사용하면 됩니다.</p>
<p><img src="/assets/img/java/springboot-mvctest/3.png" alt="@WebMvcTest사용시 주의사항 2" /></p>
<p><code class="language-plaintext highlighter-rouge">@WebMvcTest</code> 사용시 다른 설정들은 자동으로 올리지 않기 때문에 <code class="language-plaintext highlighter-rouge">@Repository</code>나 <code class="language-plaintext highlighter-rouge">@Resource</code>, <code class="language-plaintext highlighter-rouge">@Service</code>, <code class="language-plaintext highlighter-rouge">@Component</code>등은 사용할 수 없습니다.
아래 글과 같이 자동설정하는 영역은 <code class="language-plaintext highlighter-rouge">@Controller</code>, <code class="language-plaintext highlighter-rouge">@ControllerAdvice</code>, <code class="language-plaintext highlighter-rouge">@JsonComponent</code> 등등이네요.
그런데 저는 실제로 테스트 해보니 <code class="language-plaintext highlighter-rouge">@ControllerAdvice</code>도 먹히지 않았습니다. (이유는 아직도 모르는중…)</p>
<p><img src="/assets/img/java/springboot-mvctest/4.png" alt="@WebMvcTest사용시 주의사항 3" /></p>
<p><code class="language-plaintext highlighter-rouge">@WebMvcTest</code>가 포함하는 실제 설정은 <a href="http://docs.spring.io/spring-boot/docs/2.0.0.BUILD-SNAPSHOT/reference/htmlsingle/#test-auto-configuration">Appendix D. Test auto-configuration annotations</a>에서 확인 가능 합니다.</p>
<p><img src="/assets/img/java/springboot-mvctest/5.png" alt="@WebMvcTest이 포함하는 설정" /></p>
<h2 id="autoconfiguremockmvc">@AutoConfigureMockMvc</h2>
<p><code class="language-plaintext highlighter-rouge">@WebMvcTest</code>외에 MVC테스트를 할 수 있는 다른 방법입니다.
위 설정은 MVC테스트 외 모든 설정을 같이 올립니다. AOP도 되고 JPA Repository도 사용가능하네요.
실제적으로 동작하는 MVC테스트를 하려면 위 어노테이션을 사용해야 합니다.
<code class="language-plaintext highlighter-rouge">@AutoConfigureMockMvc</code>은 <code class="language-plaintext highlighter-rouge">@SpringBootTest</code>와 같이 사용 가능합니다.</p>
(SpringBoot) H2 DB 서버모드로 띄워 외부 툴(DBeaver)로 접속하기
2017-02-04T00:00:00+00:00
2017-02-04T00:00:00+00:00
https://github.com/jistol/spring/2017/02/04/springboot-h2db-servermode
<p>SpringBoot에서 H2 DB Embedded를 사용하다보면 항상 console에 들어가 쿼리해야하는 불편함이 있어 외부 툴에서 접근하는 방법을 찾아 정리해 보았습니다.</p>
<p>우선 application.xml에 정의되 있는 아래 항목을 바꿔줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[변경 전]
spring.datasource.url=jdbc:h2:file:./db/devdb;
[변경 후]
spring.datasource.url=jdbc:h2:file:./db/devdb;AUTO_SERVER=TRUE
</code></pre></div></div>
<p><em>※ 주의사항 : H2 DB를 메모리 모드로 올릴 경우에는 사용 할 수 없습니다. 반드시 file모드로 올려주세요.</em></p>
<p>위와 같이 변경후 실행 시키면 서버모드로 뜨게 되고 DBeaver를 통해 아래와 같이 설정합니다.</p>
<ul>
<li>“Create New Connect” 창을 띄우고 아래와 같이 “H2 - Embedded”를 선택합니다.</li>
</ul>
<p><img src="/assets/img/java/springboot-h2db-servermode/1.png" alt="Create New Connect" /></p>
<ul>
<li>Setting 화면에 아래와 같이 각 정보를 넣어줍니다.
<em>※ JDBC URL 항목이 빈 란일 경우 “Driver Properties”탭을 선택하여 H2 Driver를 다운 받았는지 확인합니다.</em>
<em>※ 파일 경로를 full로 적어줘야합니다.</em></li>
</ul>
<p><img src="/assets/img/java/springboot-h2db-servermode/2.png" alt="Create New Connect" /></p>
<ul>
<li>Finish 버튼을 클릭하여 마무리.</li>
</ul>
<p><img src="/assets/img/java/springboot-h2db-servermode/3.png" alt="Create New Connect" /></p>
<p>위와 같이 설정해도 한쪽에서 붙어 있는 상황에선 다른쪽이 붙질 못하더군요.
그 방법까진 아직 못찾아봐서 패스.</p>
(JPA,SpringData) Date타입 컬럼 - Date, Calendar, Timestamp
2017-02-03T00:00:00+00:00
2017-02-03T00:00:00+00:00
https://github.com/jistol/spring/2017/02/03/jpa-datetype
<p>JPA를 사용할때 Date타입의 컬럼 사용시 어떤 Java Object를 사용해야하는지에 대한 글이 있어 옮겨 적어봅니다. <a href="http://www.developerscrappad.com/228/java/java-ee/ejb3-jpa-dealing-with-date-time-and-timestamp/">원본보기</a></p>
<h2 id="javasqldate-javasqltime-javasqltimestamp">java.sql.Date, java.sql.Time, java.sql.Timestamp</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Column(name = "DATE_FIELD")
private java.sql.Date dateField;
@Column(name = "TIME_FIELD")
private java.sql.Time timeField;
@Column(name = "DATETIME_FIELD")
private java.sql.Timestamp dateTimeField;
@Column(name = "TIMESTAMP_FIELD")
private java.sql.Timestamp timestampField;
</code></pre></div></div>
<p>응용 프로그램이 날짜 및 시간 값을 저장하기만 해도 될 경우 사용합니다.
서버 GMT 오프셋과 같이 날짜 및 시간의 확장 된 세부 정보를 저장하거나 다른 지역 또는 시간대에 다른 날짜와 시간을 저장하지 않아도 될 경우에 좋습니다.</p>
<h2 id="javautildate">java.util.Date</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Temporal(TemporalType.DATE)
@Column(name = "DATE_FIELD")
private java.util.Date dateField;
@Temporal(TemporalType.TIME)
@Column(name = "TIME_FIELD")
private java.util.Date timeField;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "DATETIME_FIELD")
private java.util.Date dateTimeField;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "TIMESTAMP_FIELD")
private java.util.Date timestampField;
</code></pre></div></div>
<p>프로그램의 날짜와 GMT값을 날짜,시간,타임스템프 필드와 함꼐 저장해야하는 경우에 사용합니다.
java.util.Date 클래스는 날짜와 시간 정보를 모두 저장할 수 있기 때문에 올바른 TemporalType 속성 (TemporalType.DATE, TemporalType.TIME 또는 TemporalType.TIMESTAMP)이있는 @Temporal 주석이 추가로 필요합니다.</p>
<h2 id="javautilcalendar">java.util.Calendar</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Column(name = "DATE_FIELD")
@Temporal(TemporalType.DATE)
private java.util.Calendar dateField;
@Column(name = "TIME_FIELD")
@Temporal(TemporalType.TIME)
private java.util.Calendar timeField;
@Column(name = "DATETIME_FIELD")
@Temporal(TemporalType.TIMESTAMP)
private java.util.Calendar datetimeField;
@Column(name = "TIMESTAMP_FIELD")
@Temporal(TemporalType.TIMESTAMP)
private java.util.Calendar timestampField;
</code></pre></div></div>
<p>국제 날짜및 시간정보 캡처가 필요한 경우 사용하면 좋습니다.
java.util.Date 클래스와 마찬가지로 @Temporal주석이 필요합니다.</p>
<p>일반적으로 개발할때는 java.sql.Timestamp만 사용해도 충분하겠네요 :)</p>
데이터베이스 정규화
2017-02-02T00:00:00+00:00
2017-02-02T00:00:00+00:00
https://github.com/jistol/database/2017/02/02/database-normalization
<h2 id="정규화란">정규화란?</h2>
<p>데이터베이스의 설계에서 중복을 최소화 하게 데이터를 구조화</p>
<h2 id="정규화의-목표">정규화의 목표</h2>
<ul>
<li>데이터의 삽입,삭제,갱신 이상 제거</li>
<li>자료저장 공간 최소화</li>
</ul>
<h2 id="정규화-단계">정규화 단계</h2>
<h3 id="제1정규화1nf">제1정규화(1NF)</h3>
<ul>
<li><strong>모든 속성은 반드시 하나의 값을 가짐(not null, 반복형태 X)</strong>.</li>
<li>레코드들은 서로 간에 식별 가능해야 합니다..</li>
</ul>
<h3 id="제2정규화2nf">제2정규화(2NF)</h3>
<ul>
<li>식별자가 아닌 모든 속성들은 <strong>식별자 전체 속성에 완전 종속</strong>되어야 합니다(완전함수종속).</li>
</ul>
<h3 id="제3정규화3nf">제3정규화(3NF)</h3>
<ul>
<li>2차 정규형을 만족하고 식별자를 제외한 나머지 <strong>속성들 간의 종속</strong>이 존재하면 안됩니다.</li>
</ul>
<h3 id="예제">예제</h3>
<p><img src="/assets/img/database/database-normalization/1.png" alt="2~3차정규화" /></p>
entity(엔터티), attribute(속성), relationship(관계) 요약 - 암기용
2017-02-01T00:00:00+00:00
2017-02-01T00:00:00+00:00
https://github.com/jistol/database/2017/02/01/database-entity-attribute-relationship
<p>학습 암기용 목적의 포스팅입니다. <br />
자세한 내용은 <a href="http://wiki.gurubee.net/pages/viewpage.action?pageId=27427060">엔터티(Entity)</a>, <a href="http://wiki.gurubee.net/pages/viewpage.action?pageId=27427060">속성(Attribute)</a>, <a href="http://wiki.gurubee.net/pages/viewpage.action?pageId=27427062">관계(Relationship)</a>를 참고하세요.</p>
<h1 id="1-entity"><strong>1. Entity</strong></h1>
<h2 id="entity-개념">Entity 개념</h2>
<p>저장되기 위한 어떤 집합적인 것(Thing:사람,장소,물건,사건,개념).</p>
<p><img src="/assets/img/database/database-entity-attribute-relationship/1.png" alt="엔터티 구성" /> <br />
출처 : http://tech.devgear.co.kr/db_kb/324</p>
<h2 id="entity-특징">Entity 특징</h2>
<ol>
<li>업무에 필요한 정보</li>
<li>의미있는 식별자에 의해 인스턴스는 1개씩만 존재(중복배제)</li>
<li>2개이상의 인스턴스 집합으로 구성</li>
<li>업무프로세스에 의해 이용되어야 함</li>
<li>속성을 포함해야 함(식별자만 있으면 의미없음)</li>
<li>관계가 존재해야함</li>
</ol>
<h2 id="entity-분류">Entity 분류</h2>
<ul>
<li>유무(有無)형에 따른 분류</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">명칭</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">유형 엔터티<br />Tangible Entity</td>
<td style="text-align: left">물리적 형태가 있음<br />ex:사원,물품,강사</td>
</tr>
<tr>
<td style="text-align: center">개념 엔터티<br />Conceptual Entity</td>
<td style="text-align: left">물리적 형태가 없음<br />ex:조직,보험상품</td>
</tr>
<tr>
<td style="text-align: center">사건 엔터티<br />Event Entity</td>
<td style="text-align: left">업무 수행에 따라 발생<br />ex:주문,청구,미납</td>
</tr>
</tbody>
</table>
<ul>
<li>발생시점에 따른 분류</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">명칭</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">기본 엔터티</td>
<td style="text-align: left">원래 존재하는 정보<br />ex:사원,부서,고객,상품</td>
</tr>
<tr>
<td style="text-align: center">중심 엔터티</td>
<td style="text-align: left">기본엔터티로부터 발생하고 다른 엔터티와의 관계를 통해 많은 행위엔터티를 발생<br />업무에 있어 중심역활<br />ex:계약,사고,청구,주문</td>
</tr>
<tr>
<td style="text-align: center">행위 엔터티</td>
<td style="text-align: left">두개이상 부모엔터티로부터 발생<br />내용이 자주 바뀌거나 데이터량이 증가<br />ex:주문목록,로그인이력</td>
</tr>
</tbody>
</table>
<h1 id="2-attribute"><strong>2. Attribute</strong></h1>
<h2 id="엔터티-인스턴스-속성-속성값의-관계">엔터티, 인스턴스, 속성, 속성값의 관계</h2>
<p><img src="/assets/img/database/database-entity-attribute-relationship/2.jpg" alt="관계도" /></p>
<ol>
<li>한 개의 엔터티는 두 개 이상의 인스턴스의 집합.</li>
<li>한 개의 엔터티는 두 개 이상의 속성을 가짐.(식별자 외에 1개이상 필요)</li>
<li>한 개의 속성은 한 개의 속성값을 가짐.</li>
</ol>
<h2 id="속성의-특징">속성의 특징</h2>
<ul>
<li>업무에 필요한 정보</li>
<li>주식별자에 함수적 종속성</li>
<li>한 개의 속성값만 가짐, 다중값일 경우 별도의 엔터티를 이용하여 분리 필요</li>
</ul>
<h2 id="속성의-분류">속성의 분류</h2>
<ul>
<li>특성에 따른 분류</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">명칭</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">기본속성</td>
<td style="text-align: left">업무로 부터 추출한 값<br />ex:이름,전화번호,성별</td>
</tr>
<tr>
<td style="text-align: center">설계속성</td>
<td style="text-align: left">규칙화를 위해 변형/새로정의한 값<br />ex:과목코드,지역코드</td>
</tr>
<tr>
<td style="text-align: center">파생속성</td>
<td style="text-align: left">다른 속성에 영향을 받아 발생한 값<br />ex:예금이자,평균성적</td>
</tr>
</tbody>
</table>
<ul>
<li>엔터티 구성방식에 따른 분류</li>
</ul>
<table>
<thead>
<tr>
<th style="text-align: center">명칭</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">PK<br />Primary Key</td>
<td style="text-align: left">엔터티를 식별할 수 있는 속성</td>
</tr>
<tr>
<td style="text-align: center">FK<br />Foreign Key</td>
<td style="text-align: left">다른 엔터티와의 관계에서 포함된 속성</td>
</tr>
<tr>
<td style="text-align: center">일반속성</td>
<td style="text-align: left">PK,FK에 포함되지 않은 속성</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th style="text-align: center">명칭</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">단순형</td>
<td style="text-align: left">원자값 속성</td>
</tr>
<tr>
<td style="text-align: center">복합형</td>
<td style="text-align: left">여러 세부 속성으로 나뉠수 있는 속성</td>
</tr>
</tbody>
</table>
<h2 id="도메인domain">도메인(Domain)</h2>
<p>속성이 가질 수 있는 값의 범위</p>
<h1 id="3-relationship"><strong>3. Relationship</strong></h1>
<h2 id="관계의-정의">관계의 정의</h2>
<p>인스턴스 사이의 논리적인 연관성</p>
<h2 id="관계의-패어링">관계의 패어링</h2>
<p>엔터티 안에 인스턴스가 개별적으로 관계를 가지는것 <br />
패어링의 집합 -> 관계</p>
<p><img src="/assets/img/database/database-entity-attribute-relationship/3.jpg" alt="관계와 패어링" /></p>
<h2 id="관계의-표기법">관계의 표기법</h2>
<ul>
<li>관계명(Membership) : 관계의 이름</li>
<li>관계차수(Cardinality) : 1:1, 1:M, M:N</li>
<li>관계선택사양(Optionality) : 필수관계(not null), 선택관계(nullable, O를 표시)</li>
</ul>
<p><img src="/assets/img/database/database-entity-attribute-relationship/4.jpg" alt="관계의 표기법" /></p>
데이터모델링 정리
2017-01-31T00:00:00+00:00
2017-01-31T00:00:00+00:00
https://github.com/jistol/database/2017/01/31/database-data-modeling-base
<h2 id="데이터-모델링이란">데이터 모델링이란?</h2>
<p>시스템 구축을 위해 업무의 데이터를 <strong>분석하는 방법</strong>, 명확하게 표현하는 <strong>추상화 기법</strong></p>
<h2 id="중요성">중요성</h2>
<ul>
<li>파급효과(Leverage) : 데이터 구조가 프로젝트 막바지에 바뀌게 되면 영향도가 큽니다.</li>
<li>요구사항의 간결한 표현(Conciseness) : 요구사항 파악하기 좋고, 많은 관련자들이 소통하기 좋습니다.</li>
<li>데이터 품질(Data Quality) : 데이터는 시스템의 자산. 정확성이 떨어지는 데이터는 가치가 없습니다.</li>
</ul>
<h2 id="유의점">유의점</h2>
<p><strong>중복</strong>을 피하고, <strong>유연</strong>해야하며, <strong>일관성</strong>있어야 합니다.</p>
<h2 id="데이터-모델링-단계">데이터 모델링 단계</h2>
<ul>
<li>개념적 데이터 모델링 : 핵심 엔터티와 그들 간의 관계를 찾고 엔터티-관계 다이어 그램을 생성합니다.</li>
</ul>
<p><img src="/assets/img/database/database-data-modeling-base/3.jpg" alt="개념적 데이터 모델링" /> <br />
※ 출처 : http://dbteam6.pbworks.com/f/1179078501/%EC%A7%84%EC%A7%9C%EC%A7%84%EC%A7%9C%EC%B5%9C%EC%A2%85.jpg</p>
<ul>
<li>논리적 데이터 모델링 : 실질적으로 프로젝트에서 사용할 비지니스 정보의 논리적인 구조와 규칙을 명시하는 단계로 실질적으로 모델링을 완료하는 단계입니다.(정규화 활동이 포함)</li>
</ul>
<p><img src="/assets/img/database/database-data-modeling-base/4.gif" alt="논리적 데이터 모델링" /> <br />
※ 출처 : http://cfs9.tistory.com/upload_control/download.blog?fhandle=YmxvZzE5NDM0MkBmczkudGlzdG9yeS5jb206L2F0dGFjaC8wLzc5LmdpZg%3D%3D</p>
<ul>
<li>물리적 데이터 모델링 : 논리모델에서 설계된 내용을 실제 물리적으로 어떻게 표현될지를 정합니다.</li>
</ul>
<p><img src="/assets/img/database/database-data-modeling-base/5.png" alt="물리적 데이터 모델링" /> <br />
※ 출처 : https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Data_model_in_ER.png/700px-Data_model_in_ER.png</p>
<h2 id="데이터-모델링-구조-및-독립성">데이터 모델링 구조 및 독립성</h2>
<p>데이터 모델링의 구조와 독립성에 대한 여러가지 설명이 있지만 아래 두 그림으로 딱 설명이 되는것 같습니다. <br />
<img src="/assets/img/database/database-data-modeling-base/1.jpg" alt="데이터모델링 구조" /> <br />
※ 출처 : http://cfile28.uf.tistory.com/image/2119243556BD75431EB784</p>
<p><img src="/assets/img/database/database-data-modeling-base/2.png" alt="데이터모델링 예제" /> <br />
※ 출처 : http://cfile233.uf.daum.net/image/2056933C4F60B0312DC599</p>
<ul>
<li>외부스키마(외부단계)는 S/w, 개발자가 직접 접근하는 DB View입니다.</li>
<li>개념스키마(개념적단계)는 전체 DB를 기술합니다.</li>
<li>내부스키마(내부적단계)는 물리적 장치에 데이터가 실제적으로 저장되는 방법을 표현합니다.</li>
</ul>
<p>각 독립성은 각 스키마가 변경되더라도 서로 영향을 끼치지 않아야 한다는 의미입니다.</p>
<ul>
<li>논리적 독립성 : 개념스키마가 변해도 외부스키마는 변하지 않도록 지원해야함.</li>
<li>물리적 독립성 : 내부스키마가 변해도 개념스키마가 변하지 않도록 지원해야함.</li>
</ul>
<p>사상은 Mapping이라고 부르는것이 더 이해하기 쉽습니다. 각 저장구조가 바뀐다면 Mapping정보가 바뀌어야 독립성이 유지됩니다.</p>
<ul>
<li>논리적사상 : 외부뷰는 개념뷰에서 Mapping된 정보</li>
<li>물리적사상 : 개념뷰는 내부뷰에서 Mapping된 정보</li>
</ul>
Git log 주요 명령어 정리
2017-01-30T00:00:00+00:00
2017-01-30T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/30/git-log
<p><code class="language-plaintext highlighter-rouge">git log</code>의 유용하게 쓰이는 몇몇 옵션들을 정리해 봅니다. <br />
자세한 사용법은 <a href="https://git-scm.com/book/ko/v1/Git%EC%9D%98-%EA%B8%B0%EC%B4%88-%EC%BB%A4%EB%B0%8B-%ED%9E%88%EC%8A%A4%ED%86%A0%EB%A6%AC-%EC%A1%B0%ED%9A%8C%ED%95%98%EA%B8%B0">Git의 기초 - 커밋 히스토리 조회하기</a>를 참고하세요.</p>
<h2 id="출력-log-제한">출력 log 제한</h2>
<p><code class="language-plaintext highlighter-rouge">git log -(n)</code></p>
<p>최근 N개의 log만 출력합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -2 --pretty=oneline
7343a5f82686c8c94800ddf8d2cffb30f6160243 modify m3
5a65f7acf00b6c18285bc0396d463f6a7c506c91 modify m2
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">git log --since[after,until,before]</code></p>
<p>특정 날짜 이전/이후 commit만 조회합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log --since="2017-01-29"
commit 7343a5f82686c8c94800ddf8d2cffb30f6160243
Author: unknown <pptwenty@gmail.com>
Date: Mon Jan 30 22:54:30 2017 +0900
modify m3
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">git log --author[committer]</code></p>
<p>특정 저자/커미터의 commit만 조회합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log --author=unknown
commit 7343a5f82686c8c94800ddf8d2cffb30f6160243
Author: unknown <pptwenty@gmail.com>
Date: Mon Jan 30 22:54:30 2017 +0900
modify m3
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">git log -- [path1] [path2] ...</code></p>
<p>특정 경로(폴더or파일)의 변경사항에 대해서만 조회합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -p -- ./w.txt ./v.txt ./work
commit 169f19e36a4360c6a587e146a104e092a62ffc04
Author: unknown <pptwenty@gmail.com>
Date: Mon Jan 30 23:32:48 2017 +0900
work add
diff --git a/work/w1.txt b/work/w1.txt
new file mode 100644
index 0000000..e69de29
commit 6b1dbc9c20ad9be3cea60e4cf467328141b17180
Author: unknown <pptwenty@gmail.com>
Date: Sun Jan 29 23:49:28 2017 +0900
v2
diff --git a/v.txt b/v.txt
index 626799f..8c1384d 100644
--- a/v.txt
+++ b/v.txt
@@ -1 +1 @@
-v1
+v2
</code></pre></div></div>
<h2 id="diff-내용-같이-보기">diff 내용 같이 보기</h2>
<p><code class="language-plaintext highlighter-rouge">git log -p --word-diff --stat</code></p>
<table>
<thead>
<tr>
<th style="text-align: center">옵션</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">-p</code></td>
<td style="text-align: left">각 commit의 diff결과를 줄 단위로 보여줍니다.</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">--word-diff</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">-p</code>옵션과 같이 사용하면 diff결과를 단어 단위로 보여줍니다.<br />변경된 단어 단위별로 [- -]{+ +}와 같이 괄호로 쌓아 보여줍니다.</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">--stat</code></td>
<td style="text-align: left">각 commit의 변경사항에 대한 통계정보를 보여줍니다.</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">--shortstat</code></td>
<td style="text-align: left">각 commit의 변경사항에 대한 통계정보중 변경/추가/삭제 개수만 보여줍니다.</td>
</tr>
</tbody>
</table>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log -p --word-diff --stat -1
commit 7343a5f82686c8c94800ddf8d2cffb30f6160243
Author: unknown <pptwenty@gmail.com>
Date: Mon Jan 30 22:54:30 2017 +0900
modify m3
---
m.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/m.txt b/m.txt
index 5895249..86afc2c 100644
--- a/m.txt
+++ b/m.txt
@@ -1,4 +1,4 @@
m3
m4-1
[-m5-2-]{+m5-3+} mmmmmm [-aaaaaa-]{+aaxxaa+}
m6
</code></pre></div></div>
<h2 id="출력-log내용-형식-변경">출력 log내용 형식 변경</h2>
<p><code class="language-plaintext highlighter-rouge">git log --pretty=[OPTION] --abbrev-commit</code></p>
<p><code class="language-plaintext highlighter-rouge">--pretty</code>옵션 값은 oneline, short, full, fuller가 있습니다. <br />
oneline은 commit 내용을 한 줄로 표시(Hash값 , log 메시지)하며 나머지 옵션은 기본 log정보에서 추가정보를 가감합니다.
<code class="language-plaintext highlighter-rouge">--abbrev-commit</code>는 log HASH키 값을 처음 몇 자만 보여주도록 합니다. <br />
<em>※ <code class="language-plaintext highlighter-rouge">--pretty=oneline --abbrev-commit</code>의 경우 <code class="language-plaintext highlighter-rouge">--oneline</code>으로 대신 쓸 수 있습니다.</em></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log --pretty=oneline -1
7343a5f82686c8c94800ddf8d2cffb30f6160243 modify m3
$ git log --pretty=oneline --abbrev-commit -1
7343a5f modify m3
$ git log --pretty=fuller -1
commit 7343a5f82686c8c94800ddf8d2cffb30f6160243
Author: unknown <pptwenty@gmail.com>
AuthorDate: Mon Jan 30 22:54:30 2017 +0900
Commit: unknown <pptwenty@gmail.com>
CommitDate: Mon Jan 30 22:54:30 2017 +0900
modify m3
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">git log --pretty=format:"%h %s" --graph</code></p>
<p><code class="language-plaintext highlighter-rouge">--pretty=format</code>옵션은 필요한 정보에 대해 특정 format으로 표시하는데 <code class="language-plaintext highlighter-rouge">%h</code>는 log의 HASH키 값을 줄여서 보여주며 <code class="language-plaintext highlighter-rouge">%s</code>는 log 메시지를 보여줍니다. <br />
<em>※HASH키 전체값을 보고 싶을 경우 <code class="language-plaintext highlighter-rouge">%H</code>옵션을 사용합니다.</em> <br />
<code class="language-plaintext highlighter-rouge">--graph</code> 옵션은 branch와 merge한 내역을 시각화 하여 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git log --pretty=format:"%h %s" --graph
* 7343a5f modify m3
* 5a65f7a modify m2
* 01c75b6 modify m
* ae65735 Merge branch 'work2'
|\
| * 6b1dbc9 v2
| * aa8dc59 v1
|/
* 0604c1e Merge branch 'work1'
|\
| * 277030f w2
| * 4d72d03 w1
|/
* 4283fad m3
* 08e8d8d m2
* 4820c18 m1
</code></pre></div></div>
Git cherry-pick, rebase
2017-01-29T00:00:00+00:00
2017-01-29T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/29/git-cherrypick-rebase
<p>cherry-pick과 rebase에 대해 공부한 내용을 정리해 봅니다.</p>
<p>위 두 명령어는 누군가와 협업하여 소스를 관리할 때 유용한 명령어로 남의 작업과 나의 작업을 합치게 될 때 사용하게 됩니다.</p>
<h2 id="cherry-pick">cherry-pick</h2>
<p>특정 commit에 대한 이력을 가져옵니다.</p>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-1.png" alt="status1" /></p>
<p>위와 같은 상황에서 work1 branch에 master에 commit된 m3가 필요할 경우 아래와 같이 cherry-pick 명령어를 통해 추가할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[master]
$ git log --pretty=format:"%h %s"
fa16562 m4
94c79c0 m3
3654326 m2
f56f92a m1
[work1]
$ git cherry-pick 94c79c0
</code></pre></div></div>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-2.png" alt="status2" /></p>
<p>이때 cherry-pick으로 가져온 m3는 master의 m3와는 별개의 commit으로 work1 과 master를 merge 할 경우 아래와 같이 별도 commit으로 보이게 됩니다.</p>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-3.png" alt="status3" /></p>
<p>위와 같이 cherry-pick을 쓰는 경우를 예로 들면 <br />
여럿이 작업중 누군가가 공통 UTIL을 만들어 배포할 경우 해당 UTIL을 자신의 branch에 추가하는등의 작업과 같이 사용하게 됩니다.</p>
<h2 id="rebase">rebase</h2>
<p>branch를 master(또는 다른 branch)로 합치기 전에 이력을 보기 좋게 만드는데 사용하게 됩니다.</p>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-4.png" alt="status4" /></p>
<p>위와 같이 다수의 branch에서 작업하다가 merge하는 경우 서로 이력이 꼬여 보기 좋지 않을 때가 있습니다.</p>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-5.png" alt="status5" /></p>
<p>이 때 각 branch에서 rebase를 통해 commit 이력을 끌어오면 아래와 같이 예쁘게 이력을 정렬 가능합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[work1]
$ git rebase master
$ git log --pretty=format:"%h %s"
277030f w2
4d72d03 w1
4283fad m3
08e8d8d m2
4820c18 m1
</code></pre></div></div>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-6.png" alt="status6" /></p>
<p>work1의 이력에 위와 같이 master의 m2,m3가 commit이력에 추가된 것을 볼 수 있습니다. <code class="language-plaintext highlighter-rouge">git log</code>를 통해서 commit된 hash값을 보아도 같은 값인것으로 보아 work1의 branch 생성 시점이 master의 현재 HEAD부분으로 이동했다고 봐도 무방할것 같습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git merge --no-ff work1
</code></pre></div></div>
<p>위와 같이 merge하게 되면 아래 그림과 같이 예쁘게 merge가 됩니다.</p>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-7.png" alt="status7" /></p>
<p>work2도 같은 방식으로 merge하면 최종적으로 아래와 같이 보기 좋게 merge됩니다.</p>
<p><img src="/assets/img/git/git-cherrypick-rebase/git-cherrypick-rebase-8.png" alt="status8" /></p>
Git 저장소 생성(init) 및 복사(clone)
2017-01-28T00:00:00+00:00
2017-01-28T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/28/git-init-clone
<h2 id="git-init">git init</h2>
<p>저장소에 필요한 Seleton파일을 가지고 있는 “.git”이라는 하위 디렉토리를 만들어줍니다.<br />
<em>※ 아직 프로젝트의 어떤한 파일도 관리하지 않는 상태이며 commit, add등의 명령어로 파일을 추가해야합니다.</em></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git init // 현재 디렉토리에 .git 폴더를 생성합니다.
$ git init work1 // ./work1 디렉토리에 .git 폴더를 생성합니다.
</code></pre></div></div>
<h3 id="bare">–bare</h3>
<p>Bare저장소를 생성합니다. <br />
Bare저장소는 워킹디렉토리가 없는 저장소로 디렉토리는 관례에 따라 .git확장자로 끝납니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git init --bare <project_name>.git
</code></pre></div></div>
<h2 id="git-clone-bare-src-project-bare-project">git clone –bare (src-project) (bare-project)</h2>
<p>이미 <code class="language-plaintext highlighter-rouge">git init</code>을 통해 이미 관리되고 있는 프로젝트의 Bare저장소로 만들고 싶을 경우 아래와 같이 사용할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone --bare test test_bare.git
$ ls ./test_bare
config description HEAD hooks/ info/ objects/ packed-refs refs/
</code></pre></div></div>
<h2 id="git-clone-path-project-name">git clone (path) [project-name]</h2>
<p>Bare저장소로부터 프로젝트를 로컬에 복사하고 싶을 경우 아래와 같이 사용할 수 있습니다. <br />
(path)는 파일 경로도 가능하고 URL경로도 가능합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone ./test_bare.git test
$ git clone file://d/work/test_bare.git test
$ git clone https://github.com/jistol/jistol.github.io jistol-project
$ git clone git://127.0.0.1:7777/jistol/jistol.github.io jistol-project
$ git clone ssh://127.0.0.1:22/jistol/jistol.github.io jistol-project
</code></pre></div></div>
<h2 id="git-clone--b-branch-name-path-project-name">git clone -b (branch-name) (path) [project-name]</h2>
<p>Bare저장소의 특정 branch를 로컬에 복사하고 싶을 경우 아래와 같이 사용할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone -b work1 file://d/work/test_bare.git localwork1
</code></pre></div></div>
Git branch 주요 명령어 정리
2017-01-27T00:00:00+00:00
2017-01-27T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/27/git-branch
<p><code class="language-plaintext highlighter-rouge">git branch</code>명령은 branch 생성및 제거, 확인등의 기능을 하는 명령어로 주요 명령어만 요약하였습니다. <br />
자세한 내용은 <a href="https://git-scm.com/book/ko/v1/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EA%B4%80%EB%A6%AC">git-scm Git-브랜치-브랜치-관리</a>에서 확인하세요.</p>
<h2 id="git-branch--l">git branch [-l]</h2>
<p>로컬 branch 정보를 보여줍니다. (-l 옵션은 생략가능)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch
* master
work1
</code></pre></div></div>
<h2 id="git-branch--v">git branch -v</h2>
<p>로컬 branch의 정보를 마지막 커밋 내역과 함께 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -v
* master 4bbc62f commit message 'm1'
work1 fe7f049 commit message 'w1'
work_new 4bbc62f commit message 'work_new1'
</code></pre></div></div>
<h2 id="git-branch--r">git branch -r</h2>
<p>리모트 저장소의 branch 정보를 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -r
origin/master
origin/work1
</code></pre></div></div>
<h2 id="git-branch--a">git branch -a</h2>
<p>로컬/리모트 저장소의 모든 branch 정보를 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -a
* master
work1
remotes/origin/master
remotes/origin/work1
</code></pre></div></div>
<h2 id="git-branch-이름">git branch (이름)</h2>
<p>로컬에 새로운 branch를 생성합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch work_new
$ git branch -a
* master
work1
work_new
remotes/origin/master
remotes/origin/work1
</code></pre></div></div>
<p>※ 생성과 동시에 해당 branch로 이동하려면 아래 명령어를 사용합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout -b work2
</code></pre></div></div>
<p>※ 원격에 있는 branch를 가져오려면 아래 명령어를 사용합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[특정이름 지정시]
$ git checkout -b work2 origin/work2
[원격 branch 이름을 그대로 사용할 경우]
$ git checkout -t origin/work2
</code></pre></div></div>
<h2 id="git-branch-merged--no-merged">git branch (–merged | –no-merged)</h2>
<p><code class="language-plaintext highlighter-rouge">--merged</code>는 이미 merge된 branch를 표시해주고 <code class="language-plaintext highlighter-rouge">--no-merged</code>는 아직 merge가 되지 않은 branch만 표시합니다. <br />
<code class="language-plaintext highlighter-rouge">--merged</code>에 branch 목록 이미 merge되었기 때문에 *가 표시되지 않은 branch는 삭제 가능합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch --merged
* master
work_new
work_old
$ git branch --no-merged
work1
work2
</code></pre></div></div>
<h2 id="git-branch--d-branch-이름">git branch -d (branch 이름)</h2>
<p>branch를 삭제합니다. 아직 merge하지 않은 커밋을 담고 있는 경우 삭제되지 않습니다.(강제종료 옵션 <code class="language-plaintext highlighter-rouge">-D</code>으로만 삭제 가능)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -d work3
error: The branch 'work3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D work3'.
$ git branch -d work_new
Deleted branch work_new (was 4bbc62f).
</code></pre></div></div>
<h2 id="git-branch--m-변경할-branch이름-변경될-branch이름">git branch -m (변경할 branch이름) (변경될 branch이름)</h2>
<p>A 브랜치를 B 브랜치로 변경합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch -v
* master 4bbc62f m1
work2 c728ddc w2
work_old 4bbc62f m1
$ git branch -m work2 work3
$ git branch -v
* master 4bbc62f m1
work3 c728ddc w2
work_old 4bbc62f m1
</code></pre></div></div>
<p>※ -M 옵션을 사용할 경우 기존에 동일한 이름의 branch가 있더라도 덮어씁니다.</p>
Git Remote 주요 명령어 정리
2017-01-26T00:00:00+00:00
2017-01-26T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/26/git-remote
<p><code class="language-plaintext highlighter-rouge">git remote</code>명령은 프로젝트의 리모트 저장소를 관리하는 명령어로 주요 명령어만 요약하였습니다. <br />
자세한 내용은 <a href="https://git-scm.com/book/ko/v1/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%A6%AC%EB%AA%A8%ED%8A%B8-%EB%B8%8C%EB%9E%9C%EC%B9%98">git-scm Git-브랜치-리모트-브랜치</a>에서 확인하세요.</p>
<h2 id="git-remote">git remote</h2>
<p>등록된 리모트 저장소 이름만 보여줍니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git remote
origin
</code></pre></div></div>
<h2 id="git-remote--v">git remote -v</h2>
<p>등록된 저장소 이름과 URL을 표시합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git remote -v
origin D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git (fetch)
origin D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git (push)
</code></pre></div></div>
<h2 id="git-remote-add-리모트이름-경로">git remote add (리모트이름) (경로)</h2>
<p>새 리모트를 추가합니다. (경로)영역에는 URL이나 파일경로를 넣을수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git remote add jistol https://github.com/jisto.github.io
$ git remote add origin D:/dropbox/Dropbox/jekyll/git-source/test.git
$ git remote -v
jistol https://github.com/jisto.github.io (fetch)
jistol https://github.com/jisto.github.io (push)
origin D:/dropbox/Dropbox/jekyll/git-source/test.git (fetch)
origin D:/dropbox/Dropbox/jekyll/git-source/test.git (push)
</code></pre></div></div>
<h2 id="git-remote-show-리모트이름">git remote show (리모트이름)</h2>
<p>모든 리모트 경로의 branch와 정보를 표시합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git remote show origin
* remote origin
Fetch URL: D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git
Push URL: D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git
HEAD branch: master
Remote branches:
master tracked
work1 tracked
Local branches configured for 'git pull':
master merges with remote master
work1 merges with remote work1
Local refs configured for 'git push':
master pushes to master (up to date)
work1 pushes to work1 (up to date)
</code></pre></div></div>
<h2 id="git-remote-rm-리모트이름">git remote rm (리모트이름)</h2>
<p>리모트 경로를 제거합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git remote -v
jistol https://github.com/jistol/jistol.github.git (fetch)
jistol https://github.com/jistol/jistol.github.git (push)
origin D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git (fetch)
origin D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git (push)
$ git remote rm jistol
$ git remote -v
origin D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git (fetch)
origin D:/dropbox/Dropbox/jekyll/git-work/user1/../../git-source/test.git (push)
</code></pre></div></div>
Git GUI 툴 추천 - GitKraken
2017-01-25T00:00:00+00:00
2017-01-25T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/25/git-guitools-kraken
<p>Git에 대해 공부하면서 대부분 bash에서 실행했으나 전체 commit tree를 보기 위해서는 GUI툴의 도움을 받아야 했는데 처음 사용한 툴이 Atlassian의 SourceTree였습니다.
사용하다보니 무한 로딩에 짜증나고 종종 먹통이 되며 특히 reset명령을 하면 갱신이 멈춰버렸습니다. <br />
그런 와중에 다른 무료툴을 살펴보다 발견한것이 GitKraken입니다.</p>
<p><img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-1.png" alt="capture1" /></p>
<p>이 툴을 추천하는 이유는 아래와 같습니다.</p>
<ol>
<li>
<p>갱신 속도가 빠릅니다. <br />
SourceTree에 비해 엄청나게 이력 갱신이 빠릅니다. 로딩바도 없고 신세계!!</p>
</li>
<li>
<p>멀티 저장소/Branch 선택 기능 제공
저장소 선택 가능하고 <br />
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-2.png" alt="capture2" /> <br />
저장소별 branch도 선택 가능합니다. <br />
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-3.png" alt="capture3" /></p>
</li>
<li>
<p>Drag and Drop을 통한 Merge & Rebase <br />
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-4.png" alt="capture4" /> <br />
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-5.png" alt="capture5" /> <br />
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-6.png" alt="capture6" /></p>
</li>
<li>
<p>간편한 Undo / Redo 기능 <br />
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-7.png" alt="capture7" /></p>
</li>
<li>
<p>Dark계열의 디자인 <br />
IntelliJ Darcula Theme, Atom과 나란히 쓰기 좋네요~ <br />
어두운 계열 에디터 쓰기 좋아하시는 분들에게 추천합니다.
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-8.png" alt="capture8" /></p>
</li>
<li>
<p>그 외 웬만한 기능을 다 지원(Pull, Push, Branch, 변경소스 확인 …)
<img src="/assets/img/git/git-guitools-kraken/git-guitools-kraken-9.png" alt="capture9" /></p>
</li>
</ol>
<p>무료버전외에 돈주고 유료로도 사용 가능하지만 충분히 무료로도 쓰기 편합니다.</p>
Git branch + merge 사용하기 예제
2017-01-24T00:00:00+00:00
2017-01-24T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/24/git-branch-merge-exam
<p>Git의 branch 및 merge에 대해 공부한 내용을 예제를 통해 설명하고 요약해봅니다.(With SourceTree) <br />
잘 설명된 원본 내용은 아래 링크를 참고하세요 <br />
<a href="https://git-scm.com/book/ko/v1/Git-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EB%B8%8C%EB%9E%9C%EC%B9%98%EC%99%80-Merge%EC%9D%98-%EA%B8%B0%EC%B4%88">Git 브랜치 - 브랜치와 Merge의 기초</a></p>
<h2 id="branch-생성">Branch 생성</h2>
<p>보통 프로그램의 별도 기능을 작성하거나, Bug,Issue 등을 따로 처리할 경우 사용하게 됩니다.</p>
<p>예를 들어 다음과 같이 생성된 프로젝트가 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git init
$ echo 'v1' > origin.txt
$ git add .
$ git commit -m 'v1'
</code></pre></div></div>
<p>SourceTree를 통해 그래프를 보면 master노드에 ‘v1’ 내용을 담은 origin.txt 파일이 존재합니다.</p>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-1.png" alt="step1" /></p>
<p>신규기능인 feature.txt 개발을 위해 branch를 생성후 파일을 만들어 commit합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git branch feature1
$ git checkout feature1
$ echo 'f1' > feature.txt
$ git add .
$ git commit -m 'f1'
</code></pre></div></div>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-2.png" alt="step2" /></p>
<p>위와 같이 feature1이라는 branch가 생성되고 feature.txt가 ‘f1’내용으로 개발된 것을 볼 수 있습니다. <br />
다시 master 노드로 돌아가 origin.txt를 ‘v2’로 변경해 봅니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout master
$ echo 'v2' > origin.txt
$ git commit -a -m 'v2'
</code></pre></div></div>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-3.png" alt="step3" /></p>
<p>이제 좀 더 branch 가지가 분명하게 보이기 시작했습니다.</p>
<h2 id="master노드에-merge">Master노드에 Merge</h2>
<p>feature1 branch의 기능 개발이 끝났으니 master에 merge 해보도록 하겠습니다.
master에 feature1 branch 내용을 가져올 예정이니 실행은 master 노드에서 해야합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout master
$ git merge feature1 -m 'merge f1'
</code></pre></div></div>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-4.png" alt="step4" /></p>
<p>위와 같이 master노드와 feature1 branch노드가 merge되었습니다.</p>
<h2 id="reset으로-되돌리기">Reset으로 되돌리기</h2>
<p>이번엔 노드간 충돌예제를 만들어 보기 위해 merge전으로 다시 돌아가 보겠습니다. <br />
reset 명령을 사용합니다. reset관련 정보는 아래 링크를 참고하세요 <br />
<a href="https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-Reset-%EB%AA%85%ED%99%95%ED%9E%88-%EC%95%8C%EA%B3%A0-%EA%B0%80%EA%B8%B0">Git 도구 - Reset 명확히 알고 가기</a></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git reflog
8ee26d8 HEAD@{0}: merge feature1: Merge made by the 'recursive' strategy.
9e75da8 HEAD@{1}: commit: v2
23fa160 HEAD@{2}: checkout: moving from feature1 to master
73aa9b3 HEAD@{3}: commit: f1
23fa160 HEAD@{4}: checkout: moving from master to feature1
23fa160 HEAD@{5}: checkout: moving from feature1 to master
23fa160 HEAD@{6}: checkout: moving from master to feature1
23fa160 HEAD@{7}: commit (initial): v1
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">git reflog</code>명령을 통해 히스토리를 확인할 수 있습니다. ‘v2’를 commit한 ‘HEAD@{2}’ 시점으로 돌아가 보겠습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git reset --hard HEAD@{2}
</code></pre></div></div>
<p>reset의 옵션은 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">옵션</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">–soft</td>
<td style="text-align: left">Repository이력만 되돌립니다.</td>
</tr>
<tr>
<td style="text-align: center">[–mixed]</td>
<td style="text-align: left">Stage영역까지만 되돌립니다.</td>
</tr>
<tr>
<td style="text-align: center">–hard</td>
<td style="text-align: left">Working Directory까지 되돌립니다.</td>
</tr>
</tbody>
</table>
<p>위와 같이 hard옵션으로 되돌리고 SourceTree를 확인해보면 다시 merge전으로 돌아온 것을 확인할 수 있습니다.</p>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-5.png" alt="step5" /></p>
<h2 id="merge-충돌-해결하기">Merge 충돌 해결하기</h2>
<p>master노드와 feature1 branch노드간의 충돌을 만들기 위해 origin.txt파일을 수정해보겠습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout feature1
$ echo 'v2-f1' > origin.txt
$ git commit -a -m 'conflict origin.txt'
</code></pre></div></div>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-6.png" alt="step6" /></p>
<p>master노드와 feature1 branch노드의 origin.txt파일이 각각 달라졌습니다. 이제 merge를 합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git checkout master
$ git merge feature1 -m 'conflict merge'
Auto-merging origin.txt
CONFLICT (content): Merge conflict in origin.txt
Automatic merge failed; fix conflicts and then commit the result.
</code></pre></div></div>
<p>origin.txt파일의 내용을 merging하다가 충돌이 나있는 상태입니다.</p>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-7.png" alt="step7" /></p>
<p>충돌난 내용은 위와 같이 구분하여 표시되고 위 내용을 알맞는 값으로 변경한 후 다시 commit하면 merge가 완료됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git add .
$ git commit -m 'finished merge'
</code></pre></div></div>
<p><img src="/assets/img/git/git-branch-merge-exam/git-branch-merge-8.png" alt="step8" /></p>
Git 기초
2017-01-23T00:00:00+00:00
2017-01-23T00:00:00+00:00
https://github.com/jistol/vcs/2017/01/23/git-basic
<p>Git의 기초에 대해 정말 잘 설명되있는 페이지를 읽고 요약만 정리하고자 합니다. 원본은 아래링크를 참고하세요. <br />
<a href="https://git-scm.com/book/ko/v1/%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-Git-%EA%B8%B0%EC%B4%88">시작하기-Git-기초</a></p>
<h2 id="svn-vs-git">SVN vs Git</h2>
<p>SVN은 Commit 시점에 변화된 파일만 관리합니다. <br />
<img src="/assets/img/git/git-basic/git-basic-1.png" alt="SVN-Flow" /></p>
<p>Git은 Commit 시점의 스냅샷을 관리합니다. 변화되지 않은 파일도 해당 스냅샷의 링크로 저장합니다.
<img src="/assets/img/git/git-basic/git-basic-2.png" alt="Git-Flow" /></p>
<h2 id="git-특징">Git 특징</h2>
<ul>
<li>거의 모든 명령을 로컬에서 실행합니다.</li>
<li>데이터를 저장전 SHA-1 해시를 구하고 그 체크섬으로 데이터를 관리합니다.(무결성)</li>
<li>Commit한 데이터는 추가하기만 하고 삭제하지 않습니다.</li>
</ul>
<h2 id="git의-저장구조-상태-상태전이">Git의 저장구조, 상태, 상태전이</h2>
<p>git 명령어에 따른 상태전이표입니다.</p>
<p><img src="/assets/img/git/git-basic/git-basic-3.png" alt="Git-Structure" /></p>
<table>
<thead>
<tr>
<th style="text-align: center">상태</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">Modified</td>
<td style="text-align: left">작업 폴더에서 수정후 Stage에 올리지 않은 상태</td>
</tr>
<tr>
<td style="text-align: center">Staged</td>
<td style="text-align: left">수정한 파일을 Stage에 올려 Commit할 스냅샷을 만든 상태</td>
</tr>
<tr>
<td style="text-align: center">Committed</td>
<td style="text-align: left">Stage의 파일을 git 저장소에 commit한 상태</td>
</tr>
</tbody>
</table>
<h2 id="예제">예제</h2>
<p>아래 명령어를 통해 두 파일을 저장소에 만들었습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo 'v1' > 1.txt | echo 'v1' > 2.txt
$ git add 1.txt
$ echo 'v2' > 1.txt
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">git status</code>로 현재 저장소의 상태를 볼 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: 1.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: 1.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
2.txt
</code></pre></div></div>
<p>1.txt는 새로 생성되어 ‘v1’이란 내용으로 stage에 올라가 있는 상태이고 로컬엔 ‘v2’로 수정되었습니다. <br />
2.txt는 새로 생성되어 아직 로컬에만 있는 상태로 ‘Untracked files’로 표시됩니다.</p>
<p>위 상태에서 stage의 1.txt파일을 unstage하려면 <code class="language-plaintext highlighter-rouge">git reset HEAD 1.txt</code>로 실행가능합니다. <br />
위 상태에서 1.txt의 내용을 다시 ‘v1’으로 돌리려면 <code class="language-plaintext highlighter-rouge">git checkout 1.txt</code>로 실행가능합니다.</p>
<p>1.txt를 commit하고 나머지 파일을 stage에 올린 후 1.txt를 다시 수정해보겠습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git commit -m '1.txt commit'
$ git add .
$ echo 'v3' > 1.txt
</code></pre></div></div>
<p>다시 상태를 보면 아래와 같습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: 1.txt
new file: 2.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: 1.txt
</code></pre></div></div>
<p>현재 1.txt의 각 내용은 저장소별로 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">Working Directory</th>
<th style="text-align: center">Staging Area</th>
<th style="text-align: center">Git Repository</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">v3</td>
<td style="text-align: center">v2</td>
<td style="text-align: center">v1</td>
</tr>
</tbody>
</table>
<p><code class="language-plaintext highlighter-rouge">git diff</code> 명령을 통해 아래와 같이 확인할 수 있습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git diff 1.txt
diff --git a/1.txt b/1.txt
index 8c1384d..29ef827 100644
--- a/1.txt
+++ b/1.txt
@@ -1 +1 @@
-v2
+v3
warning: LF will be replaced by CRLF in 1.txt.
The file will have its original line endings in your working directory.
$ git diff HEAD 1.txt
diff --git a/1.txt b/1.txt
index 626799f..29ef827 100644
--- a/1.txt
+++ b/1.txt
@@ -1 +1 @@
-v1
+v3
warning: LF will be replaced by CRLF in 1.txt.
The file will have its original line endings in your working directory.
</code></pre></div></div>
(vi 명령어) 알아두면 유용한 set 명령어
2017-01-22T00:00:00+00:00
2017-01-22T00:00:00+00:00
https://github.com/jistol/linux/2017/01/22/vim-cmd-favorite-set-list
<p>vi 편집기 사용시 알아두면 좋을 set 명령어를 요약해 보았습니다. <br />
자주 쓰는 명령어는 계정의 home 디렉토리에 “_vimrc” 라는 파일을 만들고 set 옵션 내용을 저장하면 vi편집기 실행시마다 자동으로 적용됩니다.</p>
<table>
<thead>
<tr>
<th style="text-align: left">명령어</th>
<th style="text-align: left">축약</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set ignorecase</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set ic</code></td>
<td style="text-align: left">검색/치환시 대소문자를 구분하지 않습니다.</td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set number</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set nu</code></td>
<td style="text-align: left">문서에 줄 번호를 보여줍니다.</td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set showmatch</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set sm</code></td>
<td style="text-align: left">괄호 입력시 자동으로 대응되는 괄호를 표시해줍니다.</td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set autoindent</code></td>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set ai</code></td>
<td style="text-align: left">자동으로 들여쓰기를 합니다.</td>
</tr>
<tr>
<td style="text-align: left"><code class="language-plaintext highlighter-rouge">:set hlsearch</code></td>
<td style="text-align: left"> </td>
<td style="text-align: left">검색한 단어를 하이라이팅 합니다.</td>
</tr>
</tbody>
</table>
(vi 명령어) 문자열 치환하기
2017-01-21T00:00:00+00:00
2017-01-21T00:00:00+00:00
https://github.com/jistol/linux/2017/01/21/vim-cmd-replace
<p>세미콜론을 입력 후 아래 명령어를 통해 내용 치환이 가능합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>:[range]s/old/new/[option]
</code></pre></div></div>
<p>위 영역별 옵션은 아래와 같습니다.</p>
<h2 id="range">range</h2>
<table>
<thead>
<tr>
<th>커맨드</th>
<th>설명</th>
<th>예제</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">s</code></td>
<td>현재 행에서 치환</td>
<td><code class="language-plaintext highlighter-rouge">:s/old/new/</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">%s</code></td>
<td>모든 행에서 치환</td>
<td><code class="language-plaintext highlighter-rouge">:%s/old/new/</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">.,$s</code></td>
<td>현재부터 끝까지 치환</td>
<td><code class="language-plaintext highlighter-rouge">:.,$s/old/new/</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">D1,D2s</code></td>
<td>D1 ~ D2행까지 치환</td>
<td><code class="language-plaintext highlighter-rouge">:10,20s/old/new/</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">-N,+Ms</code></td>
<td>현재 커서위치 기준으로 -N ~ +M행까지 치환</td>
<td><code class="language-plaintext highlighter-rouge">:-3,+5s/old/new/</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">g/pattern/s</code></td>
<td>pattern에 해당하는 모든 행을 치환</td>
<td><code class="language-plaintext highlighter-rouge">:g/veryold/s/old/new/</code></td>
</tr>
</tbody>
</table>
<p>old 영역은 정규표현식을 사용가능하며 new영역은 바뀔 내용을 씁니다.</p>
<h2 id="old">old</h2>
<p>vi에서 지원하는 old영역 정규표현식은 아래와 같습니다. <br />
<em>※ vim에서는 좀더 많은 정규표현식을 지원합니다.</em></p>
<table>
<thead>
<tr>
<th style="text-align: center">표현식</th>
<th>기능</th>
<th>예제코드</th>
<th>변경케이스</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">.</code></td>
<td>문자 하나</td>
<td><code class="language-plaintext highlighter-rouge">:s/t.e/XXX/</code></td>
<td>the -> XXX<br /> there -> XXXer</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">*</code></td>
<td>0개이상 문자</td>
<td><code class="language-plaintext highlighter-rouge">:s/t*e/XXX/</code></td>
<td>the -> XXX<br /> test -> XXXst</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">^</code></td>
<td>시작</td>
<td><code class="language-plaintext highlighter-rouge">:s/^The/XXX/</code></td>
<td>The test -> XXX test<br /> test The -> test The</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">$</code></td>
<td>끝</td>
<td><code class="language-plaintext highlighter-rouge">:s/$The/XXX/</code></td>
<td>The test -> The test<br /> test The -> test XXX</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\</code></td>
<td>escape 문자</td>
<td><code class="language-plaintext highlighter-rouge">:s/\[test\]/XXX/</code></td>
<td>[test] -> XXX</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">[]</code></td>
<td>대괄호 안의 문자중 하나</td>
<td><code class="language-plaintext highlighter-rouge">:s/[a-z]*1/XXX/</code></td>
<td>test1 -> XXX</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\{n,m\}</code></td>
<td>문자 반복횟수가 n ~ m개인 가능한 많은 문자와 매칭<br /><code class="language-plaintext highlighter-rouge">\{n,\}</code> <code class="language-plaintext highlighter-rouge">\{,m\}</code> 과 같이도 사용 가능</td>
<td><code class="language-plaintext highlighter-rouge">:s/b\{2,3\}/x/g</code></td>
<td>ababbabbba -> abaxxaxxxa</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\{-n,m\}</code></td>
<td>문자 반복횟수가 n ~ m개인 가능한 적은 문자와 매칭<br /><code class="language-plaintext highlighter-rouge">\{n,\}</code> <code class="language-plaintext highlighter-rouge">\{,m\}</code> 과 같이도 사용 가능</td>
<td><code class="language-plaintext highlighter-rouge">:s/b\{2,3\}/x/g</code></td>
<td>ababbabbba -> abaxxaxxxa</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\(\)</code></td>
<td>괄호안의 패턴을 1~9까지 버퍼에 저장, new영역에서 사용</td>
<td><code class="language-plaintext highlighter-rouge">:s/\(aaa\)\(bbb\)/\2\1/</code></td>
<td>aaabbb -> bbbaaa</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\<\></code></td>
<td>문자의 앞뒤를 매칭 시킴</td>
<td><code class="language-plaintext highlighter-rouge">:s/\<The\>/XXX/</code></td>
<td>The There -> XXX There</td>
</tr>
</tbody>
</table>
<blockquote>
<p><code class="language-plaintext highlighter-rouge">\{n,m\}</code>과 <code class="language-plaintext highlighter-rouge">\{-n,m\}</code>의 차이는 가능한 많이 / 가능한 적게 적용하는 것입니다. <br />
예를 들어 <br />
x xx xxx xxxx xxxxx <br />
와 같은 문장을 치환 하면 아래와 같습니다. <br />
<code class="language-plaintext highlighter-rouge">:s/x\{2,\}/y/</code> -> x y y y y <br />
<code class="language-plaintext highlighter-rouge">:s/x\{-2,\}/y/</code> -> x y yx yy yyx</p>
</blockquote>
<h2 id="new">new</h2>
<p>new영역에서의 교체패턴은 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th style="text-align: center">표현식</th>
<th>기능</th>
<th>예제코드</th>
<th>변경케이스</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\d</code></td>
<td><code class="language-plaintext highlighter-rouge">\(\)</code>로 지정된 d번째 버퍼를 사용</td>
<td><code class="language-plaintext highlighter-rouge">:s/\(aaa\)\(bbb\)/\2\1/</code></td>
<td>aaabbb -> bbbaaa</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\</code></td>
<td>escape 문자</td>
<td><code class="language-plaintext highlighter-rouge">:s/test/\[test\]/</code></td>
<td>test -> [test]</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">&</code></td>
<td>찾기패턴</td>
<td><code class="language-plaintext highlighter-rouge">:s/test/_&_/</code></td>
<td>test -> <em>test</em></td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">~</code></td>
<td>이전 교체패턴을 사용</td>
<td><code class="language-plaintext highlighter-rouge">:s/aaa/xxx/</code><br /><code class="language-plaintext highlighter-rouge">:s/bbb/_~_/</code></td>
<td>aaabbb -> xxxbbb -> xxxxxx</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\u</code></td>
<td>교체패턴의 첫문자를 대문자로 변경</td>
<td><code class="language-plaintext highlighter-rouge">:s/aaa/\ubbb/</code></td>
<td>aaa -> Bbb</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\U</code></td>
<td>교체패턴의 모든문자를 대문자로 변경</td>
<td><code class="language-plaintext highlighter-rouge">:s/aaa/\ubbb/</code></td>
<td>aaa -> BBB</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\l</code></td>
<td>교체패턴의 첫문자를 소문자로 변경</td>
<td><code class="language-plaintext highlighter-rouge">:s/AAA/\uBBB/</code></td>
<td>AAA -> bBB</td>
</tr>
<tr>
<td style="text-align: center"><code class="language-plaintext highlighter-rouge">\L</code></td>
<td>교체패턴의 모든문자를 대문자로 변경</td>
<td><code class="language-plaintext highlighter-rouge">:s/AAA/\uBBB/</code></td>
<td>AAA -> bbb</td>
</tr>
</tbody>
</table>
<h2 id="option">option</h2>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td>g</td>
<td>한 줄 내의 모든 패턴 변경</td>
</tr>
<tr>
<td>i</td>
<td>대소문자 구분 안함</td>
</tr>
<tr>
<td>c</td>
<td>변경여부 확인</td>
</tr>
</tbody>
</table>
(vi 명령어) 라인번호(줄번호) 보기, 라인이동, 맨위/맨밑 이동
2017-01-20T00:00:00+00:00
2017-01-20T00:00:00+00:00
https://github.com/jistol/linux/2017/01/20/vim-cmd-line-move
<p>vi 편집기에서 라인을 자유롭게 이동하기 위한 몇 가지 커맨드들을 외워둡시다.</p>
<table>
<thead>
<tr>
<th>기능</th>
<th>커맨드</th>
</tr>
</thead>
<tbody>
<tr>
<td>라인번호(줄번호) 보기</td>
<td><code class="language-plaintext highlighter-rouge">:set number</code></td>
</tr>
<tr>
<td>라인이동</td>
<td>숫자입력후 <code class="language-plaintext highlighter-rouge">Shift + g</code></td>
</tr>
<tr>
<td>맨위 이동</td>
<td><code class="language-plaintext highlighter-rouge">gg</code></td>
</tr>
<tr>
<td>맨밑 이동</td>
<td><code class="language-plaintext highlighter-rouge">Shift + g</code>, <code class="language-plaintext highlighter-rouge">:$</code></td>
</tr>
</tbody>
</table>
<p><img src="/assets/img/vim/vim-cmd-line-move/1.png" alt="라인보기" /></p>
IFrame을 숨기는 방법
2017-01-19T00:00:00+00:00
2017-01-19T00:00:00+00:00
https://github.com/jistol/frontend/2017/01/19/iframe-hidden
<p>웹 작업시 내부적인 통신을 위해 iframe 숨겨서 사용하는 경우가 있습니다. <br />
이 때 iframe은 보여선 안되기 때문에 <code class="language-plaintext highlighter-rouge">width=0, height=0</code>으로 설정하는데 그럴 경우 크롬에서 아래 그림과 같이 여백으로 떠버리는 경우가 있습니다.</p>
<p><img src="/assets/img/frontend/iframe-hidden/1.png" alt="iframe이 공백으로 보이는 현상" /></p>
<p>그래서 추가하는 방법이 style에 <code class="language-plaintext highlighter-rouge">display:none;</code>을 추가하게 되는데 이 역시 다른 브라우저에서 문제를 일으킬 소지가 있습니다.</p>
<p><em>The iframes used to load test requests all have style=”display:none”. <br />
Firefox does not compute styles or perform certain other rendering tasks in elements that are display:none or are children of an element with display:none. <br />
Therefore, there are numerous test failures that are false negatives because the behavior in question isn’t being triggered. <br />
This problem can be easily resolved by using style=”visibility:hidden” instead.</em></p>
<p><code class="language-plaintext highlighter-rouge">display:none;</code>으로 할 경우 iframe이 정상 로딩 되지 않을수 있다는 점인데, 이를 해결하기 위해 <code class="language-plaintext highlighter-rouge">display:none;</code> 대신 <code class="language-plaintext highlighter-rouge">visibility:hidden;</code>을 사용하면 됩니다.</p>
<p>iframe을 숨기기 위해 최종적으로 아래와 같은 방법으로 할 수 있습니다.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"> <span class="nx">iframe</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">style</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">height:0;width:0;border:0;border:none;visibility:hidden;</span><span class="dl">'</span><span class="p">);</span>
</code></pre></figure>
Agile 방법론 정리
2017-01-18T00:00:00+00:00
2017-01-18T00:00:00+00:00
https://github.com/jistol/software%20engineering/2017/01/18/agile
<p>Agile 방법론에 관해 공부한 내용에 대해 정리한 문서입니다.</p>
<h1 id="1-배경">1. 배경</h1>
<ul>
<li><a href="https://ko.wikipedia.org/wiki/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4_%EC%9C%84%EA%B8%B0">소프트웨어 위기</a>의 원인과 해결방안을 찾음</li>
<li>변화하는 요구사항과 측정하기 힘든 작업량</li>
<li>기술적인 해결책으로 객체지향 등장, 그리고 객체지향 개발을 하기 위한 적합한 개발 프로세스 필요</li>
</ul>
<h1 id="2-waterfall의-문제점">2. Waterfall의 문제점</h1>
<ul>
<li>수행단계가 명확하게 나뉘어 있어, 앞선 파트가 끝나야만 다음 단계 실행가능</li>
<li>요구사항에 맞게 개발되고 있는지 개발 후반부가 되어야 확인 가능함</li>
<li>하위 단계 수행중 상위 단계(기획/요구사항등…)에서 수정될 경우 영향도가 매우 큼
<blockquote>
<p>가장 큰 문제는 요구사항을 내는 고객도 실체가 보이기 전까진 완벽히 원하는 것을 내놓기 힘들다는 것입니다.</p>
</blockquote>
</li>
</ul>
<h1 id="3-agile-이란">3. Agile 이란</h1>
<p>Agile방법론의 기존 방법론과 가장 큰 차이점은 사상과 철학에 있습니다.</p>
<h3 id="agile-선언문">Agile 선언문</h3>
<ul>
<li>공정과 도구보다 <strong>개인과 상호작용</strong>을</li>
<li>포괄적인 문서보다 <strong>작동하는 소프트웨어</strong>를</li>
<li>계약 협상보다 <strong>고객과의 협력</strong>을</li>
<li>계획을 따르기보다 <strong>변화에 대응</strong>하기를</li>
</ul>
<h3 id="어쩔수-없는-부분">어쩔수 없는 부분</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>고객의 요구사항은 바뀝니다.
한번에 제대로 할 수 없습니다.
개발 기간이 길수록 변경사항이 발생할 확률이 높습니다.
</code></pre></div></div>
<h3 id="해결-방안">해결 방안</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>짧은 개발기간으로 빠르게 실체를 만드며 지속적인 결과물을 전달합니다.
최대한 단순화 합니다.
고객과의 피드백/의사소통을 자주하여 바른 방향을 잡습니다.
개개인에게 동기부여하고 가장 효율을 낼 수 있는 환경을 만듭니다.
하나의 팀이 역활과 책임을 공유합니다.
</code></pre></div></div>
<h1 id="4-agile-종류">4. Agile 종류</h1>
<h2 id="extreme-programmingxp">eXtreme Programming(XP)</h2>
<table>
<thead>
<tr>
<th style="text-align: center">명칭</th>
<th style="text-align: left">설명</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center">Whole Team</td>
<td style="text-align: left">개발자외에 기획,고객,디자이너,테스터등 모든 사람을 팀원으로 구성합니다.<br />그 중 요구사항을 도출하는 고객이 가장 중요합니다.</td>
</tr>
<tr>
<td style="text-align: center">Planning Game</td>
<td style="text-align: left">이번 주기의 개발 범위를 결정하고 그 이후 개발 반복에서 무엇을 할것인가를 계획합니다.</td>
</tr>
<tr>
<td style="text-align: center">Customer Test</td>
<td style="text-align: left">의뢰인이 원했던 것과 다른지를 반복적으로 테스트 합니다.<br />짧은 주기별로 실체를 만들어 내기 때문에 가능.</td>
</tr>
<tr>
<td style="text-align: center">Small Release</td>
<td style="text-align: left">릴리즈 주기를 짧게 잡아 주기적으로 프로토타입을 제공합니다.</td>
</tr>
</tbody>
</table>
<p>XP를 지키기 위한 기법으로는 아래와 같습니다. <br />
<em>출처 : <a href="https://brunch.co.kr/@insuk/15">애자일 실천 사례 - XP편</a></em></p>
<ul>
<li>함께 앉기 : 커뮤니케이션 비용을 줄여줍니다.</li>
<li>페어프로그래밍 : 특정 사람에게 의존성이 생기는 것을 방지합니다.</li>
<li>사용자스토리 : 프로젝트에 참가하는 모든 사람이 이해하기 쉽습니다.</li>
<li>주단위 계획 : 작업계획의 크기를 2~3주 단위로 구성합니다.</li>
<li>휴식 : 집중력을 향상 시켜줍니다.</li>
<li>지속적 통합(CI) : 나중에 한번에 통합했다가는 날벼락을 맞을수 있습니다.</li>
<li>테스트 주도 프로그래밍(TDD)</li>
</ul>
<h2 id="scrum">Scrum</h2>
<p>스크럼은 제품책임자(PO), 스크럼마스터(SM), 그 외 모든 팀원으로 구성됩니다.</p>
<table>
<thead>
<tr>
<th>PO</th>
<th>SM</th>
</tr>
</thead>
<tbody>
<tr>
<td>어떤 순서로 제품이 개발되어야 하는지 결정<br />팀 외부의 이해관계 당사자들의 피드백을 필터링</td>
<td>팀원들의 이슈를 듣고 해결하는 주체</td>
</tr>
</tbody>
</table>
<p>스크럼은 아래와 같은 방식으로 진행됩니다.</p>
<table>
<thead>
<tr>
<th>순서</th>
<th>설명</th>
</tr>
</thead>
<tbody>
<tr>
<td>제품 백로그 작성</td>
<td>사용자 스토리를 수집하여 제품 개발할 항목을 설정</td>
</tr>
<tr>
<td>스프린트 계획 미팅</td>
<td>제품 백로그중 이번 스프린트에서 개발할 항목을 정하고 공수산정<br />위 과정에서 스프린트 백로그가 생성됩니다.<br />1~4주 사이로 기간을 정하고 개발을 수행합니다.</td>
</tr>
<tr>
<td>스프린트 개발 진행</td>
<td>매일 스크럼 미팅을 통해 작업 계획및 이슈사항을 공유합니다.<br />번다운 차트를 통하여 개발 진척사항을 관리합니다.</td>
</tr>
<tr>
<td>스프린트 회고</td>
<td>이번 스프린트의 장단점을 도출하여 다음 스프린트에 반영합니다.</td>
</tr>
<tr>
<td>릴리즈</td>
<td>1~N개의 스프린트를 돌고 제품을 릴리즈 합니다.</td>
</tr>
</tbody>
</table>
Bitnami를 이용한 Redmine + Agile 설치 (on Ubuntu)
2017-01-11T00:00:00+00:00
2017-01-11T00:00:00+00:00
https://github.com/jistol/its/2017/01/11/redmine-install-easy-ubuntu
<p>여분 PC가 생겨 어떻게 쓸까 고민하던중 사내에서 쓰는 Jira 대신 사이트 구축 나가서 쓸 IssueTracking 시스템을 설치해 연습삼아 써보기로 했습니다. <br />
그래서 선택한건 OpenSource중 가장 유명한 Redmine. <br />
하지만 설치하다가 지옥을 맛보기로도 유명하더군요 ㅠㅠ <br />
2~3시간 삽질하다가 Bitnami를 통해 쉽게 설치 가능함을 발견하고 10여분 만에 설치를 완료했습니다.</p>
<h2 id="bitnami">Bitnami</h2>
<p><a href="https://bitnami.com">Bitnami 바로가기</a></p>
<p>기존에 복잡하게 설치해야만 했던 환경을 단번에 구성해 줍니다. 심지어 각 OS별로 설치 할 수 있도록 제공 할 뿐만 아니라, Docker Container 및 Bitnami에서 제공하는 cloud환경에서 사용할 수도 있는것 같습니다. <br />
Applications메뉴를 눌러보면 다양한 설치할 수 있는 다양한 항목이 표시됩니다.</p>
<p><img src="/assets/img/its/redmine-install-easy-ubuntu/4.png" alt="Bitnami Applications Page" /></p>
<p>그 중에 설치할 항목인 “Redmine + Agile” 선택.</p>
<h2 id="redmine-install">Redmine Install</h2>
<p>설치할 파일을 다운로드하고 실행권한을 준 후 바로 실행하면 설치가 진행됩니다.</p>
<blockquote>
<p>Redmine 설치파일의 경우 비로그인 상태에서도 다운을 받을수 있어 wget명령어를 통해 바로 다운로드가 가능하나 Redmine+Agile 설치파일의 경우에는 반드시 로그인이 필요하여 파일을 다운받아 옮기도록 해야합니다.</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo chmod 775 ./redmineplusagile-3.3.1-1-linux-x64-installer.run
$ sudo ./redmineplusagile-3.3.1-1-linux-x64-installer.run
</code></pre></div></div>
<p>Windows Installer처럼 쉽게 선택만 하면 설치과정이 끝납니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Please select the installation language
[1] English - English
[2] Spanish - Español
[3] Japanese - 日本語
[4] Korean - 한국어
[5] Simplified Chinese - 简体中文
[6] Hebrew - עברית
[7] German - Deutsch
[8] Romanian - Română
[9] Russian - Русский
Please choose an option [1] : 4
</code></pre></div></div>
<p>설치를 진행할 언어를 선택합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>----------------------------------------------------------------------------
Redmine+Agile 설치 마법사를 시작합니다.
----------------------------------------------------------------------------
설치할 구성 요소를 선택하십시오. 설치하지 않을 구성 요소는 선택을 취소하십시오. 계속할 준비가 되면클릭하십시오.
Subversion [Y/n] :y
PhpMyAdmin [Y/n] :n
Redmine : Y (Cannot be edited)
Agile plugin : Y (Cannot be edited)
Git [Y/n] :y
위의 선택이 정확합니까? [Y/n]: y
</code></pre></div></div>
<p>같이 설치될 Plugin을 선택합니다. <br />
SVN, GIT을 같이 설치할 수 있어서 SCM연동을 별도로 해야하는 수고를 덜어주는군요</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>----------------------------------------------------------------------------
설치 경로
Redmine+Agile의 설치 경로를 선택하세요.
폴더 선택 [/opt/redmineplusagile-3.3.1-1]:
----------------------------------------------------------------------------
Admin 계정 생성
Redmine+Agile 관리자 계정을 생성합니다.
이름 [User Name]: jistol
이메일 주소 [user@example.com]: kimjh@spectra.co.kr
로그인 계정명 [user]: kimjh
패스워드 :
패스워드를 재입력 :
----------------------------------------------------------------------------
웹 서버 포트 번호
Choose a port that is not currently in use, such as port 81.
Apache 서버 포트 번호 [81]: 8080
----------------------------------------------------------------------------
MySQL 정보
MySQL 데이터베이스 정보를 입력하세요.
Choose a port that is not currently in use, such as port 3307.
MySQL 서버 포트 번호 [3307]:
</code></pre></div></div>
<p>웹 서버가 기존 Apache에서 80을 쓰고 있어 8080으로 설정했습니다</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>----------------------------------------------------------------------------
기본 데이터 설정에 사용할 언어
기본 데이터 설정 시 사용할 언어를 선택하세요.
[1] Bosnian
[2] 불가리아어
[3] Catalan
[4] 체코어
[5] Danish
[6] 독일어
[7] 영어
[8] 스페인어
[9] 프랑스어
[10] Galician
[11] 히브리어
[12] Hungarian
[13] 이탈리아어
[14] 일본어
[15] 한국어
[16] Lithuanian
[17] 네덜란드어
[18] Norwegian
[19] 폴란드어
[20] 포르투갈어
[21] 루마니아어
[22] 러시아어
[23] Slovak
[24] Slovenian
[25] 세르비아어
[26] 스웨덴어
[27] Turkish
[28] Ukrainian
[29] Vietnamese
[30] 중국어
옵션을 선택하십시오. [15] : 15
사용하시겠습니까? [y/N]: n
</code></pre></div></div>
<p>Redmine 기본 언어를 선택합니다.</p>
<p>언어 선택후 “사용하시겠습니까?”라는 질문이 나오는데 국문 설치시 당할수 있는 함정입니다. 저 선택은 언어를 선택하겠냐는 얘기가 아니라 SMTP메일 서버를 설정하여 메일을 발송 하겠냐는 얘기인데 국문 설치엔 앞뒤 다 짜르고 저렇게 나오네요.(영문에서는 설명이 잘 나옵니다.)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>----------------------------------------------------------------------------
이제 컴퓨터에 Redmine+Agile을(를) 설치할 준비가 되었습니다.
계속하시겠습니까? [Y/n]: y
----------------------------------------------------------------------------
컴퓨터에 Redmine+Agile을(를) 설치하는 동안 기다려 주십시오.
설치
0% ______________ 50% ______________ 100%
########################################
----------------------------------------------------------------------------
컴퓨터에 Redmine+Agile 설치를 완료했습니다.
Redmine 어플리케이션 구동 [Y/n]: Y
정보: To access the Redmine+Agile, go to
http://127.0.0.1:8080 from your browser.
계속하려면 [Enter] 키 누르기:
</code></pre></div></div>
<p>모든 설치가 끝나면 Redmine을 구동해주고 웹페이지에 접속하여 확인할 수 있습니다.</p>
<p><img src="/assets/img/its/redmine-install-easy-ubuntu/5.png" alt="설치완료 화면" /></p>
<p>관리화면에 가면 위와 같이 Agile Plugin이 추가되어 있는것을 확인할 수 있습니다. <br />
설치된 Agile버전은 무료버전인 Light버전으로 자세한 사항은 아래 링크에서 확인 가능합니다.</p>
<p><a href="https://www.redmineup.com/pages/plugins/agile">Redmineup - Agile 바로가기</a></p>
<h2 id="redmine-uninstall-하기">Redmine Uninstall 하기</h2>
<p>설치된 폴더에 가보면 uninstall 파일이 존재합니다. 해당 파일 실행하면 Uninstall됩니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kimjh@kimjh:/opt/redmineplusagile-3.3.1-1$ ls
README.txt ctlscript.sh perl sqlite
apache2 git php subversion
apps img postgresql uninstall
changelog.txt licenses properties.ini uninstall.dat
common manager-linux-x64.run ruby use_redmineplusagile
config mysql scripts
</code></pre></div></div>
GitHub Page 개발환경 구축하기
2017-01-09T00:00:00+00:00
2017-01-09T00:00:00+00:00
https://github.com/jistol/etc/2017/01/09/github-page-setup
<p>에버노트가 계정당 접속 제한을 두면서 너무 불편해저서 이 기회에 탈 에버노트겸 개발 관련 내용을 정리할 블로그를 찾던 중 GitHub Page에 대해 알게 되었고 이를 쓰기 위한 기본 환경 구축한 방법을 정리합니다.</p>
<h1 id="구성">구성</h1>
<ul>
<li>Windows 10 Home</li>
<li>Docker toolbox
<ul>
<li>Ubuntu</li>
<li>Jekyll</li>
</ul>
</li>
<li>Dropbox</li>
<li>Editor : Atom</li>
</ul>
<p>집 PC에 Linux를 설치 할 순 없고 Docker Windows용은 Windows 10 (Professional / Enterprise 64-bit) 부터 지원하기 때문에 하위 버전에서 사용 가능한 Docker-toolbox를 설치하고 Ubuntu를 띄워 Jekyll을 돌리기로 했습니다.<br />
블로그 소스는 Git으로 관리하면 되긴하지만 혹시나 다른 곳에서 안올리고 이동했을때 편하게 쓰려고 저장소를 Dropbox에 위치 시켰습니다. 그리고 Markdown 안드로이드 앱이 Dropbox 저장소 연동을 지원하기 때문에 PC없이 모바일에서 작성하고 서버에 올릴 수 있는 장점도 있습니다.</p>
<h1 id="설치">설치</h1>
<h2 id="dropbox-설치">Dropbox 설치</h2>
<p><a href="https://www.dropbox.com/">Dropbox 바로가기</a></p>
<p>Docker와의 공유설정을 쉽게 하려면 사용자 기본폴더 (“C:\User”)하위에 공유폴더를 위치 시키면 좋습니다.</p>
<h2 id="docker-toolbox-설치">Docker toolbox 설치</h2>
<p><a href="https://www.docker.com/products/docker-toolbox">Docker toolbox 바로가기</a></p>
<p>설치 후 공유폴더 설정을 진행합니다.</p>
<blockquote>
<p>docker-toolbox는 Oracle Virtual Box를 이용하기 때문에 공유폴더를 따로 설정해 두어야 Docker내/외부에서 파일 공유가 가능합니다.</p>
</blockquote>
<p>toolbox 설치시 “default” 이미지가 생성되며 VM관리자 화면에 접속하여 다음과 같이 공유폴더를 설정합니다.</p>
<p><img src="/assets/img/etc/github-page-setup/1.png" alt="VM관리자 설정-공유폴더" /></p>
<p>docker-machine 명령어를 통해 접속하여 해당 공유폴더를 mount합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker-machine ssh default
$ cd /var/lib/boot2docker/
$ sudo vi bootlocal.sh
#!/bin/sh
mkdir /dropbox
chmod 777 /dropbox
mount -t vboxsf dropbox /dropbox
</code></pre></div></div>
<ul>
<li>bootlocal.sh파일을 만들어 설정하면 machine 재시작시에도 공유폴더 설정이 날아가지 않습니다.</li>
<li>공유할 파일에 권한을 주지 않을 경우 Protocol Error를 반환합니다.</li>
<li>mount시 VM설정에서 설정한 공유폴더명을 그대로 써야 합니다.</li>
</ul>
<h2 id="ubuntu-image-다운로드">Ubuntu Image 다운로드</h2>
<p>docker-toolbox를 실행하고 아래 명령어를 통해 다운로드할 docker image를 찾습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker search ubuntu
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
ubuntu Ubuntu is a Debian-based Linux operating s... 5289 [OK]
ubuntu-upstart Upstart is an event-based replacement for ... 69 [OK]
rastasheep/ubuntu-sshd Dockerized SSH service, built on top of of... 61 [OK]
consol/ubuntu-xfce-vnc Ubuntu container with "headless" VNC sessi... 34 [OK]
</code></pre></div></div>
<p>여러가지 ubuntu버전을 찾을수 있는데 가장 기본 버전을 다운 받아 설치하도록 합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
b3e1c725a85f: Pull complete
4daad8bdde31: Pull complete
63fe8c0068a8: Pull complete
4a70713c436f: Pull complete
bd842a2105a8: Pull complete
Digest: sha256:7a64bc9c8843b0a8c8b8a7e4715b7615e4e1b0d8ca3c7e7a76ec8250899c397a
Status: Downloaded newer image for ubuntu:latest
</code></pre></div></div>
<blockquote>
<p>docker pull <이미지명>:<태그>
태그 미포함시 가장 최신버전을 다운받습니다.(latest)</태그></이미지명></p>
</blockquote>
<h2 id="docker-container-생성">Docker Container 생성</h2>
<p>다운 받은 이미지를 통해 실제 구동할 Docker Container를 생성합니다.<br />
생성 시 아래와 같은 설정을 추가합니다.</p>
<ul>
<li>jekyll을 통해 외부에서 웹페이지에 접근할 수 있어야 하므로 4000포트를 열어줍니다.</li>
<li>dropbox를 통해 파일을 업로드 할 수 있도록 공유폴더를 선택합니다.</li>
</ul>
<p>위 조건에 맞게 docker container를 생성하기 위해 아래와 같이 명령어를 실행합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker run -i -t -v /dropbox:/opt/dropbox -p 4000:4000 --name jekyll ubuntu /bin/bash
root@6ef6ea062b7a:/#
</code></pre></div></div>
<ul>
<li>Docker Container의 4000번 포트를 외부 환경으로 연결하기 위해 -p 옵션으로 설정합니다.</li>
<li>“VM - Docker”간 연결한 공유폴더(/dropbox)를 -v 옵션을 통해 Container에 연결해줍니다.</li>
</ul>
<blockquote>
<p>-i, –interactive=false: 표준 입력(stdin)을 활성화하며 컨테이너와 연결(attach)되어 있지 않더라도 표준 입력을 유지합니다. 보통 이 옵션을 사용하여 Bash에 명령을 입력합니다.<br />
-t, –tty=false: TTY 모드(pseudo-TTY)를 사용합니다. Bash를 사용하려면 이 옵션을 설정해야 합니다. 이 옵션을 설정하지 않으면 명령을 입력할 수는 있지만 셸이 표시되지 않습니다.<br />
-p, –publish=[]: 호스트에 연결된 컨테이너의 특정 포트를 외부에 노출합니다. 보통 웹 서버의 포트를 노출할 때 주로 사용합니다.<br />
-v 외부와 공유할 포트를 설정 합니다.</p>
</blockquote>
<h2 id="ubuntu-기본설정">Ubuntu 기본설정</h2>
<ol>
<li>
<p>jekyll을 운용할 계정을 추가합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ adduser [userid]
</code></pre></div> </div>
</li>
<li>
<p>기본적으로 사용할 패키지들을 추가합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ apt-get update
$ apt-get install net-tools sudo vim bzip2
</code></pre></div> </div>
</li>
<li>
<p>sudo 사용을 위해 아래와 같이 설정합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> chmod +w /etc/sudoers
vi /etc/sudoers
=> userid ALL=(ALL) NOPASSWD:ALL
chmod -w /etc/sudoers
</code></pre></div> </div>
</li>
</ol>
<h2 id="ruby-설치">Ruby 설치</h2>
<p>jekyll은 ruby로 작성된 프로그램입니다. jekyll을 띄우기 위해서는 ruby를 설치해야하는데 이 부분에서 가장 많이 헤맸네요. <br />
애초에 ruby설치법을 찾았으면 덜 헤맸을 것을 jekyll 설치 메뉴얼에 적힌 ruby설치법을 따라갔더니 계속 무언가 문제가 생겼었습니다.<br />
각 OS별 정확한 ruby 설치 방법은 GoRails에 친절하게 설명되어 있으니 참고 하시기 바랍니다.</p>
<p><a href="https://gorails.com/setup">Go Rails 바로가기</a><br />
<img src="/assets/img/etc/github-page-setup/2.png" alt="Go Rails Setup Page" /></p>
<p>설치방법들중 권장 방법인 rbenv를 이용하여 설치하였습니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ su userid
$ sudo apt-get install git-core curl zlib1g-dev build-essential libssl-dev libreadline-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt1-dev libcurl4-openssl-dev python-software-properties libffi-dev nodejs
$ cd
$ git clone git://github.com/sstephenson/rbenv.git .rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc
$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc
$ exec $SHELL
$ rbenv install 2.3.3
$ rbenv global 2.3.3
$ ruby -v
</code></pre></div></div>
<h2 id="bundler-jekyll-설치">bundler, jekyll 설치</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ gem install bundler
$ gem install jekyll
</code></pre></div></div>
<h2 id="jekyll-예제-서버-생성">jekyll 예제 서버 생성</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cd /opt/dropbox
$ jekyll new myblog
$ cd myblog
$ ifconfig // local ip 확인
$ jekyll serve -H [local-IP]
</code></pre></div></div>
<ul>
<li>jekyll 구동시 -H 으로 local IP를 지정하지 않을 경우 127.0.0.1로 구동하면서 외부 웹페이지에서 접근할 수 없습니다.</li>
<li>외부 연결 포트를 다르게 지정했을 경우 -P 옵션으로 변경 가능합니다.</li>
</ul>
<h2 id="pc-웹페이지에서-서버-동작-확인">PC 웹페이지에서 서버 동작 확인</h2>
<p>CMD창을 열고 docker-machine이 떠 있는 IP를 확인합니다.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default - virtualbox Running tcp://192.168.99.100:2376 v1.12.5
</code></pre></div></div>
<p>확인된 IP로 접속하면 아래와 같이 정상적으로 서버가 구동하고 있는것을 확인 할 수 있습니다.<br />
<img src="/assets/img/etc/github-page-setup/3.png" alt="Jekyll Server 구동화면" /></p>