tag:blogger.com,1999:blog-61227558086526798092024-02-20T01:17:29.011+09:00Divinespear's Librarium SanctorumKnowledge is power, Guard it well. <strike>Wargear is power, Steal it well.</strike>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.comBlogger22125tag:blogger.com,1999:blog-6122755808652679809.post-62628585947273513772019-07-19T07:01:00.001+09:002019-07-23T16:46:49.870+09:00vue-cli + nightwatch on docker 삽질기<a href="https://divinespear.github.io/tech/2019/07/19/vue-nightwatch-selenium-docker.html">https://divinespear.github.io/tech/2019/07/19/vue-nightwatch-selenium-docker.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-19076767822823267152019-04-12T12:28:00.000+09:002019-04-12T12:28:42.931+09:00흉악한 오라클 PARALLEL약 350만 건에 이르는 데이터가 들어있는 테이블의 데이터를 삭제할 일이 생겼다.<br />
특정 테이블에 값이 없는 레코드만 삭제를 해야 되는데 오라클 DELETE는 JOIN 그런거 없ㅋ어ㅋ...<br />
<br />
그래서 구글신을 영접해보니까 PARALLEL 힌트가 있드라고?<br />
해서 실험해봤습니다.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisM2bQ65UnCDW1wuyGOnaySuuD1sUekuCGmLZtaIX0f1CkGoNkr-JV1Wttr86h6rFuq4X4z8G98IzqEhJB5Ex9CMsSAlBpmhYgFA_8numFW7jeomWBnlnoLN8FNz8ciIs_BITTxVnk8lT8/s1600/Screenshot+from+2019-04-12+12-00-19.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="217" data-original-width="887" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisM2bQ65UnCDW1wuyGOnaySuuD1sUekuCGmLZtaIX0f1CkGoNkr-JV1Wttr86h6rFuq4X4z8G98IzqEhJB5Ex9CMsSAlBpmhYgFA_8numFW7jeomWBnlnoLN8FNz8ciIs_BITTxVnk8lT8/s1600/Screenshot+from+2019-04-12+12-00-19.png" /></a></div>
<br />
<br />
<br />
<br />
오늘의 슨슈들입니다.<br />
위에서부터 그냥 쿼리, DELETE에만 PARALLEL을 적용한 쿼리, 서브쿼리에도 PARALLEL을 적용한 쿼리 되시겠습니다.<br />
<br />
그냥 쿼리부터 보시겠습니다.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAMPQGKHAiLLKoL6k5H78eotXFvE_TRUOX8-CqtoeeKivuMbLhHTyC4No2mWrVm_5nLHBt9_LV1YXla9yOZtuUjbC17VvK2dvzhLkwyIPJacSx7wbhyv7Uci65sf3MWAuqsl0odSr3Q4Ui/s1600/Screenshot+from+2019-04-12+12-00-33.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="546" data-original-width="1137" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhAMPQGKHAiLLKoL6k5H78eotXFvE_TRUOX8-CqtoeeKivuMbLhHTyC4No2mWrVm_5nLHBt9_LV1YXla9yOZtuUjbC17VvK2dvzhLkwyIPJacSx7wbhyv7Uci65sf3MWAuqsl0odSr3Q4Ui/s1600/Screenshot+from+2019-04-12+12-00-33.png" /></a></div>
아 시바 할 말을 잊었습니다.<br />
저거 저대로 돌리면 최소 반나절이 증발합니다.<br />
<br />
그래서 한번 DELETE에 PARALLEL을 적용해 봤습니다.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIoFnJHZgRw2-OiSBj62xoaFtl-QoT4imkT0lOnp1_XgggSuthPJs18dnJQVYSmzj6FF3C52WFXz22pBFTrSpmfRBSm4YXKyMqWT8WYJUnVtkhf8Qrp0K-qPXbnyQe71g6Ta7TYDFB6hkz/s1600/Screenshot+from+2019-04-12+12-00-54.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="558" data-original-width="1262" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIoFnJHZgRw2-OiSBj62xoaFtl-QoT4imkT0lOnp1_XgggSuthPJs18dnJQVYSmzj6FF3C52WFXz22pBFTrSpmfRBSm4YXKyMqWT8WYJUnVtkhf8Qrp0K-qPXbnyQe71g6Ta7TYDFB6hkz/s1600/Screenshot+from+2019-04-12+12-00-54.png" /></a></div>
오 조금 나아졌네요.<br />
하지만 아직 부족합니다.<br />
<br />
여러 테이블에 걸쳐서 PARALLEL을 쓰려면 조금 귀찮은 단계를 거쳐야 합니다.<br />
하지만 성능은 확실하죠.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQH2zyHPunCi-9Qg6Of165sHui2iEthKVcXJC-0avLJ2s8SvLlSDQMuXAqd76w9L7NJqiYT1vQWxcqPb3asK6bmElDMDSVnuXd5fc_hmje8YaHSQjLmSQ05rJpEsGyzVnlVn0tqs2fx1Xi/s1600/Screenshot+from+2019-04-12+12-01-21.png" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" data-original-height="622" data-original-width="1231" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjQH2zyHPunCi-9Qg6Of165sHui2iEthKVcXJC-0avLJ2s8SvLlSDQMuXAqd76w9L7NJqiYT1vQWxcqPb3asK6bmElDMDSVnuXd5fc_hmje8YaHSQjLmSQ05rJpEsGyzVnlVn0tqs2fx1Xi/s1600/Screenshot+from+2019-04-12+12-01-21.png" /></a></div>
<span id="goog_1709020151"></span><span id="goog_1709020152"></span><br />
<br />
<br />
<br />
<br />
와아! COST가 쭈욱 내려가네요!<br />
마치 연쇄할인마 스팀을 보는 것 같습니다.<br />
<br />디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-20602546511236801462018-02-27T03:26:00.000+09:002019-07-23T18:05:53.582+09:00systemd로 파티션 마운트하기<a href="https://divinespear.github.io/tech/2018/02/27/systemd-mount.html">https://divinespear.github.io/tech/2018/02/27/systemd-mount.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-1447305308796434862017-10-02T00:51:00.000+09:002019-07-23T16:47:05.116+09:00내 사이트의 SSL/TLS 설정은 안녕하십네까?<a href="https://divinespear.github.io/tech/2017/10/02/ssl-tls-test.html">https://divinespear.github.io/tech/2017/10/02/ssl-tls-test.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-9529655327610870002017-09-21T14:39:00.004+09:002017-10-02T00:41:57.961+09:00mybatis 유감mybatis 쓰느니 그냥 SQL 날코딩해서 JDBC로 다이렉트 호출 할래...<br />
<br />
스프링이라면 JDBCTemplate이라는 거도 있고<br />
쿼리문 날코딩이 귀찮으면 querydsl로 메타데이터 뽑아서 쿼리를 코드로 짜도 되고...디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-74059090378661778232017-02-28T20:36:00.002+09:002017-03-07T22:48:46.125+09:00Hudson/Jenkins proxy with Nginx<div class="markdown-here-wrapper" data-md-url="https://www.blogger.com/blogger.g?blogID=6122755808652679809#editor/target=post;postID=7405909037866177823;onPublishedMenu=allposts;onClosedMenu=allposts;postNum=0;src=link">
허드슨이나 젠킨스를 맨날 단독으로 띄워서 쓰다가<br />
<a href="https://letsencrypt.org/" target="_blank">Let’s Encrypt</a>를 적용하면서 앞에다가 <a href="https://nginx.org/" target="_blank">nginx</a>를 두고 프록시로 요청을 받게 설정했다.<br />
<br />
그런데 자꾸 다음 메세지가 나면서 로그인할 때나 일부 링크가 엉뚱한 곳으로 간다.<br />
<pre><code class="hljs language-hljs">It appears that your reverse proxy set up is broken
</code></pre>
<br />
구글신의 신탁을 받아 이곳 저곳을 돌아봤는데 다 소용이 없었다.<br />
그래서 그냥 소스를 까볼까? 해서 봤더니…<br />
<a href="https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/model/Jenkins.java#L2330" target="_blank">소스 링크</a><br />
<br />
<code>X-Forwarded-Host</code>를 받고 계셨어요?<br />
<br />
그래서 nginx 설정에서 프록시 헤더를<br />
<pre><code class="hljs language-nginx"><span class="hljs-title">proxy_set_header</span> X-Forwarded-Host <span class="hljs-variable">$host</span>:<span class="hljs-variable">$server_port</span>;
<span class="hljs-title">proxy_set_header</span> X-Forwarded-Proto <span class="hljs-variable">$scheme</span>;
</code></pre>
이렇게 설정해주니까 잘 된다.<br />
<br />
역시 답 없을 때는 소스를 직접 들여다 보는게 답이네.<br />
<div style="font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;" title="MDH:PGRpdiBjbGFzcz0ibWFya2Rvd24taGVyZS13cmFwcGVyIiBkYXRhLWJsb2dnZXItZXNjYXBlZC1k
YXRhLW1kLXVybD0iaHR0cHM6Ly93d3cuYmxvZ2dlci5jb20vYmxvZ2dlci5nP2Jsb2dJRD02MTIy
NzU1ODA4NjUyNjc5ODA5I2VkaXRvci90YXJnZXQ9cG9zdDtwb3N0SUQ9NzQwNTkwOTAzNzg2NjE3
NzgyMztvblB1Ymxpc2hlZE1lbnU9YWxscG9zdHM7b25DbG9zZWRNZW51PWFsbHBvc3RzO3Bvc3RO
dW09MDtzcmM9bGluayI+PGRpdiBkYXRhLWJsb2dnZXItZXNjYXBlZC1zdHlsZT0ibWFyZ2luOiAw
cHggMHB4IDEuMmVtICFpbXBvcnRhbnQ7IiBzdHlsZT0ibWFyZ2luOiAwcHggMHB4IDEuMmVtOyI+
PHA+7ZeI65Oc7Iqo7J2064KYIOygoO2CqOyKpOulvCDrp6jrgqAg64uo64+F7Jy866GcIOudhOyb
jOyEnCDsk7Dri6TqsIA8L3A+PHA+W0xldCdzIEVuY3J5cHRdKGh0dHBzOi8vbGV0c2VuY3J5cHQu
b3JnLynrpbwg7KCB7Jqp7ZWY66m07IScIOyVnuyXkOuLpOqwgCBbbmdpbnhdKGh0dHBzOi8vbmdp
bngub3JnLynrpbwg65GQ6rOgIO2UhOuhneyLnOuhnCDsmpTssq3snYQg67Cb6rKMIOyEpOygle2W
iOuLpC48L3A+PHA+PGJyPjwvcD48cD7qt7jrn7DrjbAg7J6Q6r64IOuLpOydjCDrqZTshLjsp4Dq
sIAg64KY66m07IScIOuhnOq3uOyduO2VoCDrlYzrgpgg7J2867aAIOunge2BrOqwgCDsl4nrmrHt
lZwg6rOz7Jy866GcIOqwhOuLpC48L3A+PHA+YGBgaGxqczwvcD48cD5JdCBhcHBlYXJzIHRoYXQg
eW91ciByZXZlcnNlIHByb3h5IHNldCB1cCBpcyBicm9rZW48L3A+PHA+YGBgPC9wPjxwPjxicj48
L3A+PHA+6rWs6riA7Iug7J2YIOyLoO2DgeydhCDrsJvslYQg7J206rOzIOyggOqzs+ydhCDrj4zs
lYTrtKTripTrjbAg64ukIOyGjOyaqeydtCDsl4bsl4jri6QuPC9wPjxwPuq3uOuemOyEnCDqt7jr
g6Ug7IaM7Iqk66W8IOq5jOuzvOq5jD8g7ZW07IScIOu0pOuNlOuLiC4uLjwvcD48cD5b7IaM7Iqk
IOunge2BrF0oaHR0cHM6Ly9naXRodWIuY29tL2plbmtpbnNjaS9qZW5raW5zL2Jsb2IvbWFzdGVy
L2NvcmUvc3JjL21haW4vamF2YS9qZW5raW5zL21vZGVsL0plbmtpbnMuamF2YSNMMjMzMCk8L3A+
PHA+YFgtRm9yd2FyZGVkLUhvc3Rg66W8IOuwm+qzoCDqs4TshajslrTsmpQ/PC9wPjxwPjxicj48
L3A+PHA+6re4656Y7IScIG5naW54IOyEpOygleyXkOyEnCDtlITroZ3si5wg7Zek642U66W8PC9w
PjxwPmBgYG5naW54PC9wPjxwPnByb3h5X3NldF9oZWFkZXIgJm5ic3A7IFgtRm9yd2FyZGVkLUhv
c3QgJm5ic3A7ICRob3N0OiRzZXJ2ZXJfcG9ydDs8L3A+PHA+cHJveHlfc2V0X2hlYWRlciAmbmJz
cDsgWC1Gb3J3YXJkZWQtUHJvdG8gJm5ic3A7JHNjaGVtZTs8L3A+PHA+YGBgPC9wPjxwPuydtOug
h+qyjCDshKTsoJXtlbTso7zri4jquYwg7J6YIOuQnOuLpC48L3A+PHA+PGJyPjwvcD48cD7sl63s
i5wg64u1IOyXhuydhCDrlYzripQg7IaM7Iqk66W8IOyngeygkSDrk6Tsl6zri6Qg67O064qU6rKM
IOuLteydtOuEpC48L3A+PC9kaXY+PC9kaXY+">
</div>
</div>
디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-35216091664547697592017-02-24T17:53:00.003+09:002017-03-07T22:48:02.115+09:00enum 네 이놈! - 제네릭과 람다와 enum과 헬게이트<div class="markdown-here-wrapper" data-md-url="https://www.blogger.com/blogger.g?blogID=6122755808652679809#editor/target=post;postID=3521609166454769759;onPublishedMenu=allposts;onClosedMenu=allposts;postNum=1;src=link" markdown-here-wrapper-content-modified="true">
조금 전에 <code>enum</code>들에 널려있는 중복 코드들을 제거하는 와중에 일어난 이야기.<br />
<br />
원래대로라면 <code>enum</code>에는 코드값만 넣고 텍스트는 따로 <code>properties</code>에 빼놓는데, 이번 고객은 아아주 특이하게도 그렇게 하면 유지보수하기 귀찮다고 소스에 다 때려박아달랜다.<br />
그래서 다 때려박으면서 예제삼아 코드값 <-> 텍스트 상호전환 코드까지 같이 만들었는데…<br />
<br />
누가 (울 회사 직원이라고 말 안할거임) 그걸 그대로 복붙해서 <code>enum</code> 수십 개를 만들었고, <a href="http://pmd.sourceforge.net/cpd.html" target="_blank">CPD</a>는 복붙 고마해라 라고 지랄대고…<br />
해서 코드값 <-> 텍스트 상호전환 하는 부분을 별도의 유틸리티 클래스로 분리했다.<br />
<br />
문제는 여기서 발생하는데…<br />
<br />
먼저 <code>WithCodeLabel</code>이라는 인터페이스를 만들고 메소드를 정의한 다음, 해당 <code>enum</code>들에 <code>implements</code>를 걸었다.<br />
그리고 맨 처음 유틸리티 클래스를 람다식을 활용하여 만들었다.<br />
<br />
<pre><code class="hljs language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EnumCodeLabelCache</span><<span class="hljs-title">E</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Enum</span><<span class="hljs-title">E</span>> & <span class="hljs-title">WithCodeLabel</span>> </span>{
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, E> codeToLabelMap;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, E> labelToCodeMap;
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">EnumCodeLabelCache</span><span class="hljs-params">(Class<E> cls)</span> </span>{
<span class="hljs-keyword">final</span> EnumSet<E> set = EnumSet.allOf(cls);
codeToLabelMap = set.stream().collect(Collectors.toMap(WithCodeLabel::getCode, Function.identity());
labelToCodeMap = set.stream().collect(Collectors.toMap(WithCodeLabel::getLabel, Function.identity());
}
...
}
</code></pre>
<br />
그리고 테스트를 돌렸을 때, 처음 보는 당황스러운 에러가…<br />
<pre><code class="hljs language-hljs">java.lang.BootstrapMethodError: call site initialization exception
at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)
at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
</code></pre>
<br />
…모라고요?<br />
<br />
곰곰히 머리를 싸매고 생각하다가 분석을 해보니<br />
<code>enum</code>의 각 값의 RTTI에 <code>WithCodeLabel</code> 인터페이스에 대한 정보가 누락되어 있다는 결과밖에 나오지를 않는다.<br />
<br />
그래서 람다 말고 메소드 직접 호출로 변경.<br />
<br />
<pre><code class="hljs language-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EnumCodeLabelCache</span><<span class="hljs-title">E</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Enum</span><<span class="hljs-title">E</span>> & <span class="hljs-title">WithCodeLabel</span>> </span>{
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, E> codeToLabelMap = <span class="hljs-keyword">new</span> HashMap<>();
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, E> labelToCodeMap = <span class="hljs-keyword">new</span> HashMap<>();
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">EnumCodeLabelCache</span><span class="hljs-params">(Class<E> cls)</span> </span>{
<span class="hljs-keyword">final</span> EnumSet<E> set = EnumSet.allOf(cls);
set.forEach(e -> {
codeToLabelMap.putIfAbsent(e.getCode(), e);
labelToCodeMap.putIfAbsent(e.getLabel(), e);
});
}
...
}
</code></pre>
<br />
이러니까 잘 된다.<br />
<br />
이게 <code>enum</code>만 특히 이런건지, 제네릭 특징인지는 더 살펴봐야 알겠지만, 내가 알 게 뭐야.<br />
<div style="font-size: 0em; height: 0; margin: 0; max-height: 0; max-width: 0; overflow: hidden; padding: 0; width: 0;" title="MDH:PGRpdiBjbGFzcz0ibWFya2Rvd24taGVyZS13cmFwcGVyIiBkYXRhLWJsb2dnZXItZXNjYXBlZC1k
YXRhLW1kLXVybD0iaHR0cHM6Ly93d3cuYmxvZ2dlci5jb20vYmxvZ2dlci5nP2Jsb2dJRD02MTIy
NzU1ODA4NjUyNjc5ODA5I2VkaXRvci90YXJnZXQ9cG9zdDtwb3N0SUQ9MzUyMTYwOTE2NjQ1NDc2
OTc1OTtvblB1Ymxpc2hlZE1lbnU9YWxscG9zdHM7b25DbG9zZWRNZW51PWFsbHBvc3RzO3Bvc3RO
dW09MTtzcmM9bGluayI+PHA+7KGw6riIIOyghOyXkCBgZW51bWDrk6Tsl5Ag64SQ66Ck7J6I64qU
IOykkeuztSDsvZTrk5zrk6TsnYQg7KCc6rGw7ZWY64qUIOyZgOykkeyXkCDsnbzslrTrgpwg7J20
7JW86riwLjwvcD48cD48YnI+PC9wPjxwPjxicj48L3A+PHA+7JuQ656Y64yA66Gc652866m0IGBl
bnVtYOyXkOuKlCDsvZTrk5zqsJLrp4wg64Sj6rOgIO2FjeyKpO2KuOuKlCDrlLDroZwgYHByb3Bl
cnRpZXNg7JeQIOu5vOuGk+uKlOuNsCwg7J2067KIIOqzoOqwneydgCDslYTslYTso7wg7Yq57J20
7ZWY6rKM64+EIOq3uOugh+qyjCDtlZjrqbQg7Jyg7KeA67O07IiY7ZWY6riwIOq3gOywruuLpOqz
oCDshozsiqTsl5Ag64ukIOuVjOugpOuwleyVhOuLrOuenOuLpC48L3A+PHA+6re4656Y7IScIOuL
pCDrlYzroKTrsJXsnLzrqbTshJwg7JiI7KCc7IK87JWEIOy9lOuTnOqwkiAmbHQ7LSZndDsg7YWN
7Iqk7Yq4IOyDge2YuOyghO2ZmCDsvZTrk5zquYzsp4Ag6rCZ7J20IOunjOuTpOyXiOuKlOuNsC4u
LjwvcD48cD48YnI+PC9wPjxwPjxicj48L3A+PHA+64iE6rCAICjsmrgg7ZqM7IKsIOyngeybkOyd
tOudvOqzoCDrp5Ag7JWI7ZWg6rGw7J6EKSDqt7jqsbgg6re464yA66GcIOuzteu2me2VtOyEnCBg
ZW51bWAg7IiY7IutIOqwnOulvCDrp4zrk6Tsl4jqs6AsIFtDUERdKGh0dHA6Ly9wbWQuc291cmNl
Zm9yZ2UubmV0L2NwZC5odG1sKeuKlCDrs7Xrtpkg6rOg66eI7ZW06528IOudvOqzoCDsp4DrnoTr
jIDqs6AuLi4g7ZW07IScIOy9lOuTnOqwkiAmbHQ7LSZndDsg7YWN7Iqk7Yq4IOyDge2YuOyghO2Z
mCDtlZjripQg67aA67aE7J2EIOuzhOuPhOydmCDsnKDti7jrpqzti7Ag7YG0656Y7Iqk66GcIOu2
hOumrO2WiOuLpC48L3A+PHA+PGJyPjwvcD48cD48YnI+PC9wPjxwPuusuOygnOuKlCDsl6zquLDs
hJwg67Cc7IOd7ZWY64qU642wLi4uPC9wPjxwPjxicj48L3A+PHA+PGJyPjwvcD48cD7rqLzsoIAg
YFdpdGhDb2RlTGFiZWxg7J20652864qUIOyduO2EsO2OmOydtOyKpOulvCDrp4zrk6Tqs6Ag66mU
7IaM65Oc66W8IOygleydmO2VnCDri6TsnYwsIO2VtOuLuSBgZW51bWDrk6Tsl5AgYGltcGxlbWVu
dHNg66W8IOqxuOyXiOuLpC48L3A+PHA+PGJyPjwvcD48cD48YnI+PC9wPjxwPuq3uOumrOqzoCDr
p6gg7LKY7J2MIOycoO2LuOumrO2LsCDtgbTrnpjsiqTrpbwg656M64uk7Iud7J2EIO2ZnOyaqe2V
mOyXrCDrp4zrk6Tsl4jri6QuPC9wPjxwPjxicj48L3A+PHA+YGBgamF2YTwvcD48cD5wdWJsaWMg
Y2xhc3MgRW51bUNvZGVMYWJlbENhY2hlJmx0O0UgZXh0ZW5kcyBFbnVtJmx0O0UmZ3Q7ICZhbXA7
IFdpdGhDb2RlTGFiZWwmZ3Q7IHs8L3A+PHA+PGJyPjwvcD48cD4mbmJzcDsgJm5ic3A7IHByaXZh
dGUgZmluYWwgTWFwJmx0O1N0cmluZywgRSZndDsgY29kZVRvTGFiZWxNYXA7PC9wPjxwPiZuYnNw
OyAmbmJzcDsgcHJpdmF0ZSBmaW5hbCBNYXAmbHQ7U3RyaW5nLCBFJmd0OyBsYWJlbFRvQ29kZU1h
cDs8L3A+PHA+PGJyPjwvcD48cD4mbmJzcDsgJm5ic3A7IHByaXZhdGUgRW51bUNvZGVMYWJlbENh
Y2hlKENsYXNzJmx0O0UmZ3Q7IGNscykgezwvcD48cD4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJz
cDsgZmluYWwgRW51bVNldCZsdDtFJmd0OyBzZXQgPSBFbnVtU2V0LmFsbE9mKGNscyk7PC9wPjxw
PiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyBjb2RlVG9MYWJlbE1hcCA9IHNldC5zdHJlYW0o
KS5jb2xsZWN0KENvbGxlY3RvcnMudG9NYXAoV2l0aENvZGVMYWJlbDo6Z2V0Q29kZSwgRnVuY3Rp
b24uaWRlbnRpdHkoKSk7PC9wPjxwPiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyBsYWJlbFRv
Q29kZU1hcCA9IHNldC5zdHJlYW0oKS5jb2xsZWN0KENvbGxlY3RvcnMudG9NYXAoV2l0aENvZGVM
YWJlbDo6Z2V0TGFiZWwsIEZ1bmN0aW9uLmlkZW50aXR5KCkpOzwvcD48cD4mbmJzcDsgJm5ic3A7
IH08L3A+PHA+Jm5ic3A7ICZuYnNwOyAuLi48L3A+PHA+fTwvcD48cD5gYGA8L3A+PHA+PGJyPjwv
cD48cD7qt7jrpqzqs6Ag7YWM7Iqk7Yq466W8IOuPjOuguOydhCDrlYwsIOyymOydjCDrs7TripQg
64u57Zmp7Iqk65+s7Jq0IOyXkOufrOqwgC4uLjwvcD48cD5gYGBobGpzPC9wPjxwPmphdmEubGFu
Zy5Cb290c3RyYXBNZXRob2RFcnJvcjogY2FsbCBzaXRlIGluaXRpYWxpemF0aW9uIGV4Y2VwdGlv
bjwvcD48cD4mbmJzcDthdCBqYXZhLmxhbmcuaW52b2tlLkFic3RyYWN0VmFsaWRhdGluZ0xhbWJk
YU1ldGFmYWN0b3J5LnZhbGlkYXRlTWV0YWZhY3RvcnlBcmdzKEFic3RyYWN0VmFsaWRhdGluZ0xh
bWJkYU1ldGFmYWN0b3J5LmphdmE6MjMzKTwvcD48cD4mbmJzcDthdCBqYXZhLmxhbmcuaW52b2tl
LkxhbWJkYU1ldGFmYWN0b3J5Lm1ldGFmYWN0b3J5KExhbWJkYU1ldGFmYWN0b3J5LmphdmE6MzAz
KTwvcD48cD4mbmJzcDthdCBqYXZhLmxhbmcuaW52b2tlLkNhbGxTaXRlLm1ha2VTaXRlKENhbGxT
aXRlLmphdmE6MzAyKTwvcD48cD4mbmJzcDthdCBqYXZhLmxhbmcuaW52b2tlLk1ldGhvZEhhbmRs
ZU5hdGl2ZXMubGlua0NhbGxTaXRlSW1wbChNZXRob2RIYW5kbGVOYXRpdmVzLmphdmE6MzA3KTwv
cD48cD4mbmJzcDthdCBqYXZhLmxhbmcuaW52b2tlLk1ldGhvZEhhbmRsZU5hdGl2ZXMubGlua0Nh
bGxTaXRlKE1ldGhvZEhhbmRsZU5hdGl2ZXMuamF2YToyOTcpPC9wPjxwPmBgYDwvcD48cD4uLi7r
qqjrnbzqs6DsmpQ/PC9wPjxwPjxicj48L3A+PHA+PGJyPjwvcD48cD7qs7Dqs7Dtnogg66i466as
66W8IOyLuOunpOqzoCDsg53qsIHtlZjri6TqsIAg67aE7ISd7J2EIO2VtOuztOuLiDwvcD48cD5g
ZW51bWDsnZgg6rCBIOqwkuydmCBSVFRJ7JeQIGBXaXRoQ29kZUxhYmVsYCDsnbjthLDtjpjsnbTs
iqTsl5Ag64yA7ZWcIOygleuztOqwgCDriITrnb3rkJjslrQg7J6I64uk64qUIOqysOqzvOuwluyX
kCDrgpjsmKTsp4Drpbwg7JWK64qU64ukLjwvcD48cD48YnI+PC9wPjxwPjxicj48L3A+PHA+6re4
656Y7IScIOuejOuLpCDrp5Dqs6Ag66mU7IaM65OcIOyngeygkSDtmLjstpzroZwg67OA6rK9Ljwv
cD48cD48YnI+PC9wPjxwPmBgYGphdmE8L3A+PHA+cHVibGljIGNsYXNzIEVudW1Db2RlTGFiZWxD
YWNoZSZsdDtFIGV4dGVuZHMgRW51bSZsdDtFJmd0OyAmYW1wOyBXaXRoQ29kZUxhYmVsJmd0OyB7
PC9wPjxwPjxicj48L3A+PHA+Jm5ic3A7ICZuYnNwOyBwcml2YXRlIGZpbmFsIE1hcCZsdDtTdHJp
bmcsIEUmZ3Q7IGNvZGVUb0xhYmVsTWFwID0gbmV3IEhhc2hNYXAmbHQ7Jmd0OygpOzwvcD48cD4m
bmJzcDsgJm5ic3A7IHByaXZhdGUgZmluYWwgTWFwJmx0O1N0cmluZywgRSZndDsgbGFiZWxUb0Nv
ZGVNYXAgPSBuZXcgSGFzaE1hcCZsdDsmZ3Q7KCk7PC9wPjxwPjxicj48L3A+PHA+Jm5ic3A7ICZu
YnNwOyBwcml2YXRlIEVudW1Db2RlTGFiZWxDYWNoZShDbGFzcyZsdDtFJmd0OyBjbHMpIHs8L3A+
PHA+Jm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7IGZpbmFsIEVudW1TZXQmbHQ7RSZndDsgc2V0
ID0gRW51bVNldC5hbGxPZihjbHMpOzwvcD48cD4mbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsg
c2V0LmZvckVhY2goZSAtJmd0OyB7PC9wPjxwPiZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAm
bmJzcDsgJm5ic3A7IGNvZGVUb0xhYmVsTWFwLnB1dElmQWJzZW50KGUuZ2V0Q29kZSgpLCBlKTs8
L3A+PHA+Jm5ic3A7ICZuYnNwOyAmbmJzcDsgJm5ic3A7ICZuYnNwOyAmbmJzcDsgbGFiZWxUb0Nv
ZGVNYXAucHV0SWZBYnNlbnQoZS5nZXRMYWJlbCgpLCBlKTs8L3A+PHA+Jm5ic3A7ICZuYnNwOyAm
bmJzcDsgJm5ic3A7IH0pOzwvcD48cD4mbmJzcDsgJm5ic3A7IH08L3A+PHA+Jm5ic3A7ICZuYnNw
OyAuLi48L3A+PHA+fTwvcD48cD5gYGA8L3A+PHA+PGJyPjwvcD48cD7snbTrn6zri4jquYwg7J6Y
IOuQnOuLpC48L3A+PHA+PGJyPjwvcD48cD7snbTqsowgYGVudW1g66eMIO2Kue2eiCDsnbTrn7Dq
sbTsp4AsIOygnOuEpOumrSDtirnsp5Xsnbjsp4DripQg642UIOyCtO2OtOu0kOyVvCDslYzqsqDs
p4Drp4wsIOuCtOqwgCDslYwg6rKMIOutkOyVvC48L3A+PC9kaXY+">
</div>
</div>
디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-955495878524245442017-02-09T11:19:00.000+09:002019-07-23T18:05:38.011+09:00gceasy - 온라인 JVM GC 로그 분석기<a href="https://divinespear.github.io/tech/2018/02/09/gceasy.html">https://divinespear.github.io/tech/2018/02/09/gceasy.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-15220358409588188292016-08-26T01:43:00.000+09:002016-08-29T11:02:07.845+09:00Gradle + Eclipse Expert - 2016 editionSTS가 3.8.x로 업그레이드 되면서 Gradle STS Plugin이 나가리가 되고 Buildship으로 대체되었다.<br />
적용해보니 좋더라.<br />
<br />
... 생각해보니 플러그인이 바뀌어서 기능이 싸그리 증발한게 많아서... 살짝 정오표(?)를 써야겠다는 생각이 들었다.<br />
이전 글은 <a href="http://divinespear.blogspot.com/2014/04/gradle-eclipse-expert.html" target="_blank">http://divinespear.blogspot.com/2014/04/gradle-eclipse-expert.html</a> 를 참고할 것.<br />
<h3>
Project Dependency Correction with Hierarchical Project Name</h3>
예전에는 플러그인에서 지원하는걸 썼는데, 꼭 그럴 필요 없더라.<br />
<br />
예전에는 플러그인에서 "Use hierarchical project names" 옵션을 선택 한 후 <code>build.gradle</code>에서 이렇게 처리했었다. (일반 프로젝트였을 경우)<br />
<pre><code class="gradle">eclipse {
classpath {
file {
whenMerged { classpath ->
def entries = classpath.entries
entries.findAll { it.kind == 'src' && it.path.startsWith('/') }.each {
def name = it.path.substring(1)
it.path = "/${rootProject.name}.${name}"
}
}
}
}
}</code></pre>
<br />
근데 이러면 저런 삽질 할 필요가 없더라...<br />
<pre><code class="gradle">eclipse {
project {
name = rootProject.name + '.' + project.name
}
}</code></pre>
<br />
진짜로 이게 끝이다.<br />
<strike>난 그동안 뭐한거지...</strike>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-39684133289638268102015-11-06T21:28:00.000+09:002020-06-18T18:09:31.477+09:00selinux 잡담<a href="https://divinespear.github.io/tech/2020/06/18/selinux-explains.html">https://divinespear.github.io/tech/2020/06/18/selinux-explains.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-63931818081398937492015-04-15T20:15:00.001+09:002016-09-30T01:12:16.557+09:00이클립스 properties 에디터 플러그인보통 구글신께 신탁을 구하면 제일 처음 나오는 플러그인이 아마도..<br />
<a href="http://propedit.sourceforge.jp/index_en.html" target="_blank">http://propedit.sourceforge.jp/index_en.html</a><br />
이거일거다.<br />
<br />
<b><span style="font-size: large;">쓰지 마라</span></b>. 특히 OS가 Windows라면...<br />
사유는 <a href="http://kwon37xi.egloos.com/4664893" target="_blank">http://kwon37xi.egloos.com/4664893</a> 를 참고하시라.<br />
<br />
<br />
그리고 그 대안으로<br />
<a href="http://sourceforge.net/projects/eclipse-rbe/" target="_blank">http://sourceforge.net/projects/eclipse-rbe/</a><br />
이런 애가 등장했는데...<br />
<br />
<b><span style="font-size: large;">불편하다.</span></b><br />
궁금하면 한번 써봐.<br />
<br />
<br />
그래서 구글신의 신탁의 바다를 떠돌다가 새로운 플러그인을 발견했다.<br />
<a href="https://github.com/gildur/SimplePropertiesEditor" target="_blank">https://github.com/gildur/SimplePropertiesEditor</a><br />
<br />
소스를 대충 훓어보니 제일 위의 플러그인 같은 문제는 없을 것으로 보인다. <span style="font-size: x-small;"><strike>난 리눅스니 윈도우 쓰는 동무들이 테스트좀 해보라우!</strike></span><br />
그리고 두번째보다는 심플하니까 불편하지도 않고...<br />
<br />
<br />
2016-09-30 추가:<br />
이 플러그인으로 properties 파일을 열었을 경우 STS의 <code>application.properties</code> 자동완성이 적용이 되지 않는다. <span style="font-size: x-small;"><strike>그래서 난 <code>application.yml</code>을 쓰지...</strike></span><br />
<br />디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-7138522006748653402015-04-08T20:05:00.001+09:002015-04-08T20:05:34.075+09:00에휴...요새 개발자 뽑는다고 면접보는데 따라들어가서 몇가지 기술적인 질문을 한다.<br />
<br />
보통 자바 관련 업무가 많아서 자바 관련 질문을 하는데...<br />
<ul>
<li>어떻게 된 사람들이 <code>int</code>와 <code>Integer</code>의 차이를 설명을 못하지?</li>
<li>어떻게 된 사람들이 "두 문자열이 같은지 어떻게 비교하냐"는 질문에 대답을 못하지?</li>
<li>어떻게 된 사람들이 주어진 문자열을 뒤집는걸 만들어보라고 했더니 10분 가까이 해메는거지?</li>
</ul>
<div>
클래스/인터페이스 설계 관련 질문은... 뭐 상급자용 질문이니 넘어간다고 쳐도,</div>
<div>
<br /></div>
<div>
늅늅이나 2~3년차면 모르겠는데 이력서상 경력이 5년이 넘어가는 사람들도 이런다.</div>
<div>
어찌하오리까...<br />
<br />
p.s.<br />
가장 심한 사람들은 질문자가 무슨 질문을 하는지 조차 알아듣지 못하는 사람.<br />
젠장...<br />
<br /></div>
디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-79170657420444634912015-02-21T03:38:00.000+09:002015-07-31T02:28:06.621+09:00spring-boot와 함께하는 spring-batch 삽질<h2>
시작하기 전에...</h2>
여기 쓰인 예제는 연습삼아 만들어본 도로명주소 데이터 덤프 툴 중 일부입니다.<br />
전체 소스는 공개할 생각이 없으니 알아서 공부해서 만드십쇼.<br />
<br />
여기에 XML 설정은 눈 씻고 봐도 찾아볼 수 없습니다. XML 설정은 구글신께 신탁을 부탁합시다.<br />
<br />
2015-07-31에 추가:<br />
아주 마아아아아안약에 사이트에 도로명주소+기초구역코드(새 우편번호) 적용을 하고 싶은데 나온 검색 결과가 이거라면, 이걸 시도하기 전에 (즉, 자체 디비구축이라는 삽질을 하기 전에) 다음카카오를 만나세요.<br />
<a href="http://postcode.map.daum.net/guide" target="_blank">여기</a>로 가시면 자바스크립트로 한방에!<br />
<br />
<h2>
사용법</h2>
일단 이거부터...
<br />
<pre><code class="java">@EnableBatchProcessing</code></pre>
<br />
그리고...
<br />
<ol>
<li>일(Job)을 만든다.</li>
<li>단계(Step)를 만든다.</li>
</ol>
끗!<br />
<br />
<h2>
일단 일(Job)부터 만들자</h2>
<pre><code class="java">@Bean
public Job job() {
return jobBuilderFactory.get("address-import")
.start(streetStep())
.next(partitionedStreetDetailStep())
.next(partitionedStreetExtraStep())
.next(partitionedLotStep()).build();
}</code></pre>
<ol>
<li>Job을 만든다.</li>
<li>시작할 단계를 설정한다.</li>
<li>다음 단계... 다음 단계... 다음 단계...</li>
<li>설정 끗!</li>
</ol>
<br />
Job은 이게 끗이다. 진짜로...<br />
<br />
도로명주소는 넣는 순서가 중요해서 설정하지 않았는데, 여러 개의 단계를 동시에 돌릴 수도 있다.<br />
그런데 자바 설정으로는 단계를 멀티쓰레드화 하는게 쉽지 않았다. <span style="color: #999999;"><strike>나 그냥 GG칠래...</strike></span><br />
<br />
<h2>
일할 단계(Step)도 만들자</h2>
각 단계는 다음 순서대로 이루어진다.<br />
<ol>
<li>읽는다.</li>
<li>가공한다.</li>
<li>쓴다.</li>
</ol>
참 쉽죠?<br />
<br />
실제로는 쓰기 작업이 있으면 가공 작업은 생략이 가능하고, 가공 작업이 있으면 쓰기 작업을 생략할 수 있다.<br />
<pre><code class="java">@Bean
public Step streetStep() {
return stepBuilderFactory.get("street-address")
.<AddressStreet, AddressStreet> chunk(getChunkSize())
.reader(streetReader())
.writer(streetWriter())
.taskExecutor(taskExecutor)
.throttleLimit(THROTTLE_LIMIT)
.build();
}</code></pre>
이 예제에는 읽기와 쓰기만 있는데, 도로명 주소는 그냥 읽어서 쓰기만 하면 되기 때문이다.<br />
<br />
<code>chunk</code>는 읽기/쓰기 단위를 설정한다. 100을 설정하면 읽고 쓰는걸 100개 단위로 하게 된다.<br />
<code>taskExecutor</code>, <code>throttleLimit</code>는 밑에서 설명을...<br />
<br />
<h2>
읽기</h2>
여기서는 주어지는 대상이 파일이니 파일을 읽는 것으로...<br />
<pre><code class="java">@Bean
public FlatFileItemReader<AddressStreet> streetReader() {
DefaultLineMapper<AddressStreet> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(lineTokenizer);
lineMapper.setFieldSetMapper(new FieldSetMapper<AddressStreet>() {
@Override
public AddressStreet mapFieldSet(FieldSet fieldSet) throws BindException {
AddressStreet street = new AddressStreet();
street.setId(String.format("%12s%2s", fieldSet.readString(0), fieldSet.readString(3)));
street.setCode(fieldSet.readString(0));
street.setCodeIndex(fieldSet.readString(3));
street.setName(fieldSet.readString(1));
street.setRegion(fieldSet.readString(4));
street.setCity(fieldSet.readString(6));
street.setTown(fieldSet.readString(8));
street.setTownType(fieldSet.readInt(10, 2));
street.setTownCode(fieldSet.readString(11));
street.setDisabled(fieldSet.readBoolean(12, "1"));
return street;
}
});
FlatFileItemReader<AddressStreet> reader = new FlatFileItemReader<>();
reader.setLineMapper(lineMapper);
reader.setResource(new PathResource(environment.getRequiredProperty("street")));
return reader;
}</code></pre>
<ol>
<li><code>LineMapper</code>를 만들고</li>
<li><code>FileItemReader</code>를 맹근 다음에</li>
<li><code>FileItemReader</code>에 <code>LineMapper</code>와 읽을 파일을 설정</li>
</ol>
해주면 끝난다. 참 쉽죠?<br />
<br />
파일을 여러 개 읽으려면 <code>FlatFileItemReader</code>대신 <code>MultiResourceItemReader</code>를 사용하면 된다.<br />
<br />
<h2>
쓰기</h2>
파일을 읽었으면 디비에 쏴줘야지...<br />
<pre><code class="java">@Bean
public ItemWriter<AddressStreet> streetWriter() {
JdbcBatchItemWriter<AddressStreet> writer = new JdbcBatchItemWriter<>();
writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<AddressStreet>());
writer.setSql("INSERT INTO ADDRESS_STREET (ID, CODE, CODE_INDEX, NAME, REGION, CITY, TOWN, TOWN_CODE, TOWN_TYPE, IS_DISABLED) VALUES (:id, :code, :codeIndex, :name, :region, :city, :town, :townCode, :townType, :disabled)");
writer.setJdbcTemplate(jdbcTemplate);
return writer;
}</code></pre>
...설명이 필요 없을정도로 간단하다?<br />
<br />
만약에 본인처럼 Bean을 쓰는게 아니라면 <code>BeanPropertyItemSqlParameterSourceProvider</code>는 다른걸로 대체하거나 직접 맹글어야 한다?<br />
<strike><span style="color: #999999;">그러니까 다같이 JPA를 씁니다?</span></strike><br />
<br />
<h2>
멀티쓰레딩 - <code>TaskExecutor</code>와 <code>ThrottleLimit</code></h2>
위에서 대충 넘겼던거 여기와서 설명을...<br />
<br />
앞뒤 다 자르고 설명하면... 작업 단위를 멀티쓰레드로 실행한다.<br />
<code>TaskExecutor</code>는 다들 알다시피(?) 쓰레드 풀, <code>ThrottleLimit</code>는 동시실행 쓰레드 갯수 제한이다.<br />
<br />
<h3>
<code>TaskExecutor</code>, 뭘 골라야 쓰겄소?</h3>
<code>TaskExecutor</code>의 구현이 여럿 있는데, 일반적으로 원샷 실행일 경우 <code>SimpleAsyncTaskExecutor</code>를 많이 쓰는것 같다.<br />
<br />
왜인지 모르게 <code>SimpleThreadPoolTaskExecutor</code>와 <code>ThreadPoolTaskExecutor</code>가 의도한 대로 동작하지 않는데 <span style="font-size: x-small;">(싱글 쓰레드처럼 동작한다.)</span>, 그냥 <code>Executor</code>를 래핑하는 <code>ConcurrentTaskExecutor</code>를 만들어 쓰는게 더 낫다.<br />
<code><br /></code>
<code>ConcurrentTaskExecutor</code>에 <code>Executor</code>를 넣지 않으면 <code>Executors.newSingleThreadExecutor()</code>를 설정한다. <span style="color: #999999;"><strike>뭐요?</strike></span><br />
<br />
<h2>
파티셔닝 - 여러 개의 I/O를 멀티쓰레드로</h2>
위에 설명한게 I/O 단위 하나를 멀티쓰레드로 돌린다면, 이건 작업 하나에 여러 개의 I/O가 있을 때 이걸 멀티쓰레드로 돌리게 해준다.<br />
<pre><code class="java">@Bean
public Step partitionedStreetDetailStep() {
return stepBuilderFactory.get("partition.street-address-detail")
.partitioner(streetDetailStep())
.partitioner("partition.street-address-detail.", streetDetailPartitioner())
.taskExecutor(taskExecutor)
.build();
}</code></pre>
보다시피 step을 하나 감싸는 step이다.<br />
이 때, 내부 step은 <code>Partitioner</code>가 쪼개는 갯수만큼 생성된다.<br />
<br />
<h3>
파일 여러개 읽기</h3>
파티셔닝 모드에서 파일 여러개 읽기는 손이 많이 가는 작업이다.<br />
<ol>
<li>읽을 파일 목록을 <code>Partitioner</code>로 맹근다.</li>
<li><code>ItemReader</code>를 맹근다.</li>
<li>파티션 Step에서 사용하는 내부 Step에 <code>ItemReader</code>를 설정한다.</li>
</ol>
단계만 보면 참 쉬워보이는데... 전혀 그렇지 않다는게 함정.<br />
<br />
일단 파일 목록 파티션부터 맹근다.<br />
<pre><code class="java">@Bean
public Partitioner streetDetailPartitioner() {
MultiResourcePartitioner partitioner = new MultiResourcePartitioner();
List<Resource> resources = Arrays.asList(environment.getProperty("street-detail", String[].class))
.stream()
.map(PathResource::new)
.collect(Collectors.toList());
partitioner.setResources(resources.toArray(new Resource[resources.size()]));
return partitioner;
}</code></pre>
<br />
다음 <code>ItemReader</code>도 맹근 다음...<br />
<pre><code class="java">@Bean
@StepScope
public FlatFileItemReader<AddressStreetDetail> streetDetailReader(@Value("#{stepExecutionContext['fileName']}") String path) {
DefaultLineMapper<AddressStreetDetail> lineMapper = new DefaultLineMapper<>();
lineMapper.setLineTokenizer(lineTokenizer);
lineMapper.setFieldSetMapper(new FieldSetMapper<AddressStreetDetail>() {
@Override
public AddressStreetDetail mapFieldSet(FieldSet fieldSet) throws BindException {
AddressStreetDetail detail = new AddressStreetDetail();
detail.setId(fieldSet.readString(0));
detail.setUnderground(fieldSet.readBoolean(3, "1"));
detail.setBuildingNo(fieldSet.readInt(4));
detail.setBuildingNoSub(fieldSet.readInt(5));
detail.setBlockNo(fieldSet.readString(6));
detail.setExtra(fieldSet.readBoolean(10, "1"));
AddressStreet street = new AddressStreet();
street.setId(String.format("%12s%2s", fieldSet.readString(1), fieldSet.readString(2)));
detail.setStreet(street);
return detail;
};
});
FlatFileItemReader<AddressStreetDetail> reader = new FlatFileItemReader<>();
reader.setLineMapper(lineMapper);
reader.setResource(new PathResource(URI.create(path)));
return reader;
}</code></pre>
<br />
내부 Step도 맹근다.<br />
<pre><code class="java">@Bean
public Step streetDetailStep() {
return stepBuilderFactory.get("street-address-detail")
.<AddressStreetDetail, AddressStreetDetail> chunk(getChunkSize())
.reader(streetDetailReader("classpath:/empty.csv"))
.writer(streetDetailWriter())
.taskExecutor(taskExecutor)
.throttleLimit(THROTTLE_LIMIT)
.build();
}</code></pre>
<br />
<code>@StepScope</code>라는 놈이 지금 막 등장했는데, 매우 중요하다. 이게 없으면 파티션된 작업에서 읽을 파일을 제대로 설정할 수가 없다! 그리고 읽을 파일명은 <code>stepExecutionContext</code>에서 받아오게 된다. <span style="font-size: x-small;">(Job에는 같은 역할을 하는 <code>jobExecutionContext</code>가 있다.)</span><br />
<br />
아쉽게도 자바 설정으로 <code>FlatFileItemReader</code>를 생성할 때 읽을 파일을 뭐라도 설정하지 않으면 나중에 실제로 읽을 파일을 설정해 줘도 파일을 읽을 생각을 안한다. 따라서 bean을 만들 때는 아무거나 하나 던져준다. <span style="font-size: x-small;">(이 때는 실제 파일이 없어도 신경을 쓰지 않지 시포요. 아니면 아무 내용도 없는 더미 파일을 하나 맹글어서 던져주든가...)</span><br />
<br />
<h2>
삽질기 - 디비가 지저분해졌어요!</h2>
기본적으로 spring-boot는 <code>DataSource</code> bean이 있고 <code>@EnableBatchProcessing</code>이 설정되어있다면 배치 실행 정보를 디비에 우겨넣는다. 원샷 실행이고 실행 정보를 저장해야 할 필요따원 없는데 이러면 참 골치가 아프더라.<br />
원래는 <code>spring.batch.initializer.enabled</code> 속성이 <code>false</code>이면 테이블을 생성하지 않고 잘 돌아야 하는데... 실상은 에러나 막 떨궈댄다.<br />
<br />
원인은 <code>DefaultBatchConfigurer</code>가 <code>DataSource</code>를 주입받기 때문에...<br />
그러니까 <code>DataSource</code>를 무시하도록 직접 <code>BatchConfigurer</code>를 만들면 된다.<br />
<pre><code class="java">@Bean
public BatchConfigurer batchConfigurer() {
BatchConfigurer configurer = new BatchConfigurer() {
private PlatformTransactionManager transactionManager;
private JobRepository jobRepository;
private JobLauncher jobLauncher;
private JobExplorer jobExplorer;
@Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return transactionManager;
}
@Override
public JobRepository getJobRepository() throws Exception {
return jobRepository;
}
@Override
public JobLauncher getJobLauncher() throws Exception {
return jobLauncher;
}
@Override
public JobExplorer getJobExplorer() throws Exception {
return jobExplorer;
}
@PostConstruct
public void initialize() {
if (this.transactionManager == null) {
this.transactionManager = new ResourcelessTransactionManager();
}
try {
MapJobRepositoryFactoryBean jrf = new MapJobRepositoryFactoryBean(this.transactionManager);
jrf.afterPropertiesSet();
this.jobRepository = jrf.getObject();
MapJobExplorerFactoryBean jef = new MapJobExplorerFactoryBean(jrf);
jef.afterPropertiesSet();
this.jobExplorer = jef.getObject();
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(jobRepository);
jobLauncher.afterPropertiesSet();
this.jobLauncher = jobLauncher;
} catch (Exception e) {
throw new BatchConfigurationException(e);
}
}
};
return configurer;
}</code></pre>
오~예~디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-37598930794251000992014-12-16T02:47:00.000+09:002016-12-15T22:52:58.693+09:00CentOS에 JDK Alternative 멕이기 [삐-]나게 힘들구먼RHEL이나 CentOS에서 Oracle JDK 설치 관련 구글신께 신탁을 청하면<br />
그냥 "rpm 올리고 alternative 멕이세요." 라고만 되어있다. 진짜...<br />
<br />
그래서 만들었습니다.<br />
Oracle JDK Alternative 자동 등록 스크립트!<br />
기존 구글신 신탁처럼 일부분만 등록해버리는게 아니라 전체를 빠르고 편하게 등록해드립니다.<br />
<br />
<a href="https://drive.google.com/file/d/0B5nGR_vG1FdMakZiVl9NQ20yNEU/view?usp=sharing" target="_blank">이거</a> 눌러서 받으시고...<br />
<br />
0. 먼저 등록할 JDK부터 설치하고 봅니다. (대략 <code>yum localinstall</code>이 좋소)<br />
1. 일단 적절한 곳에 던저놓으신 다음<br />
2. <kbd>chmod a+x centos-jdk-alternative-update.sh</kbd><br />
3. <kbd>./centos-jdk-alternative-update.sh <JDK-DIR></kbd><br />
<br />
끗!<br />
참 쉽죠?<br />
<br />
물론 약간의 오류가 있을 수도 있지만, 그런거 내가 <a href="https://namu.wiki/w/%EC%95%8C%20%EA%B2%8C%20%EB%AD%90%EC%95%BC" target="_blank">알 게 뭐야</a><br />
<br />
일단 CentOS 7에 Oracle JDK 8 버전으로 테스트해봤습니다.<br />
아마 이전 버전들 (JDK 6, 7)에서도 큰 문제없이 돌아가지 시포요.<br />
<br />
p.s.<br />
alternative 업데이트 후에도 버전이 바뀌지 않으면 다음 명령을 입력해줍니다.<br />
<br />
최신버전으로 올릴 때:<br />
<pre><code class="bash">update-alternatives --auto java
update-alternatives --auto javac</code></pre>
<br />
특정버전으로 설정할 때:<br />
<pre><code class="bash">update-alternatives --set java <path>
update-alternatives --set javac <path></code></pre>
<br />
각 버전별 경로는 다음 명령으로 알아낼 수 있습니다.<br />
<pre><code class="bash">update-alternatives --display java
update-alternatives --display javac</code></pre>
디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-41374774625643214692014-11-19T17:45:00.000+09:002019-07-23T18:55:00.264+09:00가볍고 강력한 *nix용 웹 로그 분석 툴 - goaccess<a href="https://divinespear.github.io/tech/2014/11/19/goaccess.html">https://divinespear.github.io/tech/2014/11/19/goaccess.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-25432307996712141362014-10-06T05:37:00.003+09:002014-10-24T16:24:56.755+09:00JPA Entity와 Java 8아직 하이버네이트에 대해서는 테스트해보지 않았지만, 이클립스링크에서 문제가 발생하므로, 비슷한 처리를 하는 하이버네이트에서도 똑같은 문제가 있을 것이다.<br />
<br />
엔티티에 <code>@OneToMany</code>나 <code>@ManyToMany</code>로 컬렉션(<code>List</code>, <code>Set</code>, <code>Map</code>) 필드가 있을 때, 이 필드가 wrapper로 세팅되어있을 경우 (이클립스링크의 경우 <code>IndirectList</code>, <code>IndirectSet</code>, <code>IndirectMap</code>) 일부 Java 8 기능이 먹지를 않는다.<br />
<br />
특히 가장 중요한 stream이 동작하지 않는다.<br />
Aㅏ... 망했어요.<br />
<span style="color: #cccccc; font-size: x-small;"><strike>이보시오, 이보시오! JPA양반! 그게 무슨 소리요! Stream이 고자라니!</strike></span><br />
<br />
뭐 별 거 있나, wrap 된거를 꺼내오거나 그냥 새로운 컬렉션을 만들어서 값을 복사해서 쓰면 된다.<br />
참 쉽죠? <span style="color: #cccccc; font-size: x-small;"><strike>너무 쉬워서 예제 따위는 없어요.</strike></span><br />
<br />
<br />
<br />
뱀발:<br />
엔티티 내부에 Java 8 기능을 우겨넣으면 씐나게 로딩하다가 에러가 날 것이다.<br />
아쉽게도 JPA는 Java 8 기능이 들어간 엔티티는 엔티티로 취급하지 않는다 (...)<br />
<br />
물론 그런건 별도의 클래스로 분리해 놓고 써먹으면 그만이다.디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-9588803772246432572014-07-17T16:36:00.000+09:002015-05-20T02:22:57.709+09:00Spring Framework Error Handling: Accept 헤더와 무한루프<span style="font-size: x-small;">이거 때문에 그나마 없던 생활 리듬마저 개박살 나고 말았다아아아아아아~~~~</span><br />
<br />
<br />
Spring Framework에는 당연히 에러 처리기가 있고, 적절한 Exception을 적절한 에러 코드로 변환해서 클라이언트에 던진다.<br />
그리고 Spring Boot에는 기본 Error 처리 컨트롤러가 있어서, 적절하게 html 또는 json으로 에러 메세지를 뿌려준다.<br />
<br />
그런데, 여기서 모든 문제가 시작된다!<br />
<br />
Spring Boot를 사용하는 프로젝트를 하나 만들고, 적절한 Servlet Container <span style="font-size: x-small;">Tomcat이라거나 Jetty라거나</span> 를 사용해서 띄워본다.<br />
그리고 (리눅스의 경우) 터미널을 열고 아래 명령을 때려봐라.<br />
<br />
HTTP일 경우<br />
<pre><code class="bash">curl -H "Accept: application/octet-stream" http://localhost:8080/<context-path>/error</code></pre>
<br />
HTTPS일 경우<br />
<pre><code class="bash">curl -k -H "Accept: application/octet-stream" https://localhost:8443/<context-path>/error</code></pre>
<br />
CPU가 미친듯이 돌아가면서 <code>StackOverflowException</code>을 떨굴 것이다.<br />
지옥에 입장한 것을 축하한다.<br />
<br />
원인은 Exception에서 볼 수 있듯이 무한루프인데, <code>Accept</code>에 다른거 <span style="font-size: x-small;"><code>text/html</code>이나 <code>application/json</code> 같은거</span> 를 넘기면 정상 동작하는 것을 봐서는 에러 처리 로직에 문제가 있는 것이다.<br />
그래서 디버그를 돌려본 결과...<br />
<br />
일단 Spring Boot의 <code>BasicErrorController</code>는 정상적으로 탄다.<br />
그리고 그 결과를 JSON으로 변환하는데, <b>문제는 <a href="https://namu.wiki/w/%EC%98%81%EC%9B%85%EC%9D%80%20%EA%B3%B5%EB%B6%80%20%EB%94%B0%EC%9B%90%20%EC%95%88%20%ED%95%9C%EB%8B%A4%EB%84%A4" target="_blank">Jackson은 <code>application/octet-stream</code> 따원 모른다네</a>.</b><br />
그래서 Jackson은 난 이런거 모른다네! 하고 에러를 던지고, Spring 에러 핸들러는 이걸 HTTP 응답 코드 406으로 변환한다.<br />
<br />
여기까지는 좋았지.<br />
Request에 Accept가 그대로 남아있는고로...<br />
<br />
<b><a href="https://namu.wiki/w/%EC%9D%B8%EA%B0%84%EC%9D%98%20%EC%9A%95%EC%8B%AC%EC%9D%80%20%EB%81%9D%EC%9D%B4%20%EC%97%86%EA%B3%A0%20%EA%B0%99%EC%9D%80%20%EC%8B%A4%EC%88%98%EB%A5%BC%20%EB%B0%98%EB%B3%B5%ED%95%9C%EB%8B%A4" target="_blank">Request의 Accept는 끝이 없고<br />
같은 무한루프를 반복한다.</a></b><br />
<br />
<br />
그래서 이걸 어떻게 해결하느냐고?<br />
<a href="https://namu.wiki/w/%EC%95%88%EC%95%8C%EB%9E%B4%EC%A4%8C" target="_blank">안알랴줌</a>.<br />
<br />
<br />
<br />
<br />
<br />
<br />
이면 이 글을 안썼겠지.<br />
<br />
Filter를 하나 추가해서 406 응답코드를 인식하면 Request를 Wrapper로 감싼다.<br />
Wrapper는 <code>Accept</code> 헤더의 값을 요청할 경우 <code>null</code>을 떨구면 된다.<br />
<br />
<a href="https://namu.wiki/w/%EC%B0%B8%20%EC%89%BD%EC%A3%A0" target="_blank">참 쉽죠?</a><br />
<br />
<br />
p.s<br />
Spring Framework로 생각하고 있었는데 <b>spring-boot의 문제였고, 1.1.5에서 수정되었다.</b><br />
(<a href="https://github.com/spring-projects/spring-boot/issues/1257">https://github.com/spring-projects/spring-boot/issues/1257</a>)디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-26590532340533664802014-04-16T10:34:00.001+09:002016-08-26T01:43:22.497+09:00Gradle + Eclipse Expert일단 이 글은 <a href="http://www.gradle.org/" target="_blank">Gradle</a>을 가지고 제대로 삽질 한 번쯤 해봤으면 쉽게 이해할 수 있다.<br />
<div>
Gradle 1.11 기준이다. <span style="font-size: x-small;"><strike>난 최신을 좋아하지 (물론 삽질도...)</strike></span></div>
<div>
<br />
<h3>
시작하기 전에...</h3>
당연히 Eclipse Plugin을 사용해야 된다.<br />
일반 프로젝트라면<br />
<pre><code class="gradle">apply plugin: 'eclipse'</code></pre>
웹 프로젝트라면<br />
<pre><code class="gradle">apply plugin: 'eclipse-wtp'</code></pre>
<br />
<a href="http://kwonnam.pe.kr/wiki/gradle/eclipse" target="_blank">http://kwonnam.pe.kr/wiki/gradle/eclipse</a>도 참고하면 매우 좋다.<br />
<br /></div>
<div>
<h3>
Package Explorer와 Project Explorer 정리</h3>
</div>
<div>
Gradle로 그냥 이클립스 프로젝트를 만들면 Dependencies JAR 파일들이 Package Explorer와 Project Explorer를 점령하게 된다. 이제 그것들을 몰아낼 시간이다.</div>
<div>
<br /></div>
<div>
<code>build.gradle</code>에 다음을 추가한다.</div>
<pre><code class="gradle">eclipse {
classpath {
file {
whenMerged { classpath ->
def entries = classpath.entries
entries.findAll { it.kind == 'lib' }*.exported = false
}
}
}
}</code></pre>
<div>
그리고 Gradle -> Refresh All을 하면 Maven 쓸 때처럼 깔끔하게 정리된 모습을 볼 수 있다.<br />
<br />
<h3>
WTP Facet 설정</h3>
Gradle은 WTP Facet을 알아서 무지 구식으로(!!!) 설정해준다. 진짜로. <strike><span style="font-size: x-small;">Servlet 2.3이라니 잘도 이런 구석기 시대 세팅을!</span></strike><br />
따라서 이런 사태를 방지하기 위해 facet 설정을 따로 해줘야 한다.<br />
<br />
아래 설정은 Java 1.7, Servlet 3.0을 사용하는 facet 설정이다.</div>
<pre><code class="gradle">eclipse {
wtp {
facet {
facet name: 'jst.web', version: '3.0'
facet name: 'java', version: '1.7'
facet name: 'wst.jsdt.web', version: '1.0'
}
}
}</code></pre>
<div>
<br />
<h3>
Project Dependency Correction with Hierarchical Project Name</h3>
Spring 프로젝트에서 제공하는 이클립스 Gradle IDE 플러그인이 꽤 괜찮은 기능을 제공하는데, 그 중 하나가 Hierarchical Project Name 지원으로, 이클립스상의 프로젝트명을 <code>root.child</code> 같이 만들어준다. Gradle 프로젝트 임포트시 Use hierarchical project names를 체크하면 이 기능을 사용할 수 있다. 이를 이용해서 여러 프로젝트에 같은 이름의 서로 다른 하위 프로젝트를 가지고 있더라도 아무 문제없이 사용할 수 있다.<br />
다른 프로젝트를 참조하기 전 까지는...<br />
<br />
Hierarchical Project Name을 사용한 프로젝트를 dependencies에 추가할 경우 플러그인이 해당 프로젝트를 찾지 못해서 정신줄을 놓게 된다!<br />
<br />
뭐 별 거 있나... 직접 수정하면 될 것을...<br />
<br />
<code>build.gradle</code>에 다음을 추가한다.<br />
<h4>
For WTP Project</h4>
<pre><code class="gradle">eclipse {
wtp {
component {
file {
withXml { provider ->
def module = provider.asNode().'wb-module'
module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/resource') }.each { entry ->
def name = entry.@'handle'.substring('module:/resource/'.length(), entry.@'handle'.lastIndexOf('/'))
entry.@'handle' = "module:/resource/${rootProject.name}.${name}/${rootProject.name}.${name}"
entry.@'archiveName' = "${name}.jar"
}
}
}
}
}
}</code></pre>
<h4>
For Normal Project</h4>
<pre><code class="gradle">eclipse {
classpath {
file {
whenMerged { classpath ->
def entries = classpath.entries
entries.findAll { it.kind == 'src' && it.path.startsWith('/') }.each {
def name = it.path.substring(1)
it.path = "/${rootProject.name}.${name}"
}
}
}
}
}</code></pre>
<div>
<br />
그리고 Gradle -> Refresh All을 하면 정상적으로 프로젝트 참조를 인식한다.<br />
<br />
<span style="color: red;"><b>[2016-08-26]</b></span> 어... 플러그인이 buildship으로 바뀌면서 이건 더이상 못쓰는데, 사실 더 쉽고 간단한 방법이 있었다. (...)<br />
<a href="http://divinespear.blogspot.com/2016/08/gradle-eclipse-expert-2016-edition.html" target="_blank">http://divinespear.blogspot.com/2016/08/gradle-eclipse-expert-2016-edition.html</a> 를 보시라.<br />
<h3>
Web App Libraries 라이브러리 그룹 비우기</h3>
Web App Libraries 라이브러리 그룹은 Maven으로 프로젝트 만들 시절에는 구경도 못해본 거였는데, 이게 가끔 말썽을 부린다.<br />
그래서 이클립스에서 이를 무시하도록 설정을 좀 봐줘야 한다.<br />
<br />
<code>build.gradle</code>에 다음을 추가한다.<br />
<pre><code class="gradle">eclipse {
classpath {
containers.removeAll(EclipseWtpPlugin.WEB_LIBS_CONTAINER)
}
wtp {
component {
file {
withXml { provider ->
def module = provider.asNode().'wb-module'
module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/classpath') }*.replaceNode {}
}
}
}
}
}</code></pre>
<div>
그리고 Gradle -> Refresh All을 하면 Web App Libraries에 프로젝트 참조를 제외한 모든 JAR 참조가 날아가 있는 것을 볼 수 있다.</div>
<br />
<h3>
WAR Overlay</h3>
Gradle의 WAR Overlay 자체는 참 간단하긴 한데... 이클립스 플러그인이 이를 인식하지 못하니 참으로 통탄할 일이로다.<br />
<br />
그래서 역시 설정을 직접 고치면 된다. <strike><span style="font-size: x-small;">안되는게 어디있나... 안되면 되게하라.</span></strike><br />
<br />
<code>build.gradle</code>에 다음을 추가한다.<br />
<h4>
Eclipse 4.4 이전</h4>
<pre><code class="gradle">eclipse {
wtp {
component {
file {
withXml { provider ->
def module = provider.asNode().'wb-module'
module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/overlay') }.each { entry ->
entry.'dependency-type'.each {
it.value = 'consumes'
}
}
}
whenMerged { component ->
// 현재 프로젝트: 무조건 제일 위에 와야함
component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/slf/?includes=**/**&excludes=META-INF/MANIFEST.MF')
// overlay할 WAR 프로젝트들을 밑에 나열한다.
component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/prj/OverlayProject?includes=**/**&excludes=META-INF/MANIFEST.MF')
...
}
}
}
}
}</code></pre>
<h4>
Eclipse 4.4 이후</h4>
<pre><code class="gradle">eclipse {
wtp {
component {
file {
withXml { provider ->
def module = provider.asNode().'wb-module'
module.'dependent-module'.findAll { it.@'handle'.startsWith('module:/overlay') }.each { entry ->
entry.@'handle' = entry.@'handle'.replaceAll('module:/overlay', 'module:/resource')
entry.'dependency-type'.each {
it.value = 'consumes'
}
}
}
whenMerged { component ->
// overlay할 WAR 프로젝트들만 밑에 나열한다. 존나좋군?
component.wbModuleEntries += new org.gradle.plugins.ide.eclipse.model.WbDependentModule('/', 'module:/overlay/OverlayProject/OverlayProject?includes=**/**&excludes=META-INF/MANIFEST.MF')
...
}
}
}
}
}</code></pre>
<div>
<code>OverlayProject</code>는 overlay할 WAR 프로젝트명을 써준다. 여러 개의 프로젝트를 overlay할 경우 overlay 순서에 유의해서 추가해주면 된다.<br />
<br />
그리고 Gradle -> Refresh All을 하면 정상적으로 WAR Overlay를 인식하고, WTP Server Deploy도 잘 작동한다.<br />
<br />
잡담: 이거 삽질이 가장 힘들었다.</div>
</div>
</div>
디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-10536760050411832292014-03-10T14:31:00.001+09:002014-03-12T16:19:04.002+09:00JPA - Hibernate Specific WTF RulesJPA 구현 중 가장 널리 쓰이는 것들이 <a href="https://www.eclipse.org/eclipselink" target="_blank">EclipseLink</a>와 <a href="http://hibernate.org/" target="_blank">Hibernate</a>이다.<br />
JPA 2.0 이후로는 EclipseLink가 레퍼런스 구현임에도 일단 한국에서는 EclipseLink보다는 Hibernate를 더 많이 쓰는데, Hibernate가 아무래도 역사도 오래됐고 속도는 느리지만 그만큼 편의기능이 많아서 그렇지 않나 싶다.<br />
<span style="font-size: x-small;"><strike>한국에서 EclipseLink 미는 사람은 저밖에 없는듯?</strike></span><br />
<br />
여기서는 Hibernate 특유의 정신나간 특징들을 내 맘대로 (...) 찝어보겠다.<br />
<ol>
<li><code>SEQUENCE</code>를 지원하지 않는 DBMS(예를 들면 MSSQL이나 MySQL)를 타겟으로 <code>@GeneratedValue(strategy = GenerationType.SEQUENCE)</code>를 사용하면 좆된다.</li>
<li>Spring에서 엔티티를 <code>@SessionAttribute</code>로 물릴 경우 <code>@ElementCollection</code>의 <code>fetch</code> 속성을 <code>EAGER</code>로 설정해야 한다.<br />안그러면 나중에 엔티티 값을 수정할 때 초기화 되지 않았다고 뭐라 그런다.</li>
<li><code>@ElementCollection</code>의 <code>fetch</code> 속성을 <code>EAGER</code>로 설정했으면 Hibernate에서 제공하는 <code>@Fetch</code>로 긁어올 방식을 설정해 주어야 한다.<br />안붙여주면 처음 실행할 때부터 테이블 Alias가 없네 하면서 정신줄 놓는다.</li>
</ol>
<div>
일단은 여기까지.</div>
<div>
더 밝혀낸 것이 있으면 계속 업데이트될 지도 모른다.</div>
디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-91794625268462539582013-11-04T16:12:00.000+09:002015-05-20T02:21:23.941+09:00java.io.IOException: error=12, Cannot allocate memory? WTF?!자바의 <a href="http://bugs.sun.com/view_bug.do?bug_id=5049299" target="_blank">유서깊은(?) 골칫거리</a>로, 자바에서 <code>fork() + exec()</code> 하는 방식에 문제가 있기 때문이다.<br />
(자세한 내용은 <a href="http://serverfault.com/a/366112" target="_blank">여기</a>를 참고하면 좋다.)<br />
<br />
위 링크에서 4가지 해결책이 나온다.<br />
<ol>
<li>닥치고 메모리 증설</li>
<li><code>fork()</code> 호출시 <code>swapfile</code>을 추가하도록 꼼수 사용</li>
<li>리눅스: <code>sysctl</code>의 <code>vm.overcommit_memory</code> 속성을 <code>1</code>로 설정</li>
<li>POSIX 호환 시스템: <code>fork() + exec()</code> 대신 <code>posix_spawn()</code> 사용하기</li>
</ol>
<br />
리눅스라면 <code>vm.overcommit_memory=1</code> 이 아무래도 빠르고 편하겠지만, 동작방식이 후덜덜하므로 <span style="font-size: x-small;">overcommit이 일어날 때 커널에서 노는 메모리를 수거하는데, 이 때 어떠한 확인도 없이 막무가내로 수거하므로 <span style="color: #cc0000;">잠자고 있는 멀쩡한 다른 프로세스의 메모리를 수거할 수 있다!</span></span> 미션 크리티컬한 시스템에는 그냥 <a href="https://namu.wiki/w/%ED%95%98%EC%A7%80%EB%A7%88%EB%9D%BC" target="_blank">하지마라</a>. 그런 시스템이라면 2웨이 이상일테니 그냥 닥치고 1번이 더 정신건강에 좋을 것이다. <span style="font-size: x-small;"><strike>돈은 좀 많이 깨지겠지만 내가 <a href="https://namu.wiki/w/%EC%95%8C%20%EA%B2%8C%20%EB%AD%90%EC%95%BC" target="_blank">알 게 뭐야</a>...</strike></span><br />
<br />
또한 자바의 <code>fork() + exec()</code>를 <code>posix_spawn()</code>으로 교체해주는 <a href="https://github.com/axiak/java_posix_spawn" target="_blank"><code>java_posix_spawn</code></a>이 개발되어 있으므로, 이를 고려해보는 것도 좋을 것이다. <strike><span style="font-size: x-small;">하지만 실제로는 <code>posix_spawn()</code>을 사용하지 않는다는게 <a href="https://namu.wiki/w/YOU%20JUST%20ACTIVATED%20MY%20TRAP%20CARD" target="_blank">함정</a>. <code>vfork()</code> 쓴댄다.</span></strike>
<br />
<h4>
덤</h4>
<code>vm.overcommit_memory</code> 관련 문서는 <a href="http://www.redhat.com/magazine/001nov04/features/vm/">레드햇 문서</a>와 <a href="http://www.novell.com/support/kb/doc.php?id=7002775">노벨 문서</a>를 보면 대략 좋다.
<br />
<br />
<hr />
<b>2015-02-23</b>:<br />
리눅스에 zram이라는게 있는데, 써본 결과 매우 좋소!<br />
zram 쓰기 전에는 메모리 10그램으로도 헥헥대던게, zram 쓰고 난 다음부터는 메모리에 여유가 생겼다.<br />
<span style="font-size: x-small;"><strike>물논 CPU가 그만큼 일을 더 하게 되지만 내가 알 게 뭐야...</strike></span>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-5115651794273298242013-11-04T15:42:00.003+09:002019-07-23T19:03:15.072+09:00Ubuntu에서 LD_LIBRARY_PATH가 안먹을때<a href="https://divinespear.github.io/tech/2013/11/04/ubuntu-ld-library-path.html">https://divinespear.github.io/tech/2013/11/04/ubuntu-ld-library-path.html</a>디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0tag:blogger.com,1999:blog-6122755808652679809.post-1468938624044229632013-11-01T17:05:00.001+09:002015-05-20T02:19:57.038+09:00bash로 git의 빈 디렉토리 컨트롤하기(2014-11-19: 좀 더 깔끔하게 수정)
<br />
<br />
<br />
<a href="http://www.git-scm.com/" target="_blank">git</a>의 유일한 단점은 빈 디렉토리를 관리 할 수 없다는 것이다. 따라서 빈 디렉토리를 강제로 관리하려면 대충 아무 파일이나 던져놓는 수 밖에 없다. <br />
<a href="https://code.google.com/p/markemptydirs" target="_blank">MarkEmptyDirs</a>라는 툴이 있기는 하지만, 깔기 귀찮다. 그냥 bash 있는거 쓰고 말지…<br />
<br />
<h3>
본문</h3>
<pre><code class="bash">find . -name ".emptydir" -delete; find -empty -type d | while read x; do touch "$x/.emptydir"; done</code></pre>
<br />
<a href="https://namu.wiki/w/%EC%B0%B8%20%EC%89%BD%EC%A3%A0" target="_blank">참 쉽죠?</a><br />
<br />
<span style="color: #999999;">주의: 이 스크립트에는 버그가 있지만 여백이 부족하므로 <a href="https://namu.wiki/w/%EB%8D%94%20%EC%9D%B4%EC%83%81%EC%9D%98%20%EC%9E%90%EC%84%B8%ED%95%9C%20%EC%84%A4%EB%AA%85%EC%9D%80%20%EC%83%9D%EB%9E%B5%ED%95%9C%EB%8B%A4" target="_blank">더 이상의 자세한 설명은 생략한다</a>. <strike><span style="font-size: x-small;">차마 귀찮아서라고는 못하겠다.</span></strike></span> 예제를 수정하면서 더이상은 naver!<br />
<br />
<h3>
해설</h3>
<ol>
<li>먼저 하위 디렉토리에서 <code>.emptydir</code> 파일을 찾아서 제거한다.</li>
<li>그 다음 빈 디렉토리를 찾아서 크기가 0인 <code>.emptydir</code> 파일을 생성한다.</li>
</ol>
<a href="https://namu.wiki/w/%EC%B0%B8%20%EC%89%BD%EC%A3%A0" target="_blank">참 쉽죠?</a> (2)<br />
<br />
<h3>
응용</h3>
이클립스에 올려서 프로젝트에 먹일 수도 있다.<br />
<br />
Run → External Tools → External Tools Configuration... 에 가서 새 프로그램 항목을 만든다.<br />
<br />
<ol>
<li>이름은 적절하게 등록한다.</li>
<li>Location: <code>/bin/bash</code></li>
<li>Working Directory: <code>${project_loc}</code></li>
<li>Arguments: <code>-c "find . -name \".emptydir\" -delete; find -empty -type d | while read x; do touch \"$x/.emptydir\"; done"</code></li>
</ol>
<br />
그 다음 Run → External Tools → Organize Favorites... 에서 방금 만든 것을 선택하면 툴바에서 한 방에 실행할 수 있다.<br />
<br />
<a href="https://namu.wiki/w/%EC%B0%B8%20%EC%89%BD%EC%A3%A0" target="_blank">참 쉽죠?</a> (3)디비네스페아르http://www.blogger.com/profile/11535978720127560156noreply@blogger.com0