freeism.co.kr Report : Visit Site


  • Ranking Alexa Global: # 2,413,422

    Server:Apache...

    The main IP address: 115.68.168.105,Your server Korea, Republic of,Seoul ISP:Smileserv  TLD:kr CountryCode:KR

    The description :a programmer's blog...

    This report updates in 11-Jun-2018

Technical data of the freeism.co.kr


Geo IP provides you such as latitude, longitude and ISP (Internet Service Provider) etc. informations. Our GeoIP service found where is host freeism.co.kr. Currently, hosted in Korea, Republic of and its service provider is Smileserv .

Latitude: 37.568260192871
Longitude: 126.97782897949
Country: Korea, Republic of (KR)
City: Seoul
Region: Seoul-t'ukpyolsi
ISP: Smileserv

HTTP Header Analysis


HTTP Header information is a part of HTTP protocol that a user's browser sends to called Apache containing the details of what the browser wants and will accept back from the web server.

Transfer-Encoding:chunked
Set-Cookie:pll_language=ko; expires=Mon, 10-Jun-2019 22:47:52 GMT; Max-Age=31536000; path=/wp/; secure
Server:Apache
Connection:close
Link:; rel="https://api.w.org/", ; rel=shortlink
Date:Sun, 10 Jun 2018 22:47:52 GMT
Content-Type:text/html; charset=UTF-8

DNS

soa:ns1.iwinv.kr. root.iwinv.kr. 2017111605 3600 900 604800 86400
ipv4:IP:115.68.168.105
ASN:38700
OWNER:SMILESERV-AS-KR SMILESERV, KR
Country:KR
mx:MX preference = 10, mail exchanger = ASPMX.daum.net.
MX preference = 20, mail exchanger = ALT.ASPMX.daum.net.

HtmlToText

thinkcubes a programmer's blog 홈 programmer thinking daily life miscellanea gadget about me 최근 글 spring batch에서 application 설정 entityutil.java 구현체 독일직구 ipz-ffm = 옥천버뮤다 spring @configurationproperties 분석 결제 오류 기사에 대한 소고 intellij 단축키 – 최근 수정 내용 탐색 / 비교 spring data rest url 생성 분석 최근 댓글 freeism ( json에서는 comment(주석)이 허용되지 않음 ) 개발자a ( json에서는 comment(주석)이 허용되지 않음 ) freeism ( 블로그 웹호스팅 이전 그 이후 ) 개발자a ( 블로그 웹호스팅 이전 그 이후 ) 블로그 웹호스팅 이전 그 이후. – thinkcubes ( 블로그 웹호스팅 이전 ) 그 밖의 기능 로그인 글 rss 댓글 rss wordpress.org search spring batch에서 application 설정 2018/06/02 by freeism · 0 comments 새로 스프링 배치 프로그램을 만들었는데, 테스트 환경에서는 특별히 이슈가 없다가 운영 환경에서 오류가 발생했다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 2018 - 05 - 21 16 : 00 : 43.044 info 5372 -- - [ main ] o .s .j .e .a .annotationmbeanexporter : registering beans for jmx exposure on startup 2018 - 05 - 21 16 : 00 : 43.099 error 5372 -- - [ main ] o .apache .catalina .core .standardservice : failed to start connector [ connector [ http / 1.1 - 8080 ] ] org .apache .catalina .lifecycleexception : failed to start component [ connector [ http / 1.1 - 8080 ] ] at org .apache .catalina .util .lifecyclebase .start ( lifecyclebase .java : 167 ) ~ [ tomcat - embed - core - 8.5.29.jar ! / : 8.5.29 ] at org .apache .catalina .core .standardservice .addconnector ( standardservice .java : 225 ) ~ [ tomcat - embed - core - 8.5.29.jar ! / : 8.5.29 ] at org .springframework .boot .web .embedded .tomcat .tomcatwebserver .addpreviouslyremovedconnectors ( tomcatwebserver .java : 256 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .web .embedded .tomcat .tomcatwebserver .start ( tomcatwebserver .java : 198 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .web .servlet .context .servletwebserverapplicationcontext .startwebserver ( servletwebserverapplicationcontext .java : 300 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .web .servlet .context .servletwebserverapplicationcontext .finishrefresh ( servletwebserverapplicationcontext .java : 162 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .context .support .abstractapplicationcontext .refresh ( abstractapplicationcontext .java : 553 ) [ spring - context - 5.0.5.release.jar ! / : 5.0.5.release ] at org .springframework .boot .web .servlet .context .servletwebserverapplicationcontext .refresh ( servletwebserverapplicationcontext .java : 140 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .springapplication .refresh ( springapplication .java : 759 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .springapplication .refreshcontext ( springapplication .java : 395 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .springapplication .run ( springapplication .java : 327 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .springapplication .run ( springapplication .java : 1255 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at org .springframework .boot .springapplication .run ( springapplication .java : 1243 ) [ spring - boot - 2.0.1.release.jar ! / : 2.0.1.release ] at kr .co .freeism .batch .freeismbatchapplication .main ( freeismbatchapplication .java : 9 ) [ classes ! / : na ] at sun .reflect .nativemethodaccessorimpl .invoke0 ( native method ) ~ [ na : 1.8.0_144 ] at sun .reflect .nativemethodaccessorimpl .invoke ( nativemethodaccessorimpl .java : 62 ) ~ [ na : 1.8.0_144 ] at sun .reflect .delegatingmethodaccessorimpl .invoke ( delegatingmethodaccessorimpl .java : 43 ) ~ [ na : 1.8.0_144 ] at java .lang .reflect .method .invoke ( method .java : 498 ) ~ [ na : 1.8.0_144 ] at org .springframework .boot .loader .mainmethodrunner .run ( mainmethodrunner .java : 48 ) [ voyager - batch - 0.0.1 - snapshot .jar : na ] at org .springframework .boot .loader .launcher .launch ( launcher .java : 87 ) [ voyager - batch - 0.0.1 - snapshot .jar : na ] at org .springframework .boot .loader .launcher .launch ( launcher .java : 50 ) [ voyager - batch - 0.0.1 - snapshot .jar : na ] at org .springframework .boot .loader .jarlauncher .main ( jarlauncher .java : 51 ) [ voyager - batch - 0.0.1 - snapshot .jar : na ] caused by : org .apache .catalina .lifecycleexception : protocol handler start failed at org .apache .catalina .connector .connector .startinternal ( connector .java : 1020 ) ~ [ tomcat - embed - core - 8.5.29.jar ! / : 8.5.29 ] at org .apache .catalina .util .lifecyclebase .start ( lifecyclebase .java : 150 ) ~ [ tomcat - embed - core - 8.5.29.jar ! / : 8.5.29 ] . . . 21 common frames omitted caused by : java .net .bindexception : address already in use at sun .nio .ch .net .bind0 ( native method ) ~ [ na : 1.8.0_144 ] at sun .nio .ch .net .bind ( net .java : 433 ) ~ [ na : 1.8.0_144 ] at sun .nio .ch .net .bind ( net .java : 425 ) ~ [ na : 1.8.0_144 ] at sun .nio .ch .serversocketchannelimpl .bind ( serversocketchannelimpl .java : 223 ) ~ [ na : 1.8.0_144 ] at sun .nio .ch .serversocketadaptor .bind ( serversocketadaptor .java : 74 ) ~ [ na : 1.8.0_144 ] at org .apache .tomcat .util .net .nioendpoint .bind ( nioendpoint .java : 210 ) ~ [ tomcat - embed - core - 8.5.29.jar ! / : 8.5.29 ] 2018 - 05 - 21 16 : 00 : 43.136 error 5372 -- - [ main ] o .s .b .d .loggingfailureanalysisreporter : * * * * * * * * * * * * * * * * * * * * * * * * * * * application failed to start * * * * * * * * * * * * * * * * * * * * * * * * * * * description : the tomcat connector configured to listen on port 8080 failed to start . the port may already be in use or the connector may be misconfigured . action : verify the connector 's configuration, identify and stop any process that' s listening on port 8080 , or configure this application to listen on another port . 결국은 8080 포트가 충돌나서 오류가 발생한 것이었다. 배치를 여러 번 실행시키는 경우 톰캣이 제대로 shutdown시키지 못해서 발생하는 문제였다. 그런데 스프링 배치인데, 굳이 톰캣을 8080 포트로 실행해야 할 이유가 있을까? 찾아보니 스프링 배치에서는 기본적으로 톰캣을 8080 포트로 로딩하고, 배치 모니터링을 제공한다. (사실 엄밀히 따지면 배치가 실행되는 동안만 톰캣이 유효하도록 되어 있어서, 내 경우에는 별로 의미가 없는 모니터링이었다) 그래서 찾아봤더니, 아래처럼 설정을 하면 톰캣을 실행시키지 않는다고 한다. 1 2 main : web-environment : false 앗, 그런데 deprecate되었다고 warning이 발생한다. 해당 설정을 하는 곳을 찾아봤더니, 1 2 3 4 5 6 7 8 9 10 11 /** * sets if this application is running within a web environment. if not specified will * attempt to deduce the environment based on the classpath. * @param webenvironment if the application is running in a web environment * @deprecated since 2.0.0 in favor of * {@link #setwebapplicationtype(webapplicationtype)} */ @deprecated public void setwebenvironment ( boolean webenvironment ) { this . webapplicationtype = webenvironment ? webapplicationtype . servlet : webapplicationtype . none ; } 위 옵션으로 서블릿을 실행할 것인지 아닌지 처리하는 설정이고, 2.0 버전부터 deprecate되었다고 친절하게 적어놨다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 /** * sets the type of web application to be run. if not explicitly set the type of web * application will be deduced based on the classpath. * @param webapplicationtype the web application type * @since 2.0.0 */ public void setwebapplicationtype ( webapplicationtype webapplicationtype ) { assert . notnull ( webapplicationtype , "webapplicationtype must not be null" ) ; this . webapplicationtype = webapplicationtype ; } /** * an enumeration of possible types of web application. * * @author andy wilkinson * @author brian clozel * @since 2.0.0 */ public enum webapplicationtype { /** * the application should not run as a web application and should not start an * embedded web server. */ none , /** * the application should run as a servlet-based web application and should start an * embedded servlet web server. */ servlet , /** * the application should run as a reactive web application and should start an * embedded reactive web server. */ reactive } springboot 2.0 부터는 아래처럼 webapplicationtype을 통해서 설정이 가능하다고 한다. 결국 application.yml 에 아래처럼 설정하면 된다. 1 2 main : web-application-type : none entityutil.java 구현체 2018/06/01 by freeism · 0 comments java에서 jpa를 쓰다보면, 항상 select를 선으로 진행하게 되는 경우가 많다. 예를 들어, name과 age를 입력했는데, name이 unique하다면, db에서 name으로 select를 해보고, 있으면 setage() 를 통해서 update를 진행해야 하고, 없으면 해당 entity를 새로 생성해서 insert를 해야한다. 그래서 아래와 같은 로직을 사용하는 경우가 많다. 1 2 3 4 5 6 7 8 9 10 11 12 public void putsomeentity ( string name , int age ) { someentity someentity = someentityrepository . findbyname ( name ) ; if ( someentity == null ) { someentity = new someentity ( ) ; someentity . setname ( name ) ; } someentity . setage ( age ) ; someentityrepository . save ( someentity ) ; } 매번 null check하는 것은 불필요한데, 은근히 많이 사용되는 패턴이다. 그래서 아래처럼 utility class를 구현했다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package kr . co . freeism . util ; import com . google . common . base . preconditions ; import lombok . accesslevel ; import lombok . noargsconstructor ; import javax . persistence . entity ; /** * @author freeism * @since 2018. 6. 2. */ @noargsconstructor ( access = accesslevel . private ) public class entityutil { /** * obj가 null인지 판단해서 * null이면 명시한 class의 default 생성자를 호출한 객체를 리턴하고 * null이 아닌 경우에는 입력받은 obj를 그대로 리턴함 * * @param obj nullcheck 할 대상 객체, @entity 어노테이션이 필요함. * @param clazz null인 경우에 반환할 디폴트 객체의 타입 * @return obj != null ? obj : new obj() * @throws nullpointerexception if clazz is null. * @throws illegalargumentexception if @entity annotaion is missing. * @throws illegalargumentexception if clazz.newinstance() throws exception. */ public static <t> t defaultentity ( t obj , class <t> clazz ) { preconditions . checknotnull ( clazz , "clazz must be defined." ) ; preconditions . checkargument ( clazz . isannotationpresent ( entity . class ) , "clazz must be entity annotated class." ) ; try { return obj == null ? clazz . newinstance ( ) : obj ; } catch ( illegalaccessexception | instantiationexception e ) { throw new illegalargumentexception ( "clazz must have default constructor." ) ; } } } 1 2 3 4 5 6 7 public void putsomeentity ( string name , int age ) { someentity someentity = entityutil . defaultentity ( someentityrepository . findbyname ( name ) , someentity . class ) ; someentity . setage ( age ) ; someentityrepository . save ( someentity ) ; } 독일직구 ipz-ffm = 옥천버뮤다 2018/03/22 by freeism · 0 comments 와 독일에서 직구한 제품이 있는데, 독일 송장이 엄청 빠르게 진행되기에 “1주일 안에 받겠는데? 정말 지구촌이네” 했더니만, 저 상태로 1주일이 넘게 지나버렸다. 독일은 바다도 없는데, 배로 운송하는 건지… 중간에 제품이 유실된 건지 알 수가 없다. 그런데 인터넷에서 아래 같은 내용을 찾았다. 1 ipz-ffrm이 옥천 버뮤다만큼 유명한 곳이구나. 예전에 살 땐 모르고 지나갔었는데 ㅠ.ㅠ 한국에 옥천 버뮤다가 있다면.. 독일에는 택배 숙성 장소 ipz-ffm 이 있는듯.. 추가. 10일 뒤에 관세청에 등록되었다고 연락이 왔다. 난 내 물건들이 국제미아 된 줄 알고 걱정을… 기다리면 관세납부 하라고 연락올 듯. http://www.ppomppu.co.kr/zboard/view.php?id=bike&no=298147 ↩ spring @configurationproperties 분석 2018/03/19 by freeism · 0 comments 배경 현재 개발하고 있는 것은 ‘서비스의 정산’에 대한 서비스이다. 그런데 정산이라는 것이 결제가 있다면 항상 들어가야 하는 미들웨어와 같은 성격을 지니고 있다.그러다보니 서비스의 설정 정보들이 각 프로젝트에 혼재해있어서 정리를 해보려고 한다. 현재 상황 전체적으로 정산에 대한 공통 부분을 사용하기 위해 settlement-common 이라는 모듈을 사용하고 있다. 각 서비스에서는 settlement-common 에 대한 의존성을 연결하여 사용한다. 그런데 정산에서 사용하는 property 정보들이 모두 하위 서비스에서 정의하여 사용하고 있다. 예를 들면 erp.url 은 하위 서비스의 application.yml 에 정의되어 있다. 1 2 3 4 5 6 7 8 9 10 11 12 13 spring : profiles : default --- spring : profiles : default erp : url : "https ://localhost" --- spring : profiles : production erp : url : "https ://erp-server" --- 하고 싶은 것 보통의 경우에는 common 공통의 설정을 로딩해서 사용하고, 일부의 케이스에 대해서만 하위 서비스에서 설정을 overriding하고 싶었다. application.yml 에 다른 설정과 함께 섞여 있는 것도 제거하고 싶었다. @configurationproperties 사용 아래처럼 property정보를 bean으로 등록해서 사용하는 것이 @value 를 사용하는 것보다 나을 거란 생각을 했다. @component 가 없어도 bean으로 사용할 수 있는데 굳이 쓴 이유는, ide에서 bean mapping이 안된다는 waring이 발생하는 게 싫어서이다. 그리고 @configurationproperties 는 setter를 이용해서 값을 주입하는 것도 약간은(?) 신기한 부분이다. 최근 스프링의 추세가 reflection을 통한 강제 주입이라서. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } 파일분리 (application.yml -> settlement.yml) 애사당초 얘기했던 것처럼, 하위 프로젝트의 application.yml 에서 모든 것을 정의해서 사용하면, 어떤 property를 어떤 모듈에서 사용하는지 파악하기 힘들고, 이후에 리팩토링할 때에도 장애물이 된다. 그래서 @configurationproperties 의 locations attribute를 활용하여 파일을 분리하였다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" , locations = " classpath : / settlement . yml ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } locations attribute deprecated 그런데 locations attribute가 deprecate되었다. 심지어 최신 버전의 springboot에서는 아예 제거되었다. 1 그래서 별로 권장하지 않는다고는 하지만, @propertysource 를 활용하기로 했다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" ) @propertysource ( value = { "classpath:/settlement.yml" } ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } spring.active.profile 기능 활용 아래처럼 springel을 활용해서 각 profile별로 필요한 설정을 import할 수 있다. 다만 같은 값의 overriding의 경우 선언한 순서에 영향을 받는다. 2 특히 아래와 같은 경우에는 @propertysource 에 선언된 array 중에 나중에 선언된 것이 override된다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" ) @propertysource ( value = { "classpath:/settlement.yml" , "classpath:/settlement-${spring.profiles.active}.yml" } ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } 문제 발생, yml 파싱 불가 위처럼 로직을 짜고 테스트를 돌려보면, npe가 발생한다. 이유인즉슨, bean이 제대로 setting이 되지 않았기 때문인데, 디버거를 통해 확인했더니 yml 파일을 인식하지 못한다. 3 그래서 yml 파일을 사용할 수 있도록 factory 클래스를 추가 구현하였다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" ) @propertysource ( value = { "classpath:/settlement.yml" , "classpath:/settlement-${spring.profiles.active}.yml" } , factory = yamlpropertysourcefactory . class ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } public class yamlpropertysourcefactory implements propertysourcefactory { @override public propertysource < ? > createpropertysource ( string name , encodedresource resource ) throws ioexception { return name != null ? new yamlpropertysourceloader ( ) . load ( name , resource . getresource ( ) , null ) : new yamlpropertysourceloader ( ) . load ( getnameforresource ( resource . getresource ( ) ) , resource . getresource ( ) , null ) ; } private static string getnameforresource ( resource resource ) { string name = resource . getdescription ( ) ; if ( ! stringutils . hastext ( name ) ) { name = resource . getclass ( ) . getsimplename ( ) + "@" + system . identityhashcode ( resource ) ; } return name ; } } yml 파싱은 잘되는데, profile은 어떻게… application.yml 처럼 array 형식으로 정의하고 profile별로 property를 꺼내 쓰고 싶은데, 위의 yamlpropertysourcefactory 를 보면, profile을 null로 넘겼다. 해당 profile을 넣어주면 제대로 파싱될 것 같은데, factory 파일이 bean도 아니라서 environment 를 사용할 수 없고 어떻게 해야할지는 좀더 생각해봐야 할 것 같다. 하위 서비스에서 설정 완료 하위 서비스에서는 아래처럼 추가 설정을 overriding해서 사용할 수 있다. 1 2 3 4 5 6 7 8 9 @configuration @propertysource ( value = { "classpath:/settlement.yml" , "classpath:/settlement-${spring.profiles.active}.yml" , "classpath:/settlement-admin.yml" , "classpath:/settlement-admin-${spring.profiles.active}.yml" } , factory = yamlpropertysourcefactory . class ) public class adminsettlementconfig { } 또 다른 문제, 파일이 없을 경우 그런데 settlement-common 은 하위 프로젝트들의 모든 spring.profiles 를 알 수 없다. 그래서 정의되지 않은 profile이 들어올 경우, 리소스를 읽을 수 없다면서 settlement-${spring.profiles.active}.yml 구문에서 오류가 발생한다. 그래서 아래처럼 @propertysource 를 분리하고, ignoreresourcenotfound 속성을 추가해서 처리하였다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" ) @propertysources ( { @propertysource ( value = { "classpath:/settlement.yml" } , factory = yamlpropertysourcefactory . class ) , @propertysource ( value = { "classpath:/settlement-${spring.profiles.active}.yml" } , ignoreresourcenotfound = true , factory = yamlpropertysourcefactory . class ) } ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } 그런데 계속 오류가 발생한다. 디버거를 통해서 확인하였더니, yamlpropertysourcefactory 내에서 yamlpropertysourceloader 를 사용하고 있는데, 여기에서 리소스가 없는 경우에 대해 기존의 경우와 다르게 return을 하고 있다. 결국 npe가 발생한다. (기본 factory를 사용하는 경우에는 문제없음) 결론 yml 포맷이 훨씬 구조화된 형식이라 properties 파일보다 권장되기도 하는데, 사용하려니 현실적인 벽에 많이 부딪혀서 properties 파일을 우선 사용하는 것으로 선회하였다. 끝까지 고쳐보지 못하고, 실패를 결론으로 기록하려니 좀 아쉽긴 한데, 시간도 시간인지라… 나중에는 다시 볼 일이 생기지 않을까 싶은 생각으로 일단 여기까지 기록해둔다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @component @enableconfigurationproperties @configurationproperties ( prefix = "settlement" ) @propertysources ( { @propertysource ( value = { "classpath:/settlement.properties" } ) , @propertysource ( value = { "classpath:/settlement-${spring.profiles.active}.properties" } , ignoreresourcenotfound = true ) } ) public class settlementproperties { @getter @setter private erp erp ; public static class erp { @getter @setter string url ; } } @configuration @propertysources ( { @propertysource ( value = { "classpath:/settlement.properties" } ) , @propertysource ( value = { "classpath:/settlement-${spring.profiles.active}.properties" } , ignoreresourcenotfound = true ) , @propertysource ( value = { "classpath:/settlement-admin.properties" } ) , @propertysource ( value = { "classpath:/settlement-admin-${spring.profiles.active}.properties" } , ignoreresourcenotfound = true ) } ) public class adminsettlementconfig { } 추가1 resource 파일은 classpath로 복사될 때, 같은 이름이면 우선순위에 따라 overwrite된다. 즉, application.properties 또는 application.yml 파일이 settlement-common 에 있는데, 하위 모듈에서 동일한 파일이 있는 경우에는 내용에 상관없이 하위 모듈의 것을 사용하게 된다. 심지어 해당 property가 없는 경우라 할지라도 common의 것을 사용할 수 없다. override가 아니라 파일이 overwrite되는 것에 유의! 1 2 3 4 5 6 7 ( settlement - common ) application .properties settlement .erp .url = https : / / common ( admin ) application .properties ( 결과 ) 없음 ( npe 가 발생하거나 . . . 구현에 따라서 ) 추가2 파일이 다르고 키가 같은 property의 경우에는 해당 값을 우선순위에 따라 override한다. 1 2 3 4 5 6 7 8 ( settlement - common ) application .properties settlement .erp .url = https : / / common ( admin ) application .properties settlement .erp .url = https : / / admin ( 결과 ) https : / / admin http://kingbbode.tistory.com/39 ↩ https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html ↩ http://wonwoo.ml/index.php/post/647 ↩ 결제 오류 기사에 대한 소고 2018/03/13 by freeism · 0 comments 배달앱에서 동의없이 멋대로 신용카드 결제? … ‘있을 수 없는일’ 이라는 기사를 보고 결제 시스템 개발자로서 짧게 적어본다. (참고로 해당 업체와는 아무 관련없음) 부산시 기장군에 사는 최 모(남)씨는 지난 25일 오후 6시경 ‘요기요’ 앱으로 치킨을 주문하려고 앱을 실행했다. 치킨 전문점을 선택해 메뉴를 살피던 중 오류가 발생해 ‘주문이 취소되었습니다’란 문구가 뜨고 앱이 종료됐다. 배달앱을 이용하던 소비자가 ‘결제’ 버튼을 누르지 않았는데도 등록된 신용카드로 결제됐다며 문제 제기했다. 업체 측은 시스템상 일어날 수 없는 오류라고 반박했다. 관계자는 “‘1초 결제’가 신용카드를 등록해놓으면 사인이나 비밀번호 없이 바로 결제되는 구조라 주문을 진행하는 과정서 고객이 오해한 부분이 생겼을 수도 있지 않을까 싶다”며…(후략) 서비스를 개발하면서 수없이 많이 고객문의에 대해 응답을 해봤던 경험으로 보면, 대부분 고객은 정직하다. 그리고 서비스 담당자는 그런 관점에서 오류를 디버깅해 볼 필요가 있다고 생각한다. 혹은 고객이 어뷰징을 한다고 생각한다면, 해당 근거를 착실히 찾아볼 필요가 있다. 그래서 개발자는 로그나 db데이터를 착실하게 쌓아야 할 의무가 있다. 보통 고객이 어뷰징을 하게 되는 이유는 대부분 프로모션때문이다. 할인이나 쿠폰, 이벤트 참여 등의 어떤 이익을 가지고 어뷰징을 진행한다. 혹은 본인의 실수를 덮기 위해서인 경우도 있고, 정말 고객이 인지하지 못한 채 잘못된 경우도 있긴 하다. 나도 위와 비슷한 경험을 한 적이 있다. 아마존 앱에서 물건을 구입 했을 때 였다. 아마존 에서 krw(원화)로 구입하면 이중환전 이 된다는 사실을 알고 있기 때문에 usd(달러)인 것을 확인하고 결제를 진행하였다. 그런데 어느 날 발송된 인보이스를 확인했더니, 아마존 자체 환율(보통 환율보다 100원정도 비쌈)으로 이중환전 이 된 것을 확인했다. 마치 ‘결제 버튼을 누르지도 않았는데도 결제가 됐다’며 의문을 제기한 위 기사처럼 ‘proceed with usd’를 확인하고 버튼을 눌렀는데, krw로 결제가 되어버린 것이다. 나름 개발자로서 몇 가지 테스트를 진행해봤다. 아마존 앱에는 유저의 결제를 빠르게 진행하기 위해서 상단, 중단, 하단에 결제 버튼이 있는데, 그 중에 하나가 오작동하는 모양이다. 라벨은 ‘proceed with usd’로 되어 있는데, 내부 동작이 locale을 따라 가는 것인지 krw로 결제되어 버렸다. 명백한 버그라고 생각하고 고객센터에 연락했지만, ‘그럴리 없다'(마치 위 기사처럼)는 답변이었다. 게다가 그 친구들은 locale이 미국으로 되어 있을테니, 나와 같은 경우가 재현될리도 없다. 아마 테스트도 부족했을 것 같고… 나는 이미 재현되는 상황이었기 때문에 고객센터에서 처리하지 말고, 개발부서로 포워딩을 요청했는데, 사실상 거절당했다. 언어의 장벽일지도 모르겠다. 사실 요기요 케이스가 버그인지, 고객실수인지, 고의적인 어뷰징인지는 알 수 없다. 하지만 행동의 유인이 명확하지 않은 경우에는 항상 ‘ 무죄추정의 원칙 ‘을 적용하여 생각해봐야 한다. 아무리 서비스를 잘 만들어도 버그는 존재하기 마련이다. intellij 단축키 – 최근 수정 내용 탐색 / 비교 2018/03/12 by freeism · 0 comments 별 건 아닌데, 기존에는 몰랐던 intellij 단축키 중에 편리할 것 같은 걸 찾아서 기록해둔다. 평상시에는 cmd+] 랑 cmd+[ 를 사용해서 코드를 탐색했었는데, 수정된 것만 탐색해주는 기능이 있었다. cmd+shift+back 은 가장 최근에 수정하게 된 지점으로 커서를 옮겨준다. 거기에 cmd+option+shift+화살표 를 누르면, 수정된 내용까지 diff 해준다. 1 https://www.jetbrains.com/help/idea/navigating-to-next-previous-change.html ↩ spring data rest url 생성 분석 2018/03/08 by freeism · 0 comments 최근 업무에서 /api/entities 이란 url이 존재하는데, 아무리 source를 뒤져봐도 해당 url을 찾을 수 없었다. 1 2 3 4 5 6 7 8 9 10 class checkpage extends react . component { render ( ) { return ( < basepage resource = '/api/entities' . . . / > ) ; } } 몇 번 삽질을 하다가 결국 spring-data-rest 를 사용했음을 알게 되었고, 해당 library를 초기 분석한 내용을 기록해둔다. 공식문서 1 에 따르면 dependency는 아래처럼 설정해주면 된다. 1 2 3 4 5 6 7 8 9 dependencies { compile ( "org.springframework.boot:spring-boot-starter-data-rest" ) . . . } dependencies { compile ( "org.springframework.data:spring-data-rest-webmvc:3.0.5.release" ) . . . } 그리고 기본 baseurl은 아래처럼 설정할 수 있다. (구성에 따라 아래 3가지 방법중 어느 것을 선택해도 동작한다) 1 spring . data . rest . basepath = / api 1 2 3 4 5 6 7 8 9 10 11 12 @configuration class customrestmvcconfiguration { @bean public repositoryrestconfigurer repositoryrestconfigurer ( ) { return new repositoryrestconfigureradapter ( ) { @override public void configurerepositoryrestconfiguration ( repositoryrestconfiguration config ) { config . setbasepath ( "/api" ) ; } } ; } } 1 2 3 4 5 6 7 @component public class customizedrestmvcconfiguration extends repositoryrestconfigureradapter { @override public void configurerepositoryrestconfiguration ( repositoryrestconfiguration config ) { config . setbasepath ( "/api" ) ; } } 위 설정으로 인해 앞으로 자동으로 생성되는 모든 rest url은 /api 하위에 생성된다. 1 2 3 4 5 6 7 8 9 10 11 12 13 class repositorycollectionresourcemapping implements collectionresourcemapping { private static final boolean evo_inflector_is_present = classutils . ispresent ( "org.atteo.evo.inflector.english" , null ) ; repositorycollectionresourcemapping ( repositorymetadata metadata , relprovider relprovider , repositorydetectionstrategy strategy ) { . . . class < ? > domaintype = metadata . getdomaintype ( ) ; this . domaintypemapping = evo_inflector_is _ present ? new evoinflectortypebasedcollectionresourcemapping ( domaintype , relprovider ) : new typebasedcollectionresourcemapping ( domaintype , relprovider ) ; . . . } } evo_inflector_ispresent라는 것은 org.atteo.evo.inflector.english가 classpath에 존재하는지 체크하는 것이고, 일반적인 경우라면 spring dependency로 가져왔을테니까 일반적으로는 true값이 된다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class evoinflectortypebasedcollectionresourcemapping extends typebasedcollectionresourcemapping { @override protected string getdefaultpathfor ( class <? > type ) { return english . plural ( super . getdefaultpathfor ( type ) ) ; } } class typebasedcollectionresourcemapping implements collectionresourcemapping { @ override public path getpath ( ) { string path = annotation == null ? null : annotation . path ( ) . trim ( ) ; path = stringutils . hastext ( path ) ? path : getdefaultpathfor ( type ) ; return new path ( path ) ; } protected string getdefaultpathfor ( class <? > type ) { return getsimpletypename ( type ) ; } private string getsimpletypename ( class < ? > type ) { return stringutils . uncapitalize ( type . getsimplename ( ) ) ; } } evoinflectortypebasedcollectionresourcemapping.java는 typebasedcollectionresourcemapping.java를 래핑한다. class type이라는 것은 rest api를 생성할 entity의 class name인데, uncapitalize()를 통과하면서 첫자가 소문자로 변경된다. 그리고 evoinflectortypebasedcollectionresourcemapping.java를 실행해서 영어 기준의 복수형으로 변환하여 준다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class english extends twoforminflector { /** * returns plural form of the given word. * <p> * for instance: * <pre> * {@code * english.plural("cat") == "cats"; * } * </pre> * </p> * @param word word in singular form * @return plural form of given word */ public static string plural ( string word ) { return inflector . getplural ( word ) ; } } evoinflectortypebasedcollectionresourcemapping.java의 english.java는 영어의 복수형을 제공하는 library이다. 불규칙명사(entity class name은 일반적으로 명사)인 경우도 모두 처리해주고 있다. (단, person -> people은 처리되지 않고, persons라고 나온다) 자세한 건 evo-inflector 2 를 참고하면 좋을 것 같다. 생성되는 url은 대략 아래와 같다. 아래 url은 spring-data-rest-webmvc에서 제공하는 규칙을 따르는데, repositoryentitycontroller.java를 참고하면 된다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 2018 - 03 - 07 11 : 23 : 28.869 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}],methods=[options],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.869 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}],methods=[head],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.870 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.870 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}],methods=[get],produces=[application/x-spring-data-compact+json || text/uri-list]}" 2018 - 03 - 07 11 : 23 : 28.870 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}],methods=[post],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.871 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}],methods=[options],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.871 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}],methods=[head],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.872 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.872 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}],methods=[put],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.872 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}],methods=[patch],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.873 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}],methods=[delete],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.874 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}/{property}],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.874 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}/{property}/{propertyid}],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.875 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}/{property}],methods=[delete],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.875 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}/{property}],methods=[get],produces=[application/x-spring-data-compact+json || text/uri-list]}" 2018 - 03 - 07 11 : 23 : 28.875 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}/{property}],methods=[patch || put || post],consumes=[application/json || application/x-spring-data-compact+json || text/uri-list],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.876 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/{id}/{property}/{propertyid}],methods=[delete],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.877 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search],methods=[options],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.877 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search],methods=[head],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.877 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.878 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search/{search}],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.878 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search/{search}],methods=[get],produces=[application/x-spring-data-compact+json]}" 2018 - 03 - 07 11 : 23 : 28.878 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search/{search}],methods=[options],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.879 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/{repository}/search/{search}],methods=[head],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.879 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/ || /api],methods=[head],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.879 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/ || /api],methods=[get],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.880 info 5634 - [ main ] o .s .d .r .w .repositoryresthandlermapping : mapped "{[/api/ || /api],methods=[options],produces=[application/hal+json || application/json]}" 2018 - 03 - 07 11 : 23 : 28.887 info 5634 - [ main ] o .s .d .r .w .basepathawarehandlermapping : mapped "{[/api/profile/{repository}],methods=[get],produces=[application/schema+json]}" 2018 - 03 - 07 11 : 23 : 28.887 info 5634 - [ main ] o .s .d .r .w .basepathawarehandlermapping : mapped "{[/api/profile/{repository}],methods=[get],produces=[application/alps+json || */*]}" 2018 - 03 - 07 11 : 23 : 28.887 info 5634 - [ main ] o .s .d .r .w .basepathawarehandlermapping : mapped "{[/api/profile/{repository}],methods=[options],produces=[application/alps+json]}" 2018 - 03 - 07 11 : 23 : 28.888 info 5634 - [ main ] o .s .d .r .w .basepathawarehandlermapping : mapped "{[/api/profile],methods=[options]}" 2018 - 03 - 07 11 : 23 : 28.888 info 5634 - [ main ] o .s .d .r .w .basepathawarehandlermapping : mapped "{[/api/profile],methods=[get]}" https://docs.spring.io/spring-data/rest/docs/current/reference/html ↩ https://github.com/atteo/evo-inflector ↩ future return에 대한 테스트 만들기 2018/02/27 by freeism · 0 comments 주로 오류에 대한 모니터링 메시지 등은 logical flow에 크게 영향을 주지 않기 때문에 @async 를 사용하여 분리하는 경우가 많다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @service public class foosendservice { public void send ( foo foo ) { try { fooresponse response = foosender . send ( foo ) ; . . . } catch ( exception e ) { kakaotalksender . send ( "error" ) ; } } } @component public class kakaotalksender { @async public future <boolean> send ( string message ) { return send ( id , message ) ; } } 그런데 이런 경우 future type의 return을 가지면 어떻게 테스트 코드를 만들어야 할 지 곤란한 경우가 있어서 기록해둔다. 1 이럴 때 사용하는 것이 guava futures.java 이다. (역시 믿고 쓰는 구글) 1 2 3 4 5 6 7 8 9 10 11 private void givensuccesskakaotalksender ( ) { when ( kakaotalksender . send ( anystring ( ) ) ) . thenreturn ( futures . immediatefuture ( true ) ) ; } private void givenfailurekakaotalksender ( ) { when ( kakaotalksender . send ( anystring ( ) ) ) . thenreturn ( futures . immediatefuture ( false ) ) ; } private void givenexceptionkakaotalksender ( ) { when ( kakaotalksender . send ( anystring ( ) ) ) . thenreturn ( futures . immediatefailedfuture ( new runtimeexception ( "오류" ) ) ) ; } immediatefuture 는 listenablefuture > future 에 대한 구현체이다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 @beta public final class futures { public static <v> listenablefuture <v> immediatefuture ( @nullable v value ) { return new immediatesuccessfulfuture <v> ( value ) ; // future에 대한 성공 객체 } public static <v> listenablefuture <v> immediatefailedfuture ( throwable throwable ) { checknotnull ( throwable ) ; return new immediatefailedfuture <v> ( throwable ) ; // future에 대한 오류 객체 } private static class immediatesuccessfulfuture <v> extends immediatefuture <v> { @nullable private final v value ; immediatesuccessfulfuture ( @nullable v value ) { this . value = value ; } @override public v get ( ) { // immediatefuture class를 상속받아 get()을 구현함 return value ; } } private static class immediatefailedfuture <v> extends immediatefuture <v> { private final throwable thrown ; immediatefailedfuture ( throwable thrown ) { this . thrown = thrown ; } @override public v get ( ) throws executionexception { // immediatefuture class를 상속받아 get()을 구현함 throw new executionexception ( thrown ) ; } } private abstract static class immediatefuture <v> implements listenablefuture <v> { private static final logger log = logger . getlogger ( immediatefuture . class . getname ( ) ) ; @override public void addlistener ( runnable listener , executor executor ) { // listenablefuture 구현 메소드 checknotnull ( listener , "runnable was null." ) ; checknotnull ( executor , "executor was null." ) ; try { executor . execute ( listener ) ; } catch ( runtimeexception e ) { // listenablefuture's contract is that it will not throw unchecked // exceptions, so log the bad runnable and/or executor and swallow it. log . log ( level . severe , "runtimeexception while executing runnable " + listener + " with executor " + executor , e ) ; } } @override public boolean cancel ( boolean mayinterruptifrunning ) { // future 구현 메소드 return false ; } @override public abstract v get ( ) throws executionexception ; // 하위 클래스에 구현을 위임함 @override public v get ( long timeout , timeunit unit ) throws executionexception { // future 구현 메소드 checknotnull ( unit ) ; return get ( ) ; } @override public boolean iscancelled ( ) { // future 구현 메소드 return false ; } @override public boolean isdone ( ) { // future 구현 메소드 return true ; } } } // immediatefuture는 listenablefuture 구현체인데, 이건 future 타입을 상속함 public interface listenablefuture <v> extends future <v> { void addlistener ( runnable listener , executor executor ) ; } https://www.programcreek.com/java-api-examples/index.php?class=org.mockito.mockito&method=doreturn ↩ intellij console log line 증가 2018/02/21 by freeism · 0 comments 배치 작업을 하다보니까 테스트 케이스를 돌렸을 때, 로그가 넘쳐흘러서 보이지 않는 케이스가 발생했다. 예전에 이런 걸로 한 번씩 설정하곤 했었는데, 할 때마다 기억이 나지 않아 구글에 의존하게 된다. 어느 블로그 1 에서 아래와 같은 해답을 찾았다. 텍스트 편집기를 열고 idea.cycle.buffer.size 속성을 바꿔준다 그런데 너무 오래된 포스팅이었나 보다. 언제 업데이트 되었는지는 모르겠으나, 해당 설정이 이미 intellij 내부에 있다. 기본값은 체크해제이고, 해당 값을 체크한 뒤 기본값인 1024kb 를 수정해주면 된다. http://hskimsky.tistory.com/50 ↩ jpa page 쿼리는 immutable list를 반환한다 2018/02/21 by freeism · 0 comments 이번에 맡은 업무에서 spring batch를 중점적으로 사용하고 있는데, 기존 로직을 리팩토링하면서 겪게 된 이슈를 기록해본다. 먼저 기존 로직은 아래와 같다. (코드 스니핏이고, 일부 업무와 관계된 네이밍은 모두 dummy로 치환하였다) 결국 배치로직에서 bulk로 읽어온 것을 하나씩 read/process/write 구조로 처리하고, 해당 내용이 없어지면 다음 page를 bulk로 읽어온다. 이 포스팅에 다루는 문제의 핵심은 bulk로 받아온 list의 첫 element를 마치 queue처럼 순차적으로 빼서 쓰는 부분이다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private list <someentity> someentities = lists . newarraylist ( ) ; @beforeread public void beforeread ( ) { if ( someentities . isempty ( ) ) { page <someentity> entitiesatdb = someentityrepository . findbysomecondition ( condition , new pagerequest ( page , chunk_size ) ) ; someentities = new arraylist ( entitiesatdb . getcontent ( ) ) ; . . . } } @override public someentity read ( ) { return someentities . isempty ( ) ? null : someentities . remove ( 0 ) ; } @afterread public void afterread ( someentity item ) { if ( someentities . isempty ( ) ) { page ++ ; } } 그런데 조금 불필요한 부분(redundancy)이 보이는 것 같다. entitiesatdb . getcontent ( ) 한 내용이 list <someentity> 인데 왜 굳이 new arraylist ( ) 로 래핑을 한 것일까. 1 2 3 4 5 6 /** * returns the page content as {@link list}. * * @return */ list <t> getcontent ( ) ; 그래서 해당 부분을 제거하기로 결정했다. 1 2 3 4 5 6 7 @beforeread public void beforeread ( ) { if ( someentities . isempty ( ) ) { someentities = someentityrepository . findbysomecondition ( condition , new pagerequest ( page , chunk_size ) ) . getcontent ( ) ; . . . } } 이제 코드가 불편한 느낌이 없이 깔끔해졌다. 어? 그런데 테스트코드에서 런타임에 오류가 발생한다. 1 2 3 4 5 6 7 8 9 10 java .lang .unsupportedoperationexception : null at java .util .collections $unmodifiablelist .remove ( collections .java : 1317 ) . . . at org .springframework .cglib .proxy .methodproxy .invoke ( methodproxy .java : 204 ) at org .springframework .aop .framework .cglibaopproxy $cglibmethodinvocation .invokejoinpoint ( cglibaopproxy .java : 738 ) at org .springframework .aop .framework .reflectivemethodinvocation .proceed ( reflectivemethodinvocation .java : 157 ) at org .springframework .transaction .interceptor .transactioninterceptor $ 1.proceedwithinvocation ( transactioninterceptor .java : 99 ) at org .springframework .transaction .interceptor .transactionaspectsupport .invokewithintransaction ( transactionaspectsupport .java : 282 ) at org .springframework .transaction .interceptor .transactioninterceptor .invoke ( transactioninterceptor .java : 96 ) at org .springframework .aop .framework .reflectivemethodinvocation .proceed ( reflectivemethodinvocation .java : 179 ) new arraylist ( ) 로 래핑해놓은 이유가 있었다. 아마도 내부 list가 immutable하게 구성되어 있나보다. 말이 나온 김에 좀더 찾아보기로 했다. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface page <t> extends slice <t> { . . . // page는 slice를 상속한다. } public interface slice <t> extends iterable <t> { . . . // slice는 iterable을 상속한다. 사실상 list라고 생각할 수 있다. } public class sliceimpl <t> extends chunk <t> { . . . // slice의 구현체는 chunk를 상속한다. } abstract class chunk <t> implements slice <t> , serializable { /* * (non-javadoc) * @see org.springframework.data.domain.slice#getcontent() */ public list <t> getcontent ( ) { // chunk에서 getcontent()는 content를 unmodifiablelist로 래핑해서 반환한다. return collections . unmodifiablelist ( content ) ; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class collections { . . . static class unmodifiablelist <e> extends unmodifiablecollection <e> implements list <e> { . . . public e set ( int index , e element ) { // 당연한 얘기겠지만, 값을 변조할 수 있는 메소드들은 모두 막혀있다. throw new unsupportedoperationexception ( ) ; } public void add ( int index , e element ) { throw new unsupportedoperationexception ( ) ; } public e remove ( int index ) { throw new unsupportedoperationexception ( ) ; } public boolean addall ( int index , collection < ? extends e > c ) { throw new unsupportedoperationexception ( ) ; } @override public void replaceall ( unaryoperator <e> operator ) { throw new unsupportedoperationexception ( ) ; } @override public void sort ( comparator < ? super e > c ) { throw new unsupportedoperationexception ( ) ; } . . . } . . . } 일반적으로 jpa에서는 list 엄밀히 따지면, mutablelist를 반환한다. 그래서 element를 추가, 삭제할 수 있다. 어찌보면 jpa 철학상 당연히 그래야 하는지도 모른다. 다만, paging을 사용하는 경우에는 readonly처럼 생각한 모양이다. 그래서 element를 추가, 삭제할 수 없도록 immutablelist를 반환하고 있다. 글 내비게이션 1 2 … 34 proudly powered by wordpress . theme: flat 1.0.4 by themeisle .

URL analysis for freeism.co.kr


https://www.freeism.co.kr/wp/archives/1608#respond
https://www.freeism.co.kr/wp/category/programmer
https://www.freeism.co.kr/wp/archives/1667
https://www.freeism.co.kr/wp/comments/feed
https://www.freeism.co.kr/wp/archives/1645
https://www.freeism.co.kr/wp/archives/1511#comment-222
https://www.freeism.co.kr/wp/archives/1511#comment-223
https://www.freeism.co.kr/wp/category/thinking
https://www.freeism.co.kr/wp/#fnref-1615-1
https://www.freeism.co.kr/wp/archives/1680
https://www.freeism.co.kr/wp/#fn-1667-1
https://www.freeism.co.kr/wp/#fn-1667-3
https://www.freeism.co.kr/wp/#fn-1667-2
https://www.freeism.co.kr/wp/archives/1667#respond
https://www.freeism.co.kr/wp/#fn-1645-1

Whois Information


Whois is a protocol that is access to registering information. You can reach when the website was registered, when it will be expire, what is contact details of the site with the following informations. In a nutshell, it includes these informations;

query : freeism.co.kr


# KOREAN(UTF8)

도메인이름 : freeism.co.kr
등록인 : 강현호
책임자 : 강현호
책임자 전자우편 : [email protected]
등록일 : 2008. 11. 12.
최근 정보 변경일 : 2013. 11. 02.
사용 종료일 : 2017. 11. 12.
정보공개여부 : N
등록대행자 : 한국전자인증(http://cosmotown.co.kr)
DNSSEC : 미서명

1차 네임서버 정보
호스트이름 : ns1.web-bi.net

2차 네임서버 정보
호스트이름 : ns2.web-bi.net

네임서버 이름이 .kr이 아닌 경우는 IP주소가 보이지 않습니다.


# ENGLISH

Domain Name : freeism.co.kr
Registrant : Hyeonho Kang
Administrative Contact(AC) : Kang
AC E-Mail : [email protected]
Registered Date : 2008. 11. 12.
Last Updated Date : 2013. 11. 02.
Expiration Date : 2017. 11. 12.
Publishes : N
Authorized Agency : Korea Electronic Certification Authority, Inc.(http://cosmotown.co.kr)
DNSSEC : unsigned

Primary Name Server
Host Name : ns1.web-bi.net

Secondary Name Server
Host Name : ns2.web-bi.net



- KISA/KRNIC WHOIS Service -


SERVERS

  SERVER kr.whois-servers.net

  ARGS freeism.co.kr

  PORT 43

  TYPE domain

DOMAIN

  NAME freeism.co.kr

NSERVER

  NS2.WEB-BI.NET 220.124.143.22

  NS1.WEB-BI.NET 220.124.143.21

  REGISTERED yes

Go to top

Mistakes


The following list shows you to spelling mistakes possible of the internet users for the website searched .

  • www.ufreeism.com
  • www.7freeism.com
  • www.hfreeism.com
  • www.kfreeism.com
  • www.jfreeism.com
  • www.ifreeism.com
  • www.8freeism.com
  • www.yfreeism.com
  • www.freeismebc.com
  • www.freeismebc.com
  • www.freeism3bc.com
  • www.freeismwbc.com
  • www.freeismsbc.com
  • www.freeism#bc.com
  • www.freeismdbc.com
  • www.freeismfbc.com
  • www.freeism&bc.com
  • www.freeismrbc.com
  • www.urlw4ebc.com
  • www.freeism4bc.com
  • www.freeismc.com
  • www.freeismbc.com
  • www.freeismvc.com
  • www.freeismvbc.com
  • www.freeismvc.com
  • www.freeism c.com
  • www.freeism bc.com
  • www.freeism c.com
  • www.freeismgc.com
  • www.freeismgbc.com
  • www.freeismgc.com
  • www.freeismjc.com
  • www.freeismjbc.com
  • www.freeismjc.com
  • www.freeismnc.com
  • www.freeismnbc.com
  • www.freeismnc.com
  • www.freeismhc.com
  • www.freeismhbc.com
  • www.freeismhc.com
  • www.freeism.com
  • www.freeismc.com
  • www.freeismx.com
  • www.freeismxc.com
  • www.freeismx.com
  • www.freeismf.com
  • www.freeismfc.com
  • www.freeismf.com
  • www.freeismv.com
  • www.freeismvc.com
  • www.freeismv.com
  • www.freeismd.com
  • www.freeismdc.com
  • www.freeismd.com
  • www.freeismcb.com
  • www.freeismcom
  • www.freeism..com
  • www.freeism/com
  • www.freeism/.com
  • www.freeism./com
  • www.freeismncom
  • www.freeismn.com
  • www.freeism.ncom
  • www.freeism;com
  • www.freeism;.com
  • www.freeism.;com
  • www.freeismlcom
  • www.freeisml.com
  • www.freeism.lcom
  • www.freeism com
  • www.freeism .com
  • www.freeism. com
  • www.freeism,com
  • www.freeism,.com
  • www.freeism.,com
  • www.freeismmcom
  • www.freeismm.com
  • www.freeism.mcom
  • www.freeism.ccom
  • www.freeism.om
  • www.freeism.ccom
  • www.freeism.xom
  • www.freeism.xcom
  • www.freeism.cxom
  • www.freeism.fom
  • www.freeism.fcom
  • www.freeism.cfom
  • www.freeism.vom
  • www.freeism.vcom
  • www.freeism.cvom
  • www.freeism.dom
  • www.freeism.dcom
  • www.freeism.cdom
  • www.freeismc.om
  • www.freeism.cm
  • www.freeism.coom
  • www.freeism.cpm
  • www.freeism.cpom
  • www.freeism.copm
  • www.freeism.cim
  • www.freeism.ciom
  • www.freeism.coim
  • www.freeism.ckm
  • www.freeism.ckom
  • www.freeism.cokm
  • www.freeism.clm
  • www.freeism.clom
  • www.freeism.colm
  • www.freeism.c0m
  • www.freeism.c0om
  • www.freeism.co0m
  • www.freeism.c:m
  • www.freeism.c:om
  • www.freeism.co:m
  • www.freeism.c9m
  • www.freeism.c9om
  • www.freeism.co9m
  • www.freeism.ocm
  • www.freeism.co
  • freeism.co.krm
  • www.freeism.con
  • www.freeism.conm
  • freeism.co.krn
  • www.freeism.col
  • www.freeism.colm
  • freeism.co.krl
  • www.freeism.co
  • www.freeism.co m
  • freeism.co.kr
  • www.freeism.cok
  • www.freeism.cokm
  • freeism.co.krk
  • www.freeism.co,
  • www.freeism.co,m
  • freeism.co.kr,
  • www.freeism.coj
  • www.freeism.cojm
  • freeism.co.krj
  • www.freeism.cmo
Show All Mistakes Hide All Mistakes